Merge "Forward events to ListPopupWindow, highlight touched items" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
28dd8eb615
@ -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;
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
*
|
||||
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchForwarded(View v, MotionEvent ev) {
|
||||
if (!v.isEnabled() || mOverflowPopup == null || !mOverflowPopup.isShowing()) {
|
||||
public boolean onTouchObserved(View src, MotionEvent srcEvent) {
|
||||
if (!src.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) {
|
||||
if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) {
|
||||
// Always start forwarding events when the source view is touched.
|
||||
mActivePointerId = srcEvent.getPointerId(0);
|
||||
src.performClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user