diff options
author | Romain Guy <romainguy@android.com> | 2009-05-21 23:10:10 -0700 |
---|---|---|
committer | Romain Guy <romainguy@android.com> | 2009-05-22 01:59:59 -0700 |
commit | d6a463a9f23b3901bf729f2f27a6bb8f78b95248 (patch) | |
tree | 1371cafd6a1c0fe8d3cd4580e7878a9adb86b183 /core/java/android | |
parent | cfcc0df2658d0ce7dc753511bb44ab8ae7a636f7 (diff) | |
download | frameworks_base-d6a463a9f23b3901bf729f2f27a6bb8f78b95248.zip frameworks_base-d6a463a9f23b3901bf729f2f27a6bb8f78b95248.tar.gz frameworks_base-d6a463a9f23b3901bf729f2f27a6bb8f78b95248.tar.bz2 |
Add a new API to ListView: setGestures(int). This allows developers to enable gestures to jump inside the list or filter it. This change also introduces a new XML attribute to control this API. It also adds the ability to theme the GestureOverlayView from the gestures library. Finally, this adds a new VERSION header to the binary format used to store the letters for the recognizer.
Diffstat (limited to 'core/java/android')
-rwxr-xr-x | core/java/android/gesture/GestureOverlayView.java | 245 | ||||
-rw-r--r-- | core/java/android/gesture/GestureStroke.java | 8 | ||||
-rw-r--r-- | core/java/android/gesture/LetterRecognizer.java | 69 | ||||
-rw-r--r-- | core/java/android/gesture/TouchThroughGestureListener.java | 11 | ||||
-rw-r--r-- | core/java/android/view/View.java | 31 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 9 | ||||
-rw-r--r-- | core/java/android/widget/AbsListView.java | 389 | ||||
-rw-r--r-- | core/java/android/widget/FastScroller.java | 19 | ||||
-rw-r--r-- | core/java/android/widget/PopupWindow.java | 14 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 11 |
10 files changed, 644 insertions, 162 deletions
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java index bffd12e..64ddbbd 100755 --- a/core/java/android/gesture/GestureOverlayView.java +++ b/core/java/android/gesture/GestureOverlayView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 The Android Open Source Project + * Copyright (C) 2009 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. @@ -17,62 +17,54 @@ package android.gesture; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.Color; -import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.AccelerateDecelerateInterpolator; +import com.android.internal.R; import java.util.ArrayList; /** * A (transparent) overlay for gesture input that can be placed on top of other - * widgets. The view can also be opaque. + * widgets. + * + * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth + * @attr ref android.R.styleable#GestureOverlayView_gestureColor + * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor + * @attr ref android.R.styleable#GestureOverlayView_fadeDuration + * @attr ref android.R.styleable#GestureOverlayView_fadeOffset */ - public class GestureOverlayView extends View { - static final float TOUCH_TOLERANCE = 3; - - // TODO: Move all these values into XML attributes private static final int TRANSPARENT_BACKGROUND = 0x00000000; - - // TODO: SHOULD BE A TOTAL DURATION - private static final float FADING_ALPHA_CHANGE = 0.15f; - private static final long FADING_OFFSET = 300; - private static final long FADING_REFRESHING_RATE = 16; - - private static final int GESTURE_STROKE_WIDTH = 12; private static final boolean GESTURE_RENDERING_ANTIALIAS = true; - private static final boolean DITHER_FLAG = true; - public static final int DEFAULT_GESTURE_COLOR = 0xFFFFFF00; - public static final int DEFAULT_UNCERTAIN_GESTURE_COLOR = Color.argb(60, 255, 255, 0); - - private static final int REFRESH_RANGE = 10; - - private static final BlurMaskFilter BLUR_MASK_FILTER = - new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL); - private Paint mGesturePaint; private final Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG); - private Bitmap mBitmap; // with transparent background + private Bitmap mBitmap; private Canvas mBitmapCanvas; - private int mCertainGestureColor = DEFAULT_GESTURE_COLOR; - private int mUncertainGestureColor = DEFAULT_UNCERTAIN_GESTURE_COLOR; + private long mFadeDuration = 300; + private long mFadeOffset = 300; + private long mFadingStart; - // for rendering immediate ink feedback - private Rect mInvalidRect = new Rect(); + private float mGestureStroke = 12.0f; + private int mCertainGestureColor = 0xFFFFFF00; + private int mUncertainGestureColor = 0x3CFFFF00; + private int mInvalidateExtraBorder = 10; - private Path mPath; + // for rendering immediate ink feedback + private final Rect mInvalidRect = new Rect(); + private final Path mPath = new Path(); private float mX; private float mY; @@ -84,26 +76,32 @@ public class GestureOverlayView extends View { private Gesture mCurrentGesture = null; // TODO: Make this a list of WeakReferences - private final ArrayList<OnGestureListener> mOnGestureListeners = new ArrayList<OnGestureListener>(); - private ArrayList<GesturePoint> mPointBuffer = null; + private final ArrayList<OnGestureListener> mOnGestureListeners = + new ArrayList<OnGestureListener>(); + private final ArrayList<GesturePoint> mPointBuffer = new ArrayList<GesturePoint>(100); // fading out effect private boolean mIsFadingOut = false; private float mFadingAlpha = 1; - - private Handler mHandler = new Handler(); + private final AccelerateDecelerateInterpolator mInterpolator = + new AccelerateDecelerateInterpolator(); private final Runnable mFadingOut = new Runnable() { public void run() { if (mIsFadingOut) { - mFadingAlpha -= FADING_ALPHA_CHANGE; - if (mFadingAlpha <= 0) { + final long now = AnimationUtils.currentAnimationTimeMillis(); + final long duration = now - mFadingStart; + + if (duration > mFadeDuration) { mIsFadingOut = false; - mPath = null; + mPath.rewind(); mCurrentGesture = null; mBitmap.eraseColor(TRANSPARENT_BACKGROUND); } else { - mHandler.postDelayed(this, FADING_REFRESHING_RATE); + float interpolatedTime = Math.max(0.0f, + Math.min(1.0f, duration / (float) mFadeDuration)); + mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime); + postDelayed(this, 16); } invalidate(); } @@ -116,7 +114,27 @@ public class GestureOverlayView extends View { } public GestureOverlayView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle); + } + + public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.GestureOverlayView, defStyle, 0); + + mGestureStroke = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth, + mGestureStroke); + mInvalidateExtraBorder = Math.max(1, ((int) mGestureStroke) - 1); + mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor, + mCertainGestureColor); + mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor, + mUncertainGestureColor); + mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration); + mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset); + + a.recycle(); + init(); } @@ -139,6 +157,7 @@ public class GestureOverlayView extends View { mBitmap.eraseColor(TRANSPARENT_BACKGROUND); mCurrentGesture.draw(mBitmapCanvas, mGesturePaint); } + invalidate(); } public void setGestureColor(int color) { @@ -157,6 +176,16 @@ public class GestureOverlayView extends View { return mCertainGestureColor; } + public float getGestureStroke() { + return mGestureStroke; + } + + public void setGestureStroke(float gestureStroke) { + mGestureStroke = gestureStroke; + mInvalidateExtraBorder = Math.max(1, ((int) mGestureStroke) - 1); + mGesturePaint.setStrokeWidth(mGestureStroke); + } + /** * Set the gesture to be shown in the view * @@ -182,14 +211,12 @@ public class GestureOverlayView extends View { final Paint gesturePaint = mGesturePaint; gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS); - gesturePaint.setColor(DEFAULT_GESTURE_COLOR); + gesturePaint.setColor(mCertainGestureColor); gesturePaint.setStyle(Paint.Style.STROKE); gesturePaint.setStrokeJoin(Paint.Join.ROUND); gesturePaint.setStrokeCap(Paint.Cap.ROUND); - gesturePaint.setStrokeWidth(GESTURE_STROKE_WIDTH); + gesturePaint.setStrokeWidth(mGestureStroke); gesturePaint.setDither(DITHER_FLAG); - - mPath = null; } @Override @@ -226,6 +253,10 @@ public class GestureOverlayView extends View { mOnGestureListeners.remove(listener); } + public void removeAllOnGestureListeners() { + mOnGestureListeners.clear(); + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -240,25 +271,24 @@ public class GestureOverlayView extends View { } // draw the current stroke - if (mPath != null) { - canvas.drawPath(mPath, mGesturePaint); - } + canvas.drawPath(mPath, mGesturePaint); } /** * Clear up the overlay * * @param fadeOut whether the gesture on the overlay should fade out - * gradually or disappear immediately + * gradually or disappear immediately */ public void clear(boolean fadeOut) { if (fadeOut) { - mFadingAlpha = 1; + mFadingAlpha = 1.0f; mIsFadingOut = true; - mHandler.removeCallbacks(mFadingOut); - mHandler.postDelayed(mFadingOut, FADING_OFFSET); + removeCallbacks(mFadingOut); + mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset; + postDelayed(mFadingOut, mFadeOffset); } else { - mPath = null; + mPath.rewind(); mCurrentGesture = null; if (mBitmap != null) { mBitmap.eraseColor(TRANSPARENT_BACKGROUND); @@ -269,7 +299,7 @@ public class GestureOverlayView extends View { public void cancelFadingOut() { mIsFadingOut = false; - mHandler.removeCallbacks(mFadingOut); + removeCallbacks(mFadingOut); } @Override @@ -278,6 +308,12 @@ public class GestureOverlayView extends View { return true; } + processEvent(event); + + return true; + } + + public void processEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Rect rect = touchStart(event); @@ -290,12 +326,14 @@ public class GestureOverlayView extends View { } break; case MotionEvent.ACTION_UP: - touchUp(event); + touchUp(event, false); + invalidate(); + break; + case MotionEvent.ACTION_CANCEL: + touchUp(event, true); invalidate(); break; } - - return true; } private Rect touchStart(MotionEvent event) { @@ -310,7 +348,7 @@ public class GestureOverlayView extends View { // if there is fading out going on, stop it. if (mIsFadingOut) { mIsFadingOut = false; - mHandler.removeCallbacks(mFadingOut); + removeCallbacks(mFadingOut); mBitmap.eraseColor(TRANSPARENT_BACKGROUND); mCurrentGesture = null; } @@ -325,14 +363,13 @@ public class GestureOverlayView extends View { mCurrentGesture = new Gesture(); } - mPointBuffer = new ArrayList<GesturePoint>(); mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); - mPath = new Path(); + mPath.rewind(); mPath.moveTo(x, y); - mInvalidRect.set((int) x - REFRESH_RANGE, (int) y - REFRESH_RANGE, - (int) x + REFRESH_RANGE, (int) y + REFRESH_RANGE); + mInvalidRect.set((int) x - mInvalidateExtraBorder, (int) y - mInvalidateExtraBorder, + (int) x + mInvalidateExtraBorder, (int) y + mInvalidateExtraBorder); mCurveEndX = x; mCurveEndY = y; @@ -343,36 +380,47 @@ public class GestureOverlayView extends View { private Rect touchMove(MotionEvent event) { Rect areaToRefresh = null; - float x = event.getX(); - float y = event.getY(); + final float x = event.getX(); + final float y = event.getY(); + + final float previousX = mX; + final float previousY = mY; - float dx = Math.abs(x - mX); - float dy = Math.abs(y - mY); + final float dx = Math.abs(x - previousX); + final float dy = Math.abs(y - previousY); - if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { - + if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) { + areaToRefresh = mInvalidRect; + // start with the curve end - mInvalidRect.set((int) mCurveEndX - REFRESH_RANGE, (int) mCurveEndY - REFRESH_RANGE, - (int) mCurveEndX + REFRESH_RANGE, (int) mCurveEndY + REFRESH_RANGE); + areaToRefresh.set( + (int) mCurveEndX - mInvalidateExtraBorder, + (int) mCurveEndY - mInvalidateExtraBorder, + (int) mCurveEndX + mInvalidateExtraBorder, + (int) mCurveEndY + mInvalidateExtraBorder); - mCurveEndX = (x + mX) / 2; - mCurveEndY = (y + mY) / 2; - mPath.quadTo(mX, mY, mCurveEndX, mCurveEndY); + mCurveEndX = (x + previousX) / 2; + mCurveEndY = (y + previousY) / 2; + + mPath.quadTo(previousX, previousY, mCurveEndX, mCurveEndY); // union with the control point of the new curve - mInvalidRect.union((int) mX - REFRESH_RANGE, (int) mY - REFRESH_RANGE, - (int) mX + REFRESH_RANGE, (int) mY + REFRESH_RANGE); + areaToRefresh.union( + (int) previousX - mInvalidateExtraBorder, + (int) previousY - mInvalidateExtraBorder, + (int) previousX + mInvalidateExtraBorder, + (int) previousY + mInvalidateExtraBorder); // union with the end point of the new curve - mInvalidRect.union((int) mCurveEndX - REFRESH_RANGE, (int) mCurveEndY - REFRESH_RANGE, - (int) mCurveEndX + REFRESH_RANGE, (int) mCurveEndY + REFRESH_RANGE); + areaToRefresh.union( + (int) mCurveEndX - mInvalidateExtraBorder, + (int) mCurveEndY - mInvalidateExtraBorder, + (int) mCurveEndX + mInvalidateExtraBorder, + (int) mCurveEndY + mInvalidateExtraBorder); - areaToRefresh = mInvalidRect; - mX = x; mY = y; } - mPointBuffer.add(new GesturePoint(x, y, event.getEventTime())); @@ -386,34 +434,43 @@ public class GestureOverlayView extends View { return areaToRefresh; } - private void touchUp(MotionEvent event) { + private void touchUp(MotionEvent event, boolean cancel) { // add the stroke to the current gesture mCurrentGesture.addStroke(new GestureStroke(mPointBuffer)); // add the stroke to the double buffer - mGesturePaint.setMaskFilter(BLUR_MASK_FILTER); mBitmapCanvas.drawPath(mPath, mGesturePaint); - mGesturePaint.setMaskFilter(null); - - // pass the event to handlers - final ArrayList<OnGestureListener> listeners = mOnGestureListeners; - final int count = listeners.size(); - for (int i = 0; i < count; i++) { - listeners.get(i).onGestureEnded(this, event); + + if (!cancel) { + // pass the event to handlers + final ArrayList<OnGestureListener> listeners = mOnGestureListeners; + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureEnded(this, event); + } + } else { + // pass the event to handlers + final ArrayList<OnGestureListener> listeners = mOnGestureListeners; + final int count = listeners.size(); + for (int i = 0; i < count; i++) { + listeners.get(i).onGestureCancelled(this, event); + } } - - mPath = null; - mPointBuffer = null; + + mPath.rewind(); + mPointBuffer.clear(); } /** * An interface for processing gesture events */ public static interface OnGestureListener { - public void onGestureStarted(GestureOverlayView overlay, MotionEvent event); + void onGestureStarted(GestureOverlayView overlay, MotionEvent event); + + void onGesture(GestureOverlayView overlay, MotionEvent event); - public void onGesture(GestureOverlayView overlay, MotionEvent event); + void onGestureEnded(GestureOverlayView overlay, MotionEvent event); - public void onGestureEnded(GestureOverlayView overlay, MotionEvent event); + void onGestureCancelled(GestureOverlayView overlay, MotionEvent event); } } diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java index 5160a76..6d022b4 100644 --- a/core/java/android/gesture/GestureStroke.java +++ b/core/java/android/gesture/GestureStroke.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 The Android Open Source Project + * Copyright (C) 2009 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. @@ -31,6 +31,8 @@ import java.util.ArrayList; * A gesture stroke started on a touch down and ended on a touch up. */ public class GestureStroke { + static final float TOUCH_TOLERANCE = 3; + public final RectF boundingBox; public final float length; @@ -156,8 +158,8 @@ public class GestureStroke { } else { float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); - if (dx >= GestureOverlayView.TOUCH_TOLERANCE || - dy >= GestureOverlayView.TOUCH_TOLERANCE) { + if (dx >= TOUCH_TOLERANCE || + dy >= TOUCH_TOLERANCE) { path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; diff --git a/core/java/android/gesture/LetterRecognizer.java b/core/java/android/gesture/LetterRecognizer.java index 4476746..b26b3f2 100644 --- a/core/java/android/gesture/LetterRecognizer.java +++ b/core/java/android/gesture/LetterRecognizer.java @@ -45,32 +45,27 @@ public class LetterRecognizer { private GestureLibrary mGestureLibrary; + private final Comparator<Prediction> mComparator = new PredictionComparator(); + private static class SigmoidUnit { final float[] mWeights; - private boolean mComputed; - private float mResult; - SigmoidUnit(float[] weights) { mWeights = weights; } private float compute(float[] inputs) { - if (!mComputed) { - float sum = 0; + float sum = 0; - final int count = inputs.length; - final float[] weights = mWeights; + final int count = inputs.length; + final float[] weights = mWeights; - for (int i = 0; i < count; i++) { - sum += inputs[i] * weights[i]; - } - sum += weights[weights.length - 1]; - - mResult = 1.0f / (float) (1 + Math.exp(-sum)); - mComputed = true; + for (int i = 0; i < count; i++) { + sum += inputs[i] * weights[i]; } - return mResult; + sum += weights[weights.length - 1]; + + return 1.0f / (float) (1 + Math.exp(-sum)); } } @@ -91,16 +86,25 @@ public class LetterRecognizer { } public ArrayList<Prediction> recognize(Gesture gesture) { + return recognize(gesture, null); + } + + public ArrayList<Prediction> recognize(Gesture gesture, ArrayList<Prediction> predictions) { float[] query = GestureUtilities.spatialSampling(gesture, mPatchSize); - ArrayList<Prediction> predictions = classify(query); + predictions = classify(query, predictions); adjustPrediction(gesture, predictions); return predictions; } - private ArrayList<Prediction> classify(float[] vector) { + private ArrayList<Prediction> classify(float[] vector, ArrayList<Prediction> predictions) { + if (predictions == null) { + predictions = new ArrayList<Prediction>(); + } else { + predictions.clear(); + } + final float[] intermediateOutput = compute(mHiddenLayer, vector); final float[] output = compute(mOutputLayer, intermediateOutput); - final ArrayList<Prediction> predictions = new ArrayList<Prediction>(); double sum = 0; @@ -117,19 +121,8 @@ public class LetterRecognizer { predictions.get(i).score /= sum; } - Collections.sort(predictions, new Comparator<Prediction>() { - public int compare(Prediction object1, Prediction object2) { - double score1 = object1.score; - double score2 = object2.score; - if (score1 > score2) { - return -1; - } else if (score1 < score2) { - return 1; - } else { - return 0; - } - } - }); + Collections.sort(predictions, mComparator); + return predictions; } @@ -270,4 +263,18 @@ public class LetterRecognizer { } } } + + private static class PredictionComparator implements Comparator<Prediction> { + public int compare(Prediction object1, Prediction object2) { + double score1 = object1.score; + double score2 = object2.score; + if (score1 > score2) { + return -1; + } else if (score1 < score2) { + return 1; + } else { + return 0; + } + } + } } diff --git a/core/java/android/gesture/TouchThroughGestureListener.java b/core/java/android/gesture/TouchThroughGestureListener.java index 7621ddf..09a528d 100644 --- a/core/java/android/gesture/TouchThroughGestureListener.java +++ b/core/java/android/gesture/TouchThroughGestureListener.java @@ -55,7 +55,7 @@ public class TouchThroughGestureListener implements GestureOverlayView.OnGesture private boolean mStealEvents = false; public TouchThroughGestureListener(View model) { - this(model, false); + this(model, true); } public TouchThroughGestureListener(View model, boolean stealEvents) { @@ -125,7 +125,7 @@ public class TouchThroughGestureListener implements GestureOverlayView.OnGesture overlay.setGestureDrawingColor(overlay.getGestureColor()); if (mStealEvents) { event = MotionEvent.obtain(event.getDownTime(), System.currentTimeMillis(), - MotionEvent.ACTION_UP, x, y, event.getPressure(), event.getSize(), + MotionEvent.ACTION_CANCEL, x, y, event.getPressure(), event.getSize(), event.getMetaState(), event.getXPrecision(), event.getYPrecision(), event.getDeviceId(), event.getEdgeFlags()); } @@ -153,6 +153,13 @@ public class TouchThroughGestureListener implements GestureOverlayView.OnGesture } } + public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) { + overlay.clear(mIsGesturing); + if (!mIsGesturing) { + dispatchEventToModel(event); + } + } + public void addOnGestureActionListener(OnGesturePerformedListener listener) { mPerformedListeners.add(listener); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1f72eea..bcb97ed 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -538,21 +538,48 @@ import java.util.WeakHashMap; * take care of redrawing the appropriate views until the animation completes. * </p> * + * @attr ref android.R.styleable#View_background + * @attr ref android.R.styleable#View_clickable + * @attr ref android.R.styleable#View_contentDescription + * @attr ref android.R.styleable#View_drawingCacheQuality + * @attr ref android.R.styleable#View_duplicateParentState + * @attr ref android.R.styleable#View_id + * @attr ref android.R.styleable#View_fadingEdge + * @attr ref android.R.styleable#View_fadingEdgeLength * @attr ref android.R.styleable#View_fitsSystemWindows + * @attr ref android.R.styleable#View_isScrollContainer + * @attr ref android.R.styleable#View_focusable + * @attr ref android.R.styleable#View_focusableInTouchMode + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + * @attr ref android.R.styleable#View_keepScreenOn + * @attr ref android.R.styleable#View_longClickable + * @attr ref android.R.styleable#View_minHeight + * @attr ref android.R.styleable#View_minWidth * @attr ref android.R.styleable#View_nextFocusDown * @attr ref android.R.styleable#View_nextFocusLeft * @attr ref android.R.styleable#View_nextFocusRight * @attr ref android.R.styleable#View_nextFocusUp + * @attr ref android.R.styleable#View_onClick + * @attr ref android.R.styleable#View_padding + * @attr ref android.R.styleable#View_paddingBottom + * @attr ref android.R.styleable#View_paddingLeft + * @attr ref android.R.styleable#View_paddingRight + * @attr ref android.R.styleable#View_paddingTop + * @attr ref android.R.styleable#View_saveEnabled * @attr ref android.R.styleable#View_scrollX * @attr ref android.R.styleable#View_scrollY - * @attr ref android.R.styleable#View_scrollbarTrackHorizontal - * @attr ref android.R.styleable#View_scrollbarThumbHorizontal * @attr ref android.R.styleable#View_scrollbarSize + * @attr ref android.R.styleable#View_scrollbarStyle * @attr ref android.R.styleable#View_scrollbars + * @attr ref android.R.styleable#View_scrollbarTrackHorizontal + * @attr ref android.R.styleable#View_scrollbarThumbHorizontal * @attr ref android.R.styleable#View_scrollbarThumbVertical * @attr ref android.R.styleable#View_scrollbarTrackVertical * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack + * @attr ref android.R.styleable#View_soundEffectsEnabled + * @attr ref android.R.styleable#View_tag + * @attr ref android.R.styleable#View_visibility * * @see android.view.ViewGroup */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index bf04dcd..c6f36a0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -53,6 +53,15 @@ import java.util.ArrayList; * <p> * Also see {@link LayoutParams} for layout attributes. * </p> + * + * @attr ref android.R.styleable#ViewGroup_clipChildren + * @attr ref android.R.styleable#ViewGroup_clipToPadding + * @attr ref android.R.styleable#ViewGroup_layoutAnimation + * @attr ref android.R.styleable#ViewGroup_animationCache + * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache + * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache + * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren + * @attr ref android.R.styleable#ViewGroup_descendantFocusability */ public abstract class ViewGroup extends View implements ViewParent, ViewManager { private static final boolean DBG = false; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 1ca59b2..8a538d7 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -41,12 +41,18 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.KeyCharacterMap; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; import android.view.inputmethod.InputMethodManager; import android.view.ContextMenu.ContextMenuInfo; +import android.gesture.GestureOverlayView; +import android.gesture.TouchThroughGestureListener; +import android.gesture.Gesture; +import android.gesture.LetterRecognizer; +import android.gesture.Prediction; import com.android.internal.R; @@ -54,7 +60,9 @@ import java.util.ArrayList; import java.util.List; /** - * Common code shared between ListView and GridView + * Base class that can be used to implement virtualized lists of items. A list does + * not have a spatial definition here. For instance, subclases of this class can + * display the content of the list in a grid, in a carousel, as stack, etc. * * @attr ref android.R.styleable#AbsListView_listSelector * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop @@ -65,6 +73,7 @@ import java.util.List; * @attr ref android.R.styleable#AbsListView_cacheColorHint * @attr ref android.R.styleable#AbsListView_fastScrollEnabled * @attr ref android.R.styleable#AbsListView_smoothScrollbar + * @attr ref android.R.styleable#AbsListView_gestures */ public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, @@ -93,6 +102,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2; /** + * Disables gestures. + * + * @see #setGestures(int) + * @see #GESTURES_JUMP + * @see #GESTURES_FILTER + */ + public static final int GESTURES_NONE = 0; + /** + * When a letter gesture is recognized the list jumps to a matching position. + * + * @see #setGestures(int) + * @see #GESTURES_NONE + * @see #GESTURES_FILTER + */ + public static final int GESTURES_JUMP = 1; + /** + * When a letter gesture is recognized the letter is added to the filter. + * + * @see #setGestures(int) + * @see #GESTURES_NONE + * @see #GESTURES_JUMP + */ + public static final int GESTURES_FILTER = 2; + + /** * Indicates that we are not in the middle of a touch gesture */ static final int TOUCH_MODE_REST = -1; @@ -427,8 +461,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te */ private FastScroller mFastScroller; - private int mTouchSlop; + /** + * Indicates the type of gestures to use: GESTURES_NONE, GESTURES_FILTER or GESTURES_NONE + */ + private int mGestures; + + // Used to implement the gestures overlay + private GestureOverlayView mGesturesOverlay; + private PopupWindow mGesturesPopup; + private ViewTreeObserver.OnGlobalLayoutListener mGesturesLayoutListener; + private boolean mGlobalLayoutListenerAddedGestures; + private boolean mInstallGesturesOverlay; + private TouchThroughGestureListener mGesturesListener; + private boolean mPreviousGesturing; + + private boolean mGlobalLayoutListenerAddedFilter; + private int mTouchSlop; private float mDensityScale; private InputConnection mDefInputConnection; @@ -535,10 +584,197 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true); setSmoothScrollbarEnabled(smoothScrollbar); - + + int defaultGestures = GESTURES_NONE; + if (useTextFilter) { + defaultGestures = GESTURES_FILTER; + } else if (enableFastScroll) { + defaultGestures = GESTURES_JUMP; + } + int gestures = a.getInt(R.styleable.AbsListView_gestures, defaultGestures); + setGestures(gestures); + a.recycle(); } + private void initAbsListView() { + // Setting focusable in touch mode will set the focusable property to true + setFocusableInTouchMode(true); + setWillNotDraw(false); + setAlwaysDrawnWithCacheEnabled(false); + setScrollingCacheEnabled(true); + + mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + mDensityScale = getContext().getResources().getDisplayMetrics().density; + } + + /** + * <p>Sets the type of gestures to use with this list. When gestures are enabled, + * that is if the <code>gestures</code> parameter is not {@link #GESTURES_NONE}, + * the user can draw characters on top of this view. When a character is + * recognized and matches a known character, the list will either:</p> + * <ul> + * <li>Jump to the appropriate position ({@link #GESTURES_JUMP})</li> + * <li>Add the character to the current filter ({@link #GESTURES_FILTER})</li> + * </ul> + * <p>Using {@link #GESTURES_JUMP} requires {@link #isFastScrollEnabled()} to + * be true. Using {@link #GESTURES_FILTER} requires {@link #isTextFilterEnabled()} + * to be true.</p> + * + * @param gestures The type of gestures to enable for this list: + * {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER} + * + * @see #GESTURES_NONE + * @see #GESTURES_JUMP + * @see #GESTURES_FILTER + * @see #getGestures() + */ + public void setGestures(int gestures) { + switch (gestures) { + case GESTURES_JUMP: + if (!mFastScrollEnabled) { + throw new IllegalStateException("Jump gestures can only be used with " + + "fast scroll enabled"); + } + break; + case GESTURES_FILTER: + if (!mTextFilterEnabled) { + throw new IllegalStateException("Filter gestures can only be used with " + + "text filtering enabled"); + } + break; + } + + final int oldGestures = mGestures; + mGestures = gestures; + + // Install overlay later + if (oldGestures == GESTURES_NONE && gestures != GESTURES_NONE) { + mInstallGesturesOverlay = true; + // Uninstall overlay + } else if (oldGestures != GESTURES_NONE && gestures == GESTURES_NONE) { + uninstallGesturesOverlay(); + } + } + + /** + * Indicates what gestures are enabled on this view. + * + * @return {@link #GESTURES_NONE}, {@link #GESTURES_JUMP} or {@link #GESTURES_FILTER} + * + * @see #GESTURES_NONE + * @see #GESTURES_JUMP + * @see #GESTURES_FILTER + * @see #setGestures(int) + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = GESTURES_NONE, to = "NONE"), + @ViewDebug.IntToString(from = GESTURES_JUMP, to = "JUMP"), + @ViewDebug.IntToString(from = GESTURES_FILTER, to = "FILTER") + }) + public int getGestures() { + return mGestures; + } + + private void dismissGesturesPopup() { + if (mGesturesPopup != null) { + mGesturesPopup.dismiss(); + } + } + + private void showGesturesPopup() { + // Make sure we have a window before showing the popup + if (getWindowVisibility() == View.VISIBLE) { + installGesturesOverlay(); + positionGesturesPopup(); + } + } + + private void positionGesturesPopup() { + final int[] xy = new int[2]; + getLocationOnScreen(xy); + if (!mGesturesPopup.isShowing()) { + mGesturesPopup.showAtLocation(this, Gravity.LEFT | Gravity.TOP, xy[0], xy[1]); + } else { + mGesturesPopup.update(xy[0], xy[1], -1, -1); + } + } + + private void installGesturesOverlay() { + mInstallGesturesOverlay = false; + + if (mGesturesPopup == null) { + final Context c = getContext(); + final LayoutInflater layoutInflater = (LayoutInflater) + c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mGesturesOverlay = (GestureOverlayView) + layoutInflater.inflate(R.layout.list_gestures_overlay, null); + + final PopupWindow p = new PopupWindow(c); + p.setFocusable(false); + p.setTouchable(false); + p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + p.setContentView(mGesturesOverlay); + p.setWidth(getWidth()); + p.setHeight(getHeight()); + p.setBackgroundDrawable(null); + + if (mGesturesLayoutListener == null) { + mGesturesLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + public void onGlobalLayout() { + if (isShown()) { + showGesturesPopup(); + } else if (mGesturesPopup.isShowing()) { + dismissGesturesPopup(); + } + } + }; + } + getViewTreeObserver().addOnGlobalLayoutListener(mGesturesLayoutListener); + mGlobalLayoutListenerAddedGestures = true; + + mGesturesPopup = p; + + mGesturesOverlay.removeAllOnGestureListeners(); + mGesturesListener = new TouchThroughGestureListener(null); + mGesturesListener.setGestureType(TouchThroughGestureListener.MULTIPLE_STROKE); + mGesturesListener.addOnGestureActionListener(new GesturesProcessor()); + mGesturesOverlay.addOnGestureListener(mGesturesListener); + + mPreviousGesturing = false; + } + } + + private void uninstallGesturesOverlay() { + dismissGesturesPopup(); + mGesturesPopup = null; + if (mGesturesLayoutListener != null) { + getViewTreeObserver().removeGlobalOnLayoutListener(mGesturesLayoutListener); + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mGestures != GESTURES_NONE) { + mGesturesOverlay.processEvent(ev); + + final boolean isGesturing = mGesturesListener.isGesturing(); + + if (!isGesturing) { + mPreviousGesturing = isGesturing; + return super.dispatchTouchEvent(ev); + } else if (!mPreviousGesturing){ + mPreviousGesturing = isGesturing; + final MotionEvent event = MotionEvent.obtain(ev); + event.setAction(MotionEvent.ACTION_CANCEL); + super.dispatchTouchEvent(event); + return true; + } + } + + return super.dispatchTouchEvent(ev); + } + /** * Enables fast scrolling by letting the user quickly scroll through lists by * dragging the fast scroll thumb. The adapter attached to the list may want @@ -712,17 +948,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - private void initAbsListView() { - // Setting focusable in touch mode will set the focusable property to true - setFocusableInTouchMode(true); - setWillNotDraw(false); - setAlwaysDrawnWithCacheEnabled(false); - setScrollingCacheEnabled(true); - - mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - mDensityScale = getContext().getResources().getDisplayMetrics().density; - } - private void useDefaultSelector() { setSelector(getResources().getDrawable( com.android.internal.R.drawable.list_selector_background)); @@ -908,11 +1133,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private boolean acceptFilter() { - if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) || - ((Filterable) getAdapter()).getFilter() == null) { - return false; - } - return true; + return mTextFilterEnabled && getAdapter() instanceof Filterable && + ((Filterable) getAdapter()).getFilter() != null; } /** @@ -1096,6 +1318,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te listPadding.bottom = mSelectionBottomPadding + mPaddingBottom; } + /** + * Subclasses should NOT override this method but + * {@link #layoutChildren()} instead. + */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); @@ -1111,17 +1337,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te protected boolean setFrame(int left, int top, int right, int bottom) { final boolean changed = super.setFrame(left, top, right, bottom); - // Reposition the popup when the frame has changed. This includes - // translating the widget, not just changing its dimension. The - // filter popup needs to follow the widget. - if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null && - mPopup.isShowing()) { - positionPopup(); + if (changed) { + // Reposition the popup when the frame has changed. This includes + // translating the widget, not just changing its dimension. The + // filter popup needs to follow the widget. + final boolean visible = getWindowVisibility() == View.VISIBLE; + if (mFiltered && visible && mPopup != null && mPopup.isShowing()) { + positionPopup(); + } + + if (mGestures != GESTURES_NONE && visible && mGesturesPopup != null && + mGesturesPopup.isShowing()) { + positionGesturesPopup(); + } } return changed; } + /** + * Subclasses must override this method to layout their children. + */ protected void layoutChildren() { } @@ -1324,9 +1560,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mDataChanged = true; rememberSyncState(); } + if (mFastScroller != null) { mFastScroller.onSizeChanged(w, h, oldw, oldh); } + + if (mInstallGesturesOverlay) { + installGesturesOverlay(); + positionGesturesPopup(); + } else if (mGesturesPopup != null) { + mGesturesPopup.update(w, h); + } } /** @@ -1510,6 +1754,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final ViewTreeObserver treeObserver = getViewTreeObserver(); if (treeObserver != null) { treeObserver.addOnTouchModeChangeListener(this); + if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) { + treeObserver.addOnGlobalLayoutListener(this); + } + if (mGestures != GESTURES_NONE && mGesturesPopup != null && + !mGlobalLayoutListenerAddedGestures) { + treeObserver.addOnGlobalLayoutListener(mGesturesLayoutListener); + } } } @@ -1520,6 +1771,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final ViewTreeObserver treeObserver = getViewTreeObserver(); if (treeObserver != null) { treeObserver.removeOnTouchModeChangeListener(this); + if (mTextFilterEnabled && mPopup != null) { + treeObserver.removeGlobalOnLayoutListener(this); + mGlobalLayoutListenerAddedFilter = false; + } + if (mGesturesLayoutListener != null && mGesturesPopup != null) { + mGlobalLayoutListenerAddedGestures = false; + treeObserver.removeGlobalOnLayoutListener(mGesturesLayoutListener); + } } } @@ -1534,6 +1793,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeCallbacks(mFlingRunnable); // Always hide the type filter dismissPopup(); + dismissGesturesPopup(); if (touchMode == TOUCH_MODE_OFF) { // Remember the last selected element @@ -1544,6 +1804,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Show the type filter only if a filter is in effect showPopup(); } + if (mGestures != GESTURES_NONE) { + showGesturesPopup(); + } // If we changed touch mode since the last time we had focus if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { @@ -2775,7 +3038,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Removes the filter window */ - void dismissPopup() { + private void dismissPopup() { if (mPopup != null) { mPopup.dismiss(); } @@ -3017,6 +3280,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te p.setBackgroundDrawable(null); mPopup = p; getViewTreeObserver().addOnGlobalLayoutListener(this); + mGlobalLayoutListenerAddedFilter = true; } if (animateEntrance) { mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter); @@ -3583,4 +3847,77 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } + + private class GesturesProcessor implements + TouchThroughGestureListener.OnGesturePerformedListener { + + private static final double SCORE_THRESHOLD = 0.1; + + private LetterRecognizer mRecognizer; + private ArrayList<Prediction> mPredictions; + private final KeyCharacterMap mKeyMap; + private final char[] mHolder; + + GesturesProcessor() { + mRecognizer = LetterRecognizer.getLetterRecognizer(getContext(), + LetterRecognizer.RECOGNIZER_LATIN_LOWERCASE); + if (mGestures == GESTURES_FILTER) { + mKeyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + mHolder = new char[1]; + } else { + mKeyMap = null; + mHolder = null; + } + } + + public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) { + mPredictions = mRecognizer.recognize(gesture, mPredictions); + if (!mPredictions.isEmpty()) { + final Prediction prediction = mPredictions.get(0); + if (prediction.score > SCORE_THRESHOLD) { + switch (mGestures) { + case GESTURES_JUMP: + processJump(prediction); + break; + case GESTURES_FILTER: + processFilter(prediction); + break; + } + } + } + } + + private void processJump(Prediction prediction) { + final Object[] sections = mFastScroller.getSections(); + if (sections != null) { + final String name = prediction.name; + final int count = sections.length; + + int index = -1; + for (int i = 0; i < count; i++) { + if (name.equalsIgnoreCase((String) sections[i])) { + index = i; + break; + } + } + + if (index != -1) { + final SectionIndexer indexer = mFastScroller.getSectionIndexer(); + final int position = indexer.getPositionForSection(index); + setSelection(position); + } + } + } + + private void processFilter(Prediction prediction) { + mHolder[0] = prediction.name.charAt(0); + final KeyEvent[] events = mKeyMap.getEvents(mHolder); + if (events != null) { + for (KeyEvent event : events) { + sendToTextFilter(event.getKeyCode(), event.getRepeatCount(), + event); + } + } + } + } } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 3368477..b9fd5a6 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -134,7 +134,7 @@ class FastScroller { mScrollCompleted = true; - getSections(); + getSectionsFromIndexer(); mOverlayPos = new RectF(); mScrollFade = new ScrollFade(); @@ -250,7 +250,18 @@ class FastScroller { } } - private void getSections() { + SectionIndexer getSectionIndexer() { + return mSectionIndexer; + } + + Object[] getSections() { + if (mListAdapter == null && mList != null) { + getSectionsFromIndexer(); + } + return mSections; + } + + private void getSectionsFromIndexer() { Adapter adapter = mList.getAdapter(); mSectionIndexer = null; if (adapter instanceof HeaderViewListAdapter) { @@ -411,7 +422,7 @@ class FastScroller { setState(STATE_DRAGGING); if (mListAdapter == null && mList != null) { - getSections(); + getSectionsFromIndexer(); } cancelFling(); @@ -448,7 +459,7 @@ class FastScroller { } return false; } - + public class ScrollFade implements Runnable { long mStartTime; diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 975277b..68764a5 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1072,6 +1072,20 @@ public class PopupWindow { mWindowManager.updateViewLayout(mPopupView, p); } } + + /** + * <p>Updates the dimension of the popup window. Calling this function + * also updates the window with the current popup state as described + * for {@link #update()}.</p> + * + * @param width the new width + * @param height the new height + */ + public void update(int width, int height) { + WindowManager.LayoutParams p = (WindowManager.LayoutParams) + mPopupView.getLayoutParams(); + update(p.x, p.y, width, height, false); + } /** * <p>Updates the position and the dimension of the popup window. Width and diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 40a72a4..5c75af2c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -127,6 +127,8 @@ import java.util.ArrayList; * @attr ref android.R.styleable#TextView_textColor * @attr ref android.R.styleable#TextView_textColorHighlight * @attr ref android.R.styleable#TextView_textColorHint + * @attr ref android.R.styleable#TextView_textAppearance + * @attr ref android.R.styleable#TextView_textColorLink * @attr ref android.R.styleable#TextView_textSize * @attr ref android.R.styleable#TextView_textScaleX * @attr ref android.R.styleable#TextView_typeface @@ -164,13 +166,22 @@ import java.util.ArrayList; * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText * @attr ref android.R.styleable#TextView_editable + * @attr ref android.R.styleable#TextView_freezesText + * @attr ref android.R.styleable#TextView_ellipsize * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableBottom * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableLeft + * @attr ref android.R.styleable#TextView_drawablePadding * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier * @attr ref android.R.styleable#TextView_marqueeRepeatLimit + * @attr ref android.R.styleable#TextView_inputType + * @attr ref android.R.styleable#TextView_imeOptions + * @attr ref android.R.styleable#TextView_privateImeOptions + * @attr ref android.R.styleable#TextView_imeActionLabel + * @attr ref android.R.styleable#TextView_imeActionId + * @attr ref android.R.styleable#TextView_editorExtras */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { |