PointerLocationView.java revision f8d0f095e34f8d661ca5b7d555d8610272099bff
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.Paint.FontMetricsInt;
23import android.util.Log;
24import android.view.MotionEvent;
25import android.view.VelocityTracker;
26import android.view.View;
27import android.view.ViewConfiguration;
28
29import java.util.ArrayList;
30
31public class PointerLocationView extends View {
32    public static class PointerState {
33        private final ArrayList<Float> mXs = new ArrayList<Float>();
34        private final ArrayList<Float> mYs = new ArrayList<Float>();
35        private boolean mCurDown;
36        private int mCurX;
37        private int mCurY;
38        private float mCurPressure;
39        private float mCurSize;
40        private int mCurWidth;
41        private VelocityTracker mVelocity;
42    }
43
44    private final ViewConfiguration mVC;
45    private final Paint mTextPaint;
46    private final Paint mTextBackgroundPaint;
47    private final Paint mTextLevelPaint;
48    private final Paint mPaint;
49    private final Paint mTargetPaint;
50    private final Paint mPathPaint;
51    private final FontMetricsInt mTextMetrics = new FontMetricsInt();
52    private int mHeaderBottom;
53    private boolean mCurDown;
54    private int mCurNumPointers;
55    private int mMaxNumPointers;
56    private final ArrayList<PointerState> mPointers
57             = new ArrayList<PointerState>();
58
59    private boolean mPrintCoords = true;
60
61    public PointerLocationView(Context c) {
62        super(c);
63        mVC = ViewConfiguration.get(c);
64        mTextPaint = new Paint();
65        mTextPaint.setAntiAlias(true);
66        mTextPaint.setTextSize(10
67                * getResources().getDisplayMetrics().density);
68        mTextPaint.setARGB(255, 0, 0, 0);
69        mTextBackgroundPaint = new Paint();
70        mTextBackgroundPaint.setAntiAlias(false);
71        mTextBackgroundPaint.setARGB(128, 255, 255, 255);
72        mTextLevelPaint = new Paint();
73        mTextLevelPaint.setAntiAlias(false);
74        mTextLevelPaint.setARGB(192, 255, 0, 0);
75        mPaint = new Paint();
76        mPaint.setAntiAlias(true);
77        mPaint.setARGB(255, 255, 255, 255);
78        mPaint.setStyle(Paint.Style.STROKE);
79        mPaint.setStrokeWidth(2);
80        mTargetPaint = new Paint();
81        mTargetPaint.setAntiAlias(false);
82        mTargetPaint.setARGB(255, 0, 0, 192);
83        mPathPaint = new Paint();
84        mPathPaint.setAntiAlias(false);
85        mPathPaint.setARGB(255, 0, 96, 255);
86        mPaint.setStyle(Paint.Style.STROKE);
87        mPaint.setStrokeWidth(1);
88
89        PointerState ps = new PointerState();
90        ps.mVelocity = VelocityTracker.obtain();
91        mPointers.add(ps);
92    }
93
94    public void setPrintCoords(boolean state) {
95        mPrintCoords = state;
96    }
97
98    @Override
99    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
100        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
101        mTextPaint.getFontMetricsInt(mTextMetrics);
102        mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2;
103        if (false) {
104            Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
105                    + " descent=" + mTextMetrics.descent
106                    + " leading=" + mTextMetrics.leading
107                    + " top=" + mTextMetrics.top
108                    + " bottom=" + mTextMetrics.bottom);
109        }
110    }
111
112    @Override
113    protected void onDraw(Canvas canvas) {
114        synchronized (mPointers) {
115            final int w = getWidth();
116            final int itemW = w/7;
117            final int base = -mTextMetrics.ascent+1;
118            final int bottom = mHeaderBottom;
119
120            final int NP = mPointers.size();
121
122            if (NP > 0) {
123                final PointerState ps = mPointers.get(0);
124                canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
125                canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers,
126                        1, base, mTextPaint);
127
128                final int N = ps.mXs.size();
129                if ((mCurDown && ps.mCurDown) || N == 0) {
130                    canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
131                    canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint);
132                    canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
133                    canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint);
134                } else {
135                    float dx = ps.mXs.get(N-1) - ps.mXs.get(0);
136                    float dy = ps.mYs.get(N-1) - ps.mYs.get(0);
137                    canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
138                            Math.abs(dx) < mVC.getScaledTouchSlop()
139                            ? mTextBackgroundPaint : mTextLevelPaint);
140                    canvas.drawText("dX: " + String.format("%.1f", dx), 1 + itemW, base, mTextPaint);
141                    canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
142                            Math.abs(dy) < mVC.getScaledTouchSlop()
143                            ? mTextBackgroundPaint : mTextLevelPaint);
144                    canvas.drawText("dY: " + String.format("%.1f", dy), 1 + itemW * 2, base, mTextPaint);
145                }
146
147                canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
148                int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000);
149                canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint);
150
151                canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
152                velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000);
153                canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint);
154
155                canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
156                canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1,
157                        bottom, mTextLevelPaint);
158                canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5,
159                        base, mTextPaint);
160
161                canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
162                canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1,
163                        bottom, mTextLevelPaint);
164                canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6,
165                        base, mTextPaint);
166            }
167
168            for (int p=0; p<NP; p++) {
169                final PointerState ps = mPointers.get(p);
170
171                if (mCurDown && ps.mCurDown) {
172                    canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint);
173                    canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint);
174                    int pressureLevel = (int)(ps.mCurPressure*255);
175                    mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
176                    canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint);
177                    canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint);
178                }
179            }
180
181            for (int p=0; p<NP; p++) {
182                final PointerState ps = mPointers.get(p);
183
184                final int N = ps.mXs.size();
185                float lastX=0, lastY=0;
186                boolean haveLast = false;
187                boolean drawn = false;
188                mPaint.setARGB(255, 128, 255, 255);
189                for (int i=0; i<N; i++) {
190                    float x = ps.mXs.get(i);
191                    float y = ps.mYs.get(i);
192                    if (Float.isNaN(x)) {
193                        haveLast = false;
194                        continue;
195                    }
196                    if (haveLast) {
197                        canvas.drawLine(lastX, lastY, x, y, mPathPaint);
198                        canvas.drawPoint(lastX, lastY, mPaint);
199                        drawn = true;
200                    }
201                    lastX = x;
202                    lastY = y;
203                    haveLast = true;
204                }
205
206                if (drawn) {
207                    if (ps.mVelocity != null) {
208                        mPaint.setARGB(255, 255, 64, 128);
209                        float xVel = ps.mVelocity.getXVelocity() * (1000/60);
210                        float yVel = ps.mVelocity.getYVelocity() * (1000/60);
211                        canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
212                    } else {
213                        canvas.drawPoint(lastX, lastY, mPaint);
214                    }
215                }
216            }
217        }
218    }
219
220    public void addTouchEvent(MotionEvent event) {
221        synchronized (mPointers) {
222            int action = event.getAction();
223
224            //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action)
225            //        + " pointers=" + event.getPointerCount());
226
227            int NP = mPointers.size();
228
229            //mRect.set(0, 0, getWidth(), mHeaderBottom+1);
230            //invalidate(mRect);
231            //if (mCurDown) {
232            //    mRect.set(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
233            //            mCurX+mCurWidth+3, mCurY+mCurWidth+3);
234            //} else {
235            //    mRect.setEmpty();
236            //}
237            if (action == MotionEvent.ACTION_DOWN) {
238                for (int p=0; p<NP; p++) {
239                    final PointerState ps = mPointers.get(p);
240                    ps.mXs.clear();
241                    ps.mYs.clear();
242                    ps.mVelocity = VelocityTracker.obtain();
243                    ps.mCurDown = false;
244                }
245                mPointers.get(0).mCurDown = true;
246                mMaxNumPointers = 0;
247                if (mPrintCoords) {
248                    Log.i("Pointer", "Pointer 1: DOWN");
249                }
250            }
251
252            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
253                final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
254                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
255                final int id = event.getPointerId(index);
256                while (NP <= id) {
257                    PointerState ps = new PointerState();
258                    ps.mVelocity = VelocityTracker.obtain();
259                    mPointers.add(ps);
260                    NP++;
261                }
262                final PointerState ps = mPointers.get(id);
263                ps.mVelocity = VelocityTracker.obtain();
264                ps.mCurDown = true;
265                if (mPrintCoords) {
266                    Log.i("Pointer", "Pointer " + (id+1) + ": DOWN");
267                }
268            }
269
270            final int NI = event.getPointerCount();
271
272            mCurDown = action != MotionEvent.ACTION_UP
273                    && action != MotionEvent.ACTION_CANCEL;
274            mCurNumPointers = mCurDown ? NI : 0;
275            if (mMaxNumPointers < mCurNumPointers) {
276                mMaxNumPointers = mCurNumPointers;
277            }
278
279            for (int i=0; i<NI; i++) {
280                final int id = event.getPointerId(i);
281                final PointerState ps = mPointers.get(id);
282                ps.mVelocity.addMovement(event);
283                ps.mVelocity.computeCurrentVelocity(1);
284                final int N = event.getHistorySize();
285                for (int j=0; j<N; j++) {
286                    if (mPrintCoords) {
287                        Log.i("Pointer", "Pointer " + (id+1) + ": ("
288                                + event.getHistoricalX(i, j)
289                                + ", " + event.getHistoricalY(i, j) + ")"
290                                + " Prs=" + event.getHistoricalPressure(i, j)
291                                + " Size=" + event.getHistoricalSize(i, j));
292                    }
293                    ps.mXs.add(event.getHistoricalX(i, j));
294                    ps.mYs.add(event.getHistoricalY(i, j));
295                }
296                if (mPrintCoords) {
297                    Log.i("Pointer", "Pointer " + (id+1) + ": ("
298                            + event.getX(i) + ", " + event.getY(i) + ")"
299                            + " Prs=" + event.getPressure(i)
300                            + " Size=" + event.getSize(i));
301                }
302                ps.mXs.add(event.getX(i));
303                ps.mYs.add(event.getY(i));
304                ps.mCurX = (int)event.getX(i);
305                ps.mCurY = (int)event.getY(i);
306                //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX
307                //        + "," + ps.mCurY + ")");
308                ps.mCurPressure = event.getPressure(i);
309                ps.mCurSize = event.getSize(i);
310                ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
311            }
312
313            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
314                final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
315                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
316                final int id = event.getPointerId(index);
317                final PointerState ps = mPointers.get(id);
318                ps.mXs.add(Float.NaN);
319                ps.mYs.add(Float.NaN);
320                ps.mCurDown = false;
321                if (mPrintCoords) {
322                    Log.i("Pointer", "Pointer " + (id+1) + ": UP");
323                }
324            }
325
326            if (action == MotionEvent.ACTION_UP) {
327                for (int i=0; i<NI; i++) {
328                    final int id = event.getPointerId(i);
329                    final PointerState ps = mPointers.get(id);
330                    if (ps.mCurDown) {
331                        ps.mCurDown = false;
332                        if (mPrintCoords) {
333                            Log.i("Pointer", "Pointer " + (id+1) + ": UP");
334                        }
335                    }
336                }
337            }
338
339            //if (mCurDown) {
340            //    mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
341            //            mCurX+mCurWidth+3, mCurY+mCurWidth+3);
342            //}
343            //invalidate(mRect);
344            postInvalidate();
345        }
346    }
347
348    @Override
349    public boolean onTouchEvent(MotionEvent event) {
350        addTouchEvent(event);
351        return true;
352    }
353}
354