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;
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;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.Locale;
}
/**
+ * 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>
*
*/
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.
*
*/
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>
*
}
/**
+ * 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>
*
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;
}
@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;
}
}
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;
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;
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