am 179ada95: Refactor CandidateView touch event handling

Merge commit '179ada958b0bb46c6b9c8eb8b220d84dd3db855a' into gingerbread-plus-aosp

* commit '179ada958b0bb46c6b9c8eb8b220d84dd3db855a':
  Refactor CandidateView touch event handling
main
Tadashi G. Takaoka 2010-09-29 03:52:13 -07:00 committed by Android Git Automerger
commit ab1348e852
2 changed files with 77 additions and 110 deletions

View File

@ -33,4 +33,5 @@
<dimen name="mini_keyboard_slide_allowance">91.8dip</dimen> <dimen name="mini_keyboard_slide_allowance">91.8dip</dimen>
<!-- -key_height x 1.0 --> <!-- -key_height x 1.0 -->
<dimen name="mini_keyboard_vertical_correction">-54dip</dimen> <dimen name="mini_keyboard_vertical_correction">-54dip</dimen>
<dimen name="candidate_min_touchable_width">0.3in</dimen>
</resources> </resources>

View File

@ -16,17 +16,13 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@ -40,75 +36,82 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CandidateView extends View { public class CandidateView extends View {
private static final int OUT_OF_BOUNDS = -1; private static final int OUT_OF_BOUNDS = -1;
private static final List<CharSequence> EMPTY_LIST = new ArrayList<CharSequence>();
private LatinIME mService; private LatinIME mService;
private List<CharSequence> mSuggestions = EMPTY_LIST; private final ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
private boolean mShowingCompletions; private boolean mShowingCompletions;
private CharSequence mSelectedString; private CharSequence mSelectedString;
private int mSelectedIndex; private int mSelectedIndex;
private int mTouchX = OUT_OF_BOUNDS; private int mTouchX = OUT_OF_BOUNDS;
private Drawable mSelectionHighlight; private final Drawable mSelectionHighlight;
private boolean mTypedWordValid; private boolean mTypedWordValid;
private boolean mHaveMinimalSuggestion; private boolean mHaveMinimalSuggestion;
private Rect mBgPadding; private Rect mBgPadding;
private TextView mPreviewText; private final TextView mPreviewText;
private PopupWindow mPreviewPopup; private final PopupWindow mPreviewPopup;
private final int mDelayAfterPreview;
private int mCurrentWordIndex; private int mCurrentWordIndex;
private Drawable mDivider; private Drawable mDivider;
private static final int MAX_SUGGESTIONS = 32; private static final int MAX_SUGGESTIONS = 32;
private static final int SCROLL_PIXELS = 20; private static final int SCROLL_PIXELS = 20;
private static final int MSG_REMOVE_PREVIEW = 1; private final int[] mWordWidth = new int[MAX_SUGGESTIONS];
private static final int MSG_REMOVE_THROUGH_PREVIEW = 2; private final int[] mWordX = new int[MAX_SUGGESTIONS];
private int[] mWordWidth = new int[MAX_SUGGESTIONS];
private int[] mWordX = new int[MAX_SUGGESTIONS];
private int mPopupPreviewX; private int mPopupPreviewX;
private int mPopupPreviewY; private int mPopupPreviewY;
private static final int X_GAP = 10; private static final int X_GAP = 10;
private int mColorNormal; private final int mColorNormal;
private int mColorRecommended; private final int mColorRecommended;
private int mColorOther; private final int mColorOther;
private Paint mPaint; private final Paint mPaint;
private int mDescent; private final int mDescent;
private boolean mScrolled; private boolean mScrolled;
private boolean mShowingAddToDictionary; private boolean mShowingAddToDictionary;
private CharSequence mAddToDictionaryHint; private CharSequence mAddToDictionaryHint;
private int mTargetScrollX; private int mTargetScrollX;
private int mMinTouchableWidth; private final int mMinTouchableWidth;
private int mTotalWidth; private int mTotalWidth;
private GestureDetector mGestureDetector; private final GestureDetector mGestureDetector;
private final UIHandler mHandler = new UIHandler();
private class UIHandler extends Handler {
private static final int MSG_DISMISS_PREVIEW = 1;
Handler mHandler = new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_REMOVE_PREVIEW: case MSG_DISMISS_PREVIEW:
mPreviewText.setVisibility(GONE); mPreviewPopup.dismiss();
break;
case MSG_REMOVE_THROUGH_PREVIEW:
mPreviewText.setVisibility(GONE);
if (mTouchX != OUT_OF_BOUNDS) {
removeHighlight();
}
break; break;
} }
} }
};
public void dismissPreview(long delay) {
sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
}
public void cancelDismissPreview() {
removeMessages(MSG_DISMISS_PREVIEW);
}
}
/** /**
* Construct a CandidateView for showing suggested words for completion. * Construct a CandidateView for showing suggested words for completion.
@ -129,6 +132,8 @@ public class CandidateView extends View {
mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mPreviewPopup.setContentView(mPreviewText); mPreviewPopup.setContentView(mPreviewText);
mPreviewPopup.setBackgroundDrawable(null); mPreviewPopup.setBackgroundDrawable(null);
mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
mColorNormal = res.getColor(R.color.candidate_normal); mColorNormal = res.getColor(R.color.candidate_normal);
mColorRecommended = res.getColor(R.color.candidate_recommended); mColorRecommended = res.getColor(R.color.candidate_recommended);
mColorOther = res.getColor(R.color.candidate_other); mColorOther = res.getColor(R.color.candidate_other);
@ -142,13 +147,10 @@ public class CandidateView extends View {
mPaint.setStrokeWidth(0); mPaint.setStrokeWidth(0);
mPaint.setTextAlign(Align.CENTER); mPaint.setTextAlign(Align.CENTER);
mDescent = (int) mPaint.descent(); mDescent = (int) mPaint.descent();
// 50 pixels for a 160dpi device would mean about 0.3 inch mMinTouchableWidth = (int)res.getDimension(R.dimen.candidate_min_touchable_width);
mMinTouchableWidth = (int) (getResources().getDisplayMetrics().density * 50);
// Slightly reluctant to scroll to be able to easily choose the suggestion // Slightly reluctant to scroll to be able to easily choose the suggestion
// 50 pixels for a 160dpi device would mean about 0.3 inch final int touchSlopSquare = mMinTouchableWidth * mMinTouchableWidth;
final int touchSlop = (int) (getResources().getDisplayMetrics().density * 50);
final int touchSlopSquare = touchSlop * touchSlop;
mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
@Override @Override
public void onLongPress(MotionEvent me) { public void onLongPress(MotionEvent me) {
@ -158,15 +160,25 @@ public class CandidateView extends View {
} }
} }
} }
@Override
public boolean onDown(MotionEvent e) {
mScrolled = false;
return false;
}
@Override @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) { float distanceX, float distanceY) {
final int deltaX = (int) (e2.getX() - e1.getX()); if (!mScrolled) {
final int deltaY = (int) (e2.getY() - e1.getY()); // This is applied only when we recognize that scrolling is starting.
final int distance = (deltaX * deltaX) + (deltaY * deltaY); final int deltaX = (int) (e2.getX() - e1.getX());
if (distance < touchSlopSquare) { final int deltaY = (int) (e2.getY() - e1.getY());
return false; final int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance < touchSlopSquare) {
return true;
}
mScrolled = true;
} }
final int width = getWidth(); final int width = getWidth();
@ -215,7 +227,6 @@ public class CandidateView extends View {
super.onDraw(canvas); super.onDraw(canvas);
} }
mTotalWidth = 0; mTotalWidth = 0;
if (mSuggestions == null) return;
final int height = getHeight(); final int height = getHeight();
if (mBgPadding == null) { if (mBgPadding == null) {
@ -226,8 +237,8 @@ public class CandidateView extends View {
mDivider.setBounds(0, 0, mDivider.getIntrinsicWidth(), mDivider.setBounds(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight()); mDivider.getIntrinsicHeight());
} }
int x = 0;
final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS); final int count = mSuggestions.size();
final Rect bgPadding = mBgPadding; final Rect bgPadding = mBgPadding;
final Paint paint = mPaint; final Paint paint = mPaint;
final int touchX = mTouchX; final int touchX = mTouchX;
@ -238,25 +249,26 @@ public class CandidateView extends View {
boolean existsAutoCompletion = false; boolean existsAutoCompletion = false;
int x = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
CharSequence suggestion = mSuggestions.get(i); CharSequence suggestion = mSuggestions.get(i);
if (suggestion == null) continue; if (suggestion == null) continue;
final int wordLength = suggestion.length();
paint.setColor(mColorNormal); paint.setColor(mColorNormal);
if (mHaveMinimalSuggestion if (mHaveMinimalSuggestion
&& ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) { && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) {
paint.setTypeface(Typeface.DEFAULT_BOLD); paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setColor(mColorRecommended); paint.setColor(mColorRecommended);
existsAutoCompletion = true; existsAutoCompletion = true;
} else if (i != 0 || (suggestion.length() == 1 && count > 1)) { } else if (i != 0 || (wordLength == 1 && count > 1)) {
// HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 and // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 and
// there are multiple suggestions, such as the default punctuation list. // there are multiple suggestions, such as the default punctuation list.
paint.setColor(mColorOther); paint.setColor(mColorOther);
} }
final int wordWidth; int wordWidth;
if (mWordWidth[i] != 0) { if ((wordWidth = mWordWidth[i]) == 0) {
wordWidth = mWordWidth[i]; float textWidth = paint.measureText(suggestion, 0, wordLength);
} else {
float textWidth = paint.measureText(suggestion, 0, suggestion.length());
wordWidth = Math.max(mMinTouchableWidth, (int) textWidth + X_GAP * 2); wordWidth = Math.max(mMinTouchableWidth, (int) textWidth + X_GAP * 2);
mWordWidth[i] = wordWidth; mWordWidth[i] = wordWidth;
} }
@ -277,7 +289,7 @@ public class CandidateView extends View {
} }
if (canvas != null) { if (canvas != null) {
canvas.drawText(suggestion, 0, suggestion.length(), x + wordWidth / 2, y, paint); canvas.drawText(suggestion, 0, wordLength, x + wordWidth / 2, y, paint);
paint.setColor(mColorOther); paint.setColor(mColorOther);
canvas.translate(x + wordWidth, 0); canvas.translate(x + wordWidth, 0);
// Draw a divider unless it's after the hint // Draw a divider unless it's after the hint
@ -324,7 +336,12 @@ public class CandidateView extends View {
boolean typedWordValid, boolean haveMinimalSuggestion) { boolean typedWordValid, boolean haveMinimalSuggestion) {
clear(); clear();
if (suggestions != null) { if (suggestions != null) {
mSuggestions = new ArrayList<CharSequence>(suggestions); int insertCount = Math.min(suggestions.size(), MAX_SUGGESTIONS);
for (CharSequence suggestion : suggestions) {
mSuggestions.add(suggestion);
if (--insertCount == 0)
break;
}
} }
mShowingCompletions = completions; mShowingCompletions = completions;
mTypedWordValid = typedWordValid; mTypedWordValid = typedWordValid;
@ -355,50 +372,6 @@ public class CandidateView extends View {
return true; return true;
} }
public void scrollPrev() {
int i = 0;
final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS);
int firstItem = 0; // Actually just before the first item, if at the boundary
while (i < count) {
if (mWordX[i] < getScrollX()
&& mWordX[i] + mWordWidth[i] >= getScrollX() - 1) {
firstItem = i;
break;
}
i++;
}
int leftEdge = mWordX[firstItem] + mWordWidth[firstItem] - getWidth();
if (leftEdge < 0) leftEdge = 0;
updateScrollPosition(leftEdge);
}
public void scrollNext() {
int i = 0;
int scrollX = getScrollX();
int targetX = scrollX;
final int count = Math.min(mSuggestions.size(), MAX_SUGGESTIONS);
int rightEdge = scrollX + getWidth();
while (i < count) {
if (mWordX[i] <= rightEdge &&
mWordX[i] + mWordWidth[i] >= rightEdge) {
targetX = Math.min(mWordX[i], mTotalWidth - getWidth());
break;
}
i++;
}
updateScrollPosition(targetX);
}
private void updateScrollPosition(int targetX) {
if (targetX != getScrollX()) {
// TODO: Animate
mTargetScrollX = targetX;
requestLayout();
invalidate();
mScrolled = true;
}
}
/* package */ List<CharSequence> getSuggestions() { /* package */ List<CharSequence> getSuggestions() {
return mSuggestions; return mSuggestions;
} }
@ -406,7 +379,7 @@ public class CandidateView extends View {
public void clear() { public void clear() {
// Don't call mSuggestions.clear() because it's being used for logging // Don't call mSuggestions.clear() because it's being used for logging
// in LatinIME.pickSuggestionManually(). // in LatinIME.pickSuggestionManually().
mSuggestions = EMPTY_LIST; mSuggestions.clear();
mTouchX = OUT_OF_BOUNDS; mTouchX = OUT_OF_BOUNDS;
mSelectedString = null; mSelectedString = null;
mSelectedIndex = -1; mSelectedIndex = -1;
@ -414,9 +387,7 @@ public class CandidateView extends View {
invalidate(); invalidate();
Arrays.fill(mWordWidth, 0); Arrays.fill(mWordWidth, 0);
Arrays.fill(mWordX, 0); Arrays.fill(mWordX, 0);
if (mPreviewPopup.isShowing()) { mPreviewPopup.dismiss();
mPreviewPopup.dismiss();
}
} }
@Override @Override
@ -433,7 +404,6 @@ public class CandidateView extends View {
switch (action) { switch (action) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
mScrolled = false;
invalidate(); invalidate();
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
@ -453,7 +423,6 @@ public class CandidateView extends View {
mSelectedIndex = -1; mSelectedIndex = -1;
} }
} }
invalidate();
break; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
if (!mScrolled) { if (!mScrolled) {
@ -473,8 +442,8 @@ public class CandidateView extends View {
mSelectedString = null; mSelectedString = null;
mSelectedIndex = -1; mSelectedIndex = -1;
removeHighlight(); removeHighlight();
hidePreview();
requestLayout(); requestLayout();
mHandler.dismissPreview(mDelayAfterPreview);
break; break;
} }
return true; return true;
@ -482,10 +451,7 @@ public class CandidateView extends View {
private void hidePreview() { private void hidePreview() {
mCurrentWordIndex = OUT_OF_BOUNDS; mCurrentWordIndex = OUT_OF_BOUNDS;
if (mPreviewPopup.isShowing()) { mPreviewPopup.dismiss();
mHandler.sendMessageDelayed(mHandler
.obtainMessage(MSG_REMOVE_PREVIEW), 60);
}
} }
private void showPreview(int wordIndex, String altText) { private void showPreview(int wordIndex, String altText) {
@ -496,6 +462,7 @@ public class CandidateView extends View {
if (wordIndex == OUT_OF_BOUNDS) { if (wordIndex == OUT_OF_BOUNDS) {
hidePreview(); hidePreview();
} else { } else {
mHandler.cancelDismissPreview();
CharSequence word = altText != null? altText : mSuggestions.get(wordIndex); CharSequence word = altText != null? altText : mSuggestions.get(wordIndex);
mPreviewText.setText(word); mPreviewText.setText(word);
mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
@ -508,7 +475,6 @@ public class CandidateView extends View {
mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - getScrollX() mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - getScrollX()
+ (mWordWidth[wordIndex] - wordWidth) / 2; + (mWordWidth[wordIndex] - wordWidth) / 2;
mPopupPreviewY = - popupHeight; mPopupPreviewY = - popupHeight;
mHandler.removeMessages(MSG_REMOVE_PREVIEW);
int [] offsetInWindow = new int[2]; int [] offsetInWindow = new int[2];
getLocationInWindow(offsetInWindow); getLocationInWindow(offsetInWindow);
if (mPreviewPopup.isShowing()) { if (mPreviewPopup.isShowing()) {