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;
|
package android.widget;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.DataSetObserver;
|
import android.database.DataSetObserver;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
@@ -23,6 +26,7 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.IntProperty;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -31,6 +35,7 @@ import android.view.View.MeasureSpec;
|
|||||||
import android.view.View.OnTouchListener;
|
import android.view.View.OnTouchListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -955,6 +960,33 @@ public class ListPopupWindow {
|
|||||||
return false;
|
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
|
* <p>Builds the popup window's content and returns the height the popup
|
||||||
* should have. Returns -1 when the content already exists.</p>
|
* 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 class DropDownListView extends ListView {
|
||||||
private static final String TAG = ListPopupWindow.TAG + ".DropDownListView";
|
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.
|
* WARNING: This is a workaround for a touch mode issue.
|
||||||
*
|
*
|
||||||
@@ -1165,6 +1218,12 @@ public class ListPopupWindow {
|
|||||||
*/
|
*/
|
||||||
private boolean mHijackFocus;
|
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>
|
* <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.
|
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
|
* <p>Avoids jarring scrolling effect by ensuring that list elements
|
||||||
* made of a text view fit on a single line.</p>
|
* 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.ViewConfiguration;
|
||||||
import android.view.View.MeasureSpec;
|
import android.view.View.MeasureSpec;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AbsListView;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ListPopupWindow;
|
|
||||||
|
|
||||||
import com.android.internal.view.ActionBarPolicy;
|
import com.android.internal.view.ActionBarPolicy;
|
||||||
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
|
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
|
||||||
@@ -694,32 +692,43 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchObserved(View v, MotionEvent ev) {
|
public boolean onTouchObserved(View src, MotionEvent srcEvent) {
|
||||||
if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && v.isEnabled()
|
if (!src.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()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) {
|
// Always start forwarding events when the source view is touched.
|
||||||
if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) {
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import android.view.View;
|
|||||||
import android.view.View.MeasureSpec;
|
import android.view.View.MeasureSpec;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.AbsListView;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.FrameLayout;
|
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;
|
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 Context mContext;
|
||||||
private final LayoutInflater mInflater;
|
private final LayoutInflater mInflater;
|
||||||
private final MenuBuilder mMenu;
|
private final MenuBuilder mMenu;
|
||||||
@@ -162,67 +159,20 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
|
|||||||
return mPopup != null && mPopup.isShowing();
|
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()) {
|
if (mPopup == null || !mPopup.isShowing()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbsListView dstView = mPopup.getListView();
|
return mPopup.onForwardedEvent(src, event, activePointerId);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user