LatinIME/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
Tadashi G. Takaoka 936371e64f Don't cancel gesture trail update drawing
Bug: 7216955
Change-Id: Ie12bf45637b1012c9addb47279f9653334fae702
2012-09-26 12:25:24 +09:00

301 lines
13 KiB
Java

/*
* 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.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
public class PreviewPlacerView extends RelativeLayout {
private final int mGestureFloatingPreviewTextColor;
private final int mGestureFloatingPreviewTextOffset;
private final int mGestureFloatingPreviewColor;
private final float mGestureFloatingPreviewHorizontalPadding;
private final float mGestureFloatingPreviewVerticalPadding;
private final float mGestureFloatingPreviewRoundRadius;
private int mXOrigin;
private int mYOrigin;
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
CollectionUtils.newSparseArray();
private final Params mGesturePreviewTrailParams;
private final Paint mGesturePaint;
private boolean mDrawsGesturePreviewTrail;
private Bitmap mOffscreenBuffer;
private final Canvas mOffscreenCanvas = new Canvas();
private final Rect mOffscreenDirtyRect = new Rect();
private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
private final Paint mTextPaint;
private String mGestureFloatingPreviewText;
private final int mGestureFloatingPreviewTextHeight;
// {@link RectF} is needed for {@link Canvas#drawRoundRect(RectF, float, float, Paint)}.
private final RectF mGestureFloatingPreviewRectangle = new RectF();
private int mLastPointerX;
private int mLastPointerY;
private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
private boolean mDrawsGestureFloatingPreviewText;
private final DrawingHandler mDrawingHandler;
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
private final Params mGesturePreviewTrailParams;
private final int mGestureFloatingPreviewTextLingerTimeout;
public DrawingHandler(final PreviewPlacerView outerInstance,
final Params gesturePreviewTrailParams,
final int getstureFloatinPreviewTextLinerTimeout) {
super(outerInstance);
mGesturePreviewTrailParams = gesturePreviewTrailParams;
mGestureFloatingPreviewTextLingerTimeout = getstureFloatinPreviewTextLinerTimeout;
}
@Override
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;
}
}
public void dismissGestureFloatingPreviewText() {
removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
mGestureFloatingPreviewTextLingerTimeout);
}
public void postUpdateGestureTrailPreview() {
removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
mGesturePreviewTrailParams.mUpdateInterval);
}
}
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context);
setWillNotDraw(false);
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
R.styleable.KeyboardView_gestureFloatingPreviewTextColor, 0);
mGestureFloatingPreviewTextOffset = keyboardViewAttr.getDimensionPixelOffset(
R.styleable.KeyboardView_gestureFloatingPreviewTextOffset, 0);
mGestureFloatingPreviewColor = keyboardViewAttr.getColor(
R.styleable.KeyboardView_gestureFloatingPreviewColor, 0);
mGestureFloatingPreviewHorizontalPadding = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
mGestureFloatingPreviewVerticalPadding = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
mGestureFloatingPreviewRoundRadius = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
final int gestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
mGesturePreviewTrailParams = new Params(keyboardViewAttr);
keyboardViewAttr.recycle();
mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams,
gestureFloatingPreviewTextLingerTimeout);
final Paint gesturePaint = new Paint();
gesturePaint.setAntiAlias(true);
gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
mGesturePaint = gesturePaint;
final Paint textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Align.CENTER);
textPaint.setTextSize(gestureFloatingPreviewTextSize);
mTextPaint = textPaint;
final Rect textRect = new Rect();
textPaint.getTextBounds(TEXT_HEIGHT_REFERENCE_CHAR, 0, 1, textRect);
mGestureFloatingPreviewTextHeight = textRect.height();
final Paint layerPaint = new Paint();
layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
}
public void setOrigin(final int x, final int y) {
mXOrigin = x;
mYOrigin = y;
}
public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
}
public void invalidatePointer(final PointerTracker tracker, final boolean isOldestTracker) {
final boolean needsToUpdateLastPointer =
isOldestTracker && mDrawsGestureFloatingPreviewText;
if (needsToUpdateLastPointer) {
mLastPointerX = tracker.getLastX();
mLastPointerY = tracker.getLastY();
}
if (mDrawsGesturePreviewTrail) {
GesturePreviewTrail trail;
synchronized (mGesturePreviewTrails) {
trail = mGesturePreviewTrails.get(tracker.mPointerId);
if (trail == null) {
trail = new GesturePreviewTrail();
mGesturePreviewTrails.put(tracker.mPointerId, trail);
}
}
trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
}
// TODO: Should narrow the invalidate region.
if (mDrawsGesturePreviewTrail || needsToUpdateLastPointer) {
invalidate();
}
}
@Override
protected void onDetachedFromWindow() {
if (mOffscreenBuffer != null) {
mOffscreenBuffer.recycle();
mOffscreenBuffer = null;
}
}
@Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mXOrigin, mYOrigin);
if (mDrawsGesturePreviewTrail) {
if (mOffscreenBuffer == null) {
mOffscreenBuffer = Bitmap.createBitmap(
getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
}
if (!mOffscreenDirtyRect.isEmpty()) {
// Clear previous dirty rectangle.
mGesturePaint.setColor(Color.TRANSPARENT);
mGesturePaint.setStyle(Paint.Style.FILL);
mOffscreenCanvas.drawRect(mOffscreenDirtyRect, mGesturePaint);
mOffscreenDirtyRect.setEmpty();
}
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(mOffscreenCanvas, mGesturePaint,
mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
// {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
mOffscreenDirtyRect.union(mGesturePreviewTrailBoundsRect);
}
}
if (!mOffscreenDirtyRect.isEmpty()) {
canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect,
mGesturePaint);
// Note: Defer clearing the dirty rectangle here because we will get cleared
// rectangle on the canvas.
}
if (needsUpdatingGesturePreviewTrail) {
mDrawingHandler.postUpdateGestureTrailPreview();
}
}
if (mDrawsGestureFloatingPreviewText) {
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
}
canvas.translate(-mXOrigin, -mYOrigin);
}
public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
if (!mDrawsGestureFloatingPreviewText) return;
mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate();
}
public void dismissGestureFloatingPreviewText() {
mDrawingHandler.dismissGestureFloatingPreviewText();
}
private void drawGestureFloatingPreviewText(final Canvas canvas,
final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return;
}
final Paint paint = mTextPaint;
final RectF rectangle = mGestureFloatingPreviewRectangle;
// TODO: Figure out how we should deal with the floating preview text with multiple moving
// fingers.
// Paint the round rectangle background.
final int textHeight = mGestureFloatingPreviewTextHeight;
final float textWidth = paint.measureText(gestureFloatingPreviewText);
final float hPad = mGestureFloatingPreviewHorizontalPadding;
final float vPad = mGestureFloatingPreviewVerticalPadding;
final float rectWidth = textWidth + hPad * 2.0f;
final float rectHeight = textHeight + vPad * 2.0f;
final int canvasWidth = canvas.getWidth();
final float rectX = Math.min(Math.max(mLastPointerX - rectWidth / 2.0f, 0.0f),
canvasWidth - rectWidth);
final float rectY = mLastPointerY - mGestureFloatingPreviewTextOffset - rectHeight;
rectangle.set(rectX, rectY, rectX + rectWidth, rectY + rectHeight);
final float round = mGestureFloatingPreviewRoundRadius;
paint.setColor(mGestureFloatingPreviewColor);
canvas.drawRoundRect(rectangle, round, round, paint);
// Paint the text preview
paint.setColor(mGestureFloatingPreviewTextColor);
final float textX = rectX + hPad + textWidth / 2.0f;
final float textY = rectY + vPad + textHeight;
canvas.drawText(gestureFloatingPreviewText, textX, textY, paint);
}
}