diff --git a/api/current.xml b/api/current.xml index 9fd12da29ec15..d98365aa3e5e0 100644 --- a/api/current.xml +++ b/api/current.xml @@ -5828,28 +5828,6 @@ visibility="public" > - - - - - - + + + + + + + + Warning: Views that use this feature should take care to disable/enable overlay + * appropriately when they are attached/detached from their window. All overlays should be + * disabled when detached. + * + * @param enabled true if overlay drawing should be enabled for this view, false otherwise + * + * @see #onDrawOverlay(Canvas) + * + * @hide + */ + protected void setOverlayEnabled(boolean enabled) { + final boolean oldValue = (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY; + mPrivateFlags = (mPrivateFlags & ~HAS_OVERLAY) | (enabled ? HAS_OVERLAY : 0); + if (enabled != oldValue) { + final ViewParent parent = getParent(); + if (parent != null) { + try { + parent.childOverlayStateChanged(this); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, "Could not propagate hasOverlay state", e); + } + } + } + } + + /** + * @return true if this View has an overlay enabled. + * + * @see #setOverlayEnabled(boolean) + * @see #onDrawOverlay(Canvas) + * + * @hide + */ + public boolean isOverlayEnabled() { + return (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY; + } + + /** + * Override this method to draw on an overlay layer above all other views in the window + * after the standard drawing pass is complete. This allows a view to draw outside its + * normal boundaries. + * @hide + */ + public void onDrawOverlay(Canvas canvas) { + } + private void resetPressedState() { if ((mViewFlags & ENABLED_MASK) == DISABLED) { return; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 28bed3a0d4896..fd6769ca64006 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -227,6 +227,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000; + /** + * When set, at least one child of this ViewGroup will return true from hasOverlay. + */ + private static final int FLAG_CHILD_HAS_OVERLAY = 0x100000; + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. @@ -854,6 +859,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; + + // Check for children with overlays first. They don't rely on hit rects to determine + // if they can accept a new touch event. + if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) { + for (int i = count - 1; i >= 0; i--) { + final View child = children[i]; + // Don't let children respond to events as an overlay during an animation. + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + && child.getAnimation() == null + && child.isOverlayEnabled()) { + // offset the event to the view's coordinate system + final float xc = scrolledXFloat - child.mLeft; + final float yc = scrolledYFloat - child.mTop; + ev.setLocation(xc, yc); + child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + if (child.dispatchTouchEvent(ev)) { + // Event handled, we have a target now. + mMotionTarget = child; + return true; + } + // The event didn't get handled, try the next view. + // Don't reset the event's location, it's not + // necessary here. + } + } + } + + // Now check views normally. for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE @@ -2312,6 +2345,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); } + + mGroupFlags &= ~FLAG_CHILD_HAS_OVERLAY; } /** @@ -2534,7 +2569,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int left = mLeft; final int top = mTop; - if (dirty.intersect(0, 0, mRight - left, mBottom - top) || + if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY || + dirty.intersect(0, 0, mRight - left, mBottom - top) || (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) { mPrivateFlags &= ~DRAWING_CACHE_VALID; @@ -3452,6 +3488,69 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mAnimationListener = animationListener; } + /** + * Called when a child's overlay state changes between enabled/disabled. + * @param child Child view whose state has changed or null + * @hide + */ + public void childOverlayStateChanged(View child) { + boolean childHasOverlay = false; + if (child != null) { + childHasOverlay = child.isOverlayEnabled(); + } else { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (childHasOverlay |= getChildAt(i).isOverlayEnabled()) { + break; + } + } + } + + final boolean hasChildWithOverlay = childHasOverlay || + (mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY; + + final boolean oldValue = isOverlayEnabled(); + mGroupFlags = (mGroupFlags & ~FLAG_CHILD_HAS_OVERLAY) | + (hasChildWithOverlay ? FLAG_CHILD_HAS_OVERLAY : 0); + if (isOverlayEnabled() != oldValue) { + final ViewParent parent = getParent(); + if (parent != null) { + try { + parent.childOverlayStateChanged(this); + } catch (AbstractMethodError e) { + Log.e("ViewGroup", "Could not propagate hasOverlay state", e); + } + } + } + } + + /** + * @hide + */ + public boolean isOverlayEnabled() { + return super.isOverlayEnabled() || + ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY); + } + + /** + * @hide + */ + @Override + public void onDrawOverlay(Canvas canvas) { + if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.isOverlayEnabled()) { + canvas.translate(child.mLeft + child.mScrollX, child.mTop + child.mScrollY); + child.onDrawOverlay(canvas); + canvas.translate(-(child.mLeft + child.mScrollX), + -(child.mTop + child.mScrollY)); + } + } + } + } + /** * LayoutParams are used by views to tell their parents how they want to be * laid out. See diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index b456c5d108d41..a0d3618486f08 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -208,4 +208,11 @@ public interface ViewParent { */ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate); + + /** + * Called when a child view's overlay state changes between enabled/disabled. + * @param child Child view whose state changed or null. + * @hide + */ + public void childOverlayStateChanged(View child); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 57c9055552b08..acec4762adf13 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -222,6 +222,8 @@ public final class ViewRoot extends Handler implements ViewParent, private final int mDensity; + private boolean mHasOverlay; + public static IWindowSession getWindowSession(Looper mainLooper) { synchronized (mStaticInit) { if (!mInitialized) { @@ -1518,6 +1520,9 @@ public final class ViewRoot extends Handler implements ViewParent, canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0); mView.draw(canvas); + if (mHasOverlay) { + mView.onDrawOverlay(canvas); + } } finally { mAttachInfo.mIgnoreDirtyState = false; canvas.restoreToCount(saveCount); @@ -2914,6 +2919,19 @@ public final class ViewRoot extends Handler implements ViewParent, return scrollToRectOrFocus(rectangle, immediate); } + /** + * @hide + */ + public void childOverlayStateChanged(View child) { + final boolean oldState = mHasOverlay; + mHasOverlay = child.isOverlayEnabled(); + // Invalidate the whole thing when we change overlay states just in case + // something left chunks of data drawn someplace it shouldn't have. + if (mHasOverlay != oldState) { + child.invalidate(); + } + } + class TakenSurfaceHolder extends BaseSurfaceHolder { @Override public boolean onAllowLockCanvas() { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 219bdf9352e24..199343a502dce 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -285,6 +285,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } InputMethodState mInputMethodState; + private int mTextSelectHandleLeftRes; + private int mTextSelectHandleRightRes; + private int mTextSelectHandleRes; + /* * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). @@ -705,6 +709,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "Failure reading input extras", e); } break; + + case com.android.internal.R.styleable.TextView_textSelectHandleLeft: + mTextSelectHandleLeftRes = a.getResourceId(attr, 0); + break; + + case com.android.internal.R.styleable.TextView_textSelectHandleRight: + mTextSelectHandleRightRes = a.getResourceId(attr, 0); + break; + + case com.android.internal.R.styleable.TextView_textSelectHandle: + mTextSelectHandleRes = a.getResourceId(attr, 0); + break; } } a.recycle(); @@ -3733,6 +3749,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener showError(); mShowErrorAfterAttach = false; } + + updateOverlay(); } @Override @@ -3750,6 +3768,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mError != null) { hideError(); } + + setOverlayEnabled(false); } @Override @@ -4100,7 +4120,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ canvas.restore(); + } + /** + * @hide + */ + @Override + public void onDrawOverlay(Canvas canvas) { if (mInsertionPointCursorController != null) { mInsertionPointCursorController.draw(canvas); } @@ -6684,10 +6710,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { + // Check to see if we're testing for our anchor overlay. + boolean handled = false; + final float x = event.getX(); + final float y = event.getY(); + if (x < mLeft || x > mRight || y < mTop || y > mBottom) { + if (mInsertionPointCursorController != null) { + handled |= mInsertionPointCursorController.onTouchEvent(event); + } + if (mSelectionModifierCursorController != null) { + handled |= mSelectionModifierCursorController.onTouchEvent(event); + } + + if (!handled) { + return false; + } + } + // Reset this state; it will be re-set if super.onTouchEvent // causes focus to move to the view. mTouchFocusSelected = false; mScrolled = false; + + if (handled) { + return true; + } } final boolean superResult = super.onTouchEvent(event); @@ -7576,6 +7623,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private void updateOverlay() { + boolean enableOverlay = false; + if (mSelectionModifierCursorController != null) { + enableOverlay |= mSelectionModifierCursorController.isShowing(); + } + if (mInsertionPointCursorController != null) { + enableOverlay |= mInsertionPointCursorController.isShowing(); + } + setOverlayEnabled(enableOverlay); + } + /** * A CursorController instance can be used to control a cursor in the text. * @@ -7598,6 +7656,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void hide(); + /** + * @return true if the CursorController is currently visible + */ + public boolean isShowing(); + /** * Update the controller's position. */ @@ -7620,7 +7683,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * a chance to become active and/or visible. * @param event The touch event */ - public void onTouchEvent(MotionEvent event); + public boolean onTouchEvent(MotionEvent event); /** * Draws a visual representation of the controller on the canvas. @@ -7654,10 +7717,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final Rect bounds = sCursorControllerTempRect; bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0) + mScrollX; - bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY; + bounds.top = (bottom ? lineBottom : lineTop) + mScrollY; mTopExtension = bottom ? 0 : drawableHeight / 2; - mBottomExtension = drawableHeight; + mBottomExtension = 0; //drawableHeight / 4; // Extend touch region up when editing the last line of text (or a single line) so that // it is easier to grab. @@ -7715,7 +7778,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener InsertionPointCursorController() { Resources res = mContext.getResources(); - mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + mHandle = new Handle(res.getDrawable(mTextSelectHandleRes)); } public void show() { @@ -7723,6 +7786,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Has to be done after updateDrawablePosition, so that previous position invalidate // in only done if necessary. mIsVisible = true; + updateOverlay(); } public void hide() { @@ -7736,6 +7800,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + public boolean isShowing() { + return mIsVisible; + } + public void draw(Canvas canvas) { if (mIsVisible) { int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); @@ -7751,6 +7819,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { mHandle.mDrawable.setAlpha(0); mIsVisible = false; + updateOverlay(); } } mHandle.mDrawable.draw(canvas); @@ -7779,6 +7848,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Should never happen, safety check. Log.w(LOG_TAG, "Update cursor controller position called with no cursor"); mIsVisible = false; + updateOverlay(); return; } @@ -7788,7 +7858,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHandle.mDrawable.setAlpha(255); } - public void onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event) { if (isFocused() && isTextEditable() && mIsVisible) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN : { @@ -7816,8 +7886,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mOffsetY += viewportToContentVerticalOffset(); mOnDownTimerStart = event.getEventTime(); + return true; } - break; + return false; } case MotionEvent.ACTION_UP : { @@ -7835,6 +7906,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } + return false; } public float getOffsetX() { @@ -7864,8 +7936,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SelectionModifierCursorController() { Resources res = mContext.getResources(); - mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); - mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + mStartHandle = new Handle(res.getDrawable(mTextSelectHandleLeftRes)); + mEndHandle = new Handle(res.getDrawable(mTextSelectHandleRightRes)); } public void show() { @@ -7873,6 +7945,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Has to be done after updateDrawablePositions, so that previous position invalidate // in only done if necessary. mIsVisible = true; + updateOverlay(); mFadeOutTimerStart = -1; hideInsertionPointCursorController(); } @@ -7885,8 +7958,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + public boolean isShowing() { + return mIsVisible; + } + public void cancelFadeOutAnimation() { mIsVisible = false; + updateOverlay(); mStartHandle.postInvalidate(); mEndHandle.postInvalidate(); } @@ -7905,6 +7983,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mStartHandle.mDrawable.setAlpha(0); mEndHandle.mDrawable.setAlpha(0); mIsVisible = false; + updateOverlay(); } } mStartHandle.mDrawable.draw(canvas); @@ -7962,6 +8041,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Should never happen, safety check. Log.w(LOG_TAG, "Update selection controller position called with no cursor"); mIsVisible = false; + updateOverlay(); return; } @@ -7974,7 +8054,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mEndHandle.mDrawable.setAlpha(255); } - public void onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event) { if (isTextEditable()) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -8009,6 +8089,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mOnDownTimerStart = event.getEventTime(); ((ArrowKeyMovementMethod)mMovement).setCursorController(this); + return true; } } } @@ -8034,6 +8115,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } } + return false; } /** diff --git a/core/res/res/drawable-hdpi/text_select_handle_left.png b/core/res/res/drawable-hdpi/text_select_handle_left.png new file mode 100644 index 0000000000000..271a6d031e1f4 Binary files /dev/null and b/core/res/res/drawable-hdpi/text_select_handle_left.png differ diff --git a/core/res/res/drawable-hdpi/text_select_handle_middle.png b/core/res/res/drawable-hdpi/text_select_handle_middle.png new file mode 100644 index 0000000000000..5a83c6cab4f36 Binary files /dev/null and b/core/res/res/drawable-hdpi/text_select_handle_middle.png differ diff --git a/core/res/res/drawable-hdpi/text_select_handle_right.png b/core/res/res/drawable-hdpi/text_select_handle_right.png new file mode 100644 index 0000000000000..dfdf899c3b3f7 Binary files /dev/null and b/core/res/res/drawable-hdpi/text_select_handle_right.png differ diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 13c3e7eacab95..ac1c0dd0fa955 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -451,6 +451,21 @@ + + + + + + + + + + + + @@ -2152,6 +2167,16 @@ EditorInfo.extras} field when the input method is connected. --> + + + + + + + + @android:drawable/text_select_handle_middle + @android:drawable/text_select_handle_middle + @android:drawable/text_select_handle_middle + @android:style/Widget.AbsListView @android:style/Widget.AutoCompleteTextView