diff options
| author | Alan Viverette <alanv@google.com> | 2013-08-15 18:05:52 -0700 |
|---|---|---|
| committer | Alan Viverette <alanv@google.com> | 2013-08-15 18:05:52 -0700 |
| commit | c05027214f1f4dda67296a072dfc9af9176dc590 (patch) | |
| tree | 8074af52165c1b1765cb2d4e2d99650e9235678e /core/java/android | |
| parent | 63169fb9a6d1abc63dd2a62691527d4a1269e5cd (diff) | |
Forward events to ListPopupWindow, highlight touched items
Moves most of the drag-to-open behavior into ListPopupWindow's
particular implementation of ListView. Uses hidden View API for
forwarding events between different windows.
Overflow menu opens on first touch, closes on touch end outside
the overflow button. Clicks that occur during drag-to-open mode
result in alpha animation of the selector drawable.
BUG: 9437139
Change-Id: I70f540555a03450638a27880b3ae3b031ca6e2ed
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/widget/ListPopupWindow.java | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 414c3187519c..2b4e5206aa47 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -16,6 +16,9 @@ package android.widget; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.content.Context; import android.database.DataSetObserver; import android.graphics.Rect; @@ -23,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.IntProperty; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -31,6 +35,7 @@ import android.view.View.MeasureSpec; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.animation.AccelerateDecelerateInterpolator; import java.util.Locale; @@ -956,6 +961,33 @@ public class ListPopupWindow { } /** + * Receives motion events forwarded from a source view. This is used + * internally to implement support for drag-to-open. + * + * @param src view from which the event was forwarded + * @param srcEvent forwarded motion event in source-local coordinates + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + * @hide + */ + public boolean onForwardedEvent(View src, MotionEvent srcEvent, int activePointerId) { + final DropDownListView dst = mDropDownList; + if (dst == null || !dst.isShown()) { + return false; + } + + // Convert event to local coordinates. + final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent); + src.toGlobalMotionEvent(dstEvent); + dst.toLocalMotionEvent(dstEvent); + + // Forward converted event, then recycle it. + final boolean handled = dst.onForwardedEvent(dstEvent, activePointerId); + dstEvent.recycle(); + return handled; + } + + /** * <p>Builds the popup window's content and returns the height the popup * should have. Returns -1 when the content already exists.</p> * @@ -1130,6 +1162,27 @@ public class ListPopupWindow { */ private static class DropDownListView extends ListView { private static final String TAG = ListPopupWindow.TAG + ".DropDownListView"; + + /** Duration in milliseconds of the drag-to-open click animation. */ + private static final long CLICK_ANIM_DURATION = 150; + + /** Target alpha value for drag-to-open click animation. */ + private static final int CLICK_ANIM_ALPHA = 0x80; + + /** Wrapper around Drawable's <code>alpha</code> property. */ + private static final IntProperty<Drawable> DRAWABLE_ALPHA = + new IntProperty<Drawable>("alpha") { + @Override + public void setValue(Drawable object, int value) { + object.setAlpha(value); + } + + @Override + public Integer get(Drawable object) { + return object.getAlpha(); + } + }; + /* * WARNING: This is a workaround for a touch mode issue. * @@ -1165,6 +1218,12 @@ public class ListPopupWindow { */ private boolean mHijackFocus; + /** Whether to force drawing of the pressed state selector. */ + private boolean mDrawsInPressedState; + + /** Current drag-to-open click animation, if any. */ + private Animator mClickAnimation; + /** * <p>Creates a new list view wrapper.</p> * @@ -1178,6 +1237,119 @@ public class ListPopupWindow { } /** + * Handles forwarded events. + * + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + */ + public boolean onForwardedEvent(MotionEvent event, int activePointerId) { + boolean handledEvent = true; + boolean clearPressedItem = false; + + final int actionMasked = event.getActionMasked(); + switch (actionMasked) { + case MotionEvent.ACTION_CANCEL: + handledEvent = false; + break; + case MotionEvent.ACTION_UP: + handledEvent = false; + // $FALL-THROUGH$ + case MotionEvent.ACTION_MOVE: + final int activeIndex = event.findPointerIndex(activePointerId); + if (activeIndex < 0) { + handledEvent = false; + break; + } + + final int x = (int) event.getX(activeIndex); + final int y = (int) event.getY(activeIndex); + final int position = pointToPosition(x, y); + if (position == INVALID_POSITION) { + clearPressedItem = true; + break; + } + + final View child = getChildAt(position - getFirstVisiblePosition()); + setPressedItem(child, position); + handledEvent = true; + + if (actionMasked == MotionEvent.ACTION_UP) { + clickPressedItem(child, position); + } + break; + } + + // Failure to handle the event cancels forwarding. + if (!handledEvent || clearPressedItem) { + clearPressedItem(); + } + + return handledEvent; + } + + /** + * Starts an alpha animation on the selector. When the animation ends, + * the list performs a click on the item. + */ + private void clickPressedItem(final View child, final int position) { + final long id = getItemIdAtPosition(position); + final Animator anim = ObjectAnimator.ofInt( + mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF); + anim.setDuration(CLICK_ANIM_DURATION); + anim.setInterpolator(new AccelerateDecelerateInterpolator()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + performItemClick(child, position, id); + } + }); + anim.start(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + } + mClickAnimation = anim; + } + + private void clearPressedItem() { + mDrawsInPressedState = false; + setPressed(false); + updateSelectorState(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + mClickAnimation = null; + } + } + + private void setPressedItem(View child, int position) { + mDrawsInPressedState = true; + + // Ordering is essential. First update the pressed state and layout + // the children. This will ensure the selector actually gets drawn. + setPressed(true); + layoutChildren(); + + // Ensure that keyboard focus starts from the last touched position. + setSelectedPositionInt(position); + positionSelector(position, child); + + // Refresh the drawable state to reflect the new pressed state, + // which will also update the selector state. + refreshDrawableState(); + + if (mClickAnimation != null) { + mClickAnimation.cancel(); + mClickAnimation = null; + } + } + + @Override + boolean touchModeDrawsInPressedState() { + return mDrawsInPressedState || super.touchModeDrawsInPressedState(); + } + + /** * <p>Avoids jarring scrolling effect by ensuring that list elements * made of a text view fit on a single line.</p> * |
