402 lines
14 KiB
Java
402 lines
14 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
|
|
}
|