Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.appcompat.widget;
     18 
     19 import android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.graphics.Rect;
     22 import android.graphics.drawable.Drawable;
     23 import android.os.Build;
     24 import android.view.MotionEvent;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.widget.AbsListView;
     28 import android.widget.ListAdapter;
     29 import android.widget.ListView;
     30 
     31 import androidx.annotation.NonNull;
     32 import androidx.appcompat.R;
     33 import androidx.appcompat.graphics.drawable.DrawableWrapper;
     34 import androidx.core.graphics.drawable.DrawableCompat;
     35 import androidx.core.view.ViewPropertyAnimatorCompat;
     36 import androidx.core.widget.ListViewAutoScrollHelper;
     37 
     38 import java.lang.reflect.Field;
     39 
     40 /**
     41  * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
     42  * make sure the list uses the appropriate drawables and states when
     43  * displayed on screen within a drop down. The focus is never actually
     44  * passed to the drop down in this mode; the list only looks focused.</p>
     45  */
     46 class DropDownListView extends ListView {
     47     public static final int INVALID_POSITION = -1;
     48     public static final int NO_POSITION = -1;
     49 
     50     private final Rect mSelectorRect = new Rect();
     51     private int mSelectionLeftPadding = 0;
     52     private int mSelectionTopPadding = 0;
     53     private int mSelectionRightPadding = 0;
     54     private int mSelectionBottomPadding = 0;
     55 
     56     private int mMotionPosition;
     57 
     58     private Field mIsChildViewEnabled;
     59 
     60     private GateKeeperDrawable mSelector;
     61 
     62     /*
     63     * WARNING: This is a workaround for a touch mode issue.
     64     *
     65     * Touch mode is propagated lazily to windows. This causes problems in
     66     * the following scenario:
     67     * - Type something in the AutoCompleteTextView and get some results
     68     * - Move down with the d-pad to select an item in the list
     69     * - Move up with the d-pad until the selection disappears
     70     * - Type more text in the AutoCompleteTextView *using the soft keyboard*
     71     *   and get new results; you are now in touch mode
     72     * - The selection comes back on the first item in the list, even though
     73     *   the list is supposed to be in touch mode
     74     *
     75     * Using the soft keyboard triggers the touch mode change but that change
     76     * is propagated to our window only after the first list layout, therefore
     77     * after the list attempts to resurrect the selection.
     78     *
     79     * The trick to work around this issue is to pretend the list is in touch
     80     * mode when we know that the selection should not appear, that is when
     81     * we know the user moved the selection away from the list.
     82     *
     83     * This boolean is set to true whenever we explicitly hide the list's
     84     * selection and reset to false whenever we know the user moved the
     85     * selection back to the list.
     86     *
     87     * When this boolean is true, isInTouchMode() returns true, otherwise it
     88     * returns super.isInTouchMode().
     89     */
     90     private boolean mListSelectionHidden;
     91 
     92     /**
     93      * True if this wrapper should fake focus.
     94      */
     95     private boolean mHijackFocus;
     96 
     97     /** Whether to force drawing of the pressed state selector. */
     98     private boolean mDrawsInPressedState;
     99 
    100     /** Current drag-to-open click animation, if any. */
    101     private ViewPropertyAnimatorCompat mClickAnimation;
    102 
    103     /** Helper for drag-to-open auto scrolling. */
    104     private ListViewAutoScrollHelper mScrollHelper;
    105 
    106     /**
    107      * Runnable posted when we are awaiting hover event resolution. When set,
    108      * drawable state changes are postponed.
    109      */
    110     private ResolveHoverRunnable mResolveHoverRunnable;
    111 
    112     /**
    113      * <p>Creates a new list view wrapper.</p>
    114      *
    115      * @param context this view's context
    116      */
    117     DropDownListView(Context context, boolean hijackFocus) {
    118         super(context, null, R.attr.dropDownListViewStyle);
    119         mHijackFocus = hijackFocus;
    120         setCacheColorHint(0); // Transparent, since the background drawable could be anything.
    121 
    122         try {
    123             mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled");
    124             mIsChildViewEnabled.setAccessible(true);
    125         } catch (NoSuchFieldException e) {
    126             e.printStackTrace();
    127         }
    128     }
    129 
    130 
    131     @Override
    132     public boolean isInTouchMode() {
    133         // WARNING: Please read the comment where mListSelectionHidden is declared
    134         return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
    135     }
    136 
    137     /**
    138      * <p>Returns the focus state in the drop down.</p>
    139      *
    140      * @return true always if hijacking focus
    141      */
    142     @Override
    143     public boolean hasWindowFocus() {
    144         return mHijackFocus || super.hasWindowFocus();
    145     }
    146 
    147     /**
    148      * <p>Returns the focus state in the drop down.</p>
    149      *
    150      * @return true always if hijacking focus
    151      */
    152     @Override
    153     public boolean isFocused() {
    154         return mHijackFocus || super.isFocused();
    155     }
    156 
    157     /**
    158      * <p>Returns the focus state in the drop down.</p>
    159      *
    160      * @return true always if hijacking focus
    161      */
    162     @Override
    163     public boolean hasFocus() {
    164         return mHijackFocus || super.hasFocus();
    165     }
    166 
    167     @Override
    168     public void setSelector(Drawable sel) {
    169         mSelector = sel != null ? new GateKeeperDrawable(sel) : null;
    170         super.setSelector(mSelector);
    171 
    172         final Rect padding = new Rect();
    173         if (sel != null) {
    174             sel.getPadding(padding);
    175         }
    176 
    177         mSelectionLeftPadding = padding.left;
    178         mSelectionTopPadding = padding.top;
    179         mSelectionRightPadding = padding.right;
    180         mSelectionBottomPadding = padding.bottom;
    181     }
    182 
    183     @Override
    184     protected void drawableStateChanged() {
    185         //postpone drawableStateChanged until pending hover to pressed transition finishes.
    186         if (mResolveHoverRunnable != null) {
    187             return;
    188         }
    189 
    190         super.drawableStateChanged();
    191 
    192         setSelectorEnabled(true);
    193         updateSelectorStateCompat();
    194     }
    195 
    196     @Override
    197     protected void dispatchDraw(Canvas canvas) {
    198         final boolean drawSelectorOnTop = false;
    199         if (!drawSelectorOnTop) {
    200             drawSelectorCompat(canvas);
    201         }
    202 
    203         super.dispatchDraw(canvas);
    204     }
    205 
    206     @Override
    207     public boolean onTouchEvent(MotionEvent ev) {
    208         switch (ev.getAction()) {
    209             case MotionEvent.ACTION_DOWN:
    210                 mMotionPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
    211                 break;
    212         }
    213         if (mResolveHoverRunnable != null) {
    214             // Resolved hover event as hover => touch transition.
    215             mResolveHoverRunnable.cancel();
    216         }
    217         return super.onTouchEvent(ev);
    218     }
    219 
    220     /**
    221      * Find a position that can be selected (i.e., is not a separator).
    222      *
    223      * @param position The starting position to look at.
    224      * @param lookDown Whether to look down for other positions.
    225      * @return The next selectable position starting at position and then searching either up or
    226      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
    227      */
    228     public int lookForSelectablePosition(int position, boolean lookDown) {
    229         final ListAdapter adapter = getAdapter();
    230         if (adapter == null || isInTouchMode()) {
    231             return INVALID_POSITION;
    232         }
    233 
    234         final int count = adapter.getCount();
    235         if (!getAdapter().areAllItemsEnabled()) {
    236             if (lookDown) {
    237                 position = Math.max(0, position);
    238                 while (position < count && !adapter.isEnabled(position)) {
    239                     position++;
    240                 }
    241             } else {
    242                 position = Math.min(position, count - 1);
    243                 while (position >= 0 && !adapter.isEnabled(position)) {
    244                     position--;
    245                 }
    246             }
    247 
    248             if (position < 0 || position >= count) {
    249                 return INVALID_POSITION;
    250             }
    251             return position;
    252         } else {
    253             if (position < 0 || position >= count) {
    254                 return INVALID_POSITION;
    255             }
    256             return position;
    257         }
    258     }
    259 
    260     /**
    261      * Measures the height of the given range of children (inclusive) and returns the height
    262      * with this ListView's padding and divider heights included. If maxHeight is provided, the
    263      * measuring will stop when the current height reaches maxHeight.
    264      *
    265      * @param widthMeasureSpec             The width measure spec to be given to a child's
    266      *                                     {@link View#measure(int, int)}.
    267      * @param startPosition                The position of the first child to be shown.
    268      * @param endPosition                  The (inclusive) position of the last child to be
    269      *                                     shown. Specify {@link #NO_POSITION} if the last child
    270      *                                     should be the last available child from the adapter.
    271      * @param maxHeight                    The maximum height that will be returned (if all the
    272      *                                     children don't fit in this value, this value will be
    273      *                                     returned).
    274      * @param disallowPartialChildPosition In general, whether the returned height should only
    275      *                                     contain entire children. This is more powerful--it is
    276      *                                     the first inclusive position at which partial
    277      *                                     children will not be allowed. Example: it looks nice
    278      *                                     to have at least 3 completely visible children, and
    279      *                                     in portrait this will most likely fit; but in
    280      *                                     landscape there could be times when even 2 children
    281      *                                     can not be completely shown, so a value of 2
    282      *                                     (remember, inclusive) would be good (assuming
    283      *                                     startPosition is 0).
    284      * @return The height of this ListView with the given children.
    285      */
    286     public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
    287             int endPosition, final int maxHeight,
    288             int disallowPartialChildPosition) {
    289 
    290         final int paddingTop = getListPaddingTop();
    291         final int paddingBottom = getListPaddingBottom();
    292         final int paddingLeft = getListPaddingLeft();
    293         final int paddingRight = getListPaddingRight();
    294         final int reportedDividerHeight = getDividerHeight();
    295         final Drawable divider = getDivider();
    296 
    297         final ListAdapter adapter = getAdapter();
    298 
    299         if (adapter == null) {
    300             return paddingTop + paddingBottom;
    301         }
    302 
    303         // Include the padding of the list
    304         int returnedHeight = paddingTop + paddingBottom;
    305         final int dividerHeight = ((reportedDividerHeight > 0) && divider != null)
    306                 ? reportedDividerHeight : 0;
    307 
    308         // The previous height value that was less than maxHeight and contained
    309         // no partial children
    310         int prevHeightWithoutPartialChild = 0;
    311 
    312         View child = null;
    313         int viewType = 0;
    314         int count = adapter.getCount();
    315         for (int i = 0; i < count; i++) {
    316             int newType = adapter.getItemViewType(i);
    317             if (newType != viewType) {
    318                 child = null;
    319                 viewType = newType;
    320             }
    321             child = adapter.getView(i, child, this);
    322 
    323             // Compute child height spec
    324             int heightMeasureSpec;
    325             ViewGroup.LayoutParams childLp = child.getLayoutParams();
    326 
    327             if (childLp == null) {
    328                 childLp = generateDefaultLayoutParams();
    329                 child.setLayoutParams(childLp);
    330             }
    331 
    332             if (childLp.height > 0) {
    333                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height,
    334                         MeasureSpec.EXACTLY);
    335             } else {
    336                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    337             }
    338             child.measure(widthMeasureSpec, heightMeasureSpec);
    339 
    340             // Since this view was measured directly against the parent measure
    341             // spec, we must measure it again before reuse.
    342             child.forceLayout();
    343 
    344             if (i > 0) {
    345                 // Count the divider for all but one child
    346                 returnedHeight += dividerHeight;
    347             }
    348 
    349             returnedHeight += child.getMeasuredHeight();
    350 
    351             if (returnedHeight >= maxHeight) {
    352                 // We went over, figure out which height to return.  If returnedHeight >
    353                 // maxHeight, then the i'th position did not fit completely.
    354                 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
    355                         && (i > disallowPartialChildPosition) // We've past the min pos
    356                         && (prevHeightWithoutPartialChild > 0) // We have a prev height
    357                         && (returnedHeight != maxHeight) // i'th child did not fit completely
    358                         ? prevHeightWithoutPartialChild
    359                         : maxHeight;
    360             }
    361 
    362             if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
    363                 prevHeightWithoutPartialChild = returnedHeight;
    364             }
    365         }
    366 
    367         // At this point, we went through the range of children, and they each
    368         // completely fit, so return the returnedHeight
    369         return returnedHeight;
    370     }
    371 
    372     private void setSelectorEnabled(boolean enabled) {
    373         if (mSelector != null) {
    374             mSelector.setEnabled(enabled);
    375         }
    376     }
    377 
    378     private static class GateKeeperDrawable extends DrawableWrapper {
    379         private boolean mEnabled;
    380 
    381         GateKeeperDrawable(Drawable drawable) {
    382             super(drawable);
    383             mEnabled = true;
    384         }
    385 
    386         void setEnabled(boolean enabled) {
    387             mEnabled = enabled;
    388         }
    389 
    390         @Override
    391         public boolean setState(int[] stateSet) {
    392             if (mEnabled) {
    393                 return super.setState(stateSet);
    394             }
    395             return false;
    396         }
    397 
    398         @Override
    399         public void draw(Canvas canvas) {
    400             if (mEnabled) {
    401                 super.draw(canvas);
    402             }
    403         }
    404 
    405         @Override
    406         public void setHotspot(float x, float y) {
    407             if (mEnabled) {
    408                 super.setHotspot(x, y);
    409             }
    410         }
    411 
    412         @Override
    413         public void setHotspotBounds(int left, int top, int right, int bottom) {
    414             if (mEnabled) {
    415                 super.setHotspotBounds(left, top, right, bottom);
    416             }
    417         }
    418 
    419         @Override
    420         public boolean setVisible(boolean visible, boolean restart) {
    421             if (mEnabled) {
    422                 return super.setVisible(visible, restart);
    423             }
    424             return false;
    425         }
    426     }
    427 
    428     @Override
    429     public boolean onHoverEvent(@NonNull MotionEvent ev) {
    430         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    431             // For SDK_INT prior to O the code below fails to change the selection.
    432             // This is because prior to O mouse events used to enable touch mode, and
    433             //  View.setSelectionFromTop does not do the right thing in touch mode.
    434             return super.onHoverEvent(ev);
    435         }
    436 
    437         final int action = ev.getActionMasked();
    438         if (action == MotionEvent.ACTION_HOVER_EXIT && mResolveHoverRunnable == null) {
    439             // This may be transitioning to TOUCH_DOWN. Postpone drawable state
    440             // updates until either the next frame or the next touch event.
    441             mResolveHoverRunnable = new ResolveHoverRunnable();
    442             mResolveHoverRunnable.post();
    443         }
    444 
    445         // Allow the super class to handle hover state management first.
    446         final boolean handled = super.onHoverEvent(ev);
    447         if (action == MotionEvent.ACTION_HOVER_ENTER
    448                 || action == MotionEvent.ACTION_HOVER_MOVE) {
    449             final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
    450 
    451             if (position != INVALID_POSITION && position != getSelectedItemPosition()) {
    452                 final View hoveredItem = getChildAt(position - getFirstVisiblePosition());
    453                 if (hoveredItem.isEnabled()) {
    454                     // Force a focus on the hovered item so that
    455                     // the proper selector state gets used when we update.
    456                     setSelectionFromTop(position, hoveredItem.getTop() - this.getTop());
    457                 }
    458                 updateSelectorStateCompat();
    459             }
    460         } else {
    461             // Do not cancel the selected position if the selection is visible
    462             // by other means.
    463             setSelection(INVALID_POSITION);
    464         }
    465 
    466         return handled;
    467     }
    468 
    469     @Override
    470     protected void onDetachedFromWindow() {
    471         mResolveHoverRunnable = null;
    472         super.onDetachedFromWindow();
    473     }
    474 
    475     /**
    476      * Handles forwarded events.
    477      *
    478      * @param activePointerId id of the pointer that activated forwarding
    479      * @return whether the event was handled
    480      */
    481     public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
    482         boolean handledEvent = true;
    483         boolean clearPressedItem = false;
    484 
    485         final int actionMasked = event.getActionMasked();
    486         switch (actionMasked) {
    487             case MotionEvent.ACTION_CANCEL:
    488                 handledEvent = false;
    489                 break;
    490             case MotionEvent.ACTION_UP:
    491                 handledEvent = false;
    492                 // $FALL-THROUGH$
    493             case MotionEvent.ACTION_MOVE:
    494                 final int activeIndex = event.findPointerIndex(activePointerId);
    495                 if (activeIndex < 0) {
    496                     handledEvent = false;
    497                     break;
    498                 }
    499 
    500                 final int x = (int) event.getX(activeIndex);
    501                 final int y = (int) event.getY(activeIndex);
    502                 final int position = pointToPosition(x, y);
    503                 if (position == INVALID_POSITION) {
    504                     clearPressedItem = true;
    505                     break;
    506                 }
    507 
    508                 final View child = getChildAt(position - getFirstVisiblePosition());
    509                 setPressedItem(child, position, x, y);
    510                 handledEvent = true;
    511 
    512                 if (actionMasked == MotionEvent.ACTION_UP) {
    513                     clickPressedItem(child, position);
    514                 }
    515                 break;
    516         }
    517 
    518         // Failure to handle the event cancels forwarding.
    519         if (!handledEvent || clearPressedItem) {
    520             clearPressedItem();
    521         }
    522 
    523         // Manage automatic scrolling.
    524         if (handledEvent) {
    525             if (mScrollHelper == null) {
    526                 mScrollHelper = new ListViewAutoScrollHelper(this);
    527             }
    528             mScrollHelper.setEnabled(true);
    529             mScrollHelper.onTouch(this, event);
    530         } else if (mScrollHelper != null) {
    531             mScrollHelper.setEnabled(false);
    532         }
    533 
    534         return handledEvent;
    535     }
    536 
    537     /**
    538      * Starts an alpha animation on the selector. When the animation ends,
    539      * the list performs a click on the item.
    540      */
    541     private void clickPressedItem(final View child, final int position) {
    542         final long id = getItemIdAtPosition(position);
    543         performItemClick(child, position, id);
    544     }
    545 
    546     /**
    547      * Sets whether the list selection is hidden, as part of a workaround for a
    548      * touch mode issue (see the declaration for mListSelectionHidden).
    549      *
    550      * @param hideListSelection {@code true} to hide list selection,
    551      *                          {@code false} to show
    552      */
    553     void setListSelectionHidden(boolean hideListSelection) {
    554         mListSelectionHidden = hideListSelection;
    555     }
    556 
    557     private void updateSelectorStateCompat() {
    558         Drawable selector = getSelector();
    559         if (selector != null && touchModeDrawsInPressedStateCompat() && isPressed()) {
    560             selector.setState(getDrawableState());
    561         }
    562     }
    563 
    564     private void drawSelectorCompat(Canvas canvas) {
    565         if (!mSelectorRect.isEmpty()) {
    566             final Drawable selector = getSelector();
    567             if (selector != null) {
    568                 selector.setBounds(mSelectorRect);
    569                 selector.draw(canvas);
    570             }
    571         }
    572     }
    573 
    574     private void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) {
    575         positionSelectorLikeFocusCompat(position, sel);
    576 
    577         Drawable selector = getSelector();
    578         if (selector != null && position != INVALID_POSITION) {
    579             DrawableCompat.setHotspot(selector, x, y);
    580         }
    581     }
    582 
    583     private void positionSelectorLikeFocusCompat(int position, View sel) {
    584         // If we're changing position, update the visibility since the selector
    585         // is technically being detached from the previous selection.
    586         final Drawable selector = getSelector();
    587         final boolean manageState = selector != null && position != INVALID_POSITION;
    588         if (manageState) {
    589             selector.setVisible(false, false);
    590         }
    591 
    592         positionSelectorCompat(position, sel);
    593 
    594         if (manageState) {
    595             final Rect bounds = mSelectorRect;
    596             final float x = bounds.exactCenterX();
    597             final float y = bounds.exactCenterY();
    598             selector.setVisible(getVisibility() == VISIBLE, false);
    599             DrawableCompat.setHotspot(selector, x, y);
    600         }
    601     }
    602 
    603     private void positionSelectorCompat(int position, View sel) {
    604         final Rect selectorRect = mSelectorRect;
    605         selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
    606 
    607         // Adjust for selection padding.
    608         selectorRect.left -= mSelectionLeftPadding;
    609         selectorRect.top -= mSelectionTopPadding;
    610         selectorRect.right += mSelectionRightPadding;
    611         selectorRect.bottom += mSelectionBottomPadding;
    612 
    613         try {
    614             // AbsListView.mIsChildViewEnabled controls the selector's state so we need to
    615             // modify its value
    616             final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this);
    617             if (sel.isEnabled() != isChildViewEnabled) {
    618                 mIsChildViewEnabled.set(this, !isChildViewEnabled);
    619                 if (position != INVALID_POSITION) {
    620                     refreshDrawableState();
    621                 }
    622             }
    623         } catch (IllegalAccessException e) {
    624             e.printStackTrace();
    625         }
    626     }
    627 
    628     private void clearPressedItem() {
    629         mDrawsInPressedState = false;
    630         setPressed(false);
    631         // This will call through to updateSelectorState()
    632         drawableStateChanged();
    633 
    634         final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
    635         if (motionView != null) {
    636             motionView.setPressed(false);
    637         }
    638 
    639         if (mClickAnimation != null) {
    640             mClickAnimation.cancel();
    641             mClickAnimation = null;
    642         }
    643     }
    644 
    645     private void setPressedItem(View child, int position, float x, float y) {
    646         mDrawsInPressedState = true;
    647 
    648         // Ordering is essential. First, update the container's pressed state.
    649         if (Build.VERSION.SDK_INT >= 21) {
    650             drawableHotspotChanged(x, y);
    651         }
    652         if (!isPressed()) {
    653             setPressed(true);
    654         }
    655 
    656         // Next, run layout to stabilize child positions.
    657         layoutChildren();
    658 
    659         // Manage the pressed view based on motion position. This allows us to
    660         // play nicely with actual touch and scroll events.
    661         if (mMotionPosition != INVALID_POSITION) {
    662             final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
    663             if (motionView != null && motionView != child && motionView.isPressed()) {
    664                 motionView.setPressed(false);
    665             }
    666         }
    667         mMotionPosition = position;
    668 
    669         // Offset for child coordinates.
    670         final float childX = x - child.getLeft();
    671         final float childY = y - child.getTop();
    672         if (Build.VERSION.SDK_INT >= 21) {
    673             child.drawableHotspotChanged(childX, childY);
    674         }
    675         if (!child.isPressed()) {
    676             child.setPressed(true);
    677         }
    678 
    679         // Ensure that keyboard focus starts from the last touched position.
    680         positionSelectorLikeTouchCompat(position, child, x, y);
    681 
    682         // This needs some explanation. We need to disable the selector for this next call
    683         // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
    684         // will draw the selector and bad things happen.
    685         setSelectorEnabled(false);
    686 
    687         // Refresh the drawable state to reflect the new pressed state,
    688         // which will also update the selector state.
    689         refreshDrawableState();
    690     }
    691 
    692     private boolean touchModeDrawsInPressedStateCompat() {
    693         return mDrawsInPressedState;
    694     }
    695 
    696     /**
    697      * Runnable that forces hover event resolution and updates drawable state.
    698      */
    699     private class ResolveHoverRunnable implements Runnable {
    700         @Override
    701         public void run() {
    702             // Resolved hover event as standard hover exit.
    703             mResolveHoverRunnable = null;
    704             drawableStateChanged();
    705         }
    706 
    707         public void cancel() {
    708             mResolveHoverRunnable = null;
    709             removeCallbacks(this);
    710         }
    711 
    712         public void post() {
    713             DropDownListView.this.post(this);
    714         }
    715     }
    716 }
    717