Please note that the contents of this offline web site may be out of date. To access the most recent documentation visit the online version .
Note that links that point to online resources are green in color and will open in a new window.
We would love it if you could give us feedback about this material by filling this form (You have to be online to fill it)
BasicMultitouch / src / com.example.android.basicmultitouch /

TouchDisplayView.java

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