GestureStrokeRecognitionPoints.java revision 7519091f7c15c50a9a1e50d82fa92400335852ec
1f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka/* 2f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Copyright (C) 2012 The Android Open Source Project 3f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 4f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * in compliance with the License. You may obtain a copy of the License at 6f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 7f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0 8f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 9f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software distributed under the License 10f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * or implied. See the License for the specific language governing permissions and limitations under 12f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * the License. 13f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka */ 14f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 15f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapackage com.android.inputmethod.keyboard.internal; 16f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 174daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Canvas; 184daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Paint; 194daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Rect; 20f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport android.util.FloatMath; 21f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 224daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport com.android.inputmethod.latin.Constants; 23f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport com.android.inputmethod.latin.InputPointers; 247519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaokaimport com.android.inputmethod.latin.ResizableIntArray; 25f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 26f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapublic class GestureStroke { 2757f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka public static final int DEFAULT_CAPACITY = 128; 2857f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka 29f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private final int mPointerId; 307519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); 317519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); 327519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); 33f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private float mLength; 34f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private float mAngle; 351e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private int mIncrementalRecognitionSize; 360c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang private int mLastIncrementalBatchSize; 37f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private long mLastPointTime; 38f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mLastPointX; 39f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mLastPointY; 40f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 41f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mMinGestureLength; 421e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private int mMinGestureLengthWhileInGesture; 43f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mMinGestureSampleLength; 444daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang private final Rect mDrawingRect = new Rect(); 45f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 461e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // TODO: Move some of these to resource. 471e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f; 481e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f; 491e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final int MIN_GESTURE_DURATION = 150; // msec 501e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec 51f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f; 52f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec 53f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f); 54f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 55f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float DOUBLE_PI = (float)(2 * Math.PI); 56f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 574daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT 584daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang private static final int DRAWING_GESTURE_FADE_START = 10; 594daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang private static final int DRAWING_GESTURE_FADE_RATE = 6; 604daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang 61f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public GestureStroke(int pointerId) { 62f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mPointerId = pointerId; 63f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka reset(); 64f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 65f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 66f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void setGestureSampleLength(final int keyWidth, final int keyHeight) { 671e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? 681e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH); 691e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mMinGestureLengthWhileInGesture = (int)( 701e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE); 71f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT); 72f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 73f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 741e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) { 751e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // The tolerance of the time duration and the stroke length to detect the start of a 761e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // gesture stroke should be eased when the previous input was a gesture input. 771e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka if (wasInGesture) { 781e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE 791e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka && mLength > mMinGestureLengthWhileInGesture; 801e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka } 81f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; 82f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 83f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 84f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void reset() { 85f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLength = 0; 86f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mAngle = 0; 871e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = 0; 880c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang mLastIncrementalBatchSize = 0; 89f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointTime = 0; 907519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mEventTimes.setLength(0); 917519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mXCoordinates.setLength(0); 927519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mYCoordinates.setLength(0); 934daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang mDrawingRect.setEmpty(); 94f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 95f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 96f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private void updateLastPoint(final int x, final int y, final int time) { 97f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointTime = time; 98f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointX = x; 99f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointY = y; 100f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 101f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 102f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { 1037519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka final int size = mEventTimes.getLength(); 104f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (size == 0) { 1057519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mEventTimes.add(time); 1067519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mXCoordinates.add(x); 1077519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mYCoordinates.add(y); 108f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (!isHistorical) { 109f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka updateLastPoint(x, y, time); 110f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 111f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return; 112f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 1137519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka 1147519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka final int lastX = mXCoordinates.get(size - 1); 1157519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka final int lastY = mYCoordinates.get(size - 1); 116f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dist = getDistance(lastX, lastY, x, y); 117f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (dist > mMinGestureSampleLength) { 1187519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mEventTimes.add(time); 1197519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mXCoordinates.add(x); 1207519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mYCoordinates.add(y); 121f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLength += dist; 122f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float angle = getAngle(lastX, lastY, x, y); 123f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (size > 1) { 1241e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka final float curvature = getAngleDiff(angle, mAngle); 125f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { 1261e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka if (size > mIncrementalRecognitionSize) { 1271e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = size; 128f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 129f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 130f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 131f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mAngle = angle; 132f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 133f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 134f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (!isHistorical) { 135f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int duration = (int)(time - mLastPointTime); 136f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (mLastPointTime != 0 && duration > 0) { 137f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration; 138f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (speed < GESTURE_RECOG_SPEED_THRESHOLD) { 1391e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = size; 140f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 141f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 142f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka updateLastPoint(x, y, time); 143f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 144f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 145f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 146f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void appendAllBatchPoints(final InputPointers out) { 1477519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka appendBatchPoints(out, mEventTimes.getLength()); 148f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 149f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 150f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void appendIncrementalBatchPoints(final InputPointers out) { 1517519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka appendBatchPoints(out, mIncrementalRecognitionSize); 1527519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka } 1537519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka 1547519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka private void appendBatchPoints(final InputPointers out, final int size) { 1557519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, 1567519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mLastIncrementalBatchSize, size - mLastIncrementalBatchSize); 1577519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka mLastIncrementalBatchSize = size; 158f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 159f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 160f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getDistance(final int p1x, final int p1y, 161f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int p2x, final int p2y) { 162f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dx = p1x - p2x; 163f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dy = p1y - p2y; 164f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka // TODO: Optimize out this {@link FloatMath#sqrt(float)} call. 165f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return FloatMath.sqrt(dx * dx + dy * dy); 166f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 167f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 168f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { 169f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int dx = p1x - p2x; 170f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int dy = p1y - p2y; 171f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (dx == 0 && dy == 0) return 0; 172f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return (float)Math.atan2(dy, dx); 173f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 174f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 175f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getAngleDiff(final float a1, final float a2) { 176f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float diff = Math.abs(a1 - a2); 177f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (diff > Math.PI) { 178f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return DOUBLE_PI - diff; 179f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 180f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return diff; 181f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 1824daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang 1834daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) { 1844daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang // TODO: These paint parameter interpolation should be tunable, possibly introduce an object 1854daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang // that implements an interface such as Paint getPaint(int step, int strokePoints) 1867519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka final int size = mXCoordinates.getLength(); 1877519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka int[] xCoords = mXCoordinates.getPrimitiveArray(); 1887519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka int[] yCoords = mYCoordinates.getPrimitiveArray(); 1894daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang int alpha = Constants.Color.ALPHA_OPAQUE; 1904daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang for (int i = size - 1; i > 0 && alpha > 0; i--) { 1914daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang paint.setAlpha(alpha); 1924daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang if (size - i > DRAWING_GESTURE_FADE_START) { 1934daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang alpha -= DRAWING_GESTURE_FADE_RATE; 1944daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang } 1954daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint); 1964daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang if (i == size - 1) { 1974daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint); 1984daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang } 1994daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang } 2004daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang } 2014daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang 2024daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang public Rect getDrawingRect() { 2034daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang return mDrawingRect; 2044daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang } 205f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka} 206