diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/widget/ListPopupWindow.java | 169 | ||||
| -rw-r--r-- | core/java/android/widget/Spinner.java | 32 |
2 files changed, 172 insertions, 29 deletions
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 2b4e520..8919248 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -33,6 +33,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AccelerateDecelerateInterpolator; @@ -961,33 +962,6 @@ 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> * @@ -1155,6 +1129,147 @@ public class ListPopupWindow { } /** + * Abstract class that forwards touch events to a {@link ListPopupWindow}. + * + * @hide + */ + public static abstract class ForwardingListener implements View.OnTouchListener { + /** Scaled touch slop, used for detecting movement outside bounds. */ + private final float mScaledTouchSlop; + + /** Whether this listener is currently forwarding touch events. */ + private boolean mForwarding; + + /** The id of the first pointer down in the current event stream. */ + private int mActivePointerId; + + public ForwardingListener(Context context) { + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + /** + * Returns the popup to which this listener is forwarding events. + * <p> + * Override this to return the correct popup. If the popup is displayed + * asynchronously, you may also need to override + * {@link #onForwardingStopped} to prevent premature cancelation of + * forwarding. + * + * @return the popup to which this listener is forwarding events + */ + public abstract ListPopupWindow getPopup(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + final boolean wasForwarding = mForwarding; + final boolean forwarding; + if (wasForwarding) { + forwarding = onTouchForwarded(v, event) || !onForwardingStopped(); + } else { + forwarding = onTouchObserved(v, event) && onForwardingStarted(); + } + + mForwarding = forwarding; + return forwarding || wasForwarding; + } + + /** + * Called when forwarding would like to start. + * <p> + * By default, this will show the popup returned by {@link #getPopup()}. + * It may be overridden to perform another action, like clicking the + * source view or preparing the popup before showing it. + * + * @return true to start forwarding, false otherwise + */ + public boolean onForwardingStarted() { + final ListPopupWindow popup = getPopup(); + if (popup != null && !popup.isShowing()) { + popup.show(); + } + return true; + } + + /** + * Called when forwarding would like to stop. + * <p> + * By default, this will dismiss the popup returned by + * {@link #getPopup()}. It may be overridden to perform some other + * action. + * + * @return true to stop forwarding, false otherwise + */ + public boolean onForwardingStopped() { + final ListPopupWindow popup = getPopup(); + if (popup != null && popup.isShowing()) { + popup.dismiss(); + } + return true; + } + + /** + * Observes motion events and determines when to start forwarding. + * + * @param src view from which the event originated + * @param srcEvent motion event in source view coordinates + * @return true to start forwarding motion events, false otherwise + */ + private boolean onTouchObserved(View src, MotionEvent srcEvent) { + if (!src.isEnabled()) { + return false; + } + + // The first pointer down is always the active pointer. + final int actionMasked = srcEvent.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN) { + mActivePointerId = srcEvent.getPointerId(0); + } + + final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); + if (activePointerIndex >= 0) { + final float x = srcEvent.getX(activePointerIndex); + final float y = srcEvent.getY(activePointerIndex); + if (!src.pointInView(x, y, mScaledTouchSlop)) { + // The pointer has moved outside of the view. + return true; + } + } + + return false; + } + + /** + * Handled forwarded motion events and determines when to stop + * forwarding. + * + * @param src view from which the event originated + * @param srcEvent motion event in source view coordinates + * @return true to continue forwarding motion events, false to cancel + */ + private boolean onTouchForwarded(View src, MotionEvent srcEvent) { + final ListPopupWindow popup = getPopup(); + if (popup == null || !popup.isShowing()) { + return false; + } + + final DropDownListView dst = popup.mDropDownList; + if (dst == null || !dst.isShown()) { + return false; + } + + // Convert event to destination-local coordinates. + final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent); + src.toGlobalMotionEvent(dstEvent); + dst.toLocalMotionEvent(dstEvent); + + // Forward converted event to destination view, then recycle it. + final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId); + dstEvent.recycle(); + return handled; + } + } + + /** * <p>Wrapper class for a ListView. This wrapper can hijack the focus to * make sure the list uses the appropriate drawables and states when * displayed on screen within a drop down. The focus is never actually diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index b707cef..7c7df96 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -30,12 +30,14 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.ListPopupWindow.ForwardingListener; import android.widget.PopupWindow.OnDismissListener; @@ -76,7 +78,10 @@ public class Spinner extends AbsSpinner implements OnClickListener { * Use the theme-supplied value to select the dropdown mode. */ private static final int MODE_THEME = -1; - + + /** Forwarding listener used to implement drag-to-open. */ + private ForwardingListener mForwardingListener; + private SpinnerPopup mPopup; private DropDownAdapter mTempAdapter; int mDropDownWidth; @@ -173,7 +178,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { } case MODE_DROPDOWN: { - DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); + final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); mDropDownWidth = a.getLayoutDimension( com.android.internal.R.styleable.Spinner_dropDownWidth, @@ -193,6 +198,20 @@ public class Spinner extends AbsSpinner implements OnClickListener { } mPopup = popup; + mForwardingListener = new ForwardingListener(context) { + @Override + public ListPopupWindow getPopup() { + return popup; + } + + @Override + public boolean onForwardingStarted() { + if (!mPopup.isShowing()) { + mPopup.show(getTextDirection(), getTextAlignment()); + } + return true; + } + }; break; } } @@ -449,6 +468,15 @@ public class Spinner extends AbsSpinner implements OnClickListener { } @Override + public boolean onTouchEvent(MotionEvent event) { + if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) { + return true; + } + + return super.onTouchEvent(event); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { |
