Add multiple gesture preview trails animation

Change-Id: I533e6de9b138317472565be82c8ba5e422472888
main
Tadashi G. Takaoka 2012-08-20 12:57:34 +09:00
parent 64a26b4389
commit 333a300586
8 changed files with 316 additions and 61 deletions

View File

@ -131,6 +131,12 @@
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" /> <attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond --> <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" /> <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
<!-- Delay after gesture trail starts fading out in millisecond. -->
<attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
<!-- Duration while gesture preview trail is fading out in millisecond. -->
<attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
<!-- Interval of updating gesture preview trail in millisecond. -->
<attr name="gesturePreviewTrailUpdateInterval" format="integer" />
<attr name="gesturePreviewTrailColor" format="color" /> <attr name="gesturePreviewTrailColor" format="color" />
<attr name="gesturePreviewTrailWidth" format="dimension" /> <attr name="gesturePreviewTrailWidth" format="dimension" />
</declare-styleable> </declare-styleable>

View File

@ -50,6 +50,9 @@
--> -->
<integer name="config_key_preview_linger_timeout">70</integer> <integer name="config_key_preview_linger_timeout">70</integer>
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer> <integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
<integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
<integer name="config_gesture_preview_trail_fadeout_duration">1000</integer>
<integer name="config_gesture_preview_trail_update_interval">20</integer>
<!-- <!--
Configuration for MainKeyboardView Configuration for MainKeyboardView
--> -->

View File

@ -78,6 +78,9 @@
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item> <item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item> <item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item> <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
<item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
<item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
<item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item> <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item> <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
<!-- Common attributes of MainKeyboardView --> <!-- Common attributes of MainKeyboardView -->

View File

@ -17,14 +17,13 @@
package com.android.inputmethod.keyboard; package com.android.inputmethod.keyboard;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke; 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.keyboard.internal.PointerTrackerQueue;
import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.InputPointers;
@ -211,7 +210,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private static final KeyboardActionListener EMPTY_LISTENER = private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter(); new KeyboardActionListener.Adapter();
private final GestureStroke mGestureStroke; private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
public static void init(boolean hasDistinctMultitouch, public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) { boolean needsPhantomSuddenMoveEventHack) {
@ -297,7 +296,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i); final PointerTracker tracker = sTrackers.get(i);
tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers); tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(
sAggregratedPointers);
} }
return sAggregratedPointers; return sAggregratedPointers;
} }
@ -308,7 +308,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i); final PointerTracker tracker = sTrackers.get(i);
tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers); tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
} }
return sAggregratedPointers; return sAggregratedPointers;
} }
@ -319,7 +319,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i); final PointerTracker tracker = sTrackers.get(i);
tracker.mGestureStroke.reset(); tracker.mGestureStrokeWithPreviewTrail.reset();
} }
sAggregratedPointers.reset(); sAggregratedPointers.reset();
} }
@ -329,7 +329,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
throw new NullPointerException(); throw new NullPointerException();
} }
mPointerId = id; mPointerId = id;
mGestureStroke = new GestureStroke(id); mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
setKeyDetectorInner(handler.getKeyDetector()); setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener(); mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy(); mDrawingProxy = handler.getDrawingProxy();
@ -429,7 +429,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
mKeyDetector = keyDetector; mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard(); mKeyboard = keyDetector.getKeyboard();
mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) { if (newKey != mCurrentKey) {
if (mDrawingProxy != null) { if (mDrawingProxy != null) {
@ -539,10 +539,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
mDrawingProxy.invalidateKey(key); mDrawingProxy.invalidateKey(key);
} }
public void drawGestureTrail(final Canvas canvas, final Paint paint) { public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
if (mInGesture) { return mGestureStrokeWithPreviewTrail;
mGestureStroke.drawGestureTrail(canvas, paint);
}
} }
public int getLastX() { public int getLastX() {
@ -692,7 +690,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
mIsPossibleGesture = true; mIsPossibleGesture = true;
// TODO: pointer times should be relative to first down even in entire batch input // TODO: pointer times should be relative to first down even in entire batch input
// instead of resetting to 0 for each new down event. // 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 long eventTime, final boolean isHistorical, final Key key) {
final int gestureTime = (int)(eventTime - tracker.getDownTime()); final int gestureTime = (int)(eventTime - tracker.getDownTime());
if (sShouldHandleGesture && mIsPossibleGesture) { if (sShouldHandleGesture && mIsPossibleGesture) {
final GestureStroke stroke = mGestureStroke; final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical); stroke.addPoint(x, y, gestureTime, isHistorical);
if (!mInGesture && stroke.isStartOfAGesture()) { if (!mInGesture && stroke.isStartOfAGesture()) {
startBatchInput(); startBatchInput();

View File

@ -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;
}
}

View File

@ -14,10 +14,6 @@
package com.android.inputmethod.keyboard.internal; 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.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray; import com.android.inputmethod.latin.ResizableIntArray;
@ -48,13 +44,8 @@ public class GestureStroke {
private static final float DOUBLE_PI = (float)(2.0f * Math.PI); 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 public GestureStroke(final int pointerId) {
private static final int DRAWING_GESTURE_FADE_START = 10;
private static final int DRAWING_GESTURE_FADE_RATE = 6;
public GestureStroke(int pointerId) {
mPointerId = pointerId; mPointerId = pointerId;
reset();
} }
public void setGestureSampleLength(final int keyWidth) { public void setGestureSampleLength(final int keyWidth) {
@ -158,7 +149,7 @@ public class GestureStroke {
if (dx == 0 && dy == 0) return 0; if (dx == 0 && dy == 0) return 0;
// Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT // Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
// does with Math.atan2(). // 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) { private static float getAngleDiff(final float a1, final float a2) {
@ -168,20 +159,4 @@ public class GestureStroke {
} }
return diff; 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);
}
}
} }

View File

@ -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();
}
}

View File

@ -28,6 +28,7 @@ import android.util.SparseArray;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker; 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.CollectionUtils;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@ -48,7 +49,9 @@ public class PreviewPlacerView extends RelativeLayout {
private int mXOrigin; private int mXOrigin;
private int mYOrigin; private int mYOrigin;
private final SparseArray<PointerTracker> mPointers = CollectionUtils.newSparseArray(); private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
CollectionUtils.newSparseArray();
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
private String mGestureFloatingPreviewText; private String mGestureFloatingPreviewText;
private int mLastPointerX; private int mLastPointerX;
@ -57,23 +60,31 @@ public class PreviewPlacerView extends RelativeLayout {
private boolean mDrawsGesturePreviewTrail; private boolean mDrawsGesturePreviewTrail;
private boolean mDrawsGestureFloatingPreviewText; private boolean mDrawsGestureFloatingPreviewText;
private final DrawingHandler mDrawingHandler = new DrawingHandler(this); private final DrawingHandler mDrawingHandler;
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> { private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0; 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); super(outerInstance);
mGesturePreviewTrailParams = gesturePreviewTrailParams;
} }
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance(); final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return; if (placerView == null) return;
switch (msg.what) { switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null); placerView.setGestureFloatingPreviewText(null);
break; break;
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
placerView.invalidate();
break;
} }
} }
@ -89,16 +100,27 @@ public class PreviewPlacerView extends RelativeLayout {
placerView.mGestureFloatingPreviewTextLingerTimeout); 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() { public void cancelAllMessages() {
cancelDismissGestureFloatingPreviewText(); cancelDismissGestureFloatingPreviewText();
cancelUpdateGestureTrailPreview();
} }
} }
public PreviewPlacerView(Context context, AttributeSet attrs) { public PreviewPlacerView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle); 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); super(context);
setWillNotDraw(false); setWillNotDraw(false);
@ -128,8 +150,11 @@ public class PreviewPlacerView extends RelativeLayout {
R.styleable.KeyboardView_gesturePreviewTrailColor, 0); R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize( final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0); R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
keyboardViewAttr.recycle(); keyboardViewAttr.recycle();
mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
mGesturePaint = new Paint(); mGesturePaint = new Paint();
mGesturePaint.setAntiAlias(true); mGesturePaint.setAntiAlias(true);
mGesturePaint.setStyle(Paint.Style.STROKE); mGesturePaint.setStyle(Paint.Style.STROKE);
@ -144,21 +169,28 @@ public class PreviewPlacerView extends RelativeLayout {
mTextPaint.setTextSize(gestureFloatingPreviewTextSize); mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
} }
public void setOrigin(int x, int y) { public void setOrigin(final int x, final int y) {
mXOrigin = x; mXOrigin = x;
mYOrigin = y; mYOrigin = y;
} }
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail, public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
boolean drawsGestureFloatingPreviewText) { final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail; mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText; mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
} }
public void invalidatePointer(PointerTracker tracker) { public void invalidatePointer(final PointerTracker tracker) {
synchronized (mPointers) { GesturePreviewTrail trail;
mPointers.put(tracker.mPointerId, tracker); 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(); mLastPointerX = tracker.getLastX();
mLastPointerY = tracker.getLastY(); mLastPointerY = tracker.getLastY();
// TODO: Should narrow the invalidate region. // TODO: Should narrow the invalidate region.
@ -166,17 +198,23 @@ public class PreviewPlacerView extends RelativeLayout {
} }
@Override @Override
public void onDraw(Canvas canvas) { public void onDraw(final Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
canvas.translate(mXOrigin, mYOrigin); canvas.translate(mXOrigin, mYOrigin);
if (mDrawsGesturePreviewTrail) { if (mDrawsGesturePreviewTrail) {
synchronized (mPointers) { boolean needsUpdatingGesturePreviewTrail = false;
final int trackerCount = mPointers.size(); synchronized (mGesturePreviewTrails) {
for (int index = 0; index < trackerCount; index++) { // Trails count == fingers count that have ever been active.
final PointerTracker tracker = mPointers.valueAt(index); final int trailsCount = mGesturePreviewTrails.size();
tracker.drawGestureTrail(canvas, mGesturePaint); for (int index = 0; index < trailsCount; index++) {
final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
needsUpdatingGesturePreviewTrail |=
trail.drawGestureTrail(canvas, mGesturePaint);
} }
} }
if (needsUpdatingGesturePreviewTrail) {
mDrawingHandler.postUpdateGestureTrailPreview();
}
} }
if (mDrawsGestureFloatingPreviewText) { if (mDrawsGestureFloatingPreviewText) {
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText); drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
@ -184,7 +222,7 @@ public class PreviewPlacerView extends RelativeLayout {
canvas.translate(-mXOrigin, -mYOrigin); canvas.translate(-mXOrigin, -mYOrigin);
} }
public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) { public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
mGestureFloatingPreviewText = gestureFloatingPreviewText; mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate(); invalidate();
} }
@ -197,7 +235,8 @@ public class PreviewPlacerView extends RelativeLayout {
mDrawingHandler.cancelAllMessages(); mDrawingHandler.cancelAllMessages();
} }
private void drawGestureFloatingPreviewText(Canvas canvas, String gestureFloatingPreviewText) { private void drawGestureFloatingPreviewText(final Canvas canvas,
final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) { if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return; return;
} }