diff options
Diffstat (limited to 'samples/browseable/BasicMultitouch/src/com.example.android.basicmultitouch/TouchDisplayView.java')
| -rw-r--r-- | samples/browseable/BasicMultitouch/src/com.example.android.basicmultitouch/TouchDisplayView.java | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/samples/browseable/BasicMultitouch/src/com.example.android.basicmultitouch/TouchDisplayView.java b/samples/browseable/BasicMultitouch/src/com.example.android.basicmultitouch/TouchDisplayView.java new file mode 100644 index 000000000..78e6abea5 --- /dev/null +++ b/samples/browseable/BasicMultitouch/src/com.example.android.basicmultitouch/TouchDisplayView.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2013 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. + */ + +package com.example.android.basicmultitouch; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.view.View; + +import com.example.android.basicmultitouch.Pools.SimplePool; + +/** + * View that shows touch events and their history. This view demonstrates the + * use of {@link #onTouchEvent(android.view.MotionEvent)} and {@link android.view.MotionEvent}s to keep + * track of touch pointers across events. + */ +public class TouchDisplayView extends View { + + // Hold data for active touch pointer IDs + private SparseArray<TouchHistory> mTouches; + + // Is there an active touch? + private boolean mHasTouch = false; + + /** + * Holds data related to a touch pointer, including its current position, + * pressure and historical positions. Objects are allocated through an + * object pool using {@link #obtain()} and {@link #recycle()} to reuse + * existing objects. + */ + static final class TouchHistory { + + // number of historical points to store + public static final int HISTORY_COUNT = 20; + + public float x; + public float y; + public float pressure = 0f; + public String label = null; + + // current position in history array + public int historyIndex = 0; + public int historyCount = 0; + + // arrray of pointer position history + public PointF[] history = new PointF[HISTORY_COUNT]; + + private static final int MAX_POOL_SIZE = 10; + private static final SimplePool<TouchHistory> sPool = + new SimplePool<TouchHistory>(MAX_POOL_SIZE); + + public static TouchHistory obtain(float x, float y, float pressure) { + TouchHistory data = sPool.acquire(); + if (data == null) { + data = new TouchHistory(); + } + + data.setTouch(x, y, pressure); + + return data; + } + + public TouchHistory() { + + // initialise history array + for (int i = 0; i < HISTORY_COUNT; i++) { + history[i] = new PointF(); + } + } + + public void setTouch(float x, float y, float pressure) { + this.x = x; + this.y = y; + this.pressure = pressure; + } + + public void recycle() { + this.historyIndex = 0; + this.historyCount = 0; + sPool.release(this); + } + + /** + * Add a point to its history. Overwrites oldest point if the maximum + * number of historical points is already stored. + * + * @param point + */ + public void addHistory(float x, float y) { + PointF p = history[historyIndex]; + p.x = x; + p.y = y; + + historyIndex = (historyIndex + 1) % history.length; + + if (historyCount < HISTORY_COUNT) { + historyCount++; + } + } + + } + + public TouchDisplayView(Context context, AttributeSet attrs) { + super(context, attrs); + + // SparseArray for touch events, indexed by touch id + mTouches = new SparseArray<TouchHistory>(10); + + initialisePaint(); + } + + // BEGIN_INCLUDE(onTouchEvent) + @Override + public boolean onTouchEvent(MotionEvent event) { + + final int action = event.getAction(); + + /* + * Switch on the action. The action is extracted from the event by + * applying the MotionEvent.ACTION_MASK. Alternatively a call to + * event.getActionMasked() would yield in the action as well. + */ + switch (action & MotionEvent.ACTION_MASK) { + + case MotionEvent.ACTION_DOWN: { + // first pressed gesture has started + + /* + * Only one touch event is stored in the MotionEvent. Extract + * the pointer identifier of this touch from the first index + * within the MotionEvent object. + */ + int id = event.getPointerId(0); + + TouchHistory data = TouchHistory.obtain(event.getX(0), event.getY(0), + event.getPressure(0)); + data.label = "id: " + 0; + + /* + * Store the data under its pointer identifier. The pointer + * number stays consistent for the duration of a gesture, + * accounting for other pointers going up or down. + */ + mTouches.put(id, data); + + mHasTouch = true; + + break; + } + + case MotionEvent.ACTION_POINTER_DOWN: { + /* + * A non-primary pointer has gone down, after an event for the + * primary pointer (ACTION_DOWN) has already been received. + */ + + /* + * The MotionEvent object contains multiple pointers. Need to + * extract the index at which the data for this particular event + * is stored. + */ + int index = event.getActionIndex(); + int id = event.getPointerId(index); + + TouchHistory data = TouchHistory.obtain(event.getX(index), event.getY(index), + event.getPressure(index)); + data.label = "id: " + id; + + /* + * Store the data under its pointer identifier. The index of + * this pointer can change over multiple events, but this + * pointer is always identified by the same identifier for this + * active gesture. + */ + mTouches.put(id, data); + + break; + } + + case MotionEvent.ACTION_UP: { + /* + * Final pointer has gone up and has ended the last pressed + * gesture. + */ + + /* + * Extract the pointer identifier for the only event stored in + * the MotionEvent object and remove it from the list of active + * touches. + */ + int id = event.getPointerId(0); + TouchHistory data = mTouches.get(id); + mTouches.remove(id); + data.recycle(); + + mHasTouch = false; + + break; + } + + case MotionEvent.ACTION_POINTER_UP: { + /* + * A non-primary pointer has gone up and other pointers are + * still active. + */ + + /* + * The MotionEvent object contains multiple pointers. Need to + * extract the index at which the data for this particular event + * is stored. + */ + int index = event.getActionIndex(); + int id = event.getPointerId(index); + + TouchHistory data = mTouches.get(id); + mTouches.remove(id); + data.recycle(); + + break; + } + + case MotionEvent.ACTION_MOVE: { + /* + * A change event happened during a pressed gesture. (Between + * ACTION_DOWN and ACTION_UP or ACTION_POINTER_DOWN and + * ACTION_POINTER_UP) + */ + + /* + * Loop through all active pointers contained within this event. + * Data for each pointer is stored in a MotionEvent at an index + * (starting from 0 up to the number of active pointers). This + * loop goes through each of these active pointers, extracts its + * data (position and pressure) and updates its stored data. A + * pointer is identified by its pointer number which stays + * constant across touch events as long as it remains active. + * This identifier is used to keep track of a pointer across + * events. + */ + for (int index = 0; index < event.getPointerCount(); index++) { + // get pointer id for data stored at this index + int id = event.getPointerId(index); + + // get the data stored externally about this pointer. + TouchHistory data = mTouches.get(id); + + // add previous position to history and add new values + data.addHistory(data.x, data.y); + data.setTouch(event.getX(index), event.getY(index), + event.getPressure(index)); + + } + + break; + } + } + + // trigger redraw on UI thread + this.postInvalidate(); + + return true; + } + + // END_INCLUDE(onTouchEvent) + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Canvas background color depends on whether there is an active touch + if (mHasTouch) { + canvas.drawColor(BACKGROUND_ACTIVE); + } else { + // draw inactive border + canvas.drawRect(mBorderWidth, mBorderWidth, getWidth() - mBorderWidth, getHeight() + - mBorderWidth, mBorderPaint); + } + + // loop through all active touches and draw them + for (int i = 0; i < mTouches.size(); i++) { + + // get the pointer id and associated data for this index + int id = mTouches.keyAt(i); + TouchHistory data = mTouches.valueAt(i); + + // draw the data and its history to the canvas + drawCircle(canvas, id, data); + } + } + + /* + * Below are only helper methods and variables required for drawing. + */ + + // radius of active touch circle in dp + private static final float CIRCLE_RADIUS_DP = 75f; + // radius of historical circle in dp + private static final float CIRCLE_HISTORICAL_RADIUS_DP = 7f; + + // calculated radiuses in px + private float mCircleRadius; + private float mCircleHistoricalRadius; + + private Paint mCirclePaint = new Paint(); + private Paint mTextPaint = new Paint(); + + private static final int BACKGROUND_ACTIVE = Color.WHITE; + + // inactive border + private static final float INACTIVE_BORDER_DP = 15f; + private static final int INACTIVE_BORDER_COLOR = 0xFFffd060; + private Paint mBorderPaint = new Paint(); + private float mBorderWidth; + + public final int[] COLORS = { + 0xFF33B5E5, 0xFFAA66CC, 0xFF99CC00, 0xFFFFBB33, 0xFFFF4444, + 0xFF0099CC, 0xFF9933CC, 0xFF669900, 0xFFFF8800, 0xFFCC0000 + }; + + /** + * Sets up the required {@link android.graphics.Paint} objects for the screen density of this + * device. + */ + private void initialisePaint() { + + // Calculate radiuses in px from dp based on screen density + float density = getResources().getDisplayMetrics().density; + mCircleRadius = CIRCLE_RADIUS_DP * density; + mCircleHistoricalRadius = CIRCLE_HISTORICAL_RADIUS_DP * density; + + // Setup text paint for circle label + mTextPaint.setTextSize(27f); + mTextPaint.setColor(Color.BLACK); + + // Setup paint for inactive border + mBorderWidth = INACTIVE_BORDER_DP * density; + mBorderPaint.setStrokeWidth(mBorderWidth); + mBorderPaint.setColor(INACTIVE_BORDER_COLOR); + mBorderPaint.setStyle(Paint.Style.STROKE); + + } + + /** + * Draws the data encapsulated by a {@link TouchDisplayView.TouchHistory} object to a canvas. + * A large circle indicates the current position held by the + * {@link TouchDisplayView.TouchHistory} object, while a smaller circle is drawn for each + * entry in its history. The size of the large circle is scaled depending on + * its pressure, clamped to a maximum of <code>1.0</code>. + * + * @param canvas + * @param id + * @param data + */ + protected void drawCircle(Canvas canvas, int id, TouchHistory data) { + // select the color based on the id + int color = COLORS[id % COLORS.length]; + mCirclePaint.setColor(color); + + /* + * Draw the circle, size scaled to its pressure. Pressure is clamped to + * 1.0 max to ensure proper drawing. (Reported pressure values can + * exceed 1.0, depending on the calibration of the touch screen). + */ + float pressure = Math.min(data.pressure, 1f); + float radius = pressure * mCircleRadius; + + canvas.drawCircle(data.x, (data.y) - (radius / 2f), radius, + mCirclePaint); + + // draw all historical points with a lower alpha value + mCirclePaint.setAlpha(125); + for (int j = 0; j < data.history.length && j < data.historyCount; j++) { + PointF p = data.history[j]; + canvas.drawCircle(p.x, p.y, mCircleHistoricalRadius, mCirclePaint); + } + + // draw its label next to the main circle + canvas.drawText(data.label, data.x + radius, data.y + - radius, mTextPaint); + } + +} |
