Loading core/java/android/widget/ListPopupWindow.java +172 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -955,6 +960,33 @@ public class ListPopupWindow { return false; } /** * 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> Loading Loading @@ -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. * Loading Loading @@ -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> * Loading @@ -1177,6 +1236,119 @@ public class ListPopupWindow { setCacheColorHint(0); // Transparent, since the background drawable could be anything. } /** * 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> Loading core/java/com/android/internal/view/menu/ActionMenuPresenter.java +27 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; 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; } if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) { return true; 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; } } mActivePointerId = MotionEvent.INVALID_POINTER_ID; popup.dismiss(); } mOverflowPopup.dismiss(); // Cancel forwarding. return false; } } Loading core/java/com/android/internal/view/menu/MenuPopupHelper.java +10 −60 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading
core/java/android/widget/ListPopupWindow.java +172 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -955,6 +960,33 @@ public class ListPopupWindow { return false; } /** * 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> Loading Loading @@ -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. * Loading Loading @@ -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> * Loading @@ -1177,6 +1236,119 @@ public class ListPopupWindow { setCacheColorHint(0); // Transparent, since the background drawable could be anything. } /** * 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> Loading
core/java/com/android/internal/view/menu/ActionMenuPresenter.java +27 −18 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; 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; } if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) { return true; 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; } } mActivePointerId = MotionEvent.INVALID_POINTER_ID; popup.dismiss(); } mOverflowPopup.dismiss(); // Cancel forwarding. return false; } } Loading
core/java/com/android/internal/view/menu/MenuPopupHelper.java +10 −60 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading