1*90c8c64dSAndroid Build Coastguard Worker /*
2*90c8c64dSAndroid Build Coastguard Worker  * Copyright (C) 2013 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker  *
4*90c8c64dSAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker  *
8*90c8c64dSAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker  *
10*90c8c64dSAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker  * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker  */
16*90c8c64dSAndroid Build Coastguard Worker 
17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.basicmultitouch;
18*90c8c64dSAndroid Build Coastguard Worker 
19*90c8c64dSAndroid Build Coastguard Worker import android.content.Context;
20*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Canvas;
21*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Color;
22*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Paint;
23*90c8c64dSAndroid Build Coastguard Worker import android.graphics.PointF;
24*90c8c64dSAndroid Build Coastguard Worker import android.util.AttributeSet;
25*90c8c64dSAndroid Build Coastguard Worker import android.util.SparseArray;
26*90c8c64dSAndroid Build Coastguard Worker import android.view.MotionEvent;
27*90c8c64dSAndroid Build Coastguard Worker import android.view.View;
28*90c8c64dSAndroid Build Coastguard Worker 
29*90c8c64dSAndroid Build Coastguard Worker import com.example.android.basicmultitouch.Pools.SimplePool;
30*90c8c64dSAndroid Build Coastguard Worker 
31*90c8c64dSAndroid Build Coastguard Worker /**
32*90c8c64dSAndroid Build Coastguard Worker  * View that shows touch events and their history. This view demonstrates the
33*90c8c64dSAndroid Build Coastguard Worker  * use of {@link #onTouchEvent(android.view.MotionEvent)} and {@link android.view.MotionEvent}s to keep
34*90c8c64dSAndroid Build Coastguard Worker  * track of touch pointers across events.
35*90c8c64dSAndroid Build Coastguard Worker  */
36*90c8c64dSAndroid Build Coastguard Worker public class TouchDisplayView extends View {
37*90c8c64dSAndroid Build Coastguard Worker 
38*90c8c64dSAndroid Build Coastguard Worker     // Hold data for active touch pointer IDs
39*90c8c64dSAndroid Build Coastguard Worker     private SparseArray<TouchHistory> mTouches;
40*90c8c64dSAndroid Build Coastguard Worker 
41*90c8c64dSAndroid Build Coastguard Worker     // Is there an active touch?
42*90c8c64dSAndroid Build Coastguard Worker     private boolean mHasTouch = false;
43*90c8c64dSAndroid Build Coastguard Worker 
44*90c8c64dSAndroid Build Coastguard Worker     /**
45*90c8c64dSAndroid Build Coastguard Worker      * Holds data related to a touch pointer, including its current position,
46*90c8c64dSAndroid Build Coastguard Worker      * pressure and historical positions. Objects are allocated through an
47*90c8c64dSAndroid Build Coastguard Worker      * object pool using {@link #obtain()} and {@link #recycle()} to reuse
48*90c8c64dSAndroid Build Coastguard Worker      * existing objects.
49*90c8c64dSAndroid Build Coastguard Worker      */
50*90c8c64dSAndroid Build Coastguard Worker     static final class TouchHistory {
51*90c8c64dSAndroid Build Coastguard Worker 
52*90c8c64dSAndroid Build Coastguard Worker         // number of historical points to store
53*90c8c64dSAndroid Build Coastguard Worker         public static final int HISTORY_COUNT = 20;
54*90c8c64dSAndroid Build Coastguard Worker 
55*90c8c64dSAndroid Build Coastguard Worker         public float x;
56*90c8c64dSAndroid Build Coastguard Worker         public float y;
57*90c8c64dSAndroid Build Coastguard Worker         public float pressure = 0f;
58*90c8c64dSAndroid Build Coastguard Worker         public String label = null;
59*90c8c64dSAndroid Build Coastguard Worker 
60*90c8c64dSAndroid Build Coastguard Worker         // current position in history array
61*90c8c64dSAndroid Build Coastguard Worker         public int historyIndex = 0;
62*90c8c64dSAndroid Build Coastguard Worker         public int historyCount = 0;
63*90c8c64dSAndroid Build Coastguard Worker 
64*90c8c64dSAndroid Build Coastguard Worker         // arrray of pointer position history
65*90c8c64dSAndroid Build Coastguard Worker         public PointF[] history = new PointF[HISTORY_COUNT];
66*90c8c64dSAndroid Build Coastguard Worker 
67*90c8c64dSAndroid Build Coastguard Worker         private static final int MAX_POOL_SIZE = 10;
68*90c8c64dSAndroid Build Coastguard Worker         private static final SimplePool<TouchHistory> sPool =
69*90c8c64dSAndroid Build Coastguard Worker                 new SimplePool<TouchHistory>(MAX_POOL_SIZE);
70*90c8c64dSAndroid Build Coastguard Worker 
obtain(float x, float y, float pressure)71*90c8c64dSAndroid Build Coastguard Worker         public static TouchHistory obtain(float x, float y, float pressure) {
72*90c8c64dSAndroid Build Coastguard Worker             TouchHistory data = sPool.acquire();
73*90c8c64dSAndroid Build Coastguard Worker             if (data == null) {
74*90c8c64dSAndroid Build Coastguard Worker                 data = new TouchHistory();
75*90c8c64dSAndroid Build Coastguard Worker             }
76*90c8c64dSAndroid Build Coastguard Worker 
77*90c8c64dSAndroid Build Coastguard Worker             data.setTouch(x, y, pressure);
78*90c8c64dSAndroid Build Coastguard Worker 
79*90c8c64dSAndroid Build Coastguard Worker             return data;
80*90c8c64dSAndroid Build Coastguard Worker         }
81*90c8c64dSAndroid Build Coastguard Worker 
TouchHistory()82*90c8c64dSAndroid Build Coastguard Worker         public TouchHistory() {
83*90c8c64dSAndroid Build Coastguard Worker 
84*90c8c64dSAndroid Build Coastguard Worker             // initialise history array
85*90c8c64dSAndroid Build Coastguard Worker             for (int i = 0; i < HISTORY_COUNT; i++) {
86*90c8c64dSAndroid Build Coastguard Worker                 history[i] = new PointF();
87*90c8c64dSAndroid Build Coastguard Worker             }
88*90c8c64dSAndroid Build Coastguard Worker         }
89*90c8c64dSAndroid Build Coastguard Worker 
setTouch(float x, float y, float pressure)90*90c8c64dSAndroid Build Coastguard Worker         public void setTouch(float x, float y, float pressure) {
91*90c8c64dSAndroid Build Coastguard Worker             this.x = x;
92*90c8c64dSAndroid Build Coastguard Worker             this.y = y;
93*90c8c64dSAndroid Build Coastguard Worker             this.pressure = pressure;
94*90c8c64dSAndroid Build Coastguard Worker         }
95*90c8c64dSAndroid Build Coastguard Worker 
recycle()96*90c8c64dSAndroid Build Coastguard Worker         public void recycle() {
97*90c8c64dSAndroid Build Coastguard Worker             this.historyIndex = 0;
98*90c8c64dSAndroid Build Coastguard Worker             this.historyCount = 0;
99*90c8c64dSAndroid Build Coastguard Worker             sPool.release(this);
100*90c8c64dSAndroid Build Coastguard Worker         }
101*90c8c64dSAndroid Build Coastguard Worker 
102*90c8c64dSAndroid Build Coastguard Worker         /**
103*90c8c64dSAndroid Build Coastguard Worker          * Add a point to its history. Overwrites oldest point if the maximum
104*90c8c64dSAndroid Build Coastguard Worker          * number of historical points is already stored.
105*90c8c64dSAndroid Build Coastguard Worker          *
106*90c8c64dSAndroid Build Coastguard Worker          * @param point
107*90c8c64dSAndroid Build Coastguard Worker          */
addHistory(float x, float y)108*90c8c64dSAndroid Build Coastguard Worker         public void addHistory(float x, float y) {
109*90c8c64dSAndroid Build Coastguard Worker             PointF p = history[historyIndex];
110*90c8c64dSAndroid Build Coastguard Worker             p.x = x;
111*90c8c64dSAndroid Build Coastguard Worker             p.y = y;
112*90c8c64dSAndroid Build Coastguard Worker 
113*90c8c64dSAndroid Build Coastguard Worker             historyIndex = (historyIndex + 1) % history.length;
114*90c8c64dSAndroid Build Coastguard Worker 
115*90c8c64dSAndroid Build Coastguard Worker             if (historyCount < HISTORY_COUNT) {
116*90c8c64dSAndroid Build Coastguard Worker                 historyCount++;
117*90c8c64dSAndroid Build Coastguard Worker             }
118*90c8c64dSAndroid Build Coastguard Worker         }
119*90c8c64dSAndroid Build Coastguard Worker 
120*90c8c64dSAndroid Build Coastguard Worker     }
121*90c8c64dSAndroid Build Coastguard Worker 
TouchDisplayView(Context context, AttributeSet attrs)122*90c8c64dSAndroid Build Coastguard Worker     public TouchDisplayView(Context context, AttributeSet attrs) {
123*90c8c64dSAndroid Build Coastguard Worker         super(context, attrs);
124*90c8c64dSAndroid Build Coastguard Worker 
125*90c8c64dSAndroid Build Coastguard Worker         // SparseArray for touch events, indexed by touch id
126*90c8c64dSAndroid Build Coastguard Worker         mTouches = new SparseArray<TouchHistory>(10);
127*90c8c64dSAndroid Build Coastguard Worker 
128*90c8c64dSAndroid Build Coastguard Worker         initialisePaint();
129*90c8c64dSAndroid Build Coastguard Worker     }
130*90c8c64dSAndroid Build Coastguard Worker 
131*90c8c64dSAndroid Build Coastguard Worker     // BEGIN_INCLUDE(onTouchEvent)
132*90c8c64dSAndroid Build Coastguard Worker     @Override
onTouchEvent(MotionEvent event)133*90c8c64dSAndroid Build Coastguard Worker     public boolean onTouchEvent(MotionEvent event) {
134*90c8c64dSAndroid Build Coastguard Worker 
135*90c8c64dSAndroid Build Coastguard Worker         final int action = event.getAction();
136*90c8c64dSAndroid Build Coastguard Worker 
137*90c8c64dSAndroid Build Coastguard Worker         /*
138*90c8c64dSAndroid Build Coastguard Worker          * Switch on the action. The action is extracted from the event by
139*90c8c64dSAndroid Build Coastguard Worker          * applying the MotionEvent.ACTION_MASK. Alternatively a call to
140*90c8c64dSAndroid Build Coastguard Worker          * event.getActionMasked() would yield in the action as well.
141*90c8c64dSAndroid Build Coastguard Worker          */
142*90c8c64dSAndroid Build Coastguard Worker         switch (action & MotionEvent.ACTION_MASK) {
143*90c8c64dSAndroid Build Coastguard Worker 
144*90c8c64dSAndroid Build Coastguard Worker             case MotionEvent.ACTION_DOWN: {
145*90c8c64dSAndroid Build Coastguard Worker                 // first pressed gesture has started
146*90c8c64dSAndroid Build Coastguard Worker 
147*90c8c64dSAndroid Build Coastguard Worker                 /*
148*90c8c64dSAndroid Build Coastguard Worker                  * Only one touch event is stored in the MotionEvent. Extract
149*90c8c64dSAndroid Build Coastguard Worker                  * the pointer identifier of this touch from the first index
150*90c8c64dSAndroid Build Coastguard Worker                  * within the MotionEvent object.
151*90c8c64dSAndroid Build Coastguard Worker                  */
152*90c8c64dSAndroid Build Coastguard Worker                 int id = event.getPointerId(0);
153*90c8c64dSAndroid Build Coastguard Worker 
154*90c8c64dSAndroid Build Coastguard Worker                 TouchHistory data = TouchHistory.obtain(event.getX(0), event.getY(0),
155*90c8c64dSAndroid Build Coastguard Worker                         event.getPressure(0));
156*90c8c64dSAndroid Build Coastguard Worker                 data.label = "id: " + 0;
157*90c8c64dSAndroid Build Coastguard Worker 
158*90c8c64dSAndroid Build Coastguard Worker                 /*
159*90c8c64dSAndroid Build Coastguard Worker                  * Store the data under its pointer identifier. The pointer
160*90c8c64dSAndroid Build Coastguard Worker                  * number stays consistent for the duration of a gesture,
161*90c8c64dSAndroid Build Coastguard Worker                  * accounting for other pointers going up or down.
162*90c8c64dSAndroid Build Coastguard Worker                  */
163*90c8c64dSAndroid Build Coastguard Worker                 mTouches.put(id, data);
164*90c8c64dSAndroid Build Coastguard Worker 
165*90c8c64dSAndroid Build Coastguard Worker                 mHasTouch = true;
166*90c8c64dSAndroid Build Coastguard Worker 
167*90c8c64dSAndroid Build Coastguard Worker                 break;
168*90c8c64dSAndroid Build Coastguard Worker             }
169*90c8c64dSAndroid Build Coastguard Worker 
170*90c8c64dSAndroid Build Coastguard Worker             case MotionEvent.ACTION_POINTER_DOWN: {
171*90c8c64dSAndroid Build Coastguard Worker                 /*
172*90c8c64dSAndroid Build Coastguard Worker                  * A non-primary pointer has gone down, after an event for the
173*90c8c64dSAndroid Build Coastguard Worker                  * primary pointer (ACTION_DOWN) has already been received.
174*90c8c64dSAndroid Build Coastguard Worker                  */
175*90c8c64dSAndroid Build Coastguard Worker 
176*90c8c64dSAndroid Build Coastguard Worker                 /*
177*90c8c64dSAndroid Build Coastguard Worker                  * The MotionEvent object contains multiple pointers. Need to
178*90c8c64dSAndroid Build Coastguard Worker                  * extract the index at which the data for this particular event
179*90c8c64dSAndroid Build Coastguard Worker                  * is stored.
180*90c8c64dSAndroid Build Coastguard Worker                  */
181*90c8c64dSAndroid Build Coastguard Worker                 int index = event.getActionIndex();
182*90c8c64dSAndroid Build Coastguard Worker                 int id = event.getPointerId(index);
183*90c8c64dSAndroid Build Coastguard Worker 
184*90c8c64dSAndroid Build Coastguard Worker                 TouchHistory data = TouchHistory.obtain(event.getX(index), event.getY(index),
185*90c8c64dSAndroid Build Coastguard Worker                         event.getPressure(index));
186*90c8c64dSAndroid Build Coastguard Worker                 data.label = "id: " + id;
187*90c8c64dSAndroid Build Coastguard Worker 
188*90c8c64dSAndroid Build Coastguard Worker                 /*
189*90c8c64dSAndroid Build Coastguard Worker                  * Store the data under its pointer identifier. The index of
190*90c8c64dSAndroid Build Coastguard Worker                  * this pointer can change over multiple events, but this
191*90c8c64dSAndroid Build Coastguard Worker                  * pointer is always identified by the same identifier for this
192*90c8c64dSAndroid Build Coastguard Worker                  * active gesture.
193*90c8c64dSAndroid Build Coastguard Worker                  */
194*90c8c64dSAndroid Build Coastguard Worker                 mTouches.put(id, data);
195*90c8c64dSAndroid Build Coastguard Worker 
196*90c8c64dSAndroid Build Coastguard Worker                 break;
197*90c8c64dSAndroid Build Coastguard Worker             }
198*90c8c64dSAndroid Build Coastguard Worker 
199*90c8c64dSAndroid Build Coastguard Worker             case MotionEvent.ACTION_UP: {
200*90c8c64dSAndroid Build Coastguard Worker                 /*
201*90c8c64dSAndroid Build Coastguard Worker                  * Final pointer has gone up and has ended the last pressed
202*90c8c64dSAndroid Build Coastguard Worker                  * gesture.
203*90c8c64dSAndroid Build Coastguard Worker                  */
204*90c8c64dSAndroid Build Coastguard Worker 
205*90c8c64dSAndroid Build Coastguard Worker                 /*
206*90c8c64dSAndroid Build Coastguard Worker                  * Extract the pointer identifier for the only event stored in
207*90c8c64dSAndroid Build Coastguard Worker                  * the MotionEvent object and remove it from the list of active
208*90c8c64dSAndroid Build Coastguard Worker                  * touches.
209*90c8c64dSAndroid Build Coastguard Worker                  */
210*90c8c64dSAndroid Build Coastguard Worker                 int id = event.getPointerId(0);
211*90c8c64dSAndroid Build Coastguard Worker                 TouchHistory data = mTouches.get(id);
212*90c8c64dSAndroid Build Coastguard Worker                 mTouches.remove(id);
213*90c8c64dSAndroid Build Coastguard Worker                 data.recycle();
214*90c8c64dSAndroid Build Coastguard Worker 
215*90c8c64dSAndroid Build Coastguard Worker                 mHasTouch = false;
216*90c8c64dSAndroid Build Coastguard Worker 
217*90c8c64dSAndroid Build Coastguard Worker                 break;
218*90c8c64dSAndroid Build Coastguard Worker             }
219*90c8c64dSAndroid Build Coastguard Worker 
220*90c8c64dSAndroid Build Coastguard Worker             case MotionEvent.ACTION_POINTER_UP: {
221*90c8c64dSAndroid Build Coastguard Worker                 /*
222*90c8c64dSAndroid Build Coastguard Worker                  * A non-primary pointer has gone up and other pointers are
223*90c8c64dSAndroid Build Coastguard Worker                  * still active.
224*90c8c64dSAndroid Build Coastguard Worker                  */
225*90c8c64dSAndroid Build Coastguard Worker 
226*90c8c64dSAndroid Build Coastguard Worker                 /*
227*90c8c64dSAndroid Build Coastguard Worker                  * The MotionEvent object contains multiple pointers. Need to
228*90c8c64dSAndroid Build Coastguard Worker                  * extract the index at which the data for this particular event
229*90c8c64dSAndroid Build Coastguard Worker                  * is stored.
230*90c8c64dSAndroid Build Coastguard Worker                  */
231*90c8c64dSAndroid Build Coastguard Worker                 int index = event.getActionIndex();
232*90c8c64dSAndroid Build Coastguard Worker                 int id = event.getPointerId(index);
233*90c8c64dSAndroid Build Coastguard Worker 
234*90c8c64dSAndroid Build Coastguard Worker                 TouchHistory data = mTouches.get(id);
235*90c8c64dSAndroid Build Coastguard Worker                 mTouches.remove(id);
236*90c8c64dSAndroid Build Coastguard Worker                 data.recycle();
237*90c8c64dSAndroid Build Coastguard Worker 
238*90c8c64dSAndroid Build Coastguard Worker                 break;
239*90c8c64dSAndroid Build Coastguard Worker             }
240*90c8c64dSAndroid Build Coastguard Worker 
241*90c8c64dSAndroid Build Coastguard Worker             case MotionEvent.ACTION_MOVE: {
242*90c8c64dSAndroid Build Coastguard Worker                 /*
243*90c8c64dSAndroid Build Coastguard Worker                  * A change event happened during a pressed gesture. (Between
244*90c8c64dSAndroid Build Coastguard Worker                  * ACTION_DOWN and ACTION_UP or ACTION_POINTER_DOWN and
245*90c8c64dSAndroid Build Coastguard Worker                  * ACTION_POINTER_UP)
246*90c8c64dSAndroid Build Coastguard Worker                  */
247*90c8c64dSAndroid Build Coastguard Worker 
248*90c8c64dSAndroid Build Coastguard Worker                 /*
249*90c8c64dSAndroid Build Coastguard Worker                  * Loop through all active pointers contained within this event.
250*90c8c64dSAndroid Build Coastguard Worker                  * Data for each pointer is stored in a MotionEvent at an index
251*90c8c64dSAndroid Build Coastguard Worker                  * (starting from 0 up to the number of active pointers). This
252*90c8c64dSAndroid Build Coastguard Worker                  * loop goes through each of these active pointers, extracts its
253*90c8c64dSAndroid Build Coastguard Worker                  * data (position and pressure) and updates its stored data. A
254*90c8c64dSAndroid Build Coastguard Worker                  * pointer is identified by its pointer number which stays
255*90c8c64dSAndroid Build Coastguard Worker                  * constant across touch events as long as it remains active.
256*90c8c64dSAndroid Build Coastguard Worker                  * This identifier is used to keep track of a pointer across
257*90c8c64dSAndroid Build Coastguard Worker                  * events.
258*90c8c64dSAndroid Build Coastguard Worker                  */
259*90c8c64dSAndroid Build Coastguard Worker                 for (int index = 0; index < event.getPointerCount(); index++) {
260*90c8c64dSAndroid Build Coastguard Worker                     // get pointer id for data stored at this index
261*90c8c64dSAndroid Build Coastguard Worker                     int id = event.getPointerId(index);
262*90c8c64dSAndroid Build Coastguard Worker 
263*90c8c64dSAndroid Build Coastguard Worker                     // get the data stored externally about this pointer.
264*90c8c64dSAndroid Build Coastguard Worker                     TouchHistory data = mTouches.get(id);
265*90c8c64dSAndroid Build Coastguard Worker 
266*90c8c64dSAndroid Build Coastguard Worker                     // add previous position to history and add new values
267*90c8c64dSAndroid Build Coastguard Worker                     data.addHistory(data.x, data.y);
268*90c8c64dSAndroid Build Coastguard Worker                     data.setTouch(event.getX(index), event.getY(index),
269*90c8c64dSAndroid Build Coastguard Worker                             event.getPressure(index));
270*90c8c64dSAndroid Build Coastguard Worker 
271*90c8c64dSAndroid Build Coastguard Worker                 }
272*90c8c64dSAndroid Build Coastguard Worker 
273*90c8c64dSAndroid Build Coastguard Worker                 break;
274*90c8c64dSAndroid Build Coastguard Worker             }
275*90c8c64dSAndroid Build Coastguard Worker         }
276*90c8c64dSAndroid Build Coastguard Worker 
277*90c8c64dSAndroid Build Coastguard Worker         // trigger redraw on UI thread
278*90c8c64dSAndroid Build Coastguard Worker         this.postInvalidate();
279*90c8c64dSAndroid Build Coastguard Worker 
280*90c8c64dSAndroid Build Coastguard Worker         return true;
281*90c8c64dSAndroid Build Coastguard Worker     }
282*90c8c64dSAndroid Build Coastguard Worker 
283*90c8c64dSAndroid Build Coastguard Worker     // END_INCLUDE(onTouchEvent)
284*90c8c64dSAndroid Build Coastguard Worker 
285*90c8c64dSAndroid Build Coastguard Worker     @Override
onDraw(Canvas canvas)286*90c8c64dSAndroid Build Coastguard Worker     protected void onDraw(Canvas canvas) {
287*90c8c64dSAndroid Build Coastguard Worker         super.onDraw(canvas);
288*90c8c64dSAndroid Build Coastguard Worker 
289*90c8c64dSAndroid Build Coastguard Worker         // Canvas background color depends on whether there is an active touch
290*90c8c64dSAndroid Build Coastguard Worker         if (mHasTouch) {
291*90c8c64dSAndroid Build Coastguard Worker             canvas.drawColor(BACKGROUND_ACTIVE);
292*90c8c64dSAndroid Build Coastguard Worker         } else {
293*90c8c64dSAndroid Build Coastguard Worker             // draw inactive border
294*90c8c64dSAndroid Build Coastguard Worker             canvas.drawRect(mBorderWidth, mBorderWidth, getWidth() - mBorderWidth, getHeight()
295*90c8c64dSAndroid Build Coastguard Worker                     - mBorderWidth, mBorderPaint);
296*90c8c64dSAndroid Build Coastguard Worker         }
297*90c8c64dSAndroid Build Coastguard Worker 
298*90c8c64dSAndroid Build Coastguard Worker         // loop through all active touches and draw them
299*90c8c64dSAndroid Build Coastguard Worker         for (int i = 0; i < mTouches.size(); i++) {
300*90c8c64dSAndroid Build Coastguard Worker 
301*90c8c64dSAndroid Build Coastguard Worker             // get the pointer id and associated data for this index
302*90c8c64dSAndroid Build Coastguard Worker             int id = mTouches.keyAt(i);
303*90c8c64dSAndroid Build Coastguard Worker             TouchHistory data = mTouches.valueAt(i);
304*90c8c64dSAndroid Build Coastguard Worker 
305*90c8c64dSAndroid Build Coastguard Worker             // draw the data and its history to the canvas
306*90c8c64dSAndroid Build Coastguard Worker             drawCircle(canvas, id, data);
307*90c8c64dSAndroid Build Coastguard Worker         }
308*90c8c64dSAndroid Build Coastguard Worker     }
309*90c8c64dSAndroid Build Coastguard Worker 
310*90c8c64dSAndroid Build Coastguard Worker     /*
311*90c8c64dSAndroid Build Coastguard Worker      * Below are only helper methods and variables required for drawing.
312*90c8c64dSAndroid Build Coastguard Worker      */
313*90c8c64dSAndroid Build Coastguard Worker 
314*90c8c64dSAndroid Build Coastguard Worker     // radius of active touch circle in dp
315*90c8c64dSAndroid Build Coastguard Worker     private static final float CIRCLE_RADIUS_DP = 75f;
316*90c8c64dSAndroid Build Coastguard Worker     // radius of historical circle in dp
317*90c8c64dSAndroid Build Coastguard Worker     private static final float CIRCLE_HISTORICAL_RADIUS_DP = 7f;
318*90c8c64dSAndroid Build Coastguard Worker 
319*90c8c64dSAndroid Build Coastguard Worker     // calculated radiuses in px
320*90c8c64dSAndroid Build Coastguard Worker     private float mCircleRadius;
321*90c8c64dSAndroid Build Coastguard Worker     private float mCircleHistoricalRadius;
322*90c8c64dSAndroid Build Coastguard Worker 
323*90c8c64dSAndroid Build Coastguard Worker     private Paint mCirclePaint = new Paint();
324*90c8c64dSAndroid Build Coastguard Worker     private Paint mTextPaint = new Paint();
325*90c8c64dSAndroid Build Coastguard Worker 
326*90c8c64dSAndroid Build Coastguard Worker     private static final int BACKGROUND_ACTIVE = Color.WHITE;
327*90c8c64dSAndroid Build Coastguard Worker 
328*90c8c64dSAndroid Build Coastguard Worker     // inactive border
329*90c8c64dSAndroid Build Coastguard Worker     private static final float INACTIVE_BORDER_DP = 15f;
330*90c8c64dSAndroid Build Coastguard Worker     private static final int INACTIVE_BORDER_COLOR = 0xFFffd060;
331*90c8c64dSAndroid Build Coastguard Worker     private Paint mBorderPaint = new Paint();
332*90c8c64dSAndroid Build Coastguard Worker     private float mBorderWidth;
333*90c8c64dSAndroid Build Coastguard Worker 
334*90c8c64dSAndroid Build Coastguard Worker     public final int[] COLORS = {
335*90c8c64dSAndroid Build Coastguard Worker             0xFF33B5E5, 0xFFAA66CC, 0xFF99CC00, 0xFFFFBB33, 0xFFFF4444,
336*90c8c64dSAndroid Build Coastguard Worker             0xFF0099CC, 0xFF9933CC, 0xFF669900, 0xFFFF8800, 0xFFCC0000
337*90c8c64dSAndroid Build Coastguard Worker     };
338*90c8c64dSAndroid Build Coastguard Worker 
339*90c8c64dSAndroid Build Coastguard Worker     /**
340*90c8c64dSAndroid Build Coastguard Worker      * Sets up the required {@link android.graphics.Paint} objects for the screen density of this
341*90c8c64dSAndroid Build Coastguard Worker      * device.
342*90c8c64dSAndroid Build Coastguard Worker      */
initialisePaint()343*90c8c64dSAndroid Build Coastguard Worker     private void initialisePaint() {
344*90c8c64dSAndroid Build Coastguard Worker 
345*90c8c64dSAndroid Build Coastguard Worker         // Calculate radiuses in px from dp based on screen density
346*90c8c64dSAndroid Build Coastguard Worker         float density = getResources().getDisplayMetrics().density;
347*90c8c64dSAndroid Build Coastguard Worker         mCircleRadius = CIRCLE_RADIUS_DP * density;
348*90c8c64dSAndroid Build Coastguard Worker         mCircleHistoricalRadius = CIRCLE_HISTORICAL_RADIUS_DP * density;
349*90c8c64dSAndroid Build Coastguard Worker 
350*90c8c64dSAndroid Build Coastguard Worker         // Setup text paint for circle label
351*90c8c64dSAndroid Build Coastguard Worker         mTextPaint.setTextSize(27f);
352*90c8c64dSAndroid Build Coastguard Worker         mTextPaint.setColor(Color.BLACK);
353*90c8c64dSAndroid Build Coastguard Worker 
354*90c8c64dSAndroid Build Coastguard Worker         // Setup paint for inactive border
355*90c8c64dSAndroid Build Coastguard Worker         mBorderWidth = INACTIVE_BORDER_DP * density;
356*90c8c64dSAndroid Build Coastguard Worker         mBorderPaint.setStrokeWidth(mBorderWidth);
357*90c8c64dSAndroid Build Coastguard Worker         mBorderPaint.setColor(INACTIVE_BORDER_COLOR);
358*90c8c64dSAndroid Build Coastguard Worker         mBorderPaint.setStyle(Paint.Style.STROKE);
359*90c8c64dSAndroid Build Coastguard Worker 
360*90c8c64dSAndroid Build Coastguard Worker     }
361*90c8c64dSAndroid Build Coastguard Worker 
362*90c8c64dSAndroid Build Coastguard Worker     /**
363*90c8c64dSAndroid Build Coastguard Worker      * Draws the data encapsulated by a {@link TouchDisplayView.TouchHistory} object to a canvas.
364*90c8c64dSAndroid Build Coastguard Worker      * A large circle indicates the current position held by the
365*90c8c64dSAndroid Build Coastguard Worker      * {@link TouchDisplayView.TouchHistory} object, while a smaller circle is drawn for each
366*90c8c64dSAndroid Build Coastguard Worker      * entry in its history. The size of the large circle is scaled depending on
367*90c8c64dSAndroid Build Coastguard Worker      * its pressure, clamped to a maximum of <code>1.0</code>.
368*90c8c64dSAndroid Build Coastguard Worker      *
369*90c8c64dSAndroid Build Coastguard Worker      * @param canvas
370*90c8c64dSAndroid Build Coastguard Worker      * @param id
371*90c8c64dSAndroid Build Coastguard Worker      * @param data
372*90c8c64dSAndroid Build Coastguard Worker      */
drawCircle(Canvas canvas, int id, TouchHistory data)373*90c8c64dSAndroid Build Coastguard Worker     protected void drawCircle(Canvas canvas, int id, TouchHistory data) {
374*90c8c64dSAndroid Build Coastguard Worker         // select the color based on the id
375*90c8c64dSAndroid Build Coastguard Worker         int color = COLORS[id % COLORS.length];
376*90c8c64dSAndroid Build Coastguard Worker         mCirclePaint.setColor(color);
377*90c8c64dSAndroid Build Coastguard Worker 
378*90c8c64dSAndroid Build Coastguard Worker         /*
379*90c8c64dSAndroid Build Coastguard Worker          * Draw the circle, size scaled to its pressure. Pressure is clamped to
380*90c8c64dSAndroid Build Coastguard Worker          * 1.0 max to ensure proper drawing. (Reported pressure values can
381*90c8c64dSAndroid Build Coastguard Worker          * exceed 1.0, depending on the calibration of the touch screen).
382*90c8c64dSAndroid Build Coastguard Worker          */
383*90c8c64dSAndroid Build Coastguard Worker         float pressure = Math.min(data.pressure, 1f);
384*90c8c64dSAndroid Build Coastguard Worker         float radius = pressure * mCircleRadius;
385*90c8c64dSAndroid Build Coastguard Worker 
386*90c8c64dSAndroid Build Coastguard Worker         canvas.drawCircle(data.x, (data.y) - (radius / 2f), radius,
387*90c8c64dSAndroid Build Coastguard Worker                 mCirclePaint);
388*90c8c64dSAndroid Build Coastguard Worker 
389*90c8c64dSAndroid Build Coastguard Worker         // draw all historical points with a lower alpha value
390*90c8c64dSAndroid Build Coastguard Worker         mCirclePaint.setAlpha(125);
391*90c8c64dSAndroid Build Coastguard Worker         for (int j = 0; j < data.history.length && j < data.historyCount; j++) {
392*90c8c64dSAndroid Build Coastguard Worker             PointF p = data.history[j];
393*90c8c64dSAndroid Build Coastguard Worker             canvas.drawCircle(p.x, p.y, mCircleHistoricalRadius, mCirclePaint);
394*90c8c64dSAndroid Build Coastguard Worker         }
395*90c8c64dSAndroid Build Coastguard Worker 
396*90c8c64dSAndroid Build Coastguard Worker         // draw its label next to the main circle
397*90c8c64dSAndroid Build Coastguard Worker         canvas.drawText(data.label, data.x + radius, data.y
398*90c8c64dSAndroid Build Coastguard Worker                 - radius, mTextPaint);
399*90c8c64dSAndroid Build Coastguard Worker     }
400*90c8c64dSAndroid Build Coastguard Worker 
401*90c8c64dSAndroid Build Coastguard Worker }
402