From 333a300586c3bedb3d51524642b542cefaa1a22d Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Mon, 20 Aug 2012 12:57:34 +0900 Subject: [PATCH] Add multiple gesture preview trails animation Change-Id: I533e6de9b138317472565be82c8ba5e422472888 --- java/res/values/attrs.xml | 6 + java/res/values/config.xml | 3 + java/res/values/styles.xml | 3 + .../inputmethod/keyboard/PointerTracker.java | 26 ++- .../internal/GesturePreviewTrail.java | 161 ++++++++++++++++++ .../keyboard/internal/GestureStroke.java | 29 +--- .../GestureStrokeWithPreviewTrail.java | 70 ++++++++ .../keyboard/internal/PreviewPlacerView.java | 79 ++++++--- 8 files changed, 316 insertions(+), 61 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 4975d6540..cced45d9f 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -131,6 +131,12 @@ + + + + + + diff --git a/java/res/values/config.xml b/java/res/values/config.xml index 54a6687a3..8477df054 100644 --- a/java/res/values/config.xml +++ b/java/res/values/config.xml @@ -50,6 +50,9 @@ --> 70 200 + 100 + 1000 + 20 diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml index ae67c4369..e5e7fed80 100644 --- a/java/res/values/styles.xml +++ b/java/res/values/styles.xml @@ -78,6 +78,9 @@ @android:color/white @dimen/gesture_floating_preview_text_connector_width @integer/config_gesture_floating_preview_text_linger_timeout + @integer/config_gesture_preview_trail_fadeout_start_delay + @integer/config_gesture_preview_trail_fadeout_duration + @integer/config_gesture_preview_trail_update_interval @android:color/holo_blue_light @dimen/gesture_preview_trail_width diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 67f37a7e7..b5b3ef65f 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -17,14 +17,13 @@ package com.android.inputmethod.keyboard; import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.internal.GestureStroke; +import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputPointers; @@ -211,7 +210,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); - private final GestureStroke mGestureStroke; + private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { @@ -297,7 +296,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers); + tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints( + sAggregratedPointers); } return sAggregratedPointers; } @@ -308,7 +308,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers); + tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers); } return sAggregratedPointers; } @@ -319,7 +319,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.reset(); + tracker.mGestureStrokeWithPreviewTrail.reset(); } sAggregratedPointers.reset(); } @@ -329,7 +329,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { throw new NullPointerException(); } mPointerId = id; - mGestureStroke = new GestureStroke(id); + mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -429,7 +429,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); - mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); + mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -539,10 +539,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { mDrawingProxy.invalidateKey(key); } - public void drawGestureTrail(final Canvas canvas, final Paint paint) { - if (mInGesture) { - mGestureStroke.drawGestureTrail(canvas, paint); - } + public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() { + return mGestureStrokeWithPreviewTrail; } public int getLastX() { @@ -692,7 +690,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { mIsPossibleGesture = true; // TODO: pointer times should be relative to first down even in entire batch input // instead of resetting to 0 for each new down event. - mGestureStroke.addPoint(x, y, 0, false); + mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false); } } } @@ -733,7 +731,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final long eventTime, final boolean isHistorical, final Key key) { final int gestureTime = (int)(eventTime - tracker.getDownTime()); if (sShouldHandleGesture && mIsPossibleGesture) { - final GestureStroke stroke = mGestureStroke; + final GestureStroke stroke = mGestureStrokeWithPreviewTrail; stroke.addPoint(x, y, gestureTime, isHistorical); if (!mInGesture && stroke.isStartOfAGesture()) { startBatchInput(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java new file mode 100644 index 000000000..edc20998f --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.SystemClock; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResizableIntArray; + +class GesturePreviewTrail { + private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY; + + private final GesturePreviewTrailParams mPreviewParams; + private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); + private int mCurrentStrokeId; + private long mCurrentDownTime; + + // Use this value as imaginary zero because x-coordinates may be zero. + private static final int DOWN_EVENT_MARKER = -128; + + static class GesturePreviewTrailParams { + public final int mFadeoutStartDelay; + public final int mFadeoutDuration; + public final int mUpdateInterval; + + public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) { + mFadeoutStartDelay = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0); + mFadeoutDuration = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0); + mUpdateInterval = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0); + } + } + + public GesturePreviewTrail(final GesturePreviewTrailParams params) { + mPreviewParams = params; + } + + private static int markAsDownEvent(final int xCoord) { + return DOWN_EVENT_MARKER - xCoord; + } + + private static boolean isDownEventXCoord(final int xCoordOrMark) { + return xCoordOrMark <= DOWN_EVENT_MARKER; + } + + private static int getXCoordValue(final int xCoordOrMark) { + return isDownEventXCoord(xCoordOrMark) + ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark; + } + + public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) { + final int strokeId = stroke.getGestureStrokeId(); + final boolean isNewStroke = strokeId != mCurrentStrokeId; + final int trailSize = mEventTimes.getLength(); + stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates); + final int newTrailSize = mEventTimes.getLength(); + if (stroke.getGestureStrokePreviewSize() == 0) { + return; + } + if (isNewStroke) { + final int elapsedTime = (int)(downTime - mCurrentDownTime); + final int[] eventTimes = mEventTimes.getPrimitiveArray(); + for (int i = 0; i < trailSize; i++) { + eventTimes[i] -= elapsedTime; + } + + if (newTrailSize > trailSize) { + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]); + } + mCurrentDownTime = downTime; + mCurrentStrokeId = strokeId; + } + } + + private int getAlpha(final int elapsedTime) { + if (elapsedTime < mPreviewParams.mFadeoutStartDelay) { + return Constants.Color.ALPHA_OPAQUE; + } + final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE + * (elapsedTime - mPreviewParams.mFadeoutStartDelay) + / mPreviewParams.mFadeoutDuration; + return Constants.Color.ALPHA_OPAQUE - decreasingAlpha; + } + + /** + * Draw gesture preview trail + * @param canvas The canvas to draw the gesture preview trail + * @param paint The paint object to be used to draw the gesture preview trail + * @return true if some gesture preview trails remain to be drawn + */ + public boolean drawGestureTrail(final Canvas canvas, final Paint paint) { + final int trailSize = mEventTimes.getLength(); + if (trailSize == 0) { + return false; + } + + final int[] eventTimes = mEventTimes.getPrimitiveArray(); + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime); + final int lingeringDuration = mPreviewParams.mFadeoutStartDelay + + mPreviewParams.mFadeoutDuration; + int startIndex; + for (startIndex = 0; startIndex < trailSize; startIndex++) { + final int elapsedTime = sinceDown - eventTimes[startIndex]; + // Skip too old trail points. + if (elapsedTime < lingeringDuration) { + break; + } + } + + if (startIndex < trailSize) { + int lastX = getXCoordValue(xCoords[startIndex]); + int lastY = yCoords[startIndex]; + for (int i = startIndex + 1; i < trailSize - 1; i++) { + final int x = xCoords[i]; + final int y = yCoords[i]; + final int elapsedTime = sinceDown - eventTimes[i]; + // Draw trail line only when the current point isn't a down point. + if (!isDownEventXCoord(x)) { + paint.setAlpha(getAlpha(elapsedTime)); + canvas.drawLine(lastX, lastY, x, y, paint); + } + lastX = getXCoordValue(x); + lastY = y; + } + } + + // TODO: Implement ring buffer to avoid moving points. + // Discard faded out points. + final int newSize = trailSize - startIndex; + System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); + System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); + System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); + mEventTimes.setLength(newSize); + mXCoordinates.setLength(newSize); + mYCoordinates.setLength(newSize); + return newSize > 0; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 79e977a40..292842d22 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -14,10 +14,6 @@ package com.android.inputmethod.keyboard.internal; -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.ResizableIntArray; @@ -48,13 +44,8 @@ public class GestureStroke { private static final float DOUBLE_PI = (float)(2.0f * Math.PI); - // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT - private static final int DRAWING_GESTURE_FADE_START = 10; - private static final int DRAWING_GESTURE_FADE_RATE = 6; - - public GestureStroke(int pointerId) { + public GestureStroke(final int pointerId) { mPointerId = pointerId; - reset(); } public void setGestureSampleLength(final int keyWidth) { @@ -158,7 +149,7 @@ public class GestureStroke { if (dx == 0 && dy == 0) return 0; // Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT // does with Math.atan2(). - return (float)Math.atan2((double)dy, (double)dx); + return (float)Math.atan2(dy, dx); } private static float getAngleDiff(final float a1, final float a2) { @@ -168,20 +159,4 @@ public class GestureStroke { } return diff; } - - public void drawGestureTrail(final Canvas canvas, final Paint paint) { - // TODO: These paint parameter interpolation should be tunable, possibly introduce an object - // that implements an interface such as Paint getPaint(int step, int strokePoints) - final int size = mXCoordinates.getLength(); - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - int alpha = Constants.Color.ALPHA_OPAQUE; - for (int i = size - 1; i > 0 && alpha > 0; i--) { - paint.setAlpha(alpha); - if (size - i > DRAWING_GESTURE_FADE_START) { - alpha -= DRAWING_GESTURE_FADE_RATE; - } - canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint); - } - } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java new file mode 100644 index 000000000..6c1a9bc01 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.latin.ResizableIntArray; + +public class GestureStrokeWithPreviewTrail extends GestureStroke { + public static final int PREVIEW_CAPACITY = 256; + + private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); + private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + + private int mStrokeId; + private int mLastPreviewSize; + + public GestureStrokeWithPreviewTrail(final int pointerId) { + super(pointerId); + } + + @Override + public void reset() { + super.reset(); + mStrokeId++; + mLastPreviewSize = 0; + mPreviewEventTimes.setLength(0); + mPreviewXCoordinates.setLength(0); + mPreviewYCoordinates.setLength(0); + } + + public int getGestureStrokeId() { + return mStrokeId; + } + + public int getGestureStrokePreviewSize() { + return mPreviewEventTimes.getLength(); + } + + @Override + public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { + super.addPoint(x, y, time, isHistorical); + mPreviewEventTimes.add(time); + mPreviewXCoordinates.add(x); + mPreviewYCoordinates.add(y); + } + + public void appendPreviewStroke(final ResizableIntArray eventTimes, + final ResizableIntArray xCoords, final ResizableIntArray yCoords) { + final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; + if (length <= 0) { + return; + } + eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); + xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); + yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); + mLastPreviewSize = mPreviewEventTimes.getLength(); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 3f33aee5a..269b202b5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -28,6 +28,7 @@ import android.util.SparseArray; import android.widget.RelativeLayout; import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -48,7 +49,9 @@ public class PreviewPlacerView extends RelativeLayout { private int mXOrigin; private int mYOrigin; - private final SparseArray mPointers = CollectionUtils.newSparseArray(); + private final SparseArray mGesturePreviewTrails = + CollectionUtils.newSparseArray(); + private final GesturePreviewTrailParams mGesturePreviewTrailParams; private String mGestureFloatingPreviewText; private int mLastPointerX; @@ -57,23 +60,31 @@ public class PreviewPlacerView extends RelativeLayout { private boolean mDrawsGesturePreviewTrail; private boolean mDrawsGestureFloatingPreviewText; - private final DrawingHandler mDrawingHandler = new DrawingHandler(this); + private final DrawingHandler mDrawingHandler; private static class DrawingHandler extends StaticInnerHandlerWrapper { private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0; + private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1; - public DrawingHandler(PreviewPlacerView outerInstance) { + private final GesturePreviewTrailParams mGesturePreviewTrailParams; + + public DrawingHandler(final PreviewPlacerView outerInstance, + final GesturePreviewTrailParams gesturePreviewTrailParams) { super(outerInstance); + mGesturePreviewTrailParams = gesturePreviewTrailParams; } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { final PreviewPlacerView placerView = getOuterInstance(); if (placerView == null) return; switch (msg.what) { case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: placerView.setGestureFloatingPreviewText(null); break; + case MSG_UPDATE_GESTURE_PREVIEW_TRAIL: + placerView.invalidate(); + break; } } @@ -89,16 +100,27 @@ public class PreviewPlacerView extends RelativeLayout { placerView.mGestureFloatingPreviewTextLingerTimeout); } + private void cancelUpdateGestureTrailPreview() { + removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL); + } + + public void postUpdateGestureTrailPreview() { + cancelUpdateGestureTrailPreview(); + sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL), + mGesturePreviewTrailParams.mUpdateInterval); + } + public void cancelAllMessages() { cancelDismissGestureFloatingPreviewText(); + cancelUpdateGestureTrailPreview(); } } - public PreviewPlacerView(Context context, AttributeSet attrs) { + public PreviewPlacerView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.keyboardViewStyle); } - public PreviewPlacerView(Context context, AttributeSet attrs, int defStyle) { + public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) { super(context); setWillNotDraw(false); @@ -128,8 +150,11 @@ public class PreviewPlacerView extends RelativeLayout { R.styleable.KeyboardView_gesturePreviewTrailColor, 0); final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize( R.styleable.KeyboardView_gesturePreviewTrailWidth, 0); + mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr); keyboardViewAttr.recycle(); + mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams); + mGesturePaint = new Paint(); mGesturePaint.setAntiAlias(true); mGesturePaint.setStyle(Paint.Style.STROKE); @@ -144,21 +169,28 @@ public class PreviewPlacerView extends RelativeLayout { mTextPaint.setTextSize(gestureFloatingPreviewTextSize); } - public void setOrigin(int x, int y) { + public void setOrigin(final int x, final int y) { mXOrigin = x; mYOrigin = y; } - public void setGesturePreviewMode(boolean drawsGesturePreviewTrail, - boolean drawsGestureFloatingPreviewText) { + public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail, + final boolean drawsGestureFloatingPreviewText) { mDrawsGesturePreviewTrail = drawsGesturePreviewTrail; mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText; } - public void invalidatePointer(PointerTracker tracker) { - synchronized (mPointers) { - mPointers.put(tracker.mPointerId, tracker); + public void invalidatePointer(final PointerTracker tracker) { + GesturePreviewTrail trail; + synchronized (mGesturePreviewTrails) { + trail = mGesturePreviewTrails.get(tracker.mPointerId); + if (trail == null) { + trail = new GesturePreviewTrail(mGesturePreviewTrailParams); + mGesturePreviewTrails.put(tracker.mPointerId, trail); + } } + trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime()); + mLastPointerX = tracker.getLastX(); mLastPointerY = tracker.getLastY(); // TODO: Should narrow the invalidate region. @@ -166,17 +198,23 @@ public class PreviewPlacerView extends RelativeLayout { } @Override - public void onDraw(Canvas canvas) { + public void onDraw(final Canvas canvas) { super.onDraw(canvas); canvas.translate(mXOrigin, mYOrigin); if (mDrawsGesturePreviewTrail) { - synchronized (mPointers) { - final int trackerCount = mPointers.size(); - for (int index = 0; index < trackerCount; index++) { - final PointerTracker tracker = mPointers.valueAt(index); - tracker.drawGestureTrail(canvas, mGesturePaint); + boolean needsUpdatingGesturePreviewTrail = false; + synchronized (mGesturePreviewTrails) { + // Trails count == fingers count that have ever been active. + final int trailsCount = mGesturePreviewTrails.size(); + for (int index = 0; index < trailsCount; index++) { + final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index); + needsUpdatingGesturePreviewTrail |= + trail.drawGestureTrail(canvas, mGesturePaint); } } + if (needsUpdatingGesturePreviewTrail) { + mDrawingHandler.postUpdateGestureTrailPreview(); + } } if (mDrawsGestureFloatingPreviewText) { drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText); @@ -184,7 +222,7 @@ public class PreviewPlacerView extends RelativeLayout { canvas.translate(-mXOrigin, -mYOrigin); } - public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) { + public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) { mGestureFloatingPreviewText = gestureFloatingPreviewText; invalidate(); } @@ -197,7 +235,8 @@ public class PreviewPlacerView extends RelativeLayout { mDrawingHandler.cancelAllMessages(); } - private void drawGestureFloatingPreviewText(Canvas canvas, String gestureFloatingPreviewText) { + private void drawGestureFloatingPreviewText(final Canvas canvas, + final String gestureFloatingPreviewText) { if (TextUtils.isEmpty(gestureFloatingPreviewText)) { return; }