Follow treslines by email clicking Here!

Wednesday, October 30, 2013

How to handle orientation changes on Android

Hi there!
Today i'm gonna share a very common task, i used to have while developing android apps. Most of the scenarios we can handle by using the conventions of foldername-port or foldername-land for Portrait and Landscape mode while developing app which supports both orientations. That´s a good practise and should be done whenever possible. But in some special cases, we need to thread the orientation changes by listening to it. If this is also an issue by you, you'll may find helpful to see how we could do it. The code segment bellow shows a class which was designed to be implemented in line in your activity every time you may need the capability of handling orientation changes. Specially when the phones stands or is lying on the side. The instructions on how to use it, is described on the class's javadoc.

The abstract, reusable class definition


import android.view.Surface;

/**
 * Handles screen orientation changes. It was designed to be used/implemented in line,

 * directly in your activity. To be able to work properly, this class needs a manifest entry in the

 * activity tag called:

 * 
 * ... 
   
 * android:screenOrientation="fullSensor"

 * ... 
 * 

 * INSTRUCTIONS ON HOW TO USE IT:

 * 1) Implement the behavior you need in the methods:

 * {@link AbstractRotationHandler#onPhoneStands()} and

 * {@link AbstractRotationHandler#onPhoneLyingOnTheSide()}

 * 2) Then pass the current orientation constant to the method:

 * {@link AbstractRotationHandler#onOrientationChanged(int)} by calling:

 * getWindowManager().getDefaultDisplay().getRotation();

 * from your activity.
 * 
 * @author Ricardo Ferreira 30/10/2013
 * 
 */
public abstract class AbstractRotationHandler {

 /**
  * Pass the current orientation constant by calling:

  * getWindowManager().getDefaultDisplay().getRotation();

  * from your activity.
  * 
  * @param rotation
  *            the current rotation constant provided by your activity
  */
 public void onOrientationChanged(final int rotation) {
  switch (rotation) {
  case Surface.ROTATION_0:
  case Surface.ROTATION_180: {
   onPhoneStands();
   break;
  }
  case Surface.ROTATION_90:
  case Surface.ROTATION_270: {
   onPhoneLyingOnTheSide();
   break;
  }
  }
 }

 /**
  * Implement the behavior you need, when the phone stands.
  */
 protected abstract void onPhoneStands();

 /**
  * Implement the behavior you need, when the phone is lying on the side.
  */
 protected abstract void onPhoneLyingOnTheSide();

}

The implementation of it


 private AbstractRotationHandler concreteRotationHandler = new AbstractRotationHandler() {

        @Override
        protected void onPhoneStands() {
               // do what you need here...
        }

        @Override
        protected void onPhoneLyingOnTheSide() {
               // do what you need here...
         }
    };

The usage of it


int rotation = getWindowManager().getDefaultDisplay().getRotation();
this.concreteRotationHandler.onOrientationChanged(rotation);

Thursday, October 17, 2013

Android AutoSave For Saving Processes After Custom Delay

Hi there! Today i'm gonna share a very common task while developing android apps. An autosave function which allow me to monitor user actions and save something after a custom delay everytime the user interacts with the UI.

Defining the common Interface

This interface will be implemented by the interested clients. For example your Activity.
/**
 * Clients who are interested in having auto save capabilities functions implements this interface.

 * 

 * Steps on how to use it:

 * - Implement this interface

 * - In your UI, in the desired onClick, onTouch or whatever method, create a new instance of {@link AutoSave}

 * - Pass this implemented instance to the constructor of {@link AutoSave}

 * - With the method setSecondsToWait() of {@link AutoSave}, set the seconds you may want to delay the save process.

 * - Obs: If you do not use the call setSecondsToWait() on {@link AutoSave}, the default behavior will be 10 seconds delay.

 * - Important: Call the start() method on {@link AutoSave} to start monitoring user actions.

 * 
 * @author Ricardo Ferreira 17/10/2013
 */
public interface SaveAction {

 public void autosave();

}

Defining the AutoSave Thread

This is the class which monitors the user's interaction.
/**
 * Use this class to start a auto save process as a Thread with some delay before saving.

 * 

 * Steps on how to use it:

 * - Instantiate this instance in your desired onClick, onTouch or whatever method

 * - Call the method setSecondsToWait() and set the seconds you want to delay

 * - (If you do not call setSecondsToWait() on {@link AutoSave}, the default behavior will be 10 seconds delay.)

 * - Important: Call the start() method on {@link AutoSave} to start monitoring user actions.

 * 
 * @author Ricardo Ferreira  17/10/2013
 * 
 */
public class AutoSave extends Thread {

 private Activity context;
 private long startTime = 0;
 private long currentTime = 0;
 private final long teenSeconds = 10000;
 private final long oneSecond = 1000;
 private boolean recording = false;
 private SaveAction saveAction;
 private long secondsToWait = 0;

 public AutoSave(Activity context, SaveAction saveAction) {
  this.context = context;
  this.saveAction = saveAction;
 }

 @Override
 public void run() {

  while (!this.context.isDestroyed()) {
   try {
    if (isRecording()) {
     setCurrentTime(System.currentTimeMillis());
     if (getCurrentTime() - getStartTime() > (secondsToWait == 0 ? teenSeconds : secondsToWait)) {
      setRecording(false);
      setStartTime(0);
      setCurrentTime(0);
      autoSave();
     }
     sleep(oneSecond);
    } else {
     sleep(oneSecond);
    }
   } catch (InterruptedException e) {
    Log.i("TEST", e.getMessage());
    e.printStackTrace();
   }
  }

 }

 public void setSecondsToWait(long secondsToWait) {
  this.secondsToWait = secondsToWait;
 }

 public void saveAfterSecondsToWait() {
  if (!isRecording()) {
   setRecording(true);
   setStartTime(System.currentTimeMillis());
  }
 }

 private void autoSave() {
  this.saveAction.autosave();
 }

 private long getStartTime() {
  return startTime;
 }

 private void setStartTime(long startTime) {
  this.startTime = startTime;
 }

 private void setCurrentTime(long currentTime) {
  this.currentTime = currentTime;
 }

 private long getCurrentTime() {
  return currentTime;
 }

 private boolean isRecording() {
  return recording;
 }

 private void setRecording(boolean recording) {
  this.recording = recording;
 }

}

Initializing the AutoSave instance

Lets assume now, that you have an Activity which implements the interface SaveAction. Lets call it: "PaintActivity" and you are interested in monitoring user actions. Every time the user draws something on the screen, you'll wait teen seconds and save the drawing. To do it so, call this two lines bellow in your onTouchListener's constructor. Then when the action happens, call saveAfterSecondsToWait() from AutoSave. Done! If you want to customize the delay time, call setSecondsToWait() from AutoSave before calling start(). Done! ;) You can now implement your auto save behavior in your Activity.

this.autosave = new AutoSave(this.context, (PainterActivity) this.context);
//this.autosave.setSecondsToWait(3000);// three seconds delay
this.autosave.start();
// call saveAfterSecondsToWait() from autosave when the action actually happens. for example: onTouch() or onClick() whatever...
this.autosave.saveAfterSecondsToWait(),


That's all! Hope you like it! :)

Thursday, October 10, 2013

Android Almanac - Code Snippet Repo

Hi there! This section i will totatlly dedicate to android dev´s out there! It is as little Android Almanac containing common code snippets for the different purposes and tasks. I will be growing permanently. So if you miss something, just leave a constructive comment and i will try my best to accomplish that ok. Hope you like it.

How to detect device's 3G, wifi or internet connection

private boolean isConnectedToInternet(Context ctx) {

        NetworkInfo info = (NetworkInfo) ((ConnectivityManager) ctx
                .getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();

        if (info == null || !info.isConnected()) {
            return false;
        }
        if (info.isRoaming()) {
            // here is the roaming option you can change it if you want to
            // disable internet while roaming, just return false
            return false;
        }
        return true;
    }

Easy way to give feedback to users while pressing ImageView

final ImageView eraseBtn = (ImageView)findViewById(R.id.eraseBtn);
         eraseBtn.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    eraseBtn.setColorFilter(getResources().getColor(R.color.translucent_black));
                    eraseBtn.invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    eraseBtn.setColorFilter(null);
                    eraseBtn.invalidate();
                    break;
                default:
                    break;
                }
                return false;
            }
        });
Define the translucent black color like this in your strings.xml file:
< ? xml version="1.0" encoding="utf-8" ? >
< resources >
     ....
    < color name="translucent_black" >#51000000< / color>
     ....
< / resources>

How to compute degrees from MouseEvent (x,y coordinates)

In some cases, we need to compute the degrees from a give coordinate. Like from an event. Here is a simple way how to do it:

    private float getDegrees(MotionEvent event) {
        double radians = Math.atan2(event.getY(1) - event.getY(0), event.getX(1) - event.getX(0));
        return (float) Math.toDegrees(radians);
    }

Listening to orientation changes onSensorChanged()

In some cases, we need to implement SensorEventListener and implement onSensorChanded. While listening to it, i lost many yours looking for a simple approach which allows me to simple decide if my fone is lying on the side or if it stands. A very nice and simple way is to listen to its angles like that:

... code omitted ...

public void onSensorChanged(SensorEvent event) {
        float pitch = event.values[1];
        float roll = event.values[2];
        if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            if (pitch < -45 && pitch > -135) {
                onLandscapePhoneStands();
            } else if (pitch > 45 && pitch < 135) {
                onLandscapePhoneOnHead();
            } else if (roll > 45) {
                onLandscapePhoneLyingOnTheLeftSide();
            } else if (roll < -45) {
                onLandscapePhoneLyingOnTheRightSide();
            }
        } else if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            if (pitch < -45 && pitch > -135) {
                onPhoneStands();
            } else if (pitch > 45 && pitch < 135) {
                onPhoneOnHead();
            } else if (roll > 45) {
                onPhoneLyingOnTheLeftSide();
            } else if (roll < -45) {
                onPhoneLyingOnTheRightSide();
            }
        }
    }

... code omitted ...

How to remove padding from AlertDialog on Android

I had a task to solve, which was very tricky. I needed to display an alert dialog, which presents an image (using the whole space of the dialog) without any buttons. Just a fullscreen picture on it. The problem was, that on the top side of the dialog, was always a border line remaining. No matter what i did or set in the layout.xml. I lost many hours searching for a proper solution. At the end the solution was as simple as i thought, but very tricky. All you need to do, is to call the following piece of code after calling the show() method on your dialogBuilder or on your dialog itself.

... code ommited...

dialogBuilder.show(); 

ViewGroup parent = (ViewGroup)view.getParent();

parent.setPadding(0, 0, 0, 0);

... code ommited ...

Getting the current display size

That´s a very common task for diffent purposes. Here a snippet for it:

DisplayMetrics metrics = getResources().getDisplayMetrics();
  int displayWidth = metrics.widthPixels;
  int displayHeight = metrics.heightPixels;

Rounded Corners by Layouts

Sometimes you'll need to be a little more sofisticated with your layout. One task i had, was to round the corners from my fragment layout, setting stroke type, color and radius. This may also be helpfull to you. In the folder drawable define a shape.xml called: rounded_layout_corners.xml with the content bellow and set it to the android:background attribute of your layout in the xml file.:


    
    
    
    


Rounded Corners by Layouts without bottom line

More examples with rounded corners but this way hidding the bottom line. For this kind of layout you'll need to create the drawable folder for portrait and drawable-land for landscape mode.

Here's the portrait layout i called rounded_layout_corner.xml
 
    
    
     
     
    
    
     
 
And here's the landscape layout i called also rounded_layout_corner.xml but put it in the drawable-land folder.
 
     
    
     
     
    
    
     
 

Supporting Multiple Screen Sizes

That´s a very common task. Here the most important infos you need to know.

drawable-ldpi (120 dpi, Low density screen) - 36px x 36px
drawable-mdpi (160 dpi, Medium density screen) - 48px x 48px
drawable-hdpi (240 dpi, High density screen) - 72px x 72px
drawable-xhdpi (320 dpi, Extra-high density screen) - 96px x 96px
drawable-xxhdpi or drawable-480dpi (480 dpi, XHDPI density screen) - 144px x 144px

Reacting to different orientations

That´s another very common task. Here one option you may like it:

private boolean onPhoneMovement() {
  boolean isPhoneLyingOnTheSide = false;
  DisplayMetrics metrics = getResources().getDisplayMetrics();
  int displayWidth = metrics.widthPixels;
  int displayHeight = metrics.heightPixels;
  switch (getWindowManager().getDefaultDisplay().getRotation()) {
  case Surface.ROTATION_0:
  case Surface.ROTATION_180: {
   onPhoneStands(displayHeight);
   isPhoneLyingOnTheSide = false;
   break;
  }
  case Surface.ROTATION_90:
  case Surface.ROTATION_270: {
   onPhoneLiesOnTheSide(displayWidth);
   isPhoneLyingOnTheSide = true;
   break;
  }
  }
  return isPhoneLyingOnTheSide;
 }

Setting Width and Height to Layouts

That can be very usefull in some cases:

private void onPhoneLiesOnTheSide(int displayWidth) {
  this.toReplace = (LinearLayout) findViewById(R.id.toReplace);
  this.toReplace.getLayoutParams().height = this.buttonBackground.getHeight();
  this.toReplace.getLayoutParams().width = displayWidth - this.buttonBackground.getWidth();
 }

 private void onPhoneStands(int displayHeight) {
  this.toReplace = (LinearLayout) findViewById(R.id.toReplace);
  this.toReplace.getLayoutParams().height = displayHeight - this.buttonBackground.getHeight();
  this.toReplace.getLayoutParams().width = this.buttonBackground.getWidth();
 }

Fragment Replacement and Animation (Slide In and Slide Out)

Attention: If you've never worked with fragments, so please read my previous post about it first. dynamically fragment replacement android This snippet could be a differencial in your app. create a folder anim and anim-land in the folder res. Then put those xml-files in it. In folder anim create a xml file called slide_in.xml


    


The in the same folder anim create a xml file called slide_out.xml


    


The in the folder anim-land create a xml file called slide_in.xml


    


And last but no least in the folder anim-land create a xml file called slide_out.xml


    



Then try this here:
 private void hideFragmen(final Fragment framgmentToShow, boolean isPhoneLyingOnTheSide) {
  FragmentTransaction transaction = getFragmentManager().beginTransaction();
  transaction.setCustomAnimations(R.anim.slide_out, R.anim.slide_in);
  transaction.replace(R.id.toReplace, framgmentToShow);
  transaction.hide(framgmentToShow);
  transaction.commit();
 }

 private void showFragment(final Fragment framgmentToShow, boolean isPhoneLyingOnTheSide) {
  FragmentTransaction transaction = getFragmentManager().beginTransaction();
  transaction.setCustomAnimations(R.anim.slide_out, R.anim.slide_in);
  transaction.replace(R.id.toReplace, framgmentToShow);
  transaction.show(framgmentToShow);
  transaction.commit();
 }

Load/Decode Bitmap from Resource with BitmapFactory

You'll need this line several times while developing with Android.

  Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);

Rotate icons depending on phone Orientation

That´s a nice one. Small, fine and native. Because buttons are also views you can use this i a very generic way.

private void setIconRotation(int degrees) {
  for (View view : viewsToRotate) {
   view.setRotation(degrees);
  }
}

Fix/fixing a layout on the right side of your phone

That's also a good one. No matter what kind of the screen orientation you have. Just fix(post it) your layout in the right corner of your phone.

public void handleRotationChange(int rotation) {
  switch (rotation) {
  case Surface.ROTATION_0:
  case Surface.ROTATION_180: {
   // ***********************************************************
   // rotate toolbar (fix it on the right)
   // ***********************************************************
   this.layoutToolbar.setOrientation(LinearLayout.HORIZONTAL);
   this.layoutToolbar.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
   LayoutParams params = (LayoutParams) this.layoutToolbar.getLayoutParams();
   params.gravity = Gravity.BOTTOM;
   params.width = LayoutParams.MATCH_PARENT;
   params.height = LayoutParams.WRAP_CONTENT;
   // ***********************************************************
   // rotate icons
   // ***********************************************************
   setIconRotation(0);
  }
   break;

  case Surface.ROTATION_90:
  case Surface.ROTATION_270: {
   // ***********************************************************
   // rotate toolbar (fix it on the bottom)
   // ***********************************************************
   this.layoutToolbar.setOrientation(LinearLayout.VERTICAL);
   this.layoutToolbar.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
   LayoutParams params = (LayoutParams) this.layoutToolbar.getLayoutParams();
   params.gravity = Gravity.RIGHT;
   params.width = LayoutParams.WRAP_CONTENT;
   params.height = LayoutParams.MATCH_PARENT;
   // ***********************************************************
   // rotate icons
   // ***********************************************************
   setIconRotation(0);
  }
   break;
  }
 }

Showing/show simple info message on the screen

private void showMessage(String msg) {
  Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
 }

GPS distance calculation from one point to another


public double calculeDistanciaEmMetrosComoDouble(double lat1, double lng1, double lat2, double lng2) {
  return calculeDistancia(lat1, lng1, lat2, lng2);
 }

 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância com o suffix "m" no final*/
 public String calculeDistanciaEmMetros(double lat1, double lng1, double lat2, double lng2) {
  double distanciaCalculada = calculeDistancia(lat1, lng1, lat2, lng2);
  return definaQuantosNumerosAposVirgula(2, distanciaCalculada) + " m";
 }
 
 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância com o suffix "km" no final*/
 public String calculeDistanciaEmKilometros(double lat1, double lng1, double lat2, double lng2) {
  double distanciaCalculada = calculeDistancia(lat1, lng1, lat2, lng2);
  double kilometros = distanciaCalculada / 1000;
  return definaQuantosNumerosAposVirgula(2, kilometros) + " km";
 }
 
 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância em miles (1.609km) com o suffix "miles" no final*/
 public String calculeDistanciaEmMiles(double lat1, double lng1, double lat2, double lng2) {
  double distanciaCalculada = calculeDistancia(lat1, lng1, lat2, lng2);
  double miles = distanciaCalculada / 1609.344;
  return definaQuantosNumerosAposVirgula(2, miles) + " miles";
 }
 
 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância em yards (0.9144m) com o suffix "yards" no final*/
 public String calculeDistanciaEmYards(double lat1, double lng1, double lat2, double lng2) {
  double distanciaCalculada = calculeDistancia(lat1, lng1, lat2, lng2);
  double yards = distanciaCalculada / 0.9144;
  return definaQuantosNumerosAposVirgula(2, yards) + " yards";
 }
 
 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância em feets (0.3048m) com o suffix "feets" no final*/
 public String calculeDistanciaEmFeets(double lat1, double lng1, double lat2, double lng2) {
  double distanciaCalculada = calculeDistancia(lat1, lng1, lat2, lng2);
  double feets = distanciaCalculada / 0.3048;
  return definaQuantosNumerosAposVirgula(2, feets) + " feets";
 }
 
 /** calcula a distância de um ponto ao outro (longitude, latitude) retornando a distância em metros"*/
 private double calculeDistancia(double lat1, double lng1, double lat2, double lng2) {
  double earthRadius = 3958.75;
  double dLat = Math.toRadians(lat2 - lat1);
  double dLng = Math.toRadians(lng2 - lng1);
  double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
    + Math.cos(Math.toRadians(lat1))
    * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
    * Math.sin(dLng / 2);
  double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  double dist = earthRadius * c;
  int meterConversion = 1609;
  double result = (dist * meterConversion);
  return result;

 }
 
 private String definaQuantosNumerosAposVirgula(int qnt, double amount){
  NumberFormat df = DecimalFormat.getInstance();
  df.setMinimumFractionDigits(2);
  df.setMaximumFractionDigits(2);
  return df.format(amount);
 }

Hide Application Titlebar


public void hideApplicationTitleBar() {
  requestWindowFeature(Window.FEATURE_NO_TITLE);
 }

Set Application Orientation


setRequestedOrientation(defineActivityOrientation());
public int defineActivityOrientation() {
  return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 }

GPS Locatition Manager, LastKnownLocation, Wifi, Network


protected LocationManager locationManager;
protected String provider;
protected Geocoder coder;
private void initGeoLocation() {
  this.addressFinder = new AddressFinder(this, this.autoCompleteSearch);
  this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
  showMessageOnGpsDeactivated();
  // Define the criteria how to select the locatioin provider -> use default
  this.provider = locationManager.getBestProvider(new Criteria(), false);
  this.coder = new Geocoder(this);
 }
public boolean showMessageOnGpsDeactivated() {
  boolean isGpsDeactivated = false;
  if (!isGpsProviderEnabled() || !isNetworkProviderEnabled()) {
   showModalGpsActivateDialog();
   isGpsDeactivated = true;
  }
  return isGpsDeactivated;
 }
private boolean isGpsProviderEnabled() {
  return this.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
 }

 private boolean isNetworkProviderEnabled() {
  return this.locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
 }
public boolean isOnline() {
  boolean isConnected = false;
  ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  NetworkInfo netInfo = cm.getActiveNetworkInfo();
  if (netInfo != null && netInfo.isConnectedOrConnecting()) {
   isConnected = true;
  }
  return isConnected;
 }
public boolean isWiFiOnline() {
  boolean isConnected = false;
  ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  NetworkInfo mWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
  if (mWifi != null && mWifi.isConnectedOrConnecting()) {
   isConnected = true;
  }
  return isConnected;
 }
public void showModalGpsActivateDialog() {

  LayoutInflater inflater = getLayoutInflater();
  View view = inflater.inflate(R.layout.layout_toast, (ViewGroup) findViewById(R.id.layout_toast_gps));

  new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(getResources().getText(R.string.toast_gps_dialog_title))
    .setPositiveButton(getResources().getText(R.string.toast_gps_dialog_button_text), new DialogInterface.OnClickListener() {
     @Override
     public void onClick(DialogInterface dialog, int which) {
      navigateTo(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
     }

    }).setView(view).setCancelable(false).show();
 }
public Location getLastKnownLocation() {
  Location location = null;
  if (isOnline()) {
   if (!showMessageOnGpsDeactivated()) {
    if (isLocationAvailable()) {
     location = this.locationManager.getLastKnownLocation(this.provider);
    } else {
     showMessage(this, getResources().getString(R.string.toast_gps_dialog_no_gps_available));
    }
   }
  } else {
   showModalNoNetworkDialog();
  }
  return location;
 }

 public boolean isLocationAvailable() {
  this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
  this.provider = locationManager.getBestProvider(new Criteria(), false);
  this.coder = new Geocoder(this);
  return this.locationManager.getLastKnownLocation(this.provider) != null;
 }

Navigation, Intent, Google Play Store, Webpage


/** Use this method to get passed values over Intent.SetExtras(...) */
 public String getPassedUserInputSelection(String key) {
  return (String) getIntent().getExtras().get(key);
 }

 /** Use this method to navigate from one activity to another passing values to the target activity */
 public void navigateToPassingValues(Context fromActivity, Class toActivityClass, String bundleKey, Bundle bundle) {
  Intent activityToStart = createActivityToStart(fromActivity, toActivityClass);
  activityToStart.putExtra(bundleKey, bundle);
  startActivity(activityToStart);
 }

 /** Use this method to navigate from one activity to another passing values to the target activity */
 public void navigateToPassingValues(Context fromActivity, Class toActivityClass, String key, String value) {
  Intent activityToStart = createActivityToStart(fromActivity, toActivityClass);
  activityToStart.putExtra(key, value);
  startActivity(activityToStart);
 }

 /** Use this method to navigate from one activity to another */
 public void navigateTo(Context fromActivity, Class toActivityClass) {
  startActivity(createActivityToStart(fromActivity, toActivityClass));
 }

 /** Use this method to navigate directly to a given intent */
 public void navigateTo(Intent intent) {
  startActivity(intent);
 }

 private Intent createActivityToStart(Context fromActivity, Class toActivityClass) {
  return new Intent(fromActivity, toActivityClass);
 }

 /** Use this method to open the google play store from this app */
 public void navigateToGooglePlayStore() {
  final String appName = "com.treslines.onibuspe";
  try {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appName)));
  } catch (android.content.ActivityNotFoundException anfe) {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + appName)));
  }
 }

 public void navigateToTreslines() {
  final String webpage = "http://www.treslines.com/index.html";
  try {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(webpage)));
  } catch (android.content.ActivityNotFoundException anfe) {
   // If anything goes wrong, don't disturb the user experience. just don't open the webpage
  }
 }
 
 public void navigateToWebpage(String address) {
  final String webpage = address;
  try {
   startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(webpage)));
  } catch (android.content.ActivityNotFoundException anfe) {
   // If anything goes wrong, don't disturb the user experience. just don't open the webpage
  }
 }

 /** Use this method to display messages to the user */
 public void showMessage(Context context, String msg) {
  Toast toast = Toast.makeText(context, msg, Toast.LENGTH_LONG);
  toast.setGravity(Gravity.CENTER, 0, 0);
  toast.show();
 }

AutoComplete, MultiAutoCompleteTextView


/** Use this method to create a custom autocomplete with NoTokenizer and no suggestions flags */
 public void createAutoComplete(MultiAutoCompleteTextView autoComplete, String[] contentToShow) {
  autoComplete.setAdapter(new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, contentToShow));
  autoComplete.setTokenizer(getTokenizerForMultiAutoCompleteTextView());
  autoComplete.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
 }

 /** Use this method to create a custom Tokenizer with no comma(,) termination */
 public NoTokenizer getTokenizerForMultiAutoCompleteTextView() {
  return new NoTokenizer();
 }

 private class NoTokenizer extends MultiAutoCompleteTextView.CommaTokenizer {

  @Override
  public int findTokenEnd(CharSequence text, int cursor) {
   return super.findTokenEnd(text, cursor);
  }

  @Override
  public int findTokenStart(CharSequence text, int cursor) {
   return super.findTokenStart(text, cursor);
  }

  @Override
  public CharSequence terminateToken(CharSequence text) {
   CharSequence terminateToken = super.terminateToken(text);
   terminateToken = terminateToken.toString().replace(" ,", "").replace(", ", "");
   return terminateToken;
  }

 }

Creating array from string.xml


public String[] createLinhasFromStringXml() {
  return getResources().getStringArray(R.array.linha_array);
 }

Inflater to put into your abstracted class


public View inflateContentView(int layoutId) {
  LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  return inflater.inflate(layoutId, null);
 }

DB, database date parser


import android.annotation.SuppressLint;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@SuppressLint("SimpleDateFormat")
public class DbDateParser {
 private static final String format = "yyyy-MM-dd";

 public static Date stringToDate(String data) {
  Date dDate = null;
  SimpleDateFormat formatter = new SimpleDateFormat(format);
  try {
   dDate = formatter.parse(data);
  } catch (ParseException e) {
  }
  return dDate;
 }

 public static String dateToString(Date data) {
  return dateToString(data, format);
 }

 public static String dateToString(Date data, String format) {
  SimpleDateFormat formatter = new SimpleDateFormat(format);
  return formatter.format(data);
 }

 public static int getHour(Date data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("HH");
   return Integer.parseInt(formatter.format(data));
  } catch (Exception ex) {
   return -1;
  }
 }

 public static int getMinute(Date data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("mm");
   return Integer.parseInt(formatter.format(data));
  } catch (Exception ex) {
   return -1;
  }
 }

 public static int getDay(Date data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("dd");
   return Integer.parseInt(formatter.format(data));
  } catch (Exception ex) {
   return -1;
  }
 }

 public static int getMonth(Date data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("MM");
   return Integer.parseInt(formatter.format(data));
  } catch (Exception ex) {
   return -1;
  }
 }

 public static int getYear(Date data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("yyyy");
   return Integer.parseInt(formatter.format(data));
  } catch (Exception ex) {
   return -1;
  }
 }

 public static Date getFullHour_String2Date(String data) {
  try {
   SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");
   return formatter.parse(data);
  } catch (Exception ex) {
   return new Date();
  }
 }
}

Ormlite, SQLite, DatabaseHelper


import java.sql.SQLException;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import com.treslines.entity.EmpresaEntity;
import com.treslines.entity.ItinerarioEntity;
import com.treslines.entity.LinhaEntity;
import com.treslines.entity.ParadaEntity;
import com.treslines.entity.ReferenciaEntity;
import com.treslines.entity.RotaEntity;
import com.treslines.entity.RoteiroEntity;

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

 private static final String DATABASE_NAME = "databasename.db";
 private static final int DATABASE_VERSION = 1;

 public DatabaseHelper(Context context) {
  super(context, DATABASE_NAME, null, DATABASE_VERSION);
 }

 @Override
 public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
  try {
   Log.i("DB", "onCreate: create tables if not exists");
   TableUtils.createTableIfNotExists(connectionSource, LinhaEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, ReferenciaEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, RoteiroEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, RotaEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, EmpresaEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, ItinerarioEntity.class);
   TableUtils.createTableIfNotExists(connectionSource, ParadaEntity.class);
  } catch (SQLException e) {
   Log.e(DatabaseHelper.class.getSimpleName(), "Can't create database", e);
   throw new RuntimeException(e);
  }
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
  try {
   // delete all tables
   Log.i("DB", "onUpgrade: drop old tables and create new tables");
   TableUtils.dropTable(connectionSource, ParadaEntity.class, true);
   TableUtils.dropTable(connectionSource, ItinerarioEntity.class, true);
   TableUtils.dropTable(connectionSource, EmpresaEntity.class, true);
   TableUtils.dropTable(connectionSource, RotaEntity.class, true);
   TableUtils.dropTable(connectionSource, RoteiroEntity.class, true);
   TableUtils.dropTable(connectionSource, ReferenciaEntity.class, true);
   TableUtils.dropTable(connectionSource, LinhaEntity.class, true);
   // create new tables
   onCreate(db, connectionSource);
  } catch (SQLException e) {
   Log.e(DatabaseHelper.class.getSimpleName(), "Can't drop databases", e);
   throw new RuntimeException(e);
  }
 }

 @Override
 public void close() {
  super.close();
 }
}

import com.j256.ormlite.field.DatabaseField;

// ORMLite download addresses
// ormlite-android-4.45: //www.java2s.com/Code/Jar/o/Downloadormliteandroid445jar.htm
// ormlite-core-4.45: http://sourceforge.net/projects/ormlite/files/

public abstract class AbstractEntity {

 @DatabaseField(columnName="_id", generatedId = true)
 private Integer id;
 @DatabaseField(columnName="timestamp")
 private String timestamp;
 
 // ormlite require default constructor
 public AbstractEntity() {
  super();
 }
 
 public AbstractEntity(Integer id, String timestamp) {
  setId(id);
  setTimestamp(timestamp);
 }

 public Integer getId() {return id;}
 public void setId(Integer id) {this.id = id;}
 public String getTimestamp() {return timestamp;}
 public void setTimestamp(String timestamp) {this.timestamp = timestamp;}

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  AbstractEntity other = (AbstractEntity) obj;
  if (id == null) {
   if (other.id != null)
    return false;
  } else if (!id.equals(other.id))
   return false;
  if (timestamp == null) {
   if (other.timestamp != null)
    return false;
  } else if (!timestamp.equals(other.timestamp))
   return false;
  return true;
 } 
}

import java.util.ArrayList;
import java.util.Collection;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.ForeignCollectionField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName="empresa")
public class EmpresaEntity extends AbstractEntity implements Comparable{

 @DatabaseField(columnName="empresa_nome")
 private String nome;
 @DatabaseField(columnName="empresa_abreviacao")
 private String abreviacao;
 @DatabaseField(columnName="empresa_codigo")
 private String codigo;
 @ForeignCollectionField(columnName="empresa_linhas")
 private Collection linhas = new ArrayList();
 
 // ormlite require default constructor
 public EmpresaEntity(){
  super();
 }
 
 public EmpresaEntity(String codigo,String abreviacao,String nome, Collection linhas){
  setCodigo(codigo);
  setAbreviacao(abreviacao);
  setNome(nome);
  setLinhas(linhas);
 }
 
 public EmpresaEntity(String codigo,String abreviacao,String nome){
  setCodigo(codigo);
  setAbreviacao(abreviacao);
  setNome(nome);
 }
 
 public Collection getLinhas() {return linhas;}
 public void setLinhas(Collection linhas) {this.linhas = linhas;}
 public String getNome() {return nome;}
 public void setNome(String nome) {this.nome = nome;}
 public String getAbreviacao() {return abreviacao;}
 public void setAbreviacao(String abreviacao) {this.abreviacao = abreviacao;}
 public String getCodigo() {return codigo;}
 public void setCodigo(String codigo) {this.codigo = codigo;}

 @Override
 public int compareTo(EmpresaEntity another) {
  if (equals(another)) {
   return 0;
  }
  return -1;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = super.hashCode();
  result = prime * result + ((abreviacao == null) ? 0 : abreviacao.hashCode());
  result = prime * result + ((codigo == null) ? 0 : codigo.hashCode());
  result = prime * result + ((linhas == null) ? 0 : linhas.hashCode());
  result = prime * result + ((nome == null) ? 0 : nome.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (!super.equals(obj))
   return false;
  if (getClass() != obj.getClass())
   return false;
  EmpresaEntity other = (EmpresaEntity) obj;
  if (abreviacao == null) {
   if (other.abreviacao != null)
    return false;
  } else if (!abreviacao.equals(other.abreviacao))
   return false;
  if (codigo == null) {
   if (other.codigo != null)
    return false;
  } else if (!codigo.equals(other.codigo))
   return false;
  if (linhas == null) {
   if (other.linhas != null)
    return false;
  } else if (!linhas.equals(other.linhas))
   return false;
  if (nome == null) {
   if (other.nome != null)
    return false;
  } else if (!nome.equals(other.nome))
   return false;
  return true;
 }
}


Tuesday, October 8, 2013

Dynamically Fragment Replacement Android

Hi there! Today i'm gonna show how to insert(add) fragments dynamically. How to replace it thru other fragments with totally diferent layouts. This is a very common task while developing scalable, reusable and good looking apps.

I'm trying to do it in a very practical way from developer to developer. Less words and more code (complete code, and not only fragments...) to be sure you'll not run away and enjoy the sample. ;-)

There are some restrictions you must know, while handling with fragments.

First: You can only replace fragments which has been added dynamically. Burn this in your mind! ;-) I will be helpful! Believe me!
Second: You must define a "placeholder" layout, which will be your point to insert(add) and replace fragments with.

Ok lets put hands on. ;-)  The result at the end will be something like this:



Create a new Android Project with a blank template. 

Call it FragmentReplacement. Ensure that you have defined the "minsdkversion=11".

Controlling your AndroidManifest.xml 

Open it and be sure you have something similar to this:



    

    
        
            
                

                
            
        
    


Activity_main.xml (Layout) 

In the main activity layout, create a layout structure like this bellow:



    
    

    

Creating a Fragment 

In the folder "layout" create a new layout, name it "fragment_horizontal.xml" and copy/paste the code bellow in it. Save it. Done! Lets move ahead. ;-) (Don't worry about the details right now. Let's first create a simple structure before explaning everything.)



    

        


Create the next Fragment 

The main difference here is that the layout will change it here from horizontal to vertical. This because we wanna see the beautiful effect from having and using Fragments. Name it "fragment_vertical.xml" and again copy/paste the next code section in it.



    

        


Creating the class responsible for the First Fragment 

Create a new class and name it "FragmentHorizontal". While creating a new class, be sure you extend it from Fragment when using the "New Class Wizard". Or just copy the code into your new created class. That's a very simple class. It is only responsible for creating a new Fragment from the given layout we have just defined in the sections above.

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentHorizontal extends Fragment {
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  View view = inflater.inflate(R.layout.fragment_horizontal, null);
  Button button = (Button) view.findViewById(R.id.hbutton1);
  button.setText("horizontal");
  return view;
 }

}

Creating the next Fragment with vertical layout 

Do it analogue the section above. Create a new class and name it "FragmentVertical". Copy/Paste the code and go to the next step. ;-)

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class FragmentVertical extends Fragment {
 
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  View view = inflater.inflate(R.layout.fragment_vertical, null);
  Button button = (Button) view.findViewById(R.id.vbutton1);
  button.setText("vertical");
  return view;
 }

}

We are almost done! 

The main activity. Place the codefragment bellow into it. After that press the button in the middle to replace it as many times as you want. Press Run>Android Application

import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

 private boolean flip = false;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  FragmentVertical fv = new FragmentVertical();
  FragmentManager fm = getFragmentManager();
  FragmentTransaction ft = fm.beginTransaction();
  ft.add(R.id.layoutToReplace, fv);
  ft.commit();

  Button b = (Button) findViewById(R.id.button1);
  b.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    if (!flip) {
     FragmentHorizontal fh = new FragmentHorizontal();
     FragmentManager fm = getFragmentManager();
     FragmentTransaction ft = fm.beginTransaction();
     ft.replace(R.id.layoutToReplace, fh);
     ft.commit();
     flip = true;
    } else {
     FragmentVertical fv = new FragmentVertical();
     FragmentManager fm = getFragmentManager();
     FragmentTransaction ft = fm.beginTransaction();
     ft.replace(R.id.layoutToReplace, fv);
     ft.commit();
     flip = false;
    }
   }
  });
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

}
If everything went well, you should see something like that.



So that's all. I hope you like it. Have fun! ;-)

Thursday, October 3, 2013

Drawing and mixing colors with Canvas / View in Android

Hi there! Today i'm gonna share something very cool with you. It took me a lot of days "scanning" the web for a solution, trying by myself, debugging and so on. unfortunately i didn't find i solution that fits my needs. Solving problems using canvas can be very exciting, but it can also quickly become very depressing if you do not have the perseverance necessary or basic understanding of how things work at this level

To prevent you from getting discouraged, I'll share my solution with you. Here's the challange: Draw arbitrary shapes (splashes) in different colors in a view.(using canvas, drawing methods, paints etc.) Be able to drag and drop it around the view. If the splashes intersects, the intersection should be colored according to the color mixture of the two parts. Then i should be able to read the pixel of the new color to use it the way i want. (Ohhhh my god...!!!!) That's a challange right? ;-) 

So i did it!!! ;-) There is a lot of fragments out there, but nothing complete and because i know this is a very common task desired by many developers, i took the time to do it as clean and reusable as i could.  The result will be something like this:









ShapeTypes

Let's define the shape types we wanna use it first. For sure you could enhance it by adding new types if you need.




public enum ShapeType {
    CIRCLE, RECTANBLE, ARBITRARY_SHAPE
}


ShapeFactory

Then let's define a class that is responsible for creating the shapes. In our cases the splashes. The empty cases can be enhenced by you by adding new shapes to it.




public class ShapeFactory {

    public static void createShape(Path path, float shapeSizeFactor, final int randomShapeId) {

        switch (randomShapeId) {
        case 1:
            path.moveTo(379 * shapeSizeFactor, 616 * shapeSizeFactor);
            int[] shapeCoordinates;
            shapeCoordinates = new int[] { 379, 616, 379, 614, 379, 613, 379, 612, 379, 610, 379, 609, 379, 607, 381, 600, 383, 593, 384, 585, 381, 565, 375, 555, 361, 530, 354, 517,
                    345, 503, 337, 489, 315, 457, 303, 441, 292, 425, 283, 412, 274, 398, 266, 384, 261, 373, 256, 363, 253, 354, 252, 334, 253, 322, 255, 309, 258, 298, 262, 288,
                    266, 280, 270, 272, 276, 265, 283, 256, 294, 249, 304, 244, 314, 239, 322, 236, 329, 235, 336, 235, 355, 237, 373, 242, 380, 245, 390, 251, 393, 254, 402, 263,
                    408, 269, 416, 278, 422, 289, 430, 300, 437, 313, 450, 340, 453, 355, 456, 369, 458, 381, 461, 402, 462, 412, 464, 421, 465, 430, 466, 438, 467, 446, 470, 453,
                    476, 465, 481, 470, 490, 474, 499, 476, 510, 478, 520, 477, 529, 475, 538, 472, 545, 468, 553, 462, 559, 455, 565, 446, 570, 435, 578, 414, 581, 402, 588, 374,
                    595, 347, 599, 333, 610, 300, 616, 287, 622, 275, 630, 265, 639, 256, 648, 247, 656, 239, 665, 232, 672, 226, 681, 222, 692, 217, 704, 215, 713, 213, 728, 213,
                    734, 213, 740, 214, 747, 215, 753, 218, 759, 221, 765, 226, 771, 231, 777, 237, 781, 245, 785, 252, 786, 259, 788, 267, 788, 276, 788, 285, 787, 294, 784, 303,
                    783, 312, 780, 320, 776, 328, 771, 336, 767, 345, 762, 354, 752, 374, 748, 385, 744, 394, 741, 403, 735, 414, 729, 427, 721, 440, 715, 452, 708, 463, 702, 474,
                    694, 486, 690, 499, 685, 511, 678, 534, 677, 542, 676, 553, 677, 558, 684, 563, 693, 563, 705, 560, 718, 554, 730, 547, 747, 539, 785, 520, 800, 512, 835, 496,
                    850, 490, 871, 481, 880, 478, 888, 477, 894, 476, 900, 476, 906, 477, 910, 478, 915, 481, 918, 484, 927, 492, 931, 500, 933, 507, 935, 515, 936, 522, 937, 537,
                    933, 552, 930, 561, 919, 575, 912, 581, 904, 588, 896, 594, 888, 599, 879, 603, 855, 611, 839, 614, 827, 616, 818, 620, 801, 630, 793, 637, 782, 653, 780, 661,
                    780, 670, 782, 678, 789, 689, 797, 703, 806, 718, 829, 750, 842, 768, 853, 787, 861, 802, 869, 819, 879, 857, 882, 874, 885, 892, 887, 913, 888, 930, 887, 947,
                    885, 966, 880, 986, 873, 1002, 863, 1019, 849, 1035, 832, 1049, 795, 1064, 771, 1062, 757, 1051, 744, 1037, 730, 1018, 719, 996, 712, 975, 708, 954, 706, 907, 706,
                    888, 704, 868, 701, 853, 695, 839, 688, 828, 663, 813, 637, 813, 616, 817, 600, 826, 567, 863, 558, 883, 554, 908, 554, 933, 570, 982, 581, 1006, 590, 1029, 598,
                    1055, 602, 1104, 597, 1133, 588, 1161, 573, 1191, 533, 1244, 505, 1268, 477, 1286, 402, 1315, 364, 1324, 339, 1326, 286, 1316, 271, 1306, 253, 1291, 236, 1269,
                    219, 1227, 219, 1205, 222, 1188, 228, 1172, 256, 1137, 275, 1116, 300, 1094, 321, 1074, 356, 1032, 373, 1011, 385, 991, 394, 961, 393, 947, 388, 934, 368, 915,
                    352, 907, 314, 892, 305, 886, 309, 866, 332, 831, 344, 814, 363, 782, 367, 764, 365, 759, 354, 750, 344, 748, 330, 747, 310, 747, 278, 754, 243, 769, 195, 799,
                    160, 810, 142, 809, 136, 805, 124, 789, 119, 774, 116, 760, 115, 743, 117, 710, 121, 695, 126, 681, 140, 652, 151, 641, 168, 629, 192, 619, 204, 619, 218, 619,
                    231, 620, 253, 626, 261, 629, 269, 633, 278, 636, 306, 637, 318, 634, 330, 631, 342, 627, 370, 615, 373, 614, 373, 614 };
            for (int i = 0; i < shapeCoordinates.length - 2; i += 2) {
                path.lineTo(shapeCoordinates[i] * shapeSizeFactor, shapeCoordinates[i + 1] * shapeSizeFactor);
            }
            path.close();
            break;
        case 2:

            break;
        case 3:

            break;
        default:
            break;
        }
   
    }

}


ObjectView

That's the class responsible for drawing the shapes.




import java.util.Random;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelXorXfermode;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class ObjectView extends View {

    private Paint paint;
    private boolean isTouched;
    private ShapeType shapeType;
    private float shapeSizeFactor;

    public ObjectView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}
    public ObjectView(Context context, AttributeSet attrs) {super(context, attrs);}

    public ObjectView(Context context, int shapeColor, int initialPositionX, int initialPositionY, ShapeType type) {
        super(context);
        setBackgroundColor(Color.TRANSPARENT);
        paint = new Paint();
        paint.setColor(shapeColor);
        paint.setXfermode(new PorterDuffXfermode(Mode.ADD));
        // paint.setXfermode(new PixelXorXfermode(0xFFFFFFFF));
        shapeType = type;
        setTranslationX(initialPositionX);
        setTranslationY(initialPositionY);
    }

    public ObjectView(Context context, int shapeColor, int initialPositionX, int initialPositionY, ShapeType type, float shapeSizeFactor) {
        this(context, shapeColor, initialPositionX, initialPositionY, type);
        this.shapeSizeFactor = shapeSizeFactor;
    }

    public void onDrawEx(Canvas canvas) {
        switch (shapeType) {
        case CIRCLE:
            int circleXCoordinate = getWidth() / 2;
            int circleYCoordinate = getHeight() / 2;
            int circleRadius = getWidth() / 2;
            canvas.drawCircle(circleXCoordinate, circleYCoordinate, circleRadius, paint);
            break;
        case RECTANBLE:
            float left = 0;
            float top = 0;
            float right = getWidth();
            float bottom = getHeight();
            canvas.drawRect(left, top, right, bottom, paint);
            break;
        case ARBITRARY_SHAPE:
            Path path = new Path();
            path.addPath(path, new Matrix());
            int min = 1;
            int max = 10;
            Random randomShapeIdGenerator = new Random();
            int randomShapeId = randomShapeIdGenerator.nextInt(max - min + 1) + min;
            ShapeFactory.createShape(path, this.shapeSizeFactor, /*randomShapeId*/1);
            canvas.drawPath(path, paint);
            break;
        }
    }

    public boolean isTouched() {return isTouched;}
    public void setTouched(boolean touched) {isTouched = touched;}

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        isTouched = true;
        return super.onTouchEvent(event);
    }
}


ObjectViewLayout

thats the main layout that holds all views and it is responsible for moving the shapes around and find out the intersection's color, if the shapes are mixed in the middle of the screen.




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

public class ObjectViewLayout extends FrameLayout {

    public ObjectViewLayout(Context context) {
        super(context);
    }

    public ObjectViewLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ObjectViewLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_UP:
            for (int i = 0; i < getChildCount() - 1; i++) {
                ObjectView objectView = (ObjectView) getChildAt(i);
                objectView.setTouched(false);
            }
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:

            for (int i = 0; i < getChildCount() - 1; i++) {
                ObjectView objectView = (ObjectView) getChildAt(i);
                if (objectView.isTouched()) {
                    objectView.setTranslationX(event.getX() - objectView.getWidth() / 2);
                    objectView.setTranslationY(event.getY() - objectView.getHeight() / 2);

                    break;
                }
            }
        }
        // THE NEXT LINE REPRESENTS/CALLS THE OVERLAYVIEW
        getChildAt(getChildCount() - 1).invalidate();
        // THIS LINE SETS/CHECKS MIXED COLORS TO USE THE WAY YOU MAY WANT
        new Colorize().setColorToPen(getColorFromMiddleOfThisObject());
        return true;
    }

    public int getColorFromMiddleOfThisObject() {
        Bitmap returnedBitmap = getBitmapFromView(this);
        int pixel = returnedBitmap.getPixel(this.getWidth()/2, this.getHeight()/2);
        Log.i("TEST", "Pixel value ObjectViewLayout: "+pixel);
        return pixel;
    }
   
    private Bitmap getBitmapFromView(View view) {
        Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);
        Drawable backgroundDrawable = view.getBackground();
        if (backgroundDrawable != null)
            backgroundDrawable.draw(canvas);
        else
            // does not have background drawable, then draw dark grey background
            // on the canvas to be able to detect it.
            canvas.drawColor(Color.DKGRAY);
        view.draw(canvas);
        return returnedBitmap;
    }

}


OverlayView

That's the view responsible for triggering the repaint/redrawing process.




import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

@SuppressLint("DrawAllocation")
public class OverlayView extends View {

    public OverlayView(Context context) {
        super(context);
    }

    public OverlayView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public OverlayView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
        Canvas canvasBitmap = new Canvas(bitmap);
        ViewGroup viewGroup = (ViewGroup) getParent();
        for (int i = 0; i < viewGroup.getChildCount() - 1; i++) {

            ObjectView objectView = (ObjectView) viewGroup.getChildAt(i);
            canvasBitmap.save();
            canvasBitmap.translate(objectView.getTranslationX(), objectView.getTranslationY());
            objectView.onDrawEx(canvasBitmap);
            canvasBitmap.restore();
        }
        float left = 0;
        float top = 0;
        canvas.drawBitmap(bitmap, left, top, new Paint());

    }

}


Colorize

This is the class that interprets the mixed pixel color. Here you could do / set what ever you want.




import android.graphics.Color;
import android.util.Log;

public class Colorize {
   
    public void setColorToPen(int pixelColor) {
       

        if (pixelColor == Color.MAGENTA) {
            Log.i("TEST", "It is MAGENTA");
        }
        if (pixelColor == Color.RED) {
            Log.i("TEST", "It is RED");
        }
        if (pixelColor == Color.BLUE) {
            Log.i("TEST", "It is BLUE");
        }
        if (pixelColor == Color.GREEN) {
            Log.i("TEST", "It is GREEN");
        }
        if (pixelColor == Color.YELLOW) {
            Log.i("TEST", "It is YELLOW");
        }
        if (pixelColor == Color.CYAN) {
            Log.i("TEST", "It is CYAN");
        }
        if (pixelColor == Color.BLACK) {
            Log.i("TEST", "It is BLACK");
        }
        if (pixelColor == Color.TRANSPARENT) {
            Log.i("TEST", "It is TRANSPARENT");
        }
        if (pixelColor == Color.WHITE) {
            Log.i("TEST", "It is WHITE");
        }
        if (pixelColor == Color.GRAY) {
            Log.i("TEST", "It is GREY");
        }
    }

}


MainActivity

Finally let's take a look at the main activity class.




import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.widget.FrameLayout;

public class MainActivity extends Activity {

    private OverlayView overlayView;
    private ObjectViewLayout objectViewLayout;
    private ObjectView shape1;
    private ObjectView shape2;
    private ObjectView shape3;
    private int initialPositionX = 70;
    private int initialPositionY = 70;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        float sizeFactor = 0.1f;
        objectViewLayout = new ObjectViewLayout(this);

        initialPositionX = 70;
        initialPositionY = 70;
        shape1 = new ObjectView(this, Color.RED, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
        objectViewLayout.addView(shape1, new FrameLayout.LayoutParams(150, 150));

        initialPositionX = 140;
        initialPositionY = 140;
        shape2 = new ObjectView(this, Color.BLUE, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
        objectViewLayout.addView(shape2, new FrameLayout.LayoutParams(150, 150));

        int initialPositionX = 210;
        int initialPositionY = 210;
        shape3 = new ObjectView(this, Color.GREEN, initialPositionX, initialPositionY, ShapeType.ARBITRARY_SHAPE, sizeFactor);
        objectViewLayout.addView(shape3, new FrameLayout.LayoutParams(150, 150));

        overlayView = new OverlayView(this);
        objectViewLayout.addView(overlayView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));

        setContentView(objectViewLayout);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}


Thats all. Hope you like it. :)  If you run this project now, you should get the result i showed to you at the beginning of this post.