Add prebuilt browseable samples as static files.
Change-Id: Ifb5382223343400882834d2dd9c182c3df602e34
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* This is an example of keeping track of individual touches across multiple
|
||||
* {@link android.view.MotionEvent}s.
|
||||
* <p>
|
||||
* This is illustrated by a View ({@link TouchDisplayView}) that responds to
|
||||
* touch events and draws coloured circles for each pointer, stores the last
|
||||
* positions of this pointer and draws them. This example shows the relationship
|
||||
* between MotionEvent indices, pointer identifiers and actions.
|
||||
*
|
||||
* @see android.view.MotionEvent
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
TouchDisplayView mView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.layout_mainactivity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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;
|
||||
|
||||
/**
|
||||
* Helper class for crating pools of objects. An example use looks like this:
|
||||
* <pre>
|
||||
* public class MyPooledClass {
|
||||
*
|
||||
* private static final SynchronizedPool<MyPooledClass> sPool =
|
||||
* new SynchronizedPool<MyPooledClass>(10);
|
||||
*
|
||||
* public static MyPooledClass obtain() {
|
||||
* MyPooledClass instance = sPool.acquire();
|
||||
* return (instance != null) ? instance : new MyPooledClass();
|
||||
* }
|
||||
*
|
||||
* public void recycle() {
|
||||
* // Clear state if needed.
|
||||
* sPool.release(this);
|
||||
* }
|
||||
*
|
||||
* . . .
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class Pools {
|
||||
|
||||
/**
|
||||
* Interface for managing a pool of objects.
|
||||
*
|
||||
* @param <T> The pooled type.
|
||||
*/
|
||||
public static interface Pool<T> {
|
||||
|
||||
/**
|
||||
* @return An instance from the pool if such, null otherwise.
|
||||
*/
|
||||
public T acquire();
|
||||
|
||||
/**
|
||||
* Release an instance to the pool.
|
||||
*
|
||||
* @param instance The instance to release.
|
||||
* @return Whether the instance was put in the pool.
|
||||
*
|
||||
* @throws IllegalStateException If the instance is already in the pool.
|
||||
*/
|
||||
public boolean release(T instance);
|
||||
}
|
||||
|
||||
private Pools() {
|
||||
/* do nothing - hiding constructor */
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple (non-synchronized) pool of objects.
|
||||
*
|
||||
* @param <T> The pooled type.
|
||||
*/
|
||||
public static class SimplePool<T> implements Pool<T> {
|
||||
private final Object[] mPool;
|
||||
|
||||
private int mPoolSize;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxPoolSize The max pool size.
|
||||
*
|
||||
* @throws IllegalArgumentException If the max pool size is less than zero.
|
||||
*/
|
||||
public SimplePool(int maxPoolSize) {
|
||||
if (maxPoolSize <= 0) {
|
||||
throw new IllegalArgumentException("The max pool size must be > 0");
|
||||
}
|
||||
mPool = new Object[maxPoolSize];
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T acquire() {
|
||||
if (mPoolSize > 0) {
|
||||
final int lastPooledIndex = mPoolSize - 1;
|
||||
T instance = (T) mPool[lastPooledIndex];
|
||||
mPool[lastPooledIndex] = null;
|
||||
mPoolSize--;
|
||||
return instance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(T instance) {
|
||||
if (isInPool(instance)) {
|
||||
throw new IllegalStateException("Already in the pool!");
|
||||
}
|
||||
if (mPoolSize < mPool.length) {
|
||||
mPool[mPoolSize] = instance;
|
||||
mPoolSize++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isInPool(T instance) {
|
||||
for (int i = 0; i < mPoolSize; i++) {
|
||||
if (mPool[i] == instance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronized) pool of objects.
|
||||
*
|
||||
* @param <T> The pooled type.
|
||||
*/
|
||||
public static class SynchronizedPool<T> extends SimplePool<T> {
|
||||
private final Object mLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxPoolSize The max pool size.
|
||||
*
|
||||
* @throws IllegalArgumentException If the max pool size is less than zero.
|
||||
*/
|
||||
public SynchronizedPool(int maxPoolSize) {
|
||||
super(maxPoolSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T acquire() {
|
||||
synchronized (mLock) {
|
||||
return super.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(T element) {
|
||||
synchronized (mLock) {
|
||||
return super.release(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user