summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/widget/ListPopupWindow.java169
-rw-r--r--core/java/android/widget/Spinner.java32
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) {