Merge "Move forwarding code to ListPopupWindow, add drag-to-open in Spinner" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a2e88ef896
@ -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;
|
||||
@ -960,33 +961,6 @@ 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>
|
||||
@ -1154,6 +1128,147 @@ public class ListPopupWindow {
|
||||
return listContent + otherHeights;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
@ -448,6 +467,15 @@ public class Spinner extends AbsSpinner implements OnClickListener {
|
||||
super.setOnItemClickListener(l);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@ -32,6 +32,8 @@ import android.view.View.MeasureSpec;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListPopupWindow;
|
||||
import android.widget.ListPopupWindow.ForwardingListener;
|
||||
|
||||
import com.android.internal.view.ActionBarPolicy;
|
||||
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
|
||||
@ -562,7 +564,36 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
setFocusable(true);
|
||||
setVisibility(VISIBLE);
|
||||
setEnabled(true);
|
||||
setOnTouchListener(new OverflowForwardListener(context));
|
||||
|
||||
setOnTouchListener(new ForwardingListener(context) {
|
||||
@Override
|
||||
public ListPopupWindow getPopup() {
|
||||
if (mOverflowPopup == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mOverflowPopup.getPopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onForwardingStarted() {
|
||||
showOverflowMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onForwardingStopped() {
|
||||
// Displaying the popup occurs asynchronously, so wait for
|
||||
// the runnable to finish before deciding whether to stop
|
||||
// forwarding.
|
||||
if (mPostedOpenRunnable != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hideOverflowMenu();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -687,56 +718,4 @@ public class ActionMenuPresenter extends BaseMenuPresenter
|
||||
mPostedOpenRunnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class OverflowForwardListener extends TouchForwardingListener {
|
||||
/** Scaled touch slop, used for detecting movement outside bounds. */
|
||||
private final float mScaledTouchSlop;
|
||||
|
||||
private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;
|
||||
|
||||
public OverflowForwardListener(Context context) {
|
||||
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchObserved(View src, MotionEvent srcEvent) {
|
||||
if (!src.isEnabled()) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Cancel forwarding.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
|
||||
}
|
||||
}
|
||||
|
||||
public ListPopupWindow getPopup() {
|
||||
return mPopup;
|
||||
}
|
||||
|
||||
public boolean tryShow() {
|
||||
mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
|
||||
mPopup.setOnDismissListener(this);
|
||||
@ -159,22 +163,6 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
|
||||
return mPopup != null && mPopup.isShowing();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return mPopup.onForwardedEvent(src, event, activePointerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
MenuAdapter adapter = mAdapter;
|
||||
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.view.menu;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Touch listener used to intercept touches and forward them out of a view.
|
||||
*/
|
||||
abstract class TouchForwardingListener implements View.OnTouchListener {
|
||||
/** Whether this listener is currently forwarding touch events. */
|
||||
private boolean mForwarding;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
final int actionMasked = ev.getActionMasked();
|
||||
|
||||
if (mForwarding) {
|
||||
// Rejecting the event or ending the stream stops forwarding.
|
||||
if (!onTouchForwarded(v, ev) || actionMasked == MotionEvent.ACTION_UP
|
||||
|| actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||
stopForwarding();
|
||||
}
|
||||
} else {
|
||||
if (onTouchObserved(v, ev)) {
|
||||
startForwarding();
|
||||
}
|
||||
}
|
||||
|
||||
return mForwarding;
|
||||
}
|
||||
|
||||
public void startForwarding() {
|
||||
mForwarding = true;
|
||||
}
|
||||
|
||||
public void stopForwarding() {
|
||||
mForwarding = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start forwarding motion events.
|
||||
*
|
||||
* @param v The view that triggered forwarding.
|
||||
* @return True to start forwarding motion events, or false to cancel.
|
||||
*/
|
||||
public abstract boolean onTouchObserved(View v, MotionEvent ev);
|
||||
|
||||
/**
|
||||
* Handles forwarded motion events.
|
||||
*
|
||||
* @param v The view from which the event was forwarded.
|
||||
* @param ev The forwarded motion event.
|
||||
* @return True to continue forwarding motion events, or false to cancel.
|
||||
*/
|
||||
public abstract boolean onTouchForwarded(View v, MotionEvent ev);
|
||||
}
|
Reference in New Issue
Block a user