Android Tutorial - User Event : Touch

 Report your Multitouch operation

   
package app.test;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;

public class Test extends Activity implements OnTouchListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        RelativeLayout layout1 = (RelativeLayout) findViewById(R.id.layout1);
        layout1.setOnTouchListener(this);
    }

  public boolean onTouch(View v, MotionEvent event) {
    String myTag = v.getTag().toString();
    Log.v(myTag, describeEvent(event));
    logAction(event);
    if( "true".equals(myTag.substring(0, 4))) {
      return true;
    }
    else {
      return false;
    }
  }

  protected static String describeEvent(MotionEvent event) {
    StringBuilder result = new StringBuilder(500);
    result.append("Action: ").append(event.getAction()).append("\n");
    int numPointers = event.getPointerCount();
    result.append("Number of pointers: ").append(numPointers).append("\n");
        int ptrIdx = 0;
    while (ptrIdx < numPointers) {
        int ptrId = event.getPointerId(ptrIdx);
            result.append("Pointer Index: ").append(ptrIdx);
            result.append(", Pointer Id: ").append(ptrId).append("\n");
            result.append("   Location: ").append(event.getX(ptrIdx));
            result.append(" x ").append(event.getY(ptrIdx)).append("\n");
            result.append("   Pressure: ").append(event.getPressure(ptrIdx));
            result.append("   Size: ").append(event.getSize(ptrIdx)).append("\n");

            ptrIdx++;
        }
        result.append("Downtime: ").append(event.getDownTime()).append("ms\n");
        result.append("Event time: ").append(event.getEventTime()).append("ms");
        result.append("  Elapsed: ");
        result.append(event.getEventTime()-event.getDownTime());
        result.append(" ms\n");
        return result.toString();
    }

  private void logAction(MotionEvent event) {
    int action = event.getAction();
    int ptrIndex = (action & MotionEvent.ACTION_POINTER_ID_MASK) >>> MotionEvent.ACTION_POINTER_ID_SHIFT;
    action = action & MotionEvent.ACTION_MASK;
    if(action == 5 || action == 6)
      action = action - 5;
    int ptrId = event.getPointerId(ptrIndex);
    Log.v("Action", "Pointer index: " + ptrIndex);
    Log.v("Action", "Pointer Id: " + ptrId);
    Log.v("Action", "True action value: " + action);
  }
}

//main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is /res/layout/main.xml -->
<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout1"  android:tag="trueLayout"
    android:orientation="vertical"
    android:layout_width="fill_parent"  android:layout_height="wrap_content"
    android:layout_weight="1" >

    <TextView android:text="Touch fingers on the screen and look at LogCat"
    android:id="@+id/message"  android:tag="trueText"
    android:layout_width="wrap_content"  android:layout_height="wrap_content"
    android:layout_alignParentBottom="true" />
</RelativeLayout>

Touch screen

   

package app.test;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.RelativeLayout;

class TrueButton extends BooleanButton {
  protected boolean myValue() {
    return true;
  }

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

abstract class BooleanButton extends Button {
  protected boolean myValue() {
    return false;
  }

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

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    String myTag = this.getTag().toString();
    Log.v(myTag, Test.describeEvent(this, event));
    Log.v(myTag, "super onTouchEvent() returns " + super.onTouchEvent(event));
    Log.v(myTag, "and I'm returning " + myValue());
    return (myValue());
  }
}

class FalseButton extends BooleanButton {

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

public class Test extends Activity implements OnTouchListener {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    RelativeLayout layout1 = (RelativeLayout) findViewById(R.id.layout1);
    layout1.setOnTouchListener(this);
    Button trueBtn1 = (Button) findViewById(R.id.trueBtn1);
    trueBtn1.setOnTouchListener(this);
    Button falseBtn1 = (Button) findViewById(R.id.falseBtn1);
    falseBtn1.setOnTouchListener(this);

    RelativeLayout layout2 = (RelativeLayout) findViewById(R.id.layout2);
    layout2.setOnTouchListener(this);
    Button trueBtn2 = (Button) findViewById(R.id.trueBtn2);
    trueBtn2.setOnTouchListener(this);
    Button falseBtn2 = (Button) findViewById(R.id.falseBtn2);
    falseBtn2.setOnTouchListener(this);
  }

  public boolean onTouch(View v, MotionEvent event) {
    String myTag = v.getTag().toString();
    Log.v(myTag, describeEvent(v, event));
    if ("true".equals(myTag.substring(0, 4))) {
      return true;
    } else {
      return false;
    }
  }

  protected static String describeEvent(View view, MotionEvent event) {
    StringBuilder result = new StringBuilder(300);
    result.append("Action: ").append(event.getAction()).append("\n");
    result.append("Location: ").append(event.getX()).append(" x ")
        .append(event.getY()).append("\n");
    if (event.getX() < 0 || event.getX() > view.getWidth()
        || event.getY() < 0 || event.getY() > view.getHeight()) {
      result.append(">>> Touch has left the view <<<\n");
    }
    result.append("Edge flags: ").append(event.getEdgeFlags()).append("\n");
    result.append("Pressure: ").append(event.getPressure()).append("   ");
    result.append("Size: ").append(event.getSize()).append("\n");
    result.append("Down time: ").append(event.getDownTime()).append("ms\n");
    result.append("Event time: ").append(event.getEventTime()).append("ms");
    result.append("  Elapsed: ").append(
        event.getEventTime() - event.getDownTime());
    result.append(" ms\n");
    return result.toString();
  }
}
//main.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is res/layout/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"  android:layout_height="fill_parent"
    android:orientation="vertical" >

  <RelativeLayout  android:id="@+id/layout1"
    android:tag="trueLayoutTop"  android:orientation="vertical"
    android:layout_width="fill_parent"  android:layout_height="wrap_content"
    android:layout_weight="1" >

    <com.androidbook.touch.demo1.TrueButton android:text="returns true" 
    android:id="@+id/trueBtn1"  android:tag="trueBtnTop"
    android:layout_width="wrap_content"  android:layout_height="wrap_content" />

    <com.androidbook.touch.demo1.FalseButton android:text="returns false"
    android:id="@+id/falseBtn1"  android:tag="falseBtnTop"
    android:layout_width="wrap_content"  android:layout_height="wrap_content"
    android:layout_below="@id/trueBtn1" />

  </RelativeLayout>
  <RelativeLayout  android:id="@+id/layout2"
    android:tag="falseLayoutBottom"  android:orientation="vertical"
    android:layout_width="fill_parent"  android:layout_height="wrap_content"
    android:layout_weight="1"  android:background="#FF00FF" >

    <com.androidbook.touch.demo1.TrueButton android:text="returns true" 
    android:id="@+id/trueBtn2"  android:tag="trueBtnBottom"
    android:layout_width="wrap_content"  android:layout_height="wrap_content" />

    <com.androidbook.touch.demo1.FalseButton android:text="returns false"
    android:id="@+id/falseBtn2"  android:tag="falseBtnBottom"
    android:layout_width="wrap_content"  android:layout_height="wrap_content"
    android:layout_below="@id/trueBtn2" />

  </RelativeLayout>
</LinearLayout>

Single Touch Test

   

package app.test;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;

public class Test extends Activity implements OnTouchListener {
  StringBuilder builder = new StringBuilder();
  TextView textView;

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    textView = new TextView(this);
    textView.setText("Touch and drag (one finger only)!");
    textView.setOnTouchListener(this);
    setContentView(textView);
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    builder.setLength(0);
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      builder.append("down, ");
      break;
    case MotionEvent.ACTION_MOVE:
      builder.append("move, ");
      break;
    case MotionEvent.ACTION_CANCEL:
      builder.append("cancle, ");
      break;
    case MotionEvent.ACTION_UP:
      builder.append("up, ");
      break;
    }
    builder.append(event.getX());
    builder.append(", ");
    builder.append(event.getY());
    String text = builder.toString();
    Log.d("TouchTest", text);
    textView.setText(text);
    return true;
  }
}

Testing the Multitouch API

   
package app.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;

public class Test extends Activity implements OnTouchListener {
  StringBuilder builder = new StringBuilder();
  TextView textView;
  float[] x = new float[10];
  float[] y = new float[10];
  boolean[] touched = new boolean[10];

  private void updateTextView() {
    builder.setLength(0);
    for (int i = 0; i < 10; i++) {
      builder.append(touched[i]);
      builder.append(", ");
      builder.append(x[i]);
      builder.append(", ");
      builder.append(y[i]);
      builder.append("\n");
    }
    textView.setText(builder.toString());
  }

  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    textView = new TextView(this);
    textView.setText("Touch and drag multiple fingers supported.");
    textView.setOnTouchListener(this);
    setContentView(textView);
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction() & MotionEvent.ACTION_MASK;
    int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
    int pointerId = event.getPointerId(pointerIndex);
    switch (action) {
    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_POINTER_DOWN:
      touched[pointerId] = true;
      x[pointerId] = (int) event.getX(pointerIndex);
      y[pointerId] = (int) event.getY(pointerIndex);
      break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
    case MotionEvent.ACTION_CANCEL:
      touched[pointerId] = false;
      x[pointerId] = (int) event.getX(pointerIndex);
      y[pointerId] = (int) event.getY(pointerIndex);
      break;
    case MotionEvent.ACTION_MOVE:
      int pointerCount = event.getPointerCount();
      for (int i = 0; i < pointerCount; i++) {
        pointerIndex = i;
        pointerId = event.getPointerId(pointerIndex);
        x[pointerId] = (int) event.getX(pointerIndex);
        y[pointerId] = (int) event.getY(pointerIndex);
      }
      break;
    }
    updateTextView();
    return true;
  }
}
Demonstrates the handling of touch screen and trackball events to implement a simple painting app.
package app.test;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

public class Test extends GraphicsActivity {
  /** Used as a pulse to gradually fade the contents of the window. */
  private static final int FADE_MSG = 1;

  /** Menu ID for the command to clear the window. */
  private static final int CLEAR_ID = Menu.FIRST;
  /** Menu ID for the command to toggle fading. */
  private static final int FADE_ID = Menu.FIRST+1;

  /** How often to fade the contents of the window (in ms). */
  private static final int FADE_DELAY = 100;

  /** The view responsible for drawing the window. */
  MyView mView;
  /** Is fading mode enabled? */
  boolean mFading;

  @Override protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      // Create and attach the view that is responsible for painting.
      mView = new MyView(this);
      setContentView(mView);
      mView.requestFocus();

      // Restore the fading option if we are being thawed from a
      // previously saved state.  Note that we are not currently remembering
      // the contents of the bitmap.
      mFading = savedInstanceState != null ? savedInstanceState.getBoolean("fading", true) : true;
  }

  @Override public boolean onCreateOptionsMenu(Menu menu) {
      menu.add(0, CLEAR_ID, 0, "Clear");
      menu.add(0, FADE_ID, 0, "Fade").setCheckable(true);
      return super.onCreateOptionsMenu(menu);
  }

  @Override public boolean onPrepareOptionsMenu(Menu menu) {
      menu.findItem(FADE_ID).setChecked(mFading);
      return super.onPrepareOptionsMenu(menu);
  }

  @Override public boolean onOptionsItemSelected(MenuItem item) {
      switch (item.getItemId()) {
          case CLEAR_ID:
              mView.clear();
              return true;
          case FADE_ID:
              mFading = !mFading;
              if (mFading) {
                  startFading();
              } else {
                  stopFading();
              }
              return true;
          default:
              return super.onOptionsItemSelected(item);
      }
  }

  @Override protected void onResume() {
      super.onResume();
      // If fading mode is enabled, then as long as we are resumed we want
      // to run pulse to fade the contents.
      if (mFading) {
          startFading();
      }
  }

  @Override protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      // Save away the fading state to restore if needed later.  Note that
      // we do not currently save the contents of the display.
      outState.putBoolean("fading", mFading);
  }

  @Override protected void onPause() {
      super.onPause();
      // Make sure to never run the fading pulse while we are paused or
      // stopped.
      stopFading();
  }

  /**
   * Start up the pulse to fade the screen, clearing any existing pulse to
   * ensure that we don't have multiple pulses running at a time.
   */
  void startFading() {
      mHandler.removeMessages(FADE_MSG);
      mHandler.sendMessageDelayed(
              mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
  }

  /**
   * Stop the pulse to fade the screen.
   */
  void stopFading() {
      mHandler.removeMessages(FADE_MSG);
  }

  private Handler mHandler = new Handler() {
      @Override public void handleMessage(Message msg) {
          switch (msg.what) {
              // Upon receiving the fade pulse, we have the view perform a
              // fade and then enqueue a new message to pulse at the desired
              // next time.
              case FADE_MSG: {
                  mView.fade();
                  mHandler.sendMessageDelayed(
                          mHandler.obtainMessage(FADE_MSG), FADE_DELAY);
                  break;
              }
              default:
                  super.handleMessage(msg);
          }
      }
  };

  public class MyView extends View {
      private static final int FADE_ALPHA = 0x06;
      private static final int MAX_FADE_STEPS = 256/FADE_ALPHA + 4;
      private static final int TRACKBALL_SCALE = 10;

      private Bitmap mBitmap;
      private Canvas mCanvas;
      private final Rect mRect = new Rect();
      private final Paint mPaint;
      private final Paint mFadePaint;
      private float mCurX;
      private float mCurY;
      private int mFadeSteps = MAX_FADE_STEPS;

      public MyView(Context c) {
          super(c);
          setFocusable(true);
          mPaint = new Paint();
          mPaint.setAntiAlias(true);
          mPaint.setARGB(255, 255, 255, 255);
          mFadePaint = new Paint();
          mFadePaint.setDither(true);
          mFadePaint.setARGB(FADE_ALPHA, 0, 0, 0);
      }

      public void clear() {
          if (mCanvas != null) {
              mPaint.setARGB(0xff, 0, 0, 0);
              mCanvas.drawPaint(mPaint);
              invalidate();
              mFadeSteps = MAX_FADE_STEPS;
          }
      }

      public void fade() {
          if (mCanvas != null && mFadeSteps < MAX_FADE_STEPS) {
              mCanvas.drawPaint(mFadePaint);
              invalidate();
              mFadeSteps++;
          }
      }

      @Override protected void onSizeChanged(int w, int h, int oldw,
              int oldh) {
          int curW = mBitmap != null ? mBitmap.getWidth() : 0;
          int curH = mBitmap != null ? mBitmap.getHeight() : 0;
          if (curW >= w && curH >= h) {
              return;
          }

          if (curW < w) curW = w;
          if (curH < h) curH = h;

          Bitmap newBitmap = Bitmap.createBitmap(curW, curH,
                                                 Bitmap.Config.RGB_565);
          Canvas newCanvas = new Canvas();
          newCanvas.setBitmap(newBitmap);
          if (mBitmap != null) {
              newCanvas.drawBitmap(mBitmap, 0, 0, null);
          }
          mBitmap = newBitmap;
          mCanvas = newCanvas;
          mFadeSteps = MAX_FADE_STEPS;
      }

      @Override protected void onDraw(Canvas canvas) {
          if (mBitmap != null) {
              canvas.drawBitmap(mBitmap, 0, 0, null);
          }
      }

      @Override public boolean onTrackballEvent(MotionEvent event) {
          int N = event.getHistorySize();
          final float scaleX = event.getXPrecision() * TRACKBALL_SCALE;
          final float scaleY = event.getYPrecision() * TRACKBALL_SCALE;
          for (int i=0; i<N; i++) {
              //Log.i("TouchPaint", "Intermediate trackball #" + i
              //        + ": x=" + event.getHistoricalX(i)
              //        + ", y=" + event.getHistoricalY(i));
              mCurX += event.getHistoricalX(i) * scaleX;
              mCurY += event.getHistoricalY(i) * scaleY;
              drawPoint(mCurX, mCurY, 1.0f, 16.0f);
          }
          //Log.i("TouchPaint", "Trackball: x=" + event.getX()
          //        + ", y=" + event.getY());
          mCurX += event.getX() * scaleX;
          mCurY += event.getY() * scaleY;
          drawPoint(mCurX, mCurY, 1.0f, 16.0f);
          return true;
      }

      @Override public boolean onTouchEvent(MotionEvent event) {
          int action = event.getActionMasked();
          if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL) {
              int N = event.getHistorySize();
              int P = event.getPointerCount();
              for (int i = 0; i < N; i++) {
                  for (int j = 0; j < P; j++) {
                      mCurX = event.getHistoricalX(j, i);
                      mCurY = event.getHistoricalY(j, i);
                      drawPoint(mCurX, mCurY,
                              event.getHistoricalPressure(j, i),
                              event.getHistoricalTouchMajor(j, i));
                  }
              }
              for (int j = 0; j < P; j++) {
                  mCurX = event.getX(j);
                  mCurY = event.getY(j);
                  drawPoint(mCurX, mCurY, event.getPressure(j), event.getTouchMajor(j));
              }
          }
          return true;
      }

      private void drawPoint(float x, float y, float pressure, float width) {
          //Log.i("TouchPaint", "Drawing: " + x + "x" + y + " p="
          //        + pressure + " width=" + width);
          if (width < 1) width = 1;
          if (mBitmap != null) {
              float radius = width / 2;
              int pressureLevel = (int)(pressure * 255);
              mPaint.setARGB(pressureLevel, 255, 255, 255);
              mCanvas.drawCircle(x, y, radius, mPaint);
              mRect.set((int) (x - radius - 2), (int) (y - radius - 2),
                      (int) (x + radius + 2), (int) (y + radius + 2));
              invalidate(mRect);
          }
          mFadeSteps = 0;
      }
  }
}

class GraphicsActivity extends Activity {
  // set to true to test Picture
  private static final boolean TEST_PICTURE = false;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }

  @Override
  public void setContentView(View view) {
    if (TEST_PICTURE) {
      ViewGroup vg = new PictureLayout(this);
      vg.addView(view);
      view = vg;
    }

    super.setContentView(view);
  }
}

class PictureLayout extends ViewGroup {
  private final Picture mPicture = new Picture();

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

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

  @Override
  public void addView(View child) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child);
  }

  @Override
  public void addView(View child, int index) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, index);
  }

  @Override
  public void addView(View child, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, params);
  }

  @Override
  public void addView(View child, int index, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }

    super.addView(child, index, params);
  }

  @Override
  protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT,
        LayoutParams.MATCH_PARENT);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int count = getChildCount();

    int maxHeight = 0;
    int maxWidth = 0;

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
      }
    }

    maxWidth += getPaddingLeft() + getPaddingRight();
    maxHeight += getPaddingTop() + getPaddingBottom();

    Drawable drawable = getBackground();
    if (drawable != null) {
      maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
      maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
        resolveSize(maxHeight, heightMeasureSpec));
  }

  private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
      float sy) {
    canvas.save();
    canvas.translate(x, y);
    canvas.clipRect(0, 0, w, h);
    canvas.scale(0.5f, 0.5f);
    canvas.scale(sx, sy, w, h);
    canvas.drawPicture(mPicture);
    canvas.restore();
  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
    mPicture.endRecording();

    int x = getWidth() / 2;
    int y = getHeight() / 2;

    if (false) {
      canvas.drawPicture(mPicture);
    } else {
      drawPict(canvas, 0, 0, x, y, 1, 1);
      drawPict(canvas, x, 0, x, y, -1, 1);
      drawPict(canvas, 0, y, x, y, 1, -1);
      drawPict(canvas, x, y, x, y, -1, -1);
    }
  }

  @Override
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    location[0] = getLeft();
    location[1] = getTop();
    dirty.set(0, 0, getWidth(), getHeight());
    return getParent();
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = super.getChildCount();

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        child.layout(childLeft, childTop,
            childLeft + child.getMeasuredWidth(),
            childTop + child.getMeasuredHeight());

      }
    }
  }
}
Demonstrates splitting touch events across multiple views within a view group
package com.example.android.apis.view;

import com.example.android.apis.R;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;


/**
 * Demonstrates splitting touch events across multiple views within a view group.
 */
public class SplitTouchView extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.split_touch_view);
        ListView list1 = (ListView) findViewById(R.id.list1);
        ListView list2 = (ListView) findViewById(R.id.list2);
        ListAdapter adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings);
        list1.setAdapter(adapter);
        list2.setAdapter(adapter);

        list1.setOnItemClickListener(itemClickListener);
        list2.setOnItemClickListener(itemClickListener);
    }

    private int responseIndex = 0;

    private final OnItemClickListener itemClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String[] responses = new String[]{};
            String response = responses[responseIndex++ % responses.length];

            String message = getResources().getString(R.string.split_touch_view_cheese_toast,
                    Cheeses.sCheeseStrings[position], response);

            Toast toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT);
            toast.show();
        }
    };
}
class Cheeses {

    public static final String[] sCheeseStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
    };

}


//layout/split_touch_view.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!-- Split touch demo. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/split_touch_view_description"/>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:splitMotionEvents="true">
        <ListView android:id="@+id/list1"
                  android:layout_width="0dip"
                  android:layout_height="match_parent"
                  android:layout_weight="1" />
        <ListView android:id="@+id/list2"
                  android:layout_width="0dip"
                  android:layout_height="match_parent"
                  android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

Using your finger to draw

   
package app.test;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class Test extends Activity implements OnTouchListener {
  ImageView imageView;
  Bitmap bitmap;
  Canvas canvas;
  Paint paint;
  float downx = 0, downy = 0, upx = 0, upy = 0;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    imageView = (ImageView) this.findViewById(R.id.ImageView);

    Display currentDisplay = getWindowManager().getDefaultDisplay();
    float dw = currentDisplay.getWidth();
    float dh = currentDisplay.getHeight();

    bitmap = Bitmap.createBitmap((int) dw, (int) dh,
        Bitmap.Config.ARGB_8888);
    canvas = new Canvas(bitmap);
    paint = new Paint();
    paint.setColor(Color.GREEN);
    imageView.setImageBitmap(bitmap);

    imageView.setOnTouchListener(this);
  }

  public boolean onTouch(View v, MotionEvent event) {
    int action = event.getAction();
    switch (action) {
    case MotionEvent.ACTION_DOWN:
      downx = event.getX();
      downy = event.getY();
      break;
    case MotionEvent.ACTION_MOVE:
      break;
    case MotionEvent.ACTION_UP:
      upx = event.getX();
      upy = event.getY();
      canvas.drawLine(downx, downy, upx, upy, paint);
      imageView.invalidate();
      break;
    case MotionEvent.ACTION_CANCEL:
      break;
    default:
      break;
    }
    return true;
  }
}