Loading core/java/android/widget/ActionMenuPresenter.java +3 −3 Original line number Diff line number Diff line Loading @@ -37,7 +37,6 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ListPopupWindow.ForwardingListener; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.BaseMenuPresenter; Loading @@ -45,6 +44,7 @@ import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.MenuView; import com.android.internal.view.menu.ShowableListMenu; import com.android.internal.view.menu.SubMenuBuilder; import java.util.ArrayList; Loading Loading @@ -828,7 +828,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setOnTouchListener(new ForwardingListener(this) { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { if (mOverflowPopup == null) { return null; } Loading Loading @@ -1003,7 +1003,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; } } Loading core/java/android/widget/ActivityChooserView.java +2 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.widget; import com.android.internal.R; import com.android.internal.view.menu.ShowableListMenu; import android.annotation.StringRes; import android.content.Context; Loading @@ -37,7 +38,6 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ActivityChooserModel.ActivityChooserModelClient; import android.widget.ListPopupWindow.ForwardingListener; /** * This class is a view for choosing an activity for handling a given {@link Intent}. Loading Loading @@ -263,7 +263,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod }); expandButton.setOnTouchListener(new ForwardingListener(expandButton) { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { return getListPopupWindow(); } Loading core/java/android/widget/DropDownListView.java 0 → 100644 +344 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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 android.widget; import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.IntProperty; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; import android.widget.ListView; /** * 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 * passed to the drop down in this mode; the list only looks focused. * * @hide */ public class DropDownListView extends ListView { /** 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. * * Touch mode is propagated lazily to windows. This causes problems in * the following scenario: * - Type something in the AutoCompleteTextView and get some results * - Move down with the d-pad to select an item in the list * - Move up with the d-pad until the selection disappears * - Type more text in the AutoCompleteTextView *using the soft keyboard* * and get new results; you are now in touch mode * - The selection comes back on the first item in the list, even though * the list is supposed to be in touch mode * * Using the soft keyboard triggers the touch mode change but that change * is propagated to our window only after the first list layout, therefore * after the list attempts to resurrect the selection. * * The trick to work around this issue is to pretend the list is in touch * mode when we know that the selection should not appear, that is when * we know the user moved the selection away from the list. * * This boolean is set to true whenever we explicitly hide the list's * selection and reset to false whenever we know the user moved the * selection back to the list. * * When this boolean is true, isInTouchMode() returns true, otherwise it * returns super.isInTouchMode(). */ private boolean mListSelectionHidden; /** * True if this wrapper should fake focus. */ 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; /** Helper for drag-to-open auto scrolling. */ private AbsListViewAutoScroller mScrollHelper; /** * Creates a new list view wrapper. * * @param context this view's context */ public DropDownListView(Context context, boolean hijackFocus) { this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle); } /** * Creates a new list view wrapper. * * @param context this view's context */ public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) { super(context, null, defStyleAttr); mHijackFocus = hijackFocus; // TODO: Add an API to control this 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, x, y); handledEvent = true; if (actionMasked == MotionEvent.ACTION_UP) { clickPressedItem(child, position); } break; } // Failure to handle the event cancels forwarding. if (!handledEvent || clearPressedItem) { clearPressedItem(); } // Manage automatic scrolling. if (handledEvent) { if (mScrollHelper == null) { mScrollHelper = new AbsListViewAutoScroller(this); } mScrollHelper.setEnabled(true); mScrollHelper.onTouch(this, event); } else if (mScrollHelper != null) { mScrollHelper.setEnabled(false); } return handledEvent; } /** * Sets whether the list selection is hidden, as part of a workaround for a touch mode issue * (see the declaration for mListSelectionHidden). * @param listSelectionHidden */ public void setListSelectionHidden(boolean listSelectionHidden) { this.mListSelectionHidden = listSelectionHidden; } /** * 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(); final View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } if (mClickAnimation != null) { mClickAnimation.cancel(); mClickAnimation = null; } } private void setPressedItem(View child, int position, float x, float y) { mDrawsInPressedState = true; // Ordering is essential. First, update the container's pressed state. drawableHotspotChanged(x, y); if (!isPressed()) { setPressed(true); } // Next, run layout if we need to stabilize child positions. if (mDataChanged) { layoutChildren(); } // Manage the pressed view based on motion position. This allows us to // play nicely with actual touch and scroll events. final View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null && motionView != child && motionView.isPressed()) { motionView.setPressed(false); } mMotionPosition = position; // Offset for child coordinates. final float childX = x - child.getLeft(); final float childY = y - child.getTop(); child.drawableHotspotChanged(childX, childY); if (!child.isPressed()) { child.setPressed(true); } // Ensure that keyboard focus starts from the last touched position. setSelectedPositionInt(position); positionSelectorLikeTouch(position, child, x, y); // 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(); } /** * Avoids jarring scrolling effect by ensuring that list elements * made of a text view fit on a single line. * * @param position the item index in the list to get a view for * @return the view for the specified item */ @Override View obtainView(int position, boolean[] isScrap) { View view = super.obtainView(position, isScrap); if (view instanceof TextView) { ((TextView) view).setHorizontallyScrolling(true); } return view; } @Override public boolean isInTouchMode() { // WARNING: Please read the comment where mListSelectionHidden is declared return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean hasWindowFocus() { return mHijackFocus || super.hasWindowFocus(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean isFocused() { return mHijackFocus || super.isFocused(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean hasFocus() { return mHijackFocus || super.hasFocus(); } } No newline at end of file core/java/android/widget/ForwardingListener.java 0 → 100644 +299 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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 android.widget; import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import com.android.internal.view.menu.ShowableListMenu; /** * Abstract class that forwards touch events to a {@link ListPopupWindow}. * * @hide */ public abstract class ForwardingListener implements View.OnTouchListener, View.OnAttachStateChangeListener { /** Scaled touch slop, used for detecting movement outside bounds. */ private final float mScaledTouchSlop; /** Timeout before disallowing intercept on the source's parent. */ private final int mTapTimeout; /** Timeout before accepting a long-press to start forwarding. */ private final int mLongPressTimeout; /** Source view from which events are forwarded. */ private final View mSrc; /** Runnable used to prevent conflicts with scrolling parents. */ private Runnable mDisallowIntercept; /** Runnable used to trigger forwarding on long-press. */ private Runnable mTriggerLongPress; /** Whether this listener is currently forwarding touch events. */ private boolean mForwarding; /** * Whether forwarding was initiated by a long-press. If so, we won't * force the window to dismiss when the touch stream ends. */ private boolean mWasLongPress; /** The id of the first pointer down in the current event stream. */ private int mActivePointerId; public ForwardingListener(View src) { mSrc = src; mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop(); mTapTimeout = ViewConfiguration.getTapTimeout(); // Use a medium-press timeout. Halfway between tap and long-press. mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2; src.addOnAttachStateChangeListener(this); } /** * 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 cancellation of * forwarding. * * @return the popup to which this listener is forwarding events */ public abstract ShowableListMenu getPopup(); @Override public boolean onTouch(View v, MotionEvent event) { final boolean wasForwarding = mForwarding; final boolean forwarding; if (wasForwarding) { forwarding = onTouchForwarded(event) || !onForwardingStopped(); } else { forwarding = onTouchObserved(event) && onForwardingStarted(); if (forwarding) { // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); mSrc.onTouchEvent(e); e.recycle(); } } mForwarding = forwarding; return forwarding || wasForwarding; } @Override public void onViewAttachedToWindow(View v) { } @Override public void onViewDetachedFromWindow(View v) { mForwarding = false; mActivePointerId = MotionEvent.INVALID_POINTER_ID; if (mDisallowIntercept != null) { mSrc.removeCallbacks(mDisallowIntercept); } } /** * 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 */ protected boolean onForwardingStarted() { final ShowableListMenu 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 */ protected boolean onForwardingStopped() { final ShowableListMenu popup = getPopup(); if (popup != null && popup.isShowing()) { popup.dismiss(); } return true; } /** * Observes motion events and determines when to start forwarding. * * @param srcEvent motion event in source view coordinates * @return true to start forwarding motion events, false otherwise */ private boolean onTouchObserved(MotionEvent srcEvent) { final View src = mSrc; if (!src.isEnabled()) { return false; } final int actionMasked = srcEvent.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mActivePointerId = srcEvent.getPointerId(0); mWasLongPress = false; if (mDisallowIntercept == null) { mDisallowIntercept = new DisallowIntercept(); } src.postDelayed(mDisallowIntercept, mTapTimeout); if (mTriggerLongPress == null) { mTriggerLongPress = new TriggerLongPress(); } src.postDelayed(mTriggerLongPress, mLongPressTimeout); break; case MotionEvent.ACTION_MOVE: final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { final float x = srcEvent.getX(activePointerIndex); final float y = srcEvent.getY(activePointerIndex); // Has the pointer moved outside of the view? if (!src.pointInView(x, y, mScaledTouchSlop)) { clearCallbacks(); // Don't let the parent intercept our events. src.getParent().requestDisallowInterceptTouchEvent(true); return true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: clearCallbacks(); break; } return false; } private void clearCallbacks() { if (mTriggerLongPress != null) { mSrc.removeCallbacks(mTriggerLongPress); } if (mDisallowIntercept != null) { mSrc.removeCallbacks(mDisallowIntercept); } } private void onLongPress() { clearCallbacks(); final View src = mSrc; if (!src.isEnabled() || src.isLongClickable()) { // Ignore long-press if the view is disabled or has its own // handler. return; } if (!onForwardingStarted()) { return; } // Don't let the parent intercept our events. src.getParent().requestDisallowInterceptTouchEvent(true); // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); src.onTouchEvent(e); e.recycle(); mForwarding = true; mWasLongPress = true; } /** * Handles forwarded motion events and determines when to stop * forwarding. * * @param srcEvent motion event in source view coordinates * @return true to continue forwarding motion events, false to cancel */ private boolean onTouchForwarded(MotionEvent srcEvent) { final View src = mSrc; final ShowableListMenu popup = getPopup(); if (popup == null || !popup.isShowing()) { return false; } final DropDownListView dst = (DropDownListView) popup.getListView(); 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(); // Always cancel forwarding when the touch stream ends. final int action = srcEvent.getActionMasked(); final boolean keepForwarding = action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL; return handled && keepForwarding; } private class DisallowIntercept implements Runnable { @Override public void run() { final ViewParent parent = mSrc.getParent(); parent.requestDisallowInterceptTouchEvent(true); } } private class TriggerLongPress implements Runnable { @Override public void run() { onLongPress(); } } } No newline at end of file core/java/android/widget/ListPopupWindow.java +11 −570 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/widget/ActionMenuPresenter.java +3 −3 Original line number Diff line number Diff line Loading @@ -37,7 +37,6 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ListPopupWindow.ForwardingListener; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.BaseMenuPresenter; Loading @@ -45,6 +44,7 @@ import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.MenuView; import com.android.internal.view.menu.ShowableListMenu; import com.android.internal.view.menu.SubMenuBuilder; import java.util.ArrayList; Loading Loading @@ -828,7 +828,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setOnTouchListener(new ForwardingListener(this) { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { if (mOverflowPopup == null) { return null; } Loading Loading @@ -1003,7 +1003,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; } } Loading
core/java/android/widget/ActivityChooserView.java +2 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.widget; import com.android.internal.R; import com.android.internal.view.menu.ShowableListMenu; import android.annotation.StringRes; import android.content.Context; Loading @@ -37,7 +38,6 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ActivityChooserModel.ActivityChooserModelClient; import android.widget.ListPopupWindow.ForwardingListener; /** * This class is a view for choosing an activity for handling a given {@link Intent}. Loading Loading @@ -263,7 +263,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod }); expandButton.setOnTouchListener(new ForwardingListener(expandButton) { @Override public ListPopupWindow getPopup() { public ShowableListMenu getPopup() { return getListPopupWindow(); } Loading
core/java/android/widget/DropDownListView.java 0 → 100644 +344 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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 android.widget; import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.IntProperty; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; import android.widget.ListView; /** * 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 * passed to the drop down in this mode; the list only looks focused. * * @hide */ public class DropDownListView extends ListView { /** 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. * * Touch mode is propagated lazily to windows. This causes problems in * the following scenario: * - Type something in the AutoCompleteTextView and get some results * - Move down with the d-pad to select an item in the list * - Move up with the d-pad until the selection disappears * - Type more text in the AutoCompleteTextView *using the soft keyboard* * and get new results; you are now in touch mode * - The selection comes back on the first item in the list, even though * the list is supposed to be in touch mode * * Using the soft keyboard triggers the touch mode change but that change * is propagated to our window only after the first list layout, therefore * after the list attempts to resurrect the selection. * * The trick to work around this issue is to pretend the list is in touch * mode when we know that the selection should not appear, that is when * we know the user moved the selection away from the list. * * This boolean is set to true whenever we explicitly hide the list's * selection and reset to false whenever we know the user moved the * selection back to the list. * * When this boolean is true, isInTouchMode() returns true, otherwise it * returns super.isInTouchMode(). */ private boolean mListSelectionHidden; /** * True if this wrapper should fake focus. */ 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; /** Helper for drag-to-open auto scrolling. */ private AbsListViewAutoScroller mScrollHelper; /** * Creates a new list view wrapper. * * @param context this view's context */ public DropDownListView(Context context, boolean hijackFocus) { this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle); } /** * Creates a new list view wrapper. * * @param context this view's context */ public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) { super(context, null, defStyleAttr); mHijackFocus = hijackFocus; // TODO: Add an API to control this 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, x, y); handledEvent = true; if (actionMasked == MotionEvent.ACTION_UP) { clickPressedItem(child, position); } break; } // Failure to handle the event cancels forwarding. if (!handledEvent || clearPressedItem) { clearPressedItem(); } // Manage automatic scrolling. if (handledEvent) { if (mScrollHelper == null) { mScrollHelper = new AbsListViewAutoScroller(this); } mScrollHelper.setEnabled(true); mScrollHelper.onTouch(this, event); } else if (mScrollHelper != null) { mScrollHelper.setEnabled(false); } return handledEvent; } /** * Sets whether the list selection is hidden, as part of a workaround for a touch mode issue * (see the declaration for mListSelectionHidden). * @param listSelectionHidden */ public void setListSelectionHidden(boolean listSelectionHidden) { this.mListSelectionHidden = listSelectionHidden; } /** * 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(); final View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } if (mClickAnimation != null) { mClickAnimation.cancel(); mClickAnimation = null; } } private void setPressedItem(View child, int position, float x, float y) { mDrawsInPressedState = true; // Ordering is essential. First, update the container's pressed state. drawableHotspotChanged(x, y); if (!isPressed()) { setPressed(true); } // Next, run layout if we need to stabilize child positions. if (mDataChanged) { layoutChildren(); } // Manage the pressed view based on motion position. This allows us to // play nicely with actual touch and scroll events. final View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null && motionView != child && motionView.isPressed()) { motionView.setPressed(false); } mMotionPosition = position; // Offset for child coordinates. final float childX = x - child.getLeft(); final float childY = y - child.getTop(); child.drawableHotspotChanged(childX, childY); if (!child.isPressed()) { child.setPressed(true); } // Ensure that keyboard focus starts from the last touched position. setSelectedPositionInt(position); positionSelectorLikeTouch(position, child, x, y); // 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(); } /** * Avoids jarring scrolling effect by ensuring that list elements * made of a text view fit on a single line. * * @param position the item index in the list to get a view for * @return the view for the specified item */ @Override View obtainView(int position, boolean[] isScrap) { View view = super.obtainView(position, isScrap); if (view instanceof TextView) { ((TextView) view).setHorizontallyScrolling(true); } return view; } @Override public boolean isInTouchMode() { // WARNING: Please read the comment where mListSelectionHidden is declared return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean hasWindowFocus() { return mHijackFocus || super.hasWindowFocus(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean isFocused() { return mHijackFocus || super.isFocused(); } /** * Returns the focus state in the drop down. * * @return true always if hijacking focus */ @Override public boolean hasFocus() { return mHijackFocus || super.hasFocus(); } } No newline at end of file
core/java/android/widget/ForwardingListener.java 0 → 100644 +299 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 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 android.widget; import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import com.android.internal.view.menu.ShowableListMenu; /** * Abstract class that forwards touch events to a {@link ListPopupWindow}. * * @hide */ public abstract class ForwardingListener implements View.OnTouchListener, View.OnAttachStateChangeListener { /** Scaled touch slop, used for detecting movement outside bounds. */ private final float mScaledTouchSlop; /** Timeout before disallowing intercept on the source's parent. */ private final int mTapTimeout; /** Timeout before accepting a long-press to start forwarding. */ private final int mLongPressTimeout; /** Source view from which events are forwarded. */ private final View mSrc; /** Runnable used to prevent conflicts with scrolling parents. */ private Runnable mDisallowIntercept; /** Runnable used to trigger forwarding on long-press. */ private Runnable mTriggerLongPress; /** Whether this listener is currently forwarding touch events. */ private boolean mForwarding; /** * Whether forwarding was initiated by a long-press. If so, we won't * force the window to dismiss when the touch stream ends. */ private boolean mWasLongPress; /** The id of the first pointer down in the current event stream. */ private int mActivePointerId; public ForwardingListener(View src) { mSrc = src; mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop(); mTapTimeout = ViewConfiguration.getTapTimeout(); // Use a medium-press timeout. Halfway between tap and long-press. mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2; src.addOnAttachStateChangeListener(this); } /** * 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 cancellation of * forwarding. * * @return the popup to which this listener is forwarding events */ public abstract ShowableListMenu getPopup(); @Override public boolean onTouch(View v, MotionEvent event) { final boolean wasForwarding = mForwarding; final boolean forwarding; if (wasForwarding) { forwarding = onTouchForwarded(event) || !onForwardingStopped(); } else { forwarding = onTouchObserved(event) && onForwardingStarted(); if (forwarding) { // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); mSrc.onTouchEvent(e); e.recycle(); } } mForwarding = forwarding; return forwarding || wasForwarding; } @Override public void onViewAttachedToWindow(View v) { } @Override public void onViewDetachedFromWindow(View v) { mForwarding = false; mActivePointerId = MotionEvent.INVALID_POINTER_ID; if (mDisallowIntercept != null) { mSrc.removeCallbacks(mDisallowIntercept); } } /** * 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 */ protected boolean onForwardingStarted() { final ShowableListMenu 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 */ protected boolean onForwardingStopped() { final ShowableListMenu popup = getPopup(); if (popup != null && popup.isShowing()) { popup.dismiss(); } return true; } /** * Observes motion events and determines when to start forwarding. * * @param srcEvent motion event in source view coordinates * @return true to start forwarding motion events, false otherwise */ private boolean onTouchObserved(MotionEvent srcEvent) { final View src = mSrc; if (!src.isEnabled()) { return false; } final int actionMasked = srcEvent.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: mActivePointerId = srcEvent.getPointerId(0); mWasLongPress = false; if (mDisallowIntercept == null) { mDisallowIntercept = new DisallowIntercept(); } src.postDelayed(mDisallowIntercept, mTapTimeout); if (mTriggerLongPress == null) { mTriggerLongPress = new TriggerLongPress(); } src.postDelayed(mTriggerLongPress, mLongPressTimeout); break; case MotionEvent.ACTION_MOVE: final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { final float x = srcEvent.getX(activePointerIndex); final float y = srcEvent.getY(activePointerIndex); // Has the pointer moved outside of the view? if (!src.pointInView(x, y, mScaledTouchSlop)) { clearCallbacks(); // Don't let the parent intercept our events. src.getParent().requestDisallowInterceptTouchEvent(true); return true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: clearCallbacks(); break; } return false; } private void clearCallbacks() { if (mTriggerLongPress != null) { mSrc.removeCallbacks(mTriggerLongPress); } if (mDisallowIntercept != null) { mSrc.removeCallbacks(mDisallowIntercept); } } private void onLongPress() { clearCallbacks(); final View src = mSrc; if (!src.isEnabled() || src.isLongClickable()) { // Ignore long-press if the view is disabled or has its own // handler. return; } if (!onForwardingStarted()) { return; } // Don't let the parent intercept our events. src.getParent().requestDisallowInterceptTouchEvent(true); // Make sure we cancel any ongoing source event stream. final long now = SystemClock.uptimeMillis(); final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); src.onTouchEvent(e); e.recycle(); mForwarding = true; mWasLongPress = true; } /** * Handles forwarded motion events and determines when to stop * forwarding. * * @param srcEvent motion event in source view coordinates * @return true to continue forwarding motion events, false to cancel */ private boolean onTouchForwarded(MotionEvent srcEvent) { final View src = mSrc; final ShowableListMenu popup = getPopup(); if (popup == null || !popup.isShowing()) { return false; } final DropDownListView dst = (DropDownListView) popup.getListView(); 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(); // Always cancel forwarding when the touch stream ends. final int action = srcEvent.getActionMasked(); final boolean keepForwarding = action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL; return handled && keepForwarding; } private class DisallowIntercept implements Runnable { @Override public void run() { final ViewParent parent = mSrc.getParent(); parent.requestDisallowInterceptTouchEvent(true); } } private class TriggerLongPress implements Runnable { @Override public void run() { onLongPress(); } } } No newline at end of file
core/java/android/widget/ListPopupWindow.java +11 −570 File changed.Preview size limit exceeded, changes collapsed. Show changes