diff options
| author | 2013-08-16 17:56:42 +0000 | |
|---|---|---|
| committer | 2013-08-16 17:59:34 +0000 | |
| commit | 28dd8eb615a46ae169132b0ee75ad06b606f010c (patch) | |
| tree | 76ced4f0fa266d7298a6838a6f6509ddd166e5c2 | |
| parent | 960338adbcc4344b0f9e0478026e42e911eb2210 (diff) | |
| parent | c05027214f1f4dda67296a072dfc9af9176dc590 (diff) | |
Merge "Forward events to ListPopupWindow, highlight touched items" into klp-dev
3 files changed, 209 insertions, 78 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> * diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index ff9678cf03bd..863d8ccffe12 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -30,9 +30,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.View.MeasureSpec; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.ImageButton; -import android.widget.ListPopupWindow; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; @@ -694,32 +692,43 @@ public class ActionMenuPresenter extends BaseMenuPresenter } @Override - public boolean onTouchObserved(View v, MotionEvent ev) { - if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && v.isEnabled() - && !v.pointInView(ev.getX(), ev.getY(), mScaledTouchSlop)) { - mActivePointerId = ev.getPointerId(0); - v.performClick(); - return true; + public boolean onTouchObserved(View src, MotionEvent srcEvent) { + if (!src.isEnabled()) { + return false; } - return false; + // Always start forwarding events when the source view is touched. + mActivePointerId = srcEvent.getPointerId(0); + src.performClick(); + return true; } @Override - public boolean onTouchForwarded(View v, MotionEvent ev) { - if (!v.isEnabled() || mOverflowPopup == null || !mOverflowPopup.isShowing()) { - return false; - } - - if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { - if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) { + public boolean onTouchForwarded(View src, MotionEvent srcEvent) { + final OverflowPopup popup = mOverflowPopup; + if (popup != null && popup.isShowing()) { + final int activePointerId = mActivePointerId; + if (activePointerId != MotionEvent.INVALID_POINTER_ID && src.isEnabled() + && popup.forwardMotionEvent(src, srcEvent, activePointerId)) { + // Handled the motion event, continue forwarding. return true; } - mActivePointerId = MotionEvent.INVALID_POINTER_ID; + final int activePointerIndex = srcEvent.findPointerIndex(activePointerId); + if (activePointerIndex >= 0) { + final float x = srcEvent.getX(activePointerIndex); + final float y = srcEvent.getY(activePointerIndex); + if (src.pointInView(x, y, mScaledTouchSlop)) { + // The user is touching the source view. Cancel + // forwarding, but don't dismiss the popup. + return false; + } + } + + popup.dismiss(); } - mOverflowPopup.dismiss(); + // Cancel forwarding. return false; } } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 945f42b9612e..9b266dfb3ca7 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -27,7 +27,6 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; @@ -48,8 +47,6 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; - private final int[] mTempLocation = new int[2]; - private final Context mContext; private final LayoutInflater mInflater; private final MenuBuilder mMenu; @@ -162,67 +159,20 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On return mPopup != null && mPopup.isShowing(); } - public boolean forwardMotionEvent(View v, MotionEvent ev, int activePointerId) { + /** + * Forwards motion events from a source view to the popup window. + * + * @param src view from which the event was forwarded + * @param event forwarded motion event in source-local coordinates + * @param activePointerId id of the pointer that activated forwarding + * @return whether the event was handled + */ + public boolean forwardMotionEvent(View src, MotionEvent event, int activePointerId) { if (mPopup == null || !mPopup.isShowing()) { return false; } - final AbsListView dstView = mPopup.getListView(); - if (dstView == null || !dstView.isShown()) { - return false; - } - - boolean cancelForwarding = false; - final int actionMasked = ev.getActionMasked(); - switch (actionMasked) { - case MotionEvent.ACTION_CANCEL: - cancelForwarding = true; - break; - case MotionEvent.ACTION_UP: - cancelForwarding = true; - // $FALL-THROUGH$ - case MotionEvent.ACTION_MOVE: - final int activeIndex = ev.findPointerIndex(activePointerId); - if (activeIndex < 0) { - return false; - } - - final int[] location = mTempLocation; - int x = (int) ev.getX(activeIndex); - int y = (int) ev.getY(activeIndex); - - // Convert to global coordinates. - v.getLocationOnScreen(location); - x += location[0]; - y += location[1]; - - // Convert to local coordinates. - dstView.getLocationOnScreen(location); - x -= location[0]; - y -= location[1]; - - final int position = dstView.pointToPosition(x, y); - if (position >= 0) { - final int childCount = dstView.getChildCount(); - final int firstVisiblePosition = dstView.getFirstVisiblePosition(); - final int index = position - firstVisiblePosition; - if (index < childCount) { - final View child = dstView.getChildAt(index); - if (actionMasked == MotionEvent.ACTION_UP) { - // Touch ended, click highlighted item. - final long id = dstView.getItemIdAtPosition(position); - dstView.performItemClick(child, position, id); - } else if (actionMasked == MotionEvent.ACTION_MOVE) { - // TODO: Highlight touched item, activate after - // long-hover. Consider forwarding events as HOVER and - // letting ListView handle this. - } - } - } - break; - } - - return true; + return mPopup.onForwardedEvent(src, event, activePointerId); } @Override |