diff options
author | Oren Blasberg <orenb@google.com> | 2015-09-01 21:11:04 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-09-01 21:11:04 +0000 |
commit | 6b49d300346d130d2681cbd3de7165989c3a33f2 (patch) | |
tree | 821b4e9ff1820958688e5c28b02662a20d0c56fd | |
parent | 8056e4132416265196c9f0f3cc0e86bceeb7d907 (diff) | |
parent | f44d90b5c247f0629201d1fa322b83fa55b20608 (diff) |
Merge "Pull out widget helpers into their own classes."
-rw-r--r-- | core/java/android/widget/ActionMenuPresenter.java | 6 | ||||
-rw-r--r-- | core/java/android/widget/ActivityChooserView.java | 4 | ||||
-rw-r--r-- | core/java/android/widget/DropDownListView.java | 344 | ||||
-rw-r--r-- | core/java/android/widget/ForwardingListener.java | 299 | ||||
-rw-r--r-- | core/java/android/widget/ListPopupWindow.java | 581 | ||||
-rw-r--r-- | core/java/android/widget/MenuPopupWindow.java | 4 | ||||
-rw-r--r-- | core/java/android/widget/PopupMenu.java | 4 | ||||
-rw-r--r-- | core/java/android/widget/Spinner.java | 4 | ||||
-rw-r--r-- | core/java/com/android/internal/view/menu/ActionMenuItemView.java | 10 | ||||
-rw-r--r-- | core/java/com/android/internal/view/menu/MenuAdapter.java | 109 | ||||
-rw-r--r-- | core/java/com/android/internal/view/menu/MenuPopupHelper.java | 71 | ||||
-rw-r--r-- | core/java/com/android/internal/view/menu/ShowableListMenu.java | 35 |
12 files changed, 815 insertions, 656 deletions
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index a5696eefa1db..64c41039bea5 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -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; @@ -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; @@ -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; } @@ -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; } } diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index f34ad71ea76a..f5c46db94f4f 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -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; @@ -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}. @@ -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(); } diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java new file mode 100644 index 000000000000..553651308e9c --- /dev/null +++ b/core/java/android/widget/DropDownListView.java @@ -0,0 +1,344 @@ +/* + * 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 diff --git a/core/java/android/widget/ForwardingListener.java b/core/java/android/widget/ForwardingListener.java new file mode 100644 index 000000000000..fd7140f779fe --- /dev/null +++ b/core/java/android/widget/ForwardingListener.java @@ -0,0 +1,299 @@ +/* + * 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 diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index a02efcff4197..3d07d87dfe70 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -25,7 +25,6 @@ import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; import android.util.IntProperty; @@ -36,13 +35,13 @@ 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.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import com.android.internal.R; +import com.android.internal.view.menu.ShowableListMenu; import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; import java.util.Locale; @@ -58,7 +57,7 @@ import java.util.Locale; * @see android.widget.AutoCompleteTextView * @see android.widget.Spinner */ -public class ListPopupWindow { +public class ListPopupWindow implements ShowableListMenu { private static final String TAG = "ListPopupWindow"; private static final boolean DEBUG = false; @@ -580,6 +579,7 @@ public class ListPopupWindow { * Show the popup list. If the list is already showing, this method * will recalculate the popup's size and position. */ + @Override public void show() { int height = buildDropDown(); @@ -671,6 +671,7 @@ public class ListPopupWindow { /** * Dismiss the popup window. */ + @Override public void dismiss() { mPopup.dismiss(); removePromptView(); @@ -732,7 +733,7 @@ public class ListPopupWindow { public void setSelection(int position) { DropDownListView list = mDropDownList; if (isShowing() && list != null) { - list.mListSelectionHidden = false; + list.setListSelectionHidden(false); list.setSelection(position); if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) { list.setItemChecked(position, true); @@ -748,7 +749,7 @@ public class ListPopupWindow { final DropDownListView list = mDropDownList; if (list != null) { // WARNING: Please read the comment where mListSelectionHidden is declared - list.mListSelectionHidden = true; + list.setListSelectionHidden(true); list.hideSelector(); list.requestLayout(); } @@ -757,6 +758,7 @@ public class ListPopupWindow { /** * @return {@code true} if the popup is currently showing, {@code false} otherwise. */ + @Override public boolean isShowing() { return mPopup.isShowing(); } @@ -842,6 +844,7 @@ public class ListPopupWindow { * @return The {@link ListView} displayed within the popup window. * Only valid when {@link #isShowing()} == {@code true}. */ + @Override public ListView getListView() { return mDropDownList; } @@ -911,7 +914,7 @@ public class ListPopupWindow { } else { // WARNING: Please read the comment where mListSelectionHidden // is declared - mDropDownList.mListSelectionHidden = false; + mDropDownList.setListSelectionHidden(false); } consumed = mDropDownList.onKeyDown(keyCode, event); @@ -1037,7 +1040,7 @@ public class ListPopupWindow { public OnTouchListener createDragToOpenListener(View src) { return new ForwardingListener(src) { @Override - public ListPopupWindow getPopup() { + public ShowableListMenu getPopup() { return ListPopupWindow.this; } }; @@ -1088,7 +1091,7 @@ public class ListPopupWindow { DropDownListView dropDownList = mDropDownList; if (dropDownList != null) { - dropDownList.mListSelectionHidden = false; + dropDownList.setListSelectionHidden(false); } } } @@ -1219,568 +1222,6 @@ 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, 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 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(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 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 - */ - protected 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 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 has 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; - } - - /** - * Handled 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 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(); - - // 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(); - } - } - } - - /** - * <p>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.</p> - */ - static 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; - - /** - * <p>Creates a new list view wrapper.</p> - * - * @param context this view's context - */ - public DropDownListView(Context context, boolean hijackFocus) { - super(context, null, com.android.internal.R.attr.dropDownListViewStyle); - 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; - } - - /** - * 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(); - } - - /** - * <p>Avoids jarring scrolling effect by ensuring that list elements - * made of a text view fit on a single line.</p> - * - * @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(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean hasWindowFocus() { - return mHijackFocus || super.hasWindowFocus(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean isFocused() { - return mHijackFocus || super.isFocused(); - } - - /** - * <p>Returns the focus state in the drop down.</p> - * - * @return true always if hijacking focus - */ - @Override - public boolean hasFocus() { - return mHijackFocus || super.hasFocus(); - } - } - private class PopupDataSetObserver extends DataSetObserver { @Override public void onChanged() { diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java index 8d42c739b627..9e47e8513d49 100644 --- a/core/java/android/widget/MenuPopupWindow.java +++ b/core/java/android/widget/MenuPopupWindow.java @@ -36,11 +36,11 @@ public class MenuPopupWindow extends ListPopupWindow { } @Override - ListPopupWindow.DropDownListView createDropDownListView(Context context, boolean hijackFocus) { + DropDownListView createDropDownListView(Context context, boolean hijackFocus) { return new MenuDropDownListView(context, hijackFocus); } - static class MenuDropDownListView extends ListPopupWindow.DropDownListView { + static class MenuDropDownListView extends DropDownListView { private boolean mHoveredOnDisabledItem = false; private AccessibilityManager mAccessibilityManager; diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 1507dfb29285..3b2d60d1e1a4 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -20,6 +20,7 @@ import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.MenuPresenter; +import com.android.internal.view.menu.ShowableListMenu; import com.android.internal.view.menu.SubMenuBuilder; import android.annotation.MenuRes; @@ -30,7 +31,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnTouchListener; -import android.widget.ListPopupWindow.ForwardingListener; /** * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}. @@ -170,7 +170,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } @Override - public ListPopupWindow getPopup() { + public ShowableListMenu getPopup() { // This will be null until show() is called. return mPopup.getPopup(); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index f3cf61c7a1eb..c79e18449e39 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -17,6 +17,7 @@ package android.widget; import com.android.internal.R; +import com.android.internal.view.menu.ShowableListMenu; import android.annotation.DrawableRes; import android.annotation.Nullable; @@ -44,7 +45,6 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ListPopupWindow.ForwardingListener; import android.widget.PopupWindow.OnDismissListener; /** @@ -278,7 +278,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { mPopup = popup; mForwardingListener = new ForwardingListener(this) { @Override - public ListPopupWindow getPopup() { + public ShowableListMenu getPopup() { return popup; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index 8db363dadfa9..ce5bc908814c 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -29,10 +29,10 @@ import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.ActionMenuView; +import android.widget.ForwardingListener; import android.widget.ListPopupWindow; import android.widget.TextView; import android.widget.Toast; -import android.widget.ListPopupWindow.ForwardingListener; /** * @hide @@ -320,7 +320,7 @@ public class ActionMenuItemView extends TextView } @Override - public ListPopupWindow getPopup() { + public ShowableListMenu getPopup() { if (mPopupCallback != null) { return mPopupCallback.getPopup(); } @@ -331,7 +331,7 @@ public class ActionMenuItemView extends TextView protected boolean onForwardingStarted() { // Call the invoker, then check if the expected popup is showing. if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) { - final ListPopupWindow popup = getPopup(); + final ShowableListMenu popup = getPopup(); return popup != null && popup.isShowing(); } return false; @@ -339,7 +339,7 @@ public class ActionMenuItemView extends TextView @Override protected boolean onForwardingStopped() { - final ListPopupWindow popup = getPopup(); + final ShowableListMenu popup = getPopup(); if (popup != null) { popup.dismiss(); return true; @@ -349,6 +349,6 @@ public class ActionMenuItemView extends TextView } public static abstract class PopupCallback { - public abstract ListPopupWindow getPopup(); + public abstract ShowableListMenu getPopup(); } } diff --git a/core/java/com/android/internal/view/menu/MenuAdapter.java b/core/java/com/android/internal/view/menu/MenuAdapter.java new file mode 100644 index 000000000000..1e03b1f8b971 --- /dev/null +++ b/core/java/com/android/internal/view/menu/MenuAdapter.java @@ -0,0 +1,109 @@ +/* + * 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 com.android.internal.view.menu; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import java.util.ArrayList; + +public class MenuAdapter extends BaseAdapter { + static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; + + MenuBuilder mAdapterMenu; + + private int mExpandedIndex = -1; + + private boolean mForceShowIcon; + private final boolean mOverflowOnly; + private final LayoutInflater mInflater; + + public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) { + mOverflowOnly = overflowOnly; + mInflater = inflater; + mAdapterMenu = menu; + findExpandedIndex(); + } + + public void setForceShowIcon(boolean forceShow) { + mForceShowIcon = forceShow; + } + + public int getCount() { + ArrayList<MenuItemImpl> items = mOverflowOnly ? + mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); + if (mExpandedIndex < 0) { + return items.size(); + } + return items.size() - 1; + } + + public MenuBuilder getAdapterMenu() { + return mAdapterMenu; + } + + public MenuItemImpl getItem(int position) { + ArrayList<MenuItemImpl> items = mOverflowOnly ? + mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); + if (mExpandedIndex >= 0 && position >= mExpandedIndex) { + position++; + } + return items.get(position); + } + + public long getItemId(int position) { + // Since a menu item's ID is optional, we'll use the position as an + // ID for the item in the AdapterView + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); + } + + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + if (mForceShowIcon) { + ((ListMenuItemView) convertView).setForceShowIcon(true); + } + itemView.initialize(getItem(position), 0); + return convertView; + } + + void findExpandedIndex() { + final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem(); + if (expandedItem != null) { + final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems(); + final int count = items.size(); + for (int i = 0; i < count; i++) { + final MenuItemImpl item = items.get(i); + if (item == expandedItem) { + mExpandedIndex = i; + return; + } + } + } + mExpandedIndex = -1; + } + + @Override + public void notifyDataSetChanged() { + findExpandedIndex(); + super.notifyDataSetChanged(); + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 13654a695fcf..e6bc6c3ae044 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -89,7 +89,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On mContext = context; mInflater = LayoutInflater.from(context); mMenu = menu; - mAdapter = new MenuAdapter(mMenu); + mAdapter = new MenuAdapter(mMenu, mInflater, overflowOnly); mOverflowOnly = overflowOnly; mPopupStyleAttr = popupStyleAttr; mPopupStyleRes = popupStyleRes; @@ -358,73 +358,4 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On @Override public void onRestoreInstanceState(Parcelable state) { } - - private class MenuAdapter extends BaseAdapter { - private MenuBuilder mAdapterMenu; - private int mExpandedIndex = -1; - - public MenuAdapter(MenuBuilder menu) { - mAdapterMenu = menu; - findExpandedIndex(); - } - - public int getCount() { - ArrayList<MenuItemImpl> items = mOverflowOnly ? - mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); - if (mExpandedIndex < 0) { - return items.size(); - } - return items.size() - 1; - } - - public MenuItemImpl getItem(int position) { - ArrayList<MenuItemImpl> items = mOverflowOnly ? - mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems(); - if (mExpandedIndex >= 0 && position >= mExpandedIndex) { - position++; - } - return items.get(position); - } - - public long getItemId(int position) { - // Since a menu item's ID is optional, we'll use the position as an - // ID for the item in the AdapterView - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); - } - - MenuView.ItemView itemView = (MenuView.ItemView) convertView; - if (mForceShowIcon) { - ((ListMenuItemView) convertView).setForceShowIcon(true); - } - itemView.initialize(getItem(position), 0); - return convertView; - } - - void findExpandedIndex() { - final MenuItemImpl expandedItem = mMenu.getExpandedItem(); - if (expandedItem != null) { - final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); - final int count = items.size(); - for (int i = 0; i < count; i++) { - final MenuItemImpl item = items.get(i); - if (item == expandedItem) { - mExpandedIndex = i; - return; - } - } - } - mExpandedIndex = -1; - } - - @Override - public void notifyDataSetChanged() { - findExpandedIndex(); - super.notifyDataSetChanged(); - } - } } diff --git a/core/java/com/android/internal/view/menu/ShowableListMenu.java b/core/java/com/android/internal/view/menu/ShowableListMenu.java new file mode 100644 index 000000000000..ca158fdb690e --- /dev/null +++ b/core/java/com/android/internal/view/menu/ShowableListMenu.java @@ -0,0 +1,35 @@ +/* + * 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 com.android.internal.view.menu; + +import android.widget.ListView; + +/** + * A list menu which can be shown and hidden and which is internally represented by a ListView. + */ +public interface ShowableListMenu { + public void show(); + + public void dismiss(); + + public boolean isShowing(); + + /** + * @return The internal ListView for the visible menu. + */ + public ListView getListView(); +} |