1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/widget/TwoWayView.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,6543 @@ 1.4 +/* 1.5 + * Copyright (C) 2013 Lucas Rocha 1.6 + * 1.7 + * This code is based on bits and pieces of Android's AbsListView, 1.8 + * Listview, and StaggeredGridView. 1.9 + * 1.10 + * Copyright (C) 2012 The Android Open Source Project 1.11 + * 1.12 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.13 + * you may not use this file except in compliance with the License. 1.14 + * You may obtain a copy of the License at 1.15 + * 1.16 + * http://www.apache.org/licenses/LICENSE-2.0 1.17 + * 1.18 + * Unless required by applicable law or agreed to in writing, software 1.19 + * distributed under the License is distributed on an "AS IS" BASIS, 1.20 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.21 + * See the License for the specific language governing permissions and 1.22 + * limitations under the License. 1.23 + */ 1.24 + 1.25 +package org.mozilla.gecko.widget; 1.26 + 1.27 +import org.mozilla.gecko.R; 1.28 + 1.29 +import android.annotation.TargetApi; 1.30 +import android.content.Context; 1.31 +import android.content.res.TypedArray; 1.32 +import android.database.DataSetObserver; 1.33 +import android.graphics.Canvas; 1.34 +import android.graphics.Rect; 1.35 +import android.graphics.drawable.Drawable; 1.36 +import android.graphics.drawable.TransitionDrawable; 1.37 +import android.os.Build; 1.38 +import android.os.Bundle; 1.39 +import android.os.Parcel; 1.40 +import android.os.Parcelable; 1.41 +import android.os.SystemClock; 1.42 +import android.support.v4.util.LongSparseArray; 1.43 +import android.support.v4.util.SparseArrayCompat; 1.44 +import android.support.v4.view.AccessibilityDelegateCompat; 1.45 +import android.support.v4.view.KeyEventCompat; 1.46 +import android.support.v4.view.MotionEventCompat; 1.47 +import android.support.v4.view.VelocityTrackerCompat; 1.48 +import android.support.v4.view.ViewCompat; 1.49 +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 1.50 +import android.support.v4.widget.EdgeEffectCompat; 1.51 +import android.util.AttributeSet; 1.52 +import android.util.Log; 1.53 +import android.util.SparseBooleanArray; 1.54 +import android.view.ContextMenu.ContextMenuInfo; 1.55 +import android.view.FocusFinder; 1.56 +import android.view.HapticFeedbackConstants; 1.57 +import android.view.KeyEvent; 1.58 +import android.view.MotionEvent; 1.59 +import android.view.SoundEffectConstants; 1.60 +import android.view.VelocityTracker; 1.61 +import android.view.View; 1.62 +import android.view.ViewConfiguration; 1.63 +import android.view.ViewGroup; 1.64 +import android.view.ViewParent; 1.65 +import android.view.ViewTreeObserver; 1.66 +import android.view.accessibility.AccessibilityEvent; 1.67 +import android.view.accessibility.AccessibilityNodeInfo; 1.68 +import android.widget.AdapterView; 1.69 +import android.widget.Checkable; 1.70 +import android.widget.ListAdapter; 1.71 +import android.widget.Scroller; 1.72 + 1.73 +import java.util.ArrayList; 1.74 +import java.util.List; 1.75 + 1.76 +/* 1.77 + * Implementation Notes: 1.78 + * 1.79 + * Some terminology: 1.80 + * 1.81 + * index - index of the items that are currently visible 1.82 + * position - index of the items in the cursor 1.83 + * 1.84 + * Given the bi-directional nature of this view, the source code 1.85 + * usually names variables with 'start' to mean 'top' or 'left'; and 1.86 + * 'end' to mean 'bottom' or 'right', depending on the current 1.87 + * orientation of the widget. 1.88 + */ 1.89 + 1.90 +/** 1.91 + * A view that shows items in a vertical or horizontal scrolling list. 1.92 + * The items come from the {@link ListAdapter} associated with this view. 1.93 + */ 1.94 +public class TwoWayView extends AdapterView<ListAdapter> implements 1.95 + ViewTreeObserver.OnTouchModeChangeListener { 1.96 + private static final String LOGTAG = "TwoWayView"; 1.97 + 1.98 + private static final int NO_POSITION = -1; 1.99 + private static final int INVALID_POINTER = -1; 1.100 + 1.101 + public static final int[] STATE_NOTHING = new int[] { 0 }; 1.102 + 1.103 + private static final int TOUCH_MODE_REST = -1; 1.104 + private static final int TOUCH_MODE_DOWN = 0; 1.105 + private static final int TOUCH_MODE_TAP = 1; 1.106 + private static final int TOUCH_MODE_DONE_WAITING = 2; 1.107 + private static final int TOUCH_MODE_DRAGGING = 3; 1.108 + private static final int TOUCH_MODE_FLINGING = 4; 1.109 + private static final int TOUCH_MODE_OVERSCROLL = 5; 1.110 + 1.111 + private static final int TOUCH_MODE_UNKNOWN = -1; 1.112 + private static final int TOUCH_MODE_ON = 0; 1.113 + private static final int TOUCH_MODE_OFF = 1; 1.114 + 1.115 + private static final int LAYOUT_NORMAL = 0; 1.116 + private static final int LAYOUT_FORCE_TOP = 1; 1.117 + private static final int LAYOUT_SET_SELECTION = 2; 1.118 + private static final int LAYOUT_FORCE_BOTTOM = 3; 1.119 + private static final int LAYOUT_SPECIFIC = 4; 1.120 + private static final int LAYOUT_SYNC = 5; 1.121 + private static final int LAYOUT_MOVE_SELECTION = 6; 1.122 + 1.123 + private static final int SYNC_SELECTED_POSITION = 0; 1.124 + private static final int SYNC_FIRST_POSITION = 1; 1.125 + 1.126 + private static final int SYNC_MAX_DURATION_MILLIS = 100; 1.127 + 1.128 + private static final int CHECK_POSITION_SEARCH_DISTANCE = 20; 1.129 + 1.130 + private static final float MAX_SCROLL_FACTOR = 0.33f; 1.131 + 1.132 + private static final int MIN_SCROLL_PREVIEW_PIXELS = 10; 1.133 + 1.134 + public static enum ChoiceMode { 1.135 + NONE, 1.136 + SINGLE, 1.137 + MULTIPLE 1.138 + } 1.139 + 1.140 + public static enum Orientation { 1.141 + HORIZONTAL, 1.142 + VERTICAL; 1.143 + }; 1.144 + 1.145 + private ListAdapter mAdapter; 1.146 + 1.147 + private boolean mIsVertical; 1.148 + 1.149 + private int mItemMargin; 1.150 + 1.151 + private boolean mInLayout; 1.152 + private boolean mBlockLayoutRequests; 1.153 + 1.154 + private boolean mIsAttached; 1.155 + 1.156 + private final RecycleBin mRecycler; 1.157 + private AdapterDataSetObserver mDataSetObserver; 1.158 + 1.159 + private boolean mItemsCanFocus; 1.160 + 1.161 + final boolean[] mIsScrap = new boolean[1]; 1.162 + 1.163 + private boolean mDataChanged; 1.164 + private int mItemCount; 1.165 + private int mOldItemCount; 1.166 + private boolean mHasStableIds; 1.167 + private boolean mAreAllItemsSelectable; 1.168 + 1.169 + private int mFirstPosition; 1.170 + private int mSpecificStart; 1.171 + 1.172 + private SavedState mPendingSync; 1.173 + 1.174 + private final int mTouchSlop; 1.175 + private final int mMaximumVelocity; 1.176 + private final int mFlingVelocity; 1.177 + private float mLastTouchPos; 1.178 + private float mTouchRemainderPos; 1.179 + private int mActivePointerId; 1.180 + 1.181 + private final Rect mTempRect; 1.182 + 1.183 + private final ArrowScrollFocusResult mArrowScrollFocusResult; 1.184 + 1.185 + private Rect mTouchFrame; 1.186 + private int mMotionPosition; 1.187 + private CheckForTap mPendingCheckForTap; 1.188 + private CheckForLongPress mPendingCheckForLongPress; 1.189 + private CheckForKeyLongPress mPendingCheckForKeyLongPress; 1.190 + private PerformClick mPerformClick; 1.191 + private Runnable mTouchModeReset; 1.192 + private int mResurrectToPosition; 1.193 + 1.194 + private boolean mIsChildViewEnabled; 1.195 + 1.196 + private boolean mDrawSelectorOnTop; 1.197 + private Drawable mSelector; 1.198 + private int mSelectorPosition; 1.199 + private final Rect mSelectorRect; 1.200 + 1.201 + private int mOverScroll; 1.202 + private final int mOverscrollDistance; 1.203 + 1.204 + private boolean mDesiredFocusableState; 1.205 + private boolean mDesiredFocusableInTouchModeState; 1.206 + 1.207 + private SelectionNotifier mSelectionNotifier; 1.208 + 1.209 + private boolean mNeedSync; 1.210 + private int mSyncMode; 1.211 + private int mSyncPosition; 1.212 + private long mSyncRowId; 1.213 + private long mSyncHeight; 1.214 + private int mSelectedStart; 1.215 + 1.216 + private int mNextSelectedPosition; 1.217 + private long mNextSelectedRowId; 1.218 + private int mSelectedPosition; 1.219 + private long mSelectedRowId; 1.220 + private int mOldSelectedPosition; 1.221 + private long mOldSelectedRowId; 1.222 + 1.223 + private ChoiceMode mChoiceMode; 1.224 + private int mCheckedItemCount; 1.225 + private SparseBooleanArray mCheckStates; 1.226 + LongSparseArray<Integer> mCheckedIdStates; 1.227 + 1.228 + private ContextMenuInfo mContextMenuInfo; 1.229 + 1.230 + private int mLayoutMode; 1.231 + private int mTouchMode; 1.232 + private int mLastTouchMode; 1.233 + private VelocityTracker mVelocityTracker; 1.234 + private final Scroller mScroller; 1.235 + 1.236 + private EdgeEffectCompat mStartEdge; 1.237 + private EdgeEffectCompat mEndEdge; 1.238 + 1.239 + private OnScrollListener mOnScrollListener; 1.240 + private int mLastScrollState; 1.241 + 1.242 + private View mEmptyView; 1.243 + 1.244 + private ListItemAccessibilityDelegate mAccessibilityDelegate; 1.245 + 1.246 + private int mLastAccessibilityScrollEventFromIndex; 1.247 + private int mLastAccessibilityScrollEventToIndex; 1.248 + 1.249 + public interface OnScrollListener { 1.250 + 1.251 + /** 1.252 + * The view is not scrolling. Note navigating the list using the trackball counts as 1.253 + * being in the idle state since these transitions are not animated. 1.254 + */ 1.255 + public static int SCROLL_STATE_IDLE = 0; 1.256 + 1.257 + /** 1.258 + * The user is scrolling using touch, and their finger is still on the screen 1.259 + */ 1.260 + public static int SCROLL_STATE_TOUCH_SCROLL = 1; 1.261 + 1.262 + /** 1.263 + * The user had previously been scrolling using touch and had performed a fling. The 1.264 + * animation is now coasting to a stop 1.265 + */ 1.266 + public static int SCROLL_STATE_FLING = 2; 1.267 + 1.268 + /** 1.269 + * Callback method to be invoked while the list view or grid view is being scrolled. If the 1.270 + * view is being scrolled, this method will be called before the next frame of the scroll is 1.271 + * rendered. In particular, it will be called before any calls to 1.272 + * {@link Adapter#getView(int, View, ViewGroup)}. 1.273 + * 1.274 + * @param view The view whose scroll state is being reported 1.275 + * 1.276 + * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE}, 1.277 + * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. 1.278 + */ 1.279 + public void onScrollStateChanged(TwoWayView view, int scrollState); 1.280 + 1.281 + /** 1.282 + * Callback method to be invoked when the list or grid has been scrolled. This will be 1.283 + * called after the scroll has completed 1.284 + * @param view The view whose scroll state is being reported 1.285 + * @param firstVisibleItem the index of the first visible cell (ignore if 1.286 + * visibleItemCount == 0) 1.287 + * @param visibleItemCount the number of visible cells 1.288 + * @param totalItemCount the number of items in the list adaptor 1.289 + */ 1.290 + public void onScroll(TwoWayView view, int firstVisibleItem, int visibleItemCount, 1.291 + int totalItemCount); 1.292 + } 1.293 + 1.294 + /** 1.295 + * A RecyclerListener is used to receive a notification whenever a View is placed 1.296 + * inside the RecycleBin's scrap heap. This listener is used to free resources 1.297 + * associated to Views placed in the RecycleBin. 1.298 + * 1.299 + * @see TwoWayView.RecycleBin 1.300 + * @see TwoWayView#setRecyclerListener(TwoWayView.RecyclerListener) 1.301 + */ 1.302 + public static interface RecyclerListener { 1.303 + /** 1.304 + * Indicates that the specified View was moved into the recycler's scrap heap. 1.305 + * The view is not displayed on screen any more and any expensive resource 1.306 + * associated with the view should be discarded. 1.307 + * 1.308 + * @param view 1.309 + */ 1.310 + void onMovedToScrapHeap(View view); 1.311 + } 1.312 + 1.313 + public TwoWayView(Context context) { 1.314 + this(context, null); 1.315 + } 1.316 + 1.317 + public TwoWayView(Context context, AttributeSet attrs) { 1.318 + this(context, attrs, 0); 1.319 + } 1.320 + 1.321 + public TwoWayView(Context context, AttributeSet attrs, int defStyle) { 1.322 + super(context, attrs, defStyle); 1.323 + 1.324 + mNeedSync = false; 1.325 + mVelocityTracker = null; 1.326 + 1.327 + mLayoutMode = LAYOUT_NORMAL; 1.328 + mTouchMode = TOUCH_MODE_REST; 1.329 + mLastTouchMode = TOUCH_MODE_UNKNOWN; 1.330 + 1.331 + mIsAttached = false; 1.332 + 1.333 + mContextMenuInfo = null; 1.334 + 1.335 + mOnScrollListener = null; 1.336 + mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; 1.337 + 1.338 + final ViewConfiguration vc = ViewConfiguration.get(context); 1.339 + mTouchSlop = vc.getScaledTouchSlop(); 1.340 + mMaximumVelocity = vc.getScaledMaximumFlingVelocity(); 1.341 + mFlingVelocity = vc.getScaledMinimumFlingVelocity(); 1.342 + mOverscrollDistance = getScaledOverscrollDistance(vc); 1.343 + 1.344 + mOverScroll = 0; 1.345 + 1.346 + mScroller = new Scroller(context); 1.347 + 1.348 + mIsVertical = true; 1.349 + 1.350 + mItemsCanFocus = false; 1.351 + 1.352 + mTempRect = new Rect(); 1.353 + 1.354 + mArrowScrollFocusResult = new ArrowScrollFocusResult(); 1.355 + 1.356 + mSelectorPosition = INVALID_POSITION; 1.357 + 1.358 + mSelectorRect = new Rect(); 1.359 + mSelectedStart = 0; 1.360 + 1.361 + mResurrectToPosition = INVALID_POSITION; 1.362 + 1.363 + mSelectedStart = 0; 1.364 + mNextSelectedPosition = INVALID_POSITION; 1.365 + mNextSelectedRowId = INVALID_ROW_ID; 1.366 + mSelectedPosition = INVALID_POSITION; 1.367 + mSelectedRowId = INVALID_ROW_ID; 1.368 + mOldSelectedPosition = INVALID_POSITION; 1.369 + mOldSelectedRowId = INVALID_ROW_ID; 1.370 + 1.371 + mChoiceMode = ChoiceMode.NONE; 1.372 + mCheckedItemCount = 0; 1.373 + mCheckedIdStates = null; 1.374 + mCheckStates = null; 1.375 + 1.376 + mRecycler = new RecycleBin(); 1.377 + mDataSetObserver = null; 1.378 + 1.379 + mAreAllItemsSelectable = true; 1.380 + 1.381 + mStartEdge = null; 1.382 + mEndEdge = null; 1.383 + 1.384 + setClickable(true); 1.385 + setFocusableInTouchMode(true); 1.386 + setWillNotDraw(false); 1.387 + setAlwaysDrawnWithCacheEnabled(false); 1.388 + setWillNotDraw(false); 1.389 + setClipToPadding(false); 1.390 + 1.391 + ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS); 1.392 + 1.393 + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoWayView, defStyle, 0); 1.394 + initializeScrollbars(a); 1.395 + 1.396 + mDrawSelectorOnTop = a.getBoolean( 1.397 + R.styleable.TwoWayView_android_drawSelectorOnTop, false); 1.398 + 1.399 + Drawable d = a.getDrawable(R.styleable.TwoWayView_android_listSelector); 1.400 + if (d != null) { 1.401 + setSelector(d); 1.402 + } 1.403 + 1.404 + int orientation = a.getInt(R.styleable.TwoWayView_android_orientation, -1); 1.405 + if (orientation >= 0) { 1.406 + setOrientation(Orientation.values()[orientation]); 1.407 + } 1.408 + 1.409 + int choiceMode = a.getInt(R.styleable.TwoWayView_android_choiceMode, -1); 1.410 + if (choiceMode >= 0) { 1.411 + setChoiceMode(ChoiceMode.values()[choiceMode]); 1.412 + } 1.413 + 1.414 + a.recycle(); 1.415 + 1.416 + updateScrollbarsDirection(); 1.417 + } 1.418 + 1.419 + public void setOrientation(Orientation orientation) { 1.420 + final boolean isVertical = (orientation.compareTo(Orientation.VERTICAL) == 0); 1.421 + if (mIsVertical == isVertical) { 1.422 + return; 1.423 + } 1.424 + 1.425 + mIsVertical = isVertical; 1.426 + 1.427 + updateScrollbarsDirection(); 1.428 + resetState(); 1.429 + mRecycler.clear(); 1.430 + 1.431 + requestLayout(); 1.432 + } 1.433 + 1.434 + public Orientation getOrientation() { 1.435 + return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL); 1.436 + } 1.437 + 1.438 + public void setItemMargin(int itemMargin) { 1.439 + if (mItemMargin == itemMargin) { 1.440 + return; 1.441 + } 1.442 + 1.443 + mItemMargin = itemMargin; 1.444 + requestLayout(); 1.445 + } 1.446 + 1.447 + public int getItemMargin() { 1.448 + return mItemMargin; 1.449 + } 1.450 + 1.451 + /** 1.452 + * Indicates that the views created by the ListAdapter can contain focusable 1.453 + * items. 1.454 + * 1.455 + * @param itemsCanFocus true if items can get focus, false otherwise 1.456 + */ 1.457 + public void setItemsCanFocus(boolean itemsCanFocus) { 1.458 + mItemsCanFocus = itemsCanFocus; 1.459 + if (!itemsCanFocus) { 1.460 + setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1.461 + } 1.462 + } 1.463 + 1.464 + /** 1.465 + * @return Whether the views created by the ListAdapter can contain focusable 1.466 + * items. 1.467 + */ 1.468 + public boolean getItemsCanFocus() { 1.469 + return mItemsCanFocus; 1.470 + } 1.471 + 1.472 + /** 1.473 + * Set the listener that will receive notifications every time the list scrolls. 1.474 + * 1.475 + * @param l the scroll listener 1.476 + */ 1.477 + public void setOnScrollListener(OnScrollListener l) { 1.478 + mOnScrollListener = l; 1.479 + invokeOnItemScrollListener(); 1.480 + } 1.481 + 1.482 + /** 1.483 + * Sets the recycler listener to be notified whenever a View is set aside in 1.484 + * the recycler for later reuse. This listener can be used to free resources 1.485 + * associated to the View. 1.486 + * 1.487 + * @param listener The recycler listener to be notified of views set aside 1.488 + * in the recycler. 1.489 + * 1.490 + * @see TwoWayView.RecycleBin 1.491 + * @see TwoWayView.RecyclerListener 1.492 + */ 1.493 + public void setRecyclerListener(RecyclerListener l) { 1.494 + mRecycler.mRecyclerListener = l; 1.495 + } 1.496 + 1.497 + /** 1.498 + * Controls whether the selection highlight drawable should be drawn on top of the item or 1.499 + * behind it. 1.500 + * 1.501 + * @param onTop If true, the selector will be drawn on the item it is highlighting. The default 1.502 + * is false. 1.503 + * 1.504 + * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop 1.505 + */ 1.506 + public void setDrawSelectorOnTop(boolean drawSelectorOnTop) { 1.507 + mDrawSelectorOnTop = drawSelectorOnTop; 1.508 + } 1.509 + 1.510 + /** 1.511 + * Set a Drawable that should be used to highlight the currently selected item. 1.512 + * 1.513 + * @param resID A Drawable resource to use as the selection highlight. 1.514 + * 1.515 + * @attr ref android.R.styleable#AbsListView_listSelector 1.516 + */ 1.517 + public void setSelector(int resID) { 1.518 + setSelector(getResources().getDrawable(resID)); 1.519 + } 1.520 + 1.521 + /** 1.522 + * Set a Drawable that should be used to highlight the currently selected item. 1.523 + * 1.524 + * @param selector A Drawable to use as the selection highlight. 1.525 + * 1.526 + * @attr ref android.R.styleable#AbsListView_listSelector 1.527 + */ 1.528 + public void setSelector(Drawable selector) { 1.529 + if (mSelector != null) { 1.530 + mSelector.setCallback(null); 1.531 + unscheduleDrawable(mSelector); 1.532 + } 1.533 + 1.534 + mSelector = selector; 1.535 + Rect padding = new Rect(); 1.536 + selector.getPadding(padding); 1.537 + 1.538 + selector.setCallback(this); 1.539 + updateSelectorState(); 1.540 + } 1.541 + 1.542 + /** 1.543 + * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the 1.544 + * selection in the list. 1.545 + * 1.546 + * @return the drawable used to display the selector 1.547 + */ 1.548 + public Drawable getSelector() { 1.549 + return mSelector; 1.550 + } 1.551 + 1.552 + /** 1.553 + * {@inheritDoc} 1.554 + */ 1.555 + @Override 1.556 + public int getSelectedItemPosition() { 1.557 + return mNextSelectedPosition; 1.558 + } 1.559 + 1.560 + /** 1.561 + * {@inheritDoc} 1.562 + */ 1.563 + @Override 1.564 + public long getSelectedItemId() { 1.565 + return mNextSelectedRowId; 1.566 + } 1.567 + 1.568 + /** 1.569 + * Returns the number of items currently selected. This will only be valid 1.570 + * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). 1.571 + * 1.572 + * <p>To determine the specific items that are currently selected, use one of 1.573 + * the <code>getChecked*</code> methods. 1.574 + * 1.575 + * @return The number of items currently selected 1.576 + * 1.577 + * @see #getCheckedItemPosition() 1.578 + * @see #getCheckedItemPositions() 1.579 + * @see #getCheckedItemIds() 1.580 + */ 1.581 + public int getCheckedItemCount() { 1.582 + return mCheckedItemCount; 1.583 + } 1.584 + 1.585 + /** 1.586 + * Returns the checked state of the specified position. The result is only 1.587 + * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} 1.588 + * or {@link #CHOICE_MODE_MULTIPLE}. 1.589 + * 1.590 + * @param position The item whose checked state to return 1.591 + * @return The item's checked state or <code>false</code> if choice mode 1.592 + * is invalid 1.593 + * 1.594 + * @see #setChoiceMode(int) 1.595 + */ 1.596 + public boolean isItemChecked(int position) { 1.597 + if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 && mCheckStates != null) { 1.598 + return mCheckStates.get(position); 1.599 + } 1.600 + 1.601 + return false; 1.602 + } 1.603 + 1.604 + /** 1.605 + * Returns the currently checked item. The result is only valid if the choice 1.606 + * mode has been set to {@link #CHOICE_MODE_SINGLE}. 1.607 + * 1.608 + * @return The position of the currently checked item or 1.609 + * {@link #INVALID_POSITION} if nothing is selected 1.610 + * 1.611 + * @see #setChoiceMode(int) 1.612 + */ 1.613 + public int getCheckedItemPosition() { 1.614 + if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0 && 1.615 + mCheckStates != null && mCheckStates.size() == 1) { 1.616 + return mCheckStates.keyAt(0); 1.617 + } 1.618 + 1.619 + return INVALID_POSITION; 1.620 + } 1.621 + 1.622 + /** 1.623 + * Returns the set of checked items in the list. The result is only valid if 1.624 + * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. 1.625 + * 1.626 + * @return A SparseBooleanArray which will return true for each call to 1.627 + * get(int position) where position is a position in the list, 1.628 + * or <code>null</code> if the choice mode is set to 1.629 + * {@link #CHOICE_MODE_NONE}. 1.630 + */ 1.631 + public SparseBooleanArray getCheckedItemPositions() { 1.632 + if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { 1.633 + return mCheckStates; 1.634 + } 1.635 + 1.636 + return null; 1.637 + } 1.638 + 1.639 + /** 1.640 + * Returns the set of checked items ids. The result is only valid if the 1.641 + * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter 1.642 + * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) 1.643 + * 1.644 + * @return A new array which contains the id of each checked item in the 1.645 + * list. 1.646 + */ 1.647 + public long[] getCheckedItemIds() { 1.648 + if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 || 1.649 + mCheckedIdStates == null || mAdapter == null) { 1.650 + return new long[0]; 1.651 + } 1.652 + 1.653 + final LongSparseArray<Integer> idStates = mCheckedIdStates; 1.654 + final int count = idStates.size(); 1.655 + final long[] ids = new long[count]; 1.656 + 1.657 + for (int i = 0; i < count; i++) { 1.658 + ids[i] = idStates.keyAt(i); 1.659 + } 1.660 + 1.661 + return ids; 1.662 + } 1.663 + 1.664 + /** 1.665 + * Sets the checked state of the specified position. The is only valid if 1.666 + * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or 1.667 + * {@link #CHOICE_MODE_MULTIPLE}. 1.668 + * 1.669 + * @param position The item whose checked state is to be checked 1.670 + * @param value The new checked state for the item 1.671 + */ 1.672 + public void setItemChecked(int position, boolean value) { 1.673 + if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0) { 1.674 + return; 1.675 + } 1.676 + 1.677 + if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { 1.678 + boolean oldValue = mCheckStates.get(position); 1.679 + mCheckStates.put(position, value); 1.680 + 1.681 + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1.682 + if (value) { 1.683 + mCheckedIdStates.put(mAdapter.getItemId(position), position); 1.684 + } else { 1.685 + mCheckedIdStates.delete(mAdapter.getItemId(position)); 1.686 + } 1.687 + } 1.688 + 1.689 + if (oldValue != value) { 1.690 + if (value) { 1.691 + mCheckedItemCount++; 1.692 + } else { 1.693 + mCheckedItemCount--; 1.694 + } 1.695 + } 1.696 + } else { 1.697 + boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds(); 1.698 + 1.699 + // Clear all values if we're checking something, or unchecking the currently 1.700 + // selected item 1.701 + if (value || isItemChecked(position)) { 1.702 + mCheckStates.clear(); 1.703 + 1.704 + if (updateIds) { 1.705 + mCheckedIdStates.clear(); 1.706 + } 1.707 + } 1.708 + 1.709 + // This may end up selecting the value we just cleared but this way 1.710 + // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on 1.711 + if (value) { 1.712 + mCheckStates.put(position, true); 1.713 + 1.714 + if (updateIds) { 1.715 + mCheckedIdStates.put(mAdapter.getItemId(position), position); 1.716 + } 1.717 + 1.718 + mCheckedItemCount = 1; 1.719 + } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { 1.720 + mCheckedItemCount = 0; 1.721 + } 1.722 + } 1.723 + 1.724 + // Do not generate a data change while we are in the layout phase 1.725 + if (!mInLayout && !mBlockLayoutRequests) { 1.726 + mDataChanged = true; 1.727 + rememberSyncState(); 1.728 + requestLayout(); 1.729 + } 1.730 + } 1.731 + 1.732 + /** 1.733 + * Clear any choices previously set 1.734 + */ 1.735 + public void clearChoices() { 1.736 + if (mCheckStates != null) { 1.737 + mCheckStates.clear(); 1.738 + } 1.739 + 1.740 + if (mCheckedIdStates != null) { 1.741 + mCheckedIdStates.clear(); 1.742 + } 1.743 + 1.744 + mCheckedItemCount = 0; 1.745 + } 1.746 + 1.747 + /** 1.748 + * @see #setChoiceMode(int) 1.749 + * 1.750 + * @return The current choice mode 1.751 + */ 1.752 + public ChoiceMode getChoiceMode() { 1.753 + return mChoiceMode; 1.754 + } 1.755 + 1.756 + /** 1.757 + * Defines the choice behavior for the List. By default, Lists do not have any choice behavior 1.758 + * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the 1.759 + * List allows up to one item to be in a chosen state. By setting the choiceMode to 1.760 + * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. 1.761 + * 1.762 + * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or 1.763 + * {@link #CHOICE_MODE_MULTIPLE} 1.764 + */ 1.765 + public void setChoiceMode(ChoiceMode choiceMode) { 1.766 + mChoiceMode = choiceMode; 1.767 + 1.768 + if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { 1.769 + if (mCheckStates == null) { 1.770 + mCheckStates = new SparseBooleanArray(); 1.771 + } 1.772 + 1.773 + if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { 1.774 + mCheckedIdStates = new LongSparseArray<Integer>(); 1.775 + } 1.776 + } 1.777 + } 1.778 + 1.779 + @Override 1.780 + public ListAdapter getAdapter() { 1.781 + return mAdapter; 1.782 + } 1.783 + 1.784 + @Override 1.785 + public void setAdapter(ListAdapter adapter) { 1.786 + if (mAdapter != null && mDataSetObserver != null) { 1.787 + mAdapter.unregisterDataSetObserver(mDataSetObserver); 1.788 + } 1.789 + 1.790 + resetState(); 1.791 + mRecycler.clear(); 1.792 + 1.793 + mAdapter = adapter; 1.794 + mDataChanged = true; 1.795 + 1.796 + mOldSelectedPosition = INVALID_POSITION; 1.797 + mOldSelectedRowId = INVALID_ROW_ID; 1.798 + 1.799 + if (mCheckStates != null) { 1.800 + mCheckStates.clear(); 1.801 + } 1.802 + 1.803 + if (mCheckedIdStates != null) { 1.804 + mCheckedIdStates.clear(); 1.805 + } 1.806 + 1.807 + if (mAdapter != null) { 1.808 + mOldItemCount = mItemCount; 1.809 + mItemCount = adapter.getCount(); 1.810 + 1.811 + mDataSetObserver = new AdapterDataSetObserver(); 1.812 + mAdapter.registerDataSetObserver(mDataSetObserver); 1.813 + 1.814 + mRecycler.setViewTypeCount(adapter.getViewTypeCount()); 1.815 + 1.816 + mHasStableIds = adapter.hasStableIds(); 1.817 + mAreAllItemsSelectable = adapter.areAllItemsEnabled(); 1.818 + 1.819 + if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mHasStableIds && 1.820 + mCheckedIdStates == null) { 1.821 + mCheckedIdStates = new LongSparseArray<Integer>(); 1.822 + } 1.823 + 1.824 + final int position = lookForSelectablePosition(0); 1.825 + setSelectedPositionInt(position); 1.826 + setNextSelectedPositionInt(position); 1.827 + 1.828 + if (mItemCount == 0) { 1.829 + checkSelectionChanged(); 1.830 + } 1.831 + } else { 1.832 + mItemCount = 0; 1.833 + mHasStableIds = false; 1.834 + mAreAllItemsSelectable = true; 1.835 + 1.836 + checkSelectionChanged(); 1.837 + } 1.838 + 1.839 + checkFocus(); 1.840 + requestLayout(); 1.841 + } 1.842 + 1.843 + @Override 1.844 + public int getFirstVisiblePosition() { 1.845 + return mFirstPosition; 1.846 + } 1.847 + 1.848 + @Override 1.849 + public int getLastVisiblePosition() { 1.850 + return mFirstPosition + getChildCount() - 1; 1.851 + } 1.852 + 1.853 + @Override 1.854 + public int getCount() { 1.855 + return mItemCount; 1.856 + } 1.857 + 1.858 + @Override 1.859 + public int getPositionForView(View view) { 1.860 + View child = view; 1.861 + try { 1.862 + View v; 1.863 + while (!(v = (View) child.getParent()).equals(this)) { 1.864 + child = v; 1.865 + } 1.866 + } catch (ClassCastException e) { 1.867 + // We made it up to the window without find this list view 1.868 + return INVALID_POSITION; 1.869 + } 1.870 + 1.871 + // Search the children for the list item 1.872 + final int childCount = getChildCount(); 1.873 + for (int i = 0; i < childCount; i++) { 1.874 + if (getChildAt(i).equals(child)) { 1.875 + return mFirstPosition + i; 1.876 + } 1.877 + } 1.878 + 1.879 + // Child not found! 1.880 + return INVALID_POSITION; 1.881 + } 1.882 + 1.883 + @Override 1.884 + public void getFocusedRect(Rect r) { 1.885 + View view = getSelectedView(); 1.886 + 1.887 + if (view != null && view.getParent() == this) { 1.888 + // The focused rectangle of the selected view offset into the 1.889 + // coordinate space of this view. 1.890 + view.getFocusedRect(r); 1.891 + offsetDescendantRectToMyCoords(view, r); 1.892 + } else { 1.893 + super.getFocusedRect(r); 1.894 + } 1.895 + } 1.896 + 1.897 + @Override 1.898 + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 1.899 + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1.900 + 1.901 + if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) { 1.902 + if (!mIsAttached && mAdapter != null) { 1.903 + // Data may have changed while we were detached and it's valid 1.904 + // to change focus while detached. Refresh so we don't die. 1.905 + mDataChanged = true; 1.906 + mOldItemCount = mItemCount; 1.907 + mItemCount = mAdapter.getCount(); 1.908 + } 1.909 + 1.910 + resurrectSelection(); 1.911 + } 1.912 + 1.913 + final ListAdapter adapter = mAdapter; 1.914 + int closetChildIndex = INVALID_POSITION; 1.915 + int closestChildStart = 0; 1.916 + 1.917 + if (adapter != null && gainFocus && previouslyFocusedRect != null) { 1.918 + previouslyFocusedRect.offset(getScrollX(), getScrollY()); 1.919 + 1.920 + // Don't cache the result of getChildCount or mFirstPosition here, 1.921 + // it could change in layoutChildren. 1.922 + if (adapter.getCount() < getChildCount() + mFirstPosition) { 1.923 + mLayoutMode = LAYOUT_NORMAL; 1.924 + layoutChildren(); 1.925 + } 1.926 + 1.927 + // Figure out which item should be selected based on previously 1.928 + // focused rect. 1.929 + Rect otherRect = mTempRect; 1.930 + int minDistance = Integer.MAX_VALUE; 1.931 + final int childCount = getChildCount(); 1.932 + final int firstPosition = mFirstPosition; 1.933 + 1.934 + for (int i = 0; i < childCount; i++) { 1.935 + // Only consider selectable views 1.936 + if (!adapter.isEnabled(firstPosition + i)) { 1.937 + continue; 1.938 + } 1.939 + 1.940 + View other = getChildAt(i); 1.941 + other.getDrawingRect(otherRect); 1.942 + offsetDescendantRectToMyCoords(other, otherRect); 1.943 + int distance = getDistance(previouslyFocusedRect, otherRect, direction); 1.944 + 1.945 + if (distance < minDistance) { 1.946 + minDistance = distance; 1.947 + closetChildIndex = i; 1.948 + closestChildStart = (mIsVertical ? other.getTop() : other.getLeft()); 1.949 + } 1.950 + } 1.951 + } 1.952 + 1.953 + if (closetChildIndex >= 0) { 1.954 + setSelectionFromOffset(closetChildIndex + mFirstPosition, closestChildStart); 1.955 + } else { 1.956 + requestLayout(); 1.957 + } 1.958 + } 1.959 + 1.960 + @Override 1.961 + protected void onAttachedToWindow() { 1.962 + super.onAttachedToWindow(); 1.963 + 1.964 + final ViewTreeObserver treeObserver = getViewTreeObserver(); 1.965 + treeObserver.addOnTouchModeChangeListener(this); 1.966 + 1.967 + if (mAdapter != null && mDataSetObserver == null) { 1.968 + mDataSetObserver = new AdapterDataSetObserver(); 1.969 + mAdapter.registerDataSetObserver(mDataSetObserver); 1.970 + 1.971 + // Data may have changed while we were detached. Refresh. 1.972 + mDataChanged = true; 1.973 + mOldItemCount = mItemCount; 1.974 + mItemCount = mAdapter.getCount(); 1.975 + } 1.976 + 1.977 + mIsAttached = true; 1.978 + } 1.979 + 1.980 + @Override 1.981 + protected void onDetachedFromWindow() { 1.982 + super.onDetachedFromWindow(); 1.983 + 1.984 + // Detach any view left in the scrap heap 1.985 + mRecycler.clear(); 1.986 + 1.987 + final ViewTreeObserver treeObserver = getViewTreeObserver(); 1.988 + treeObserver.removeOnTouchModeChangeListener(this); 1.989 + 1.990 + if (mAdapter != null) { 1.991 + mAdapter.unregisterDataSetObserver(mDataSetObserver); 1.992 + mDataSetObserver = null; 1.993 + } 1.994 + 1.995 + if (mPerformClick != null) { 1.996 + removeCallbacks(mPerformClick); 1.997 + } 1.998 + 1.999 + if (mTouchModeReset != null) { 1.1000 + removeCallbacks(mTouchModeReset); 1.1001 + mTouchModeReset.run(); 1.1002 + } 1.1003 + 1.1004 + mIsAttached = false; 1.1005 + } 1.1006 + 1.1007 + @Override 1.1008 + public void onWindowFocusChanged(boolean hasWindowFocus) { 1.1009 + super.onWindowFocusChanged(hasWindowFocus); 1.1010 + 1.1011 + final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF; 1.1012 + 1.1013 + if (!hasWindowFocus) { 1.1014 + if (touchMode == TOUCH_MODE_OFF) { 1.1015 + // Remember the last selected element 1.1016 + mResurrectToPosition = mSelectedPosition; 1.1017 + } 1.1018 + } else { 1.1019 + // If we changed touch mode since the last time we had focus 1.1020 + if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { 1.1021 + // If we come back in trackball mode, we bring the selection back 1.1022 + if (touchMode == TOUCH_MODE_OFF) { 1.1023 + // This will trigger a layout 1.1024 + resurrectSelection(); 1.1025 + 1.1026 + // If we come back in touch mode, then we want to hide the selector 1.1027 + } else { 1.1028 + hideSelector(); 1.1029 + mLayoutMode = LAYOUT_NORMAL; 1.1030 + layoutChildren(); 1.1031 + } 1.1032 + } 1.1033 + } 1.1034 + 1.1035 + mLastTouchMode = touchMode; 1.1036 + } 1.1037 + 1.1038 + @Override 1.1039 + protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 1.1040 + boolean needsInvalidate = false; 1.1041 + 1.1042 + if (mIsVertical && mOverScroll != scrollY) { 1.1043 + onScrollChanged(getScrollX(), scrollY, getScrollX(), mOverScroll); 1.1044 + mOverScroll = scrollY; 1.1045 + needsInvalidate = true; 1.1046 + } else if (!mIsVertical && mOverScroll != scrollX) { 1.1047 + onScrollChanged(scrollX, getScrollY(), mOverScroll, getScrollY()); 1.1048 + mOverScroll = scrollX; 1.1049 + needsInvalidate = true; 1.1050 + } 1.1051 + 1.1052 + if (needsInvalidate) { 1.1053 + invalidate(); 1.1054 + awakenScrollbarsInternal(); 1.1055 + } 1.1056 + } 1.1057 + 1.1058 + @TargetApi(9) 1.1059 + private boolean overScrollByInternal(int deltaX, int deltaY, 1.1060 + int scrollX, int scrollY, 1.1061 + int scrollRangeX, int scrollRangeY, 1.1062 + int maxOverScrollX, int maxOverScrollY, 1.1063 + boolean isTouchEvent) { 1.1064 + if (Build.VERSION.SDK_INT < 9) { 1.1065 + return false; 1.1066 + } 1.1067 + 1.1068 + return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, 1.1069 + scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); 1.1070 + } 1.1071 + 1.1072 + @Override 1.1073 + @TargetApi(9) 1.1074 + public void setOverScrollMode(int mode) { 1.1075 + if (Build.VERSION.SDK_INT < 9) { 1.1076 + return; 1.1077 + } 1.1078 + 1.1079 + if (mode != ViewCompat.OVER_SCROLL_NEVER) { 1.1080 + if (mStartEdge == null) { 1.1081 + Context context = getContext(); 1.1082 + 1.1083 + mStartEdge = new EdgeEffectCompat(context); 1.1084 + mEndEdge = new EdgeEffectCompat(context); 1.1085 + } 1.1086 + } else { 1.1087 + mStartEdge = null; 1.1088 + mEndEdge = null; 1.1089 + } 1.1090 + 1.1091 + super.setOverScrollMode(mode); 1.1092 + } 1.1093 + 1.1094 + public int pointToPosition(int x, int y) { 1.1095 + Rect frame = mTouchFrame; 1.1096 + if (frame == null) { 1.1097 + mTouchFrame = new Rect(); 1.1098 + frame = mTouchFrame; 1.1099 + } 1.1100 + 1.1101 + final int count = getChildCount(); 1.1102 + for (int i = count - 1; i >= 0; i--) { 1.1103 + final View child = getChildAt(i); 1.1104 + 1.1105 + if (child.getVisibility() == View.VISIBLE) { 1.1106 + child.getHitRect(frame); 1.1107 + 1.1108 + if (frame.contains(x, y)) { 1.1109 + return mFirstPosition + i; 1.1110 + } 1.1111 + } 1.1112 + } 1.1113 + return INVALID_POSITION; 1.1114 + } 1.1115 + 1.1116 + @Override 1.1117 + protected int computeVerticalScrollExtent() { 1.1118 + final int count = getChildCount(); 1.1119 + if (count == 0) { 1.1120 + return 0; 1.1121 + } 1.1122 + 1.1123 + int extent = count * 100; 1.1124 + 1.1125 + View child = getChildAt(0); 1.1126 + final int childTop = child.getTop(); 1.1127 + 1.1128 + int childHeight = child.getHeight(); 1.1129 + if (childHeight > 0) { 1.1130 + extent += (childTop * 100) / childHeight; 1.1131 + } 1.1132 + 1.1133 + child = getChildAt(count - 1); 1.1134 + final int childBottom = child.getBottom(); 1.1135 + 1.1136 + childHeight = child.getHeight(); 1.1137 + if (childHeight > 0) { 1.1138 + extent -= ((childBottom - getHeight()) * 100) / childHeight; 1.1139 + } 1.1140 + 1.1141 + return extent; 1.1142 + } 1.1143 + 1.1144 + @Override 1.1145 + protected int computeHorizontalScrollExtent() { 1.1146 + final int count = getChildCount(); 1.1147 + if (count == 0) { 1.1148 + return 0; 1.1149 + } 1.1150 + 1.1151 + int extent = count * 100; 1.1152 + 1.1153 + View child = getChildAt(0); 1.1154 + final int childLeft = child.getLeft(); 1.1155 + 1.1156 + int childWidth = child.getWidth(); 1.1157 + if (childWidth > 0) { 1.1158 + extent += (childLeft * 100) / childWidth; 1.1159 + } 1.1160 + 1.1161 + child = getChildAt(count - 1); 1.1162 + final int childRight = child.getRight(); 1.1163 + 1.1164 + childWidth = child.getWidth(); 1.1165 + if (childWidth > 0) { 1.1166 + extent -= ((childRight - getWidth()) * 100) / childWidth; 1.1167 + } 1.1168 + 1.1169 + return extent; 1.1170 + } 1.1171 + 1.1172 + @Override 1.1173 + protected int computeVerticalScrollOffset() { 1.1174 + final int firstPosition = mFirstPosition; 1.1175 + final int childCount = getChildCount(); 1.1176 + 1.1177 + if (firstPosition < 0 || childCount == 0) { 1.1178 + return 0; 1.1179 + } 1.1180 + 1.1181 + final View child = getChildAt(0); 1.1182 + final int childTop = child.getTop(); 1.1183 + 1.1184 + int childHeight = child.getHeight(); 1.1185 + if (childHeight > 0) { 1.1186 + return Math.max(firstPosition * 100 - (childTop * 100) / childHeight, 0); 1.1187 + } 1.1188 + 1.1189 + return 0; 1.1190 + } 1.1191 + 1.1192 + @Override 1.1193 + protected int computeHorizontalScrollOffset() { 1.1194 + final int firstPosition = mFirstPosition; 1.1195 + final int childCount = getChildCount(); 1.1196 + 1.1197 + if (firstPosition < 0 || childCount == 0) { 1.1198 + return 0; 1.1199 + } 1.1200 + 1.1201 + final View child = getChildAt(0); 1.1202 + final int childLeft = child.getLeft(); 1.1203 + 1.1204 + int childWidth = child.getWidth(); 1.1205 + if (childWidth > 0) { 1.1206 + return Math.max(firstPosition * 100 - (childLeft * 100) / childWidth, 0); 1.1207 + } 1.1208 + 1.1209 + return 0; 1.1210 + } 1.1211 + 1.1212 + @Override 1.1213 + protected int computeVerticalScrollRange() { 1.1214 + int result = Math.max(mItemCount * 100, 0); 1.1215 + 1.1216 + if (mIsVertical && mOverScroll != 0) { 1.1217 + // Compensate for overscroll 1.1218 + result += Math.abs((int) ((float) mOverScroll / getHeight() * mItemCount * 100)); 1.1219 + } 1.1220 + 1.1221 + return result; 1.1222 + } 1.1223 + 1.1224 + @Override 1.1225 + protected int computeHorizontalScrollRange() { 1.1226 + int result = Math.max(mItemCount * 100, 0); 1.1227 + 1.1228 + if (!mIsVertical && mOverScroll != 0) { 1.1229 + // Compensate for overscroll 1.1230 + result += Math.abs((int) ((float) mOverScroll / getWidth() * mItemCount * 100)); 1.1231 + } 1.1232 + 1.1233 + return result; 1.1234 + } 1.1235 + 1.1236 + @Override 1.1237 + public boolean showContextMenuForChild(View originalView) { 1.1238 + final int longPressPosition = getPositionForView(originalView); 1.1239 + if (longPressPosition >= 0) { 1.1240 + final long longPressId = mAdapter.getItemId(longPressPosition); 1.1241 + boolean handled = false; 1.1242 + 1.1243 + OnItemLongClickListener listener = getOnItemLongClickListener(); 1.1244 + if (listener != null) { 1.1245 + handled = listener.onItemLongClick(TwoWayView.this, originalView, 1.1246 + longPressPosition, longPressId); 1.1247 + } 1.1248 + 1.1249 + if (!handled) { 1.1250 + mContextMenuInfo = createContextMenuInfo( 1.1251 + getChildAt(longPressPosition - mFirstPosition), 1.1252 + longPressPosition, longPressId); 1.1253 + 1.1254 + handled = super.showContextMenuForChild(originalView); 1.1255 + } 1.1256 + 1.1257 + return handled; 1.1258 + } 1.1259 + 1.1260 + return false; 1.1261 + } 1.1262 + 1.1263 + @Override 1.1264 + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 1.1265 + if (disallowIntercept) { 1.1266 + recycleVelocityTracker(); 1.1267 + } 1.1268 + 1.1269 + super.requestDisallowInterceptTouchEvent(disallowIntercept); 1.1270 + } 1.1271 + 1.1272 + @Override 1.1273 + public boolean onInterceptTouchEvent(MotionEvent ev) { 1.1274 + if (!mIsAttached) { 1.1275 + return false; 1.1276 + } 1.1277 + 1.1278 + final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1.1279 + switch (action) { 1.1280 + case MotionEvent.ACTION_DOWN: 1.1281 + initOrResetVelocityTracker(); 1.1282 + mVelocityTracker.addMovement(ev); 1.1283 + 1.1284 + mScroller.abortAnimation(); 1.1285 + 1.1286 + final float x = ev.getX(); 1.1287 + final float y = ev.getY(); 1.1288 + 1.1289 + mLastTouchPos = (mIsVertical ? y : x); 1.1290 + 1.1291 + final int motionPosition = findMotionRowOrColumn((int) mLastTouchPos); 1.1292 + 1.1293 + mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1.1294 + mTouchRemainderPos = 0; 1.1295 + 1.1296 + if (mTouchMode == TOUCH_MODE_FLINGING) { 1.1297 + return true; 1.1298 + } else if (motionPosition >= 0) { 1.1299 + mMotionPosition = motionPosition; 1.1300 + mTouchMode = TOUCH_MODE_DOWN; 1.1301 + } 1.1302 + 1.1303 + break; 1.1304 + 1.1305 + case MotionEvent.ACTION_MOVE: { 1.1306 + if (mTouchMode != TOUCH_MODE_DOWN) { 1.1307 + break; 1.1308 + } 1.1309 + 1.1310 + initVelocityTrackerIfNotExists(); 1.1311 + mVelocityTracker.addMovement(ev); 1.1312 + 1.1313 + final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1.1314 + if (index < 0) { 1.1315 + Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " + 1.1316 + mActivePointerId + " - did TwoWayView receive an inconsistent " + 1.1317 + "event stream?"); 1.1318 + return false; 1.1319 + } 1.1320 + 1.1321 + final float pos; 1.1322 + if (mIsVertical) { 1.1323 + pos = MotionEventCompat.getY(ev, index); 1.1324 + } else { 1.1325 + pos = MotionEventCompat.getX(ev, index); 1.1326 + } 1.1327 + 1.1328 + final float diff = pos - mLastTouchPos + mTouchRemainderPos; 1.1329 + final int delta = (int) diff; 1.1330 + mTouchRemainderPos = diff - delta; 1.1331 + 1.1332 + if (maybeStartScrolling(delta)) { 1.1333 + return true; 1.1334 + } 1.1335 + 1.1336 + break; 1.1337 + } 1.1338 + 1.1339 + case MotionEvent.ACTION_CANCEL: 1.1340 + case MotionEvent.ACTION_UP: 1.1341 + mActivePointerId = INVALID_POINTER; 1.1342 + mTouchMode = TOUCH_MODE_REST; 1.1343 + recycleVelocityTracker(); 1.1344 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.1345 + 1.1346 + break; 1.1347 + } 1.1348 + 1.1349 + return false; 1.1350 + } 1.1351 + 1.1352 + @Override 1.1353 + public boolean onTouchEvent(MotionEvent ev) { 1.1354 + if (!isEnabled()) { 1.1355 + // A disabled view that is clickable still consumes the touch 1.1356 + // events, it just doesn't respond to them. 1.1357 + return isClickable() || isLongClickable(); 1.1358 + } 1.1359 + 1.1360 + if (!mIsAttached) { 1.1361 + return false; 1.1362 + } 1.1363 + 1.1364 + boolean needsInvalidate = false; 1.1365 + 1.1366 + initVelocityTrackerIfNotExists(); 1.1367 + mVelocityTracker.addMovement(ev); 1.1368 + 1.1369 + final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1.1370 + switch (action) { 1.1371 + case MotionEvent.ACTION_DOWN: { 1.1372 + if (mDataChanged) { 1.1373 + break; 1.1374 + } 1.1375 + 1.1376 + mVelocityTracker.clear(); 1.1377 + mScroller.abortAnimation(); 1.1378 + 1.1379 + final float x = ev.getX(); 1.1380 + final float y = ev.getY(); 1.1381 + 1.1382 + mLastTouchPos = (mIsVertical ? y : x); 1.1383 + 1.1384 + int motionPosition = pointToPosition((int) x, (int) y); 1.1385 + 1.1386 + mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1.1387 + mTouchRemainderPos = 0; 1.1388 + 1.1389 + if (mDataChanged) { 1.1390 + break; 1.1391 + } 1.1392 + 1.1393 + if (mTouchMode == TOUCH_MODE_FLINGING) { 1.1394 + mTouchMode = TOUCH_MODE_DRAGGING; 1.1395 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 1.1396 + motionPosition = findMotionRowOrColumn((int) mLastTouchPos); 1.1397 + return true; 1.1398 + } else if (mMotionPosition >= 0 && mAdapter.isEnabled(mMotionPosition)) { 1.1399 + mTouchMode = TOUCH_MODE_DOWN; 1.1400 + triggerCheckForTap(); 1.1401 + } 1.1402 + 1.1403 + mMotionPosition = motionPosition; 1.1404 + 1.1405 + break; 1.1406 + } 1.1407 + 1.1408 + case MotionEvent.ACTION_MOVE: { 1.1409 + final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1.1410 + if (index < 0) { 1.1411 + Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " + 1.1412 + mActivePointerId + " - did TwoWayView receive an inconsistent " + 1.1413 + "event stream?"); 1.1414 + return false; 1.1415 + } 1.1416 + 1.1417 + final float pos; 1.1418 + if (mIsVertical) { 1.1419 + pos = MotionEventCompat.getY(ev, index); 1.1420 + } else { 1.1421 + pos = MotionEventCompat.getX(ev, index); 1.1422 + } 1.1423 + 1.1424 + if (mDataChanged) { 1.1425 + // Re-sync everything if data has been changed 1.1426 + // since the scroll operation can query the adapter. 1.1427 + layoutChildren(); 1.1428 + } 1.1429 + 1.1430 + final float diff = pos - mLastTouchPos + mTouchRemainderPos; 1.1431 + final int delta = (int) diff; 1.1432 + mTouchRemainderPos = diff - delta; 1.1433 + 1.1434 + switch (mTouchMode) { 1.1435 + case TOUCH_MODE_DOWN: 1.1436 + case TOUCH_MODE_TAP: 1.1437 + case TOUCH_MODE_DONE_WAITING: 1.1438 + // Check if we have moved far enough that it looks more like a 1.1439 + // scroll than a tap 1.1440 + maybeStartScrolling(delta); 1.1441 + break; 1.1442 + 1.1443 + case TOUCH_MODE_DRAGGING: 1.1444 + case TOUCH_MODE_OVERSCROLL: 1.1445 + mLastTouchPos = pos; 1.1446 + maybeScroll(delta); 1.1447 + break; 1.1448 + } 1.1449 + 1.1450 + break; 1.1451 + } 1.1452 + 1.1453 + case MotionEvent.ACTION_CANCEL: 1.1454 + cancelCheckForTap(); 1.1455 + mTouchMode = TOUCH_MODE_REST; 1.1456 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.1457 + 1.1458 + setPressed(false); 1.1459 + View motionView = this.getChildAt(mMotionPosition - mFirstPosition); 1.1460 + if (motionView != null) { 1.1461 + motionView.setPressed(false); 1.1462 + } 1.1463 + 1.1464 + if (mStartEdge != null && mEndEdge != null) { 1.1465 + needsInvalidate = mStartEdge.onRelease() | mEndEdge.onRelease(); 1.1466 + } 1.1467 + 1.1468 + recycleVelocityTracker(); 1.1469 + 1.1470 + break; 1.1471 + 1.1472 + case MotionEvent.ACTION_UP: { 1.1473 + switch (mTouchMode) { 1.1474 + case TOUCH_MODE_DOWN: 1.1475 + case TOUCH_MODE_TAP: 1.1476 + case TOUCH_MODE_DONE_WAITING: { 1.1477 + final int motionPosition = mMotionPosition; 1.1478 + final View child = getChildAt(motionPosition - mFirstPosition); 1.1479 + 1.1480 + final float x = ev.getX(); 1.1481 + final float y = ev.getY(); 1.1482 + 1.1483 + boolean inList = false; 1.1484 + if (mIsVertical) { 1.1485 + inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight(); 1.1486 + } else { 1.1487 + inList = y > getPaddingTop() && y < getHeight() - getPaddingBottom(); 1.1488 + } 1.1489 + 1.1490 + if (child != null && !child.hasFocusable() && inList) { 1.1491 + if (mTouchMode != TOUCH_MODE_DOWN) { 1.1492 + child.setPressed(false); 1.1493 + } 1.1494 + 1.1495 + if (mPerformClick == null) { 1.1496 + mPerformClick = new PerformClick(); 1.1497 + } 1.1498 + 1.1499 + final PerformClick performClick = mPerformClick; 1.1500 + performClick.mClickMotionPosition = motionPosition; 1.1501 + performClick.rememberWindowAttachCount(); 1.1502 + 1.1503 + mResurrectToPosition = motionPosition; 1.1504 + 1.1505 + if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { 1.1506 + if (mTouchMode == TOUCH_MODE_DOWN) { 1.1507 + cancelCheckForTap(); 1.1508 + } else { 1.1509 + cancelCheckForLongPress(); 1.1510 + } 1.1511 + 1.1512 + mLayoutMode = LAYOUT_NORMAL; 1.1513 + 1.1514 + if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 1.1515 + mTouchMode = TOUCH_MODE_TAP; 1.1516 + 1.1517 + setPressed(true); 1.1518 + positionSelector(mMotionPosition, child); 1.1519 + child.setPressed(true); 1.1520 + 1.1521 + if (mSelector != null) { 1.1522 + Drawable d = mSelector.getCurrent(); 1.1523 + if (d != null && d instanceof TransitionDrawable) { 1.1524 + ((TransitionDrawable) d).resetTransition(); 1.1525 + } 1.1526 + } 1.1527 + 1.1528 + if (mTouchModeReset != null) { 1.1529 + removeCallbacks(mTouchModeReset); 1.1530 + } 1.1531 + 1.1532 + mTouchModeReset = new Runnable() { 1.1533 + @Override 1.1534 + public void run() { 1.1535 + mTouchMode = TOUCH_MODE_REST; 1.1536 + 1.1537 + setPressed(false); 1.1538 + child.setPressed(false); 1.1539 + 1.1540 + if (!mDataChanged) { 1.1541 + performClick.run(); 1.1542 + } 1.1543 + 1.1544 + mTouchModeReset = null; 1.1545 + } 1.1546 + }; 1.1547 + 1.1548 + postDelayed(mTouchModeReset, 1.1549 + ViewConfiguration.getPressedStateDuration()); 1.1550 + } else { 1.1551 + mTouchMode = TOUCH_MODE_REST; 1.1552 + updateSelectorState(); 1.1553 + } 1.1554 + } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 1.1555 + performClick.run(); 1.1556 + } 1.1557 + } 1.1558 + 1.1559 + mTouchMode = TOUCH_MODE_REST; 1.1560 + updateSelectorState(); 1.1561 + 1.1562 + break; 1.1563 + } 1.1564 + 1.1565 + case TOUCH_MODE_DRAGGING: 1.1566 + if (contentFits()) { 1.1567 + mTouchMode = TOUCH_MODE_REST; 1.1568 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.1569 + break; 1.1570 + } 1.1571 + 1.1572 + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1.1573 + 1.1574 + final float velocity; 1.1575 + if (mIsVertical) { 1.1576 + velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker, 1.1577 + mActivePointerId); 1.1578 + } else { 1.1579 + velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker, 1.1580 + mActivePointerId); 1.1581 + } 1.1582 + 1.1583 + if (Math.abs(velocity) >= mFlingVelocity) { 1.1584 + mTouchMode = TOUCH_MODE_FLINGING; 1.1585 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 1.1586 + 1.1587 + mScroller.fling(0, 0, 1.1588 + (int) (mIsVertical ? 0 : velocity), 1.1589 + (int) (mIsVertical ? velocity : 0), 1.1590 + (mIsVertical ? 0 : Integer.MIN_VALUE), 1.1591 + (mIsVertical ? 0 : Integer.MAX_VALUE), 1.1592 + (mIsVertical ? Integer.MIN_VALUE : 0), 1.1593 + (mIsVertical ? Integer.MAX_VALUE : 0)); 1.1594 + 1.1595 + mLastTouchPos = 0; 1.1596 + needsInvalidate = true; 1.1597 + } else { 1.1598 + mTouchMode = TOUCH_MODE_REST; 1.1599 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.1600 + } 1.1601 + 1.1602 + break; 1.1603 + 1.1604 + case TOUCH_MODE_OVERSCROLL: 1.1605 + mTouchMode = TOUCH_MODE_REST; 1.1606 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.1607 + break; 1.1608 + } 1.1609 + 1.1610 + cancelCheckForTap(); 1.1611 + cancelCheckForLongPress(); 1.1612 + setPressed(false); 1.1613 + 1.1614 + if (mStartEdge != null && mEndEdge != null) { 1.1615 + needsInvalidate |= mStartEdge.onRelease() | mEndEdge.onRelease(); 1.1616 + } 1.1617 + 1.1618 + recycleVelocityTracker(); 1.1619 + 1.1620 + break; 1.1621 + } 1.1622 + } 1.1623 + 1.1624 + if (needsInvalidate) { 1.1625 + ViewCompat.postInvalidateOnAnimation(this); 1.1626 + } 1.1627 + 1.1628 + return true; 1.1629 + } 1.1630 + 1.1631 + @Override 1.1632 + public void onTouchModeChanged(boolean isInTouchMode) { 1.1633 + if (isInTouchMode) { 1.1634 + // Get rid of the selection when we enter touch mode 1.1635 + hideSelector(); 1.1636 + 1.1637 + // Layout, but only if we already have done so previously. 1.1638 + // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore 1.1639 + // state.) 1.1640 + if (getWidth() > 0 && getHeight() > 0 && getChildCount() > 0) { 1.1641 + layoutChildren(); 1.1642 + } 1.1643 + 1.1644 + updateSelectorState(); 1.1645 + } else { 1.1646 + final int touchMode = mTouchMode; 1.1647 + if (touchMode == TOUCH_MODE_OVERSCROLL) { 1.1648 + if (mOverScroll != 0) { 1.1649 + mOverScroll = 0; 1.1650 + finishEdgeGlows(); 1.1651 + invalidate(); 1.1652 + } 1.1653 + } 1.1654 + } 1.1655 + } 1.1656 + 1.1657 + @Override 1.1658 + public boolean onKeyDown(int keyCode, KeyEvent event) { 1.1659 + return handleKeyEvent(keyCode, 1, event); 1.1660 + } 1.1661 + 1.1662 + @Override 1.1663 + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 1.1664 + return handleKeyEvent(keyCode, repeatCount, event); 1.1665 + } 1.1666 + 1.1667 + @Override 1.1668 + public boolean onKeyUp(int keyCode, KeyEvent event) { 1.1669 + return handleKeyEvent(keyCode, 1, event); 1.1670 + } 1.1671 + 1.1672 + @Override 1.1673 + public void sendAccessibilityEvent(int eventType) { 1.1674 + // Since this class calls onScrollChanged even if the mFirstPosition and the 1.1675 + // child count have not changed we will avoid sending duplicate accessibility 1.1676 + // events. 1.1677 + if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 1.1678 + final int firstVisiblePosition = getFirstVisiblePosition(); 1.1679 + final int lastVisiblePosition = getLastVisiblePosition(); 1.1680 + 1.1681 + if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition 1.1682 + && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { 1.1683 + return; 1.1684 + } else { 1.1685 + mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; 1.1686 + mLastAccessibilityScrollEventToIndex = lastVisiblePosition; 1.1687 + } 1.1688 + } 1.1689 + 1.1690 + super.sendAccessibilityEvent(eventType); 1.1691 + } 1.1692 + 1.1693 + @Override 1.1694 + @TargetApi(14) 1.1695 + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1.1696 + super.onInitializeAccessibilityEvent(event); 1.1697 + event.setClassName(TwoWayView.class.getName()); 1.1698 + } 1.1699 + 1.1700 + @Override 1.1701 + @TargetApi(14) 1.1702 + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1.1703 + super.onInitializeAccessibilityNodeInfo(info); 1.1704 + info.setClassName(TwoWayView.class.getName()); 1.1705 + 1.1706 + AccessibilityNodeInfoCompat infoCompat = new AccessibilityNodeInfoCompat(info); 1.1707 + 1.1708 + if (isEnabled()) { 1.1709 + if (getFirstVisiblePosition() > 0) { 1.1710 + infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); 1.1711 + } 1.1712 + 1.1713 + if (getLastVisiblePosition() < getCount() - 1) { 1.1714 + infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); 1.1715 + } 1.1716 + } 1.1717 + } 1.1718 + 1.1719 + @Override 1.1720 + @TargetApi(16) 1.1721 + public boolean performAccessibilityAction(int action, Bundle arguments) { 1.1722 + if (super.performAccessibilityAction(action, arguments)) { 1.1723 + return true; 1.1724 + } 1.1725 + 1.1726 + switch (action) { 1.1727 + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 1.1728 + if (isEnabled() && getLastVisiblePosition() < getCount() - 1) { 1.1729 + final int viewportSize; 1.1730 + if (mIsVertical) { 1.1731 + viewportSize = getHeight() - getPaddingTop() - getPaddingBottom(); 1.1732 + } else { 1.1733 + viewportSize = getWidth() - getPaddingLeft() - getPaddingRight(); 1.1734 + } 1.1735 + 1.1736 + // TODO: Use some form of smooth scroll instead 1.1737 + trackMotionScroll(viewportSize); 1.1738 + return true; 1.1739 + } 1.1740 + return false; 1.1741 + 1.1742 + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 1.1743 + if (isEnabled() && mFirstPosition > 0) { 1.1744 + final int viewportSize; 1.1745 + if (mIsVertical) { 1.1746 + viewportSize = getHeight() - getPaddingTop() - getPaddingBottom(); 1.1747 + } else { 1.1748 + viewportSize = getWidth() - getPaddingLeft() - getPaddingRight(); 1.1749 + } 1.1750 + 1.1751 + // TODO: Use some form of smooth scroll instead 1.1752 + trackMotionScroll(-viewportSize); 1.1753 + return true; 1.1754 + } 1.1755 + return false; 1.1756 + } 1.1757 + 1.1758 + return false; 1.1759 + } 1.1760 + 1.1761 + /** 1.1762 + * Return true if child is an ancestor of parent, (or equal to the parent). 1.1763 + */ 1.1764 + private boolean isViewAncestorOf(View child, View parent) { 1.1765 + if (child == parent) { 1.1766 + return true; 1.1767 + } 1.1768 + 1.1769 + final ViewParent theParent = child.getParent(); 1.1770 + 1.1771 + return (theParent instanceof ViewGroup) && 1.1772 + isViewAncestorOf((View) theParent, parent); 1.1773 + } 1.1774 + 1.1775 + private void forceValidFocusDirection(int direction) { 1.1776 + if (mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) { 1.1777 + throw new IllegalArgumentException("Focus direction must be one of" 1.1778 + + " {View.FOCUS_UP, View.FOCUS_DOWN} for vertical orientation"); 1.1779 + } else if (!mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) { 1.1780 + throw new IllegalArgumentException("Focus direction must be one of" 1.1781 + + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation"); 1.1782 + } 1.1783 + } 1.1784 + 1.1785 + private void forceValidInnerFocusDirection(int direction) { 1.1786 + if (mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) { 1.1787 + throw new IllegalArgumentException("Direction must be one of" 1.1788 + + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation"); 1.1789 + } else if (!mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) { 1.1790 + throw new IllegalArgumentException("direction must be one of" 1.1791 + + " {View.FOCUS_UP, View.FOCUS_DOWN} for horizontal orientation"); 1.1792 + } 1.1793 + } 1.1794 + 1.1795 + /** 1.1796 + * Scrolls up or down by the number of items currently present on screen. 1.1797 + * 1.1798 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.1799 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.1800 + * current view orientation. 1.1801 + * 1.1802 + * @return whether selection was moved 1.1803 + */ 1.1804 + boolean pageScroll(int direction) { 1.1805 + forceValidFocusDirection(direction); 1.1806 + 1.1807 + boolean forward = false; 1.1808 + int nextPage = -1; 1.1809 + 1.1810 + if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { 1.1811 + nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1); 1.1812 + } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { 1.1813 + nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1); 1.1814 + forward = true; 1.1815 + } 1.1816 + 1.1817 + if (nextPage < 0) { 1.1818 + return false; 1.1819 + } 1.1820 + 1.1821 + final int position = lookForSelectablePosition(nextPage, forward); 1.1822 + if (position >= 0) { 1.1823 + mLayoutMode = LAYOUT_SPECIFIC; 1.1824 + mSpecificStart = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.1825 + 1.1826 + if (forward && position > mItemCount - getChildCount()) { 1.1827 + mLayoutMode = LAYOUT_FORCE_BOTTOM; 1.1828 + } 1.1829 + 1.1830 + if (!forward && position < getChildCount()) { 1.1831 + mLayoutMode = LAYOUT_FORCE_TOP; 1.1832 + } 1.1833 + 1.1834 + setSelectionInt(position); 1.1835 + invokeOnItemScrollListener(); 1.1836 + 1.1837 + if (!awakenScrollbarsInternal()) { 1.1838 + invalidate(); 1.1839 + } 1.1840 + 1.1841 + return true; 1.1842 + } 1.1843 + 1.1844 + return false; 1.1845 + } 1.1846 + 1.1847 + /** 1.1848 + * Go to the last or first item if possible (not worrying about panning across or navigating 1.1849 + * within the internal focus of the currently selected item.) 1.1850 + * 1.1851 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.1852 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.1853 + * current view orientation. 1.1854 + * 1.1855 + * @return whether selection was moved 1.1856 + */ 1.1857 + boolean fullScroll(int direction) { 1.1858 + forceValidFocusDirection(direction); 1.1859 + 1.1860 + boolean moved = false; 1.1861 + if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { 1.1862 + if (mSelectedPosition != 0) { 1.1863 + int position = lookForSelectablePosition(0, true); 1.1864 + if (position >= 0) { 1.1865 + mLayoutMode = LAYOUT_FORCE_TOP; 1.1866 + setSelectionInt(position); 1.1867 + invokeOnItemScrollListener(); 1.1868 + } 1.1869 + 1.1870 + moved = true; 1.1871 + } 1.1872 + } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { 1.1873 + if (mSelectedPosition < mItemCount - 1) { 1.1874 + int position = lookForSelectablePosition(mItemCount - 1, true); 1.1875 + if (position >= 0) { 1.1876 + mLayoutMode = LAYOUT_FORCE_BOTTOM; 1.1877 + setSelectionInt(position); 1.1878 + invokeOnItemScrollListener(); 1.1879 + } 1.1880 + 1.1881 + moved = true; 1.1882 + } 1.1883 + } 1.1884 + 1.1885 + if (moved && !awakenScrollbarsInternal()) { 1.1886 + awakenScrollbarsInternal(); 1.1887 + invalidate(); 1.1888 + } 1.1889 + 1.1890 + return moved; 1.1891 + } 1.1892 + 1.1893 + /** 1.1894 + * To avoid horizontal/vertical focus searches changing the selected item, 1.1895 + * we manually focus search within the selected item (as applicable), and 1.1896 + * prevent focus from jumping to something within another item. 1.1897 + * 1.1898 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.1899 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.1900 + * current view orientation. 1.1901 + * 1.1902 + * @return Whether this consumes the key event. 1.1903 + */ 1.1904 + private boolean handleFocusWithinItem(int direction) { 1.1905 + forceValidInnerFocusDirection(direction); 1.1906 + 1.1907 + final int numChildren = getChildCount(); 1.1908 + 1.1909 + if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) { 1.1910 + final View selectedView = getSelectedView(); 1.1911 + 1.1912 + if (selectedView != null && selectedView.hasFocus() && 1.1913 + selectedView instanceof ViewGroup) { 1.1914 + 1.1915 + final View currentFocus = selectedView.findFocus(); 1.1916 + final View nextFocus = FocusFinder.getInstance().findNextFocus( 1.1917 + (ViewGroup) selectedView, currentFocus, direction); 1.1918 + 1.1919 + if (nextFocus != null) { 1.1920 + // Do the math to get interesting rect in next focus' coordinates 1.1921 + currentFocus.getFocusedRect(mTempRect); 1.1922 + offsetDescendantRectToMyCoords(currentFocus, mTempRect); 1.1923 + offsetRectIntoDescendantCoords(nextFocus, mTempRect); 1.1924 + 1.1925 + if (nextFocus.requestFocus(direction, mTempRect)) { 1.1926 + return true; 1.1927 + } 1.1928 + } 1.1929 + 1.1930 + // We are blocking the key from being handled (by returning true) 1.1931 + // if the global result is going to be some other view within this 1.1932 + // list. This is to achieve the overall goal of having horizontal/vertical 1.1933 + // d-pad navigation remain in the current item depending on the current 1.1934 + // orientation in this view. 1.1935 + final View globalNextFocus = FocusFinder.getInstance().findNextFocus( 1.1936 + (ViewGroup) getRootView(), currentFocus, direction); 1.1937 + 1.1938 + if (globalNextFocus != null) { 1.1939 + return isViewAncestorOf(globalNextFocus, this); 1.1940 + } 1.1941 + } 1.1942 + } 1.1943 + 1.1944 + return false; 1.1945 + } 1.1946 + 1.1947 + /** 1.1948 + * Scrolls to the next or previous item if possible. 1.1949 + * 1.1950 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.1951 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.1952 + * current view orientation. 1.1953 + * 1.1954 + * @return whether selection was moved 1.1955 + */ 1.1956 + private boolean arrowScroll(int direction) { 1.1957 + forceValidFocusDirection(direction); 1.1958 + 1.1959 + try { 1.1960 + mInLayout = true; 1.1961 + 1.1962 + final boolean handled = arrowScrollImpl(direction); 1.1963 + if (handled) { 1.1964 + playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 1.1965 + } 1.1966 + 1.1967 + return handled; 1.1968 + } finally { 1.1969 + mInLayout = false; 1.1970 + } 1.1971 + } 1.1972 + 1.1973 + /** 1.1974 + * When selection changes, it is possible that the previously selected or the 1.1975 + * next selected item will change its size. If so, we need to offset some folks, 1.1976 + * and re-layout the items as appropriate. 1.1977 + * 1.1978 + * @param selectedView The currently selected view (before changing selection). 1.1979 + * should be <code>null</code> if there was no previous selection. 1.1980 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.1981 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.1982 + * current view orientation. 1.1983 + * @param newSelectedPosition The position of the next selection. 1.1984 + * @param newFocusAssigned whether new focus was assigned. This matters because 1.1985 + * when something has focus, we don't want to show selection (ugh). 1.1986 + */ 1.1987 + private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition, 1.1988 + boolean newFocusAssigned) { 1.1989 + forceValidFocusDirection(direction); 1.1990 + 1.1991 + if (newSelectedPosition == INVALID_POSITION) { 1.1992 + throw new IllegalArgumentException("newSelectedPosition needs to be valid"); 1.1993 + } 1.1994 + 1.1995 + // Whether or not we are moving down/right or up/left, we want to preserve the 1.1996 + // top/left of whatever view is at the start: 1.1997 + // - moving down/right: the view that had selection 1.1998 + // - moving up/left: the view that is getting selection 1.1999 + final int selectedIndex = mSelectedPosition - mFirstPosition; 1.2000 + final int nextSelectedIndex = newSelectedPosition - mFirstPosition; 1.2001 + int startViewIndex, endViewIndex; 1.2002 + boolean topSelected = false; 1.2003 + View startView; 1.2004 + View endView; 1.2005 + 1.2006 + if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { 1.2007 + startViewIndex = nextSelectedIndex; 1.2008 + endViewIndex = selectedIndex; 1.2009 + startView = getChildAt(startViewIndex); 1.2010 + endView = selectedView; 1.2011 + topSelected = true; 1.2012 + } else { 1.2013 + startViewIndex = selectedIndex; 1.2014 + endViewIndex = nextSelectedIndex; 1.2015 + startView = selectedView; 1.2016 + endView = getChildAt(endViewIndex); 1.2017 + } 1.2018 + 1.2019 + final int numChildren = getChildCount(); 1.2020 + 1.2021 + // start with top view: is it changing size? 1.2022 + if (startView != null) { 1.2023 + startView.setSelected(!newFocusAssigned && topSelected); 1.2024 + measureAndAdjustDown(startView, startViewIndex, numChildren); 1.2025 + } 1.2026 + 1.2027 + // is the bottom view changing size? 1.2028 + if (endView != null) { 1.2029 + endView.setSelected(!newFocusAssigned && !topSelected); 1.2030 + measureAndAdjustDown(endView, endViewIndex, numChildren); 1.2031 + } 1.2032 + } 1.2033 + 1.2034 + /** 1.2035 + * Re-measure a child, and if its height changes, lay it out preserving its 1.2036 + * top, and adjust the children below it appropriately. 1.2037 + * 1.2038 + * @param child The child 1.2039 + * @param childIndex The view group index of the child. 1.2040 + * @param numChildren The number of children in the view group. 1.2041 + */ 1.2042 + private void measureAndAdjustDown(View child, int childIndex, int numChildren) { 1.2043 + int oldHeight = child.getHeight(); 1.2044 + measureChild(child); 1.2045 + 1.2046 + if (child.getMeasuredHeight() == oldHeight) { 1.2047 + return; 1.2048 + } 1.2049 + 1.2050 + // lay out the view, preserving its top 1.2051 + relayoutMeasuredChild(child); 1.2052 + 1.2053 + // adjust views below appropriately 1.2054 + final int heightDelta = child.getMeasuredHeight() - oldHeight; 1.2055 + for (int i = childIndex + 1; i < numChildren; i++) { 1.2056 + getChildAt(i).offsetTopAndBottom(heightDelta); 1.2057 + } 1.2058 + } 1.2059 + 1.2060 + /** 1.2061 + * Do an arrow scroll based on focus searching. If a new view is 1.2062 + * given focus, return the selection delta and amount to scroll via 1.2063 + * an {@link ArrowScrollFocusResult}, otherwise, return null. 1.2064 + * 1.2065 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.2066 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.2067 + * current view orientation. 1.2068 + * 1.2069 + * @return The result if focus has changed, or <code>null</code>. 1.2070 + */ 1.2071 + private ArrowScrollFocusResult arrowScrollFocused(final int direction) { 1.2072 + forceValidFocusDirection(direction); 1.2073 + 1.2074 + final View selectedView = getSelectedView(); 1.2075 + final View newFocus; 1.2076 + final int searchPoint; 1.2077 + 1.2078 + if (selectedView != null && selectedView.hasFocus()) { 1.2079 + View oldFocus = selectedView.findFocus(); 1.2080 + newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction); 1.2081 + } else { 1.2082 + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { 1.2083 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.2084 + 1.2085 + final int selectedStart; 1.2086 + if (selectedView != null) { 1.2087 + selectedStart = (mIsVertical ? selectedView.getTop() : selectedView.getLeft()); 1.2088 + } else { 1.2089 + selectedStart = start; 1.2090 + } 1.2091 + 1.2092 + searchPoint = Math.max(selectedStart, start); 1.2093 + } else { 1.2094 + final int end = (mIsVertical ? getHeight() - getPaddingBottom() : 1.2095 + getWidth() - getPaddingRight()); 1.2096 + 1.2097 + final int selectedEnd; 1.2098 + if (selectedView != null) { 1.2099 + selectedEnd = (mIsVertical ? selectedView.getBottom() : selectedView.getRight()); 1.2100 + } else { 1.2101 + selectedEnd = end; 1.2102 + } 1.2103 + 1.2104 + searchPoint = Math.min(selectedEnd, end); 1.2105 + } 1.2106 + 1.2107 + final int x = (mIsVertical ? 0 : searchPoint); 1.2108 + final int y = (mIsVertical ? searchPoint : 0); 1.2109 + mTempRect.set(x, y, x, y); 1.2110 + 1.2111 + newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction); 1.2112 + } 1.2113 + 1.2114 + if (newFocus != null) { 1.2115 + final int positionOfNewFocus = positionOfNewFocus(newFocus); 1.2116 + 1.2117 + // If the focus change is in a different new position, make sure 1.2118 + // we aren't jumping over another selectable position. 1.2119 + if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) { 1.2120 + final int selectablePosition = lookForSelectablePositionOnScreen(direction); 1.2121 + 1.2122 + final boolean movingForward = 1.2123 + (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT); 1.2124 + final boolean movingBackward = 1.2125 + (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT); 1.2126 + 1.2127 + if (selectablePosition != INVALID_POSITION && 1.2128 + ((movingForward && selectablePosition < positionOfNewFocus) || 1.2129 + (movingBackward && selectablePosition > positionOfNewFocus))) { 1.2130 + return null; 1.2131 + } 1.2132 + } 1.2133 + 1.2134 + int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus); 1.2135 + 1.2136 + final int maxScrollAmount = getMaxScrollAmount(); 1.2137 + if (focusScroll < maxScrollAmount) { 1.2138 + // Not moving too far, safe to give next view focus 1.2139 + newFocus.requestFocus(direction); 1.2140 + mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll); 1.2141 + return mArrowScrollFocusResult; 1.2142 + } else if (distanceToView(newFocus) < maxScrollAmount){ 1.2143 + // Case to consider: 1.2144 + // Too far to get entire next focusable on screen, but by going 1.2145 + // max scroll amount, we are getting it at least partially in view, 1.2146 + // so give it focus and scroll the max amount. 1.2147 + newFocus.requestFocus(direction); 1.2148 + mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount); 1.2149 + return mArrowScrollFocusResult; 1.2150 + } 1.2151 + } 1.2152 + 1.2153 + return null; 1.2154 + } 1.2155 + 1.2156 + /** 1.2157 + * @return The maximum amount a list view will scroll in response to 1.2158 + * an arrow event. 1.2159 + */ 1.2160 + public int getMaxScrollAmount() { 1.2161 + return (int) (MAX_SCROLL_FACTOR * getHeight()); 1.2162 + } 1.2163 + 1.2164 + /** 1.2165 + * @return The amount to preview next items when arrow scrolling. 1.2166 + */ 1.2167 + private int getArrowScrollPreviewLength() { 1.2168 + // FIXME: TwoWayView has no fading edge support just yet but using it 1.2169 + // makes it convenient for defining the next item's previous length. 1.2170 + int fadingEdgeLength = 1.2171 + (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength()); 1.2172 + 1.2173 + return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, fadingEdgeLength); 1.2174 + } 1.2175 + 1.2176 + /** 1.2177 + * @param newFocus The view that would have focus. 1.2178 + * @return the position that contains newFocus 1.2179 + */ 1.2180 + private int positionOfNewFocus(View newFocus) { 1.2181 + final int numChildren = getChildCount(); 1.2182 + 1.2183 + for (int i = 0; i < numChildren; i++) { 1.2184 + final View child = getChildAt(i); 1.2185 + if (isViewAncestorOf(newFocus, child)) { 1.2186 + return mFirstPosition + i; 1.2187 + } 1.2188 + } 1.2189 + 1.2190 + throw new IllegalArgumentException("newFocus is not a child of any of the" 1.2191 + + " children of the list!"); 1.2192 + } 1.2193 + 1.2194 + /** 1.2195 + * Handle an arrow scroll going up or down. Take into account whether items are selectable, 1.2196 + * whether there are focusable items, etc. 1.2197 + * 1.2198 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.2199 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.2200 + * current view orientation. 1.2201 + * 1.2202 + * @return Whether any scrolling, selection or focus change occurred. 1.2203 + */ 1.2204 + private boolean arrowScrollImpl(int direction) { 1.2205 + forceValidFocusDirection(direction); 1.2206 + 1.2207 + if (getChildCount() <= 0) { 1.2208 + return false; 1.2209 + } 1.2210 + 1.2211 + View selectedView = getSelectedView(); 1.2212 + int selectedPos = mSelectedPosition; 1.2213 + 1.2214 + int nextSelectedPosition = lookForSelectablePositionOnScreen(direction); 1.2215 + int amountToScroll = amountToScroll(direction, nextSelectedPosition); 1.2216 + 1.2217 + // If we are moving focus, we may OVERRIDE the default behaviour 1.2218 + final ArrowScrollFocusResult focusResult = (mItemsCanFocus ? arrowScrollFocused(direction) : null); 1.2219 + if (focusResult != null) { 1.2220 + nextSelectedPosition = focusResult.getSelectedPosition(); 1.2221 + amountToScroll = focusResult.getAmountToScroll(); 1.2222 + } 1.2223 + 1.2224 + boolean needToRedraw = (focusResult != null); 1.2225 + if (nextSelectedPosition != INVALID_POSITION) { 1.2226 + handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null); 1.2227 + 1.2228 + setSelectedPositionInt(nextSelectedPosition); 1.2229 + setNextSelectedPositionInt(nextSelectedPosition); 1.2230 + 1.2231 + selectedView = getSelectedView(); 1.2232 + selectedPos = nextSelectedPosition; 1.2233 + 1.2234 + if (mItemsCanFocus && focusResult == null) { 1.2235 + // There was no new view found to take focus, make sure we 1.2236 + // don't leave focus with the old selection. 1.2237 + final View focused = getFocusedChild(); 1.2238 + if (focused != null) { 1.2239 + focused.clearFocus(); 1.2240 + } 1.2241 + } 1.2242 + 1.2243 + needToRedraw = true; 1.2244 + checkSelectionChanged(); 1.2245 + } 1.2246 + 1.2247 + if (amountToScroll > 0) { 1.2248 + trackMotionScroll(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ? 1.2249 + amountToScroll : -amountToScroll); 1.2250 + needToRedraw = true; 1.2251 + } 1.2252 + 1.2253 + // If we didn't find a new focusable, make sure any existing focused 1.2254 + // item that was panned off screen gives up focus. 1.2255 + if (mItemsCanFocus && focusResult == null && 1.2256 + selectedView != null && selectedView.hasFocus()) { 1.2257 + final View focused = selectedView.findFocus(); 1.2258 + if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) { 1.2259 + focused.clearFocus(); 1.2260 + } 1.2261 + } 1.2262 + 1.2263 + // If the current selection is panned off, we need to remove the selection 1.2264 + if (nextSelectedPosition == INVALID_POSITION && selectedView != null 1.2265 + && !isViewAncestorOf(selectedView, this)) { 1.2266 + selectedView = null; 1.2267 + hideSelector(); 1.2268 + 1.2269 + // But we don't want to set the ressurect position (that would make subsequent 1.2270 + // unhandled key events bring back the item we just scrolled off) 1.2271 + mResurrectToPosition = INVALID_POSITION; 1.2272 + } 1.2273 + 1.2274 + if (needToRedraw) { 1.2275 + if (selectedView != null) { 1.2276 + positionSelector(selectedPos, selectedView); 1.2277 + mSelectedStart = selectedView.getTop(); 1.2278 + } 1.2279 + 1.2280 + if (!awakenScrollbarsInternal()) { 1.2281 + invalidate(); 1.2282 + } 1.2283 + 1.2284 + invokeOnItemScrollListener(); 1.2285 + return true; 1.2286 + } 1.2287 + 1.2288 + return false; 1.2289 + } 1.2290 + 1.2291 + /** 1.2292 + * Determine how much we need to scroll in order to get the next selected view 1.2293 + * visible. The amount is capped at {@link #getMaxScrollAmount()}. 1.2294 + * 1.2295 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.2296 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.2297 + * current view orientation. 1.2298 + * @param nextSelectedPosition The position of the next selection, or 1.2299 + * {@link #INVALID_POSITION} if there is no next selectable position 1.2300 + * 1.2301 + * @return The amount to scroll. Note: this is always positive! Direction 1.2302 + * needs to be taken into account when actually scrolling. 1.2303 + */ 1.2304 + private int amountToScroll(int direction, int nextSelectedPosition) { 1.2305 + forceValidFocusDirection(direction); 1.2306 + 1.2307 + final int numChildren = getChildCount(); 1.2308 + 1.2309 + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { 1.2310 + final int end = (mIsVertical ? getHeight() - getPaddingBottom() : 1.2311 + getWidth() - getPaddingRight()); 1.2312 + 1.2313 + int indexToMakeVisible = numChildren - 1; 1.2314 + if (nextSelectedPosition != INVALID_POSITION) { 1.2315 + indexToMakeVisible = nextSelectedPosition - mFirstPosition; 1.2316 + } 1.2317 + 1.2318 + final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; 1.2319 + final View viewToMakeVisible = getChildAt(indexToMakeVisible); 1.2320 + 1.2321 + int goalEnd = end; 1.2322 + if (positionToMakeVisible < mItemCount - 1) { 1.2323 + goalEnd -= getArrowScrollPreviewLength(); 1.2324 + } 1.2325 + 1.2326 + final int viewToMakeVisibleStart = 1.2327 + (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); 1.2328 + final int viewToMakeVisibleEnd = 1.2329 + (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); 1.2330 + 1.2331 + if (viewToMakeVisibleEnd <= goalEnd) { 1.2332 + // Target item is fully visible 1.2333 + return 0; 1.2334 + } 1.2335 + 1.2336 + if (nextSelectedPosition != INVALID_POSITION && 1.2337 + (goalEnd - viewToMakeVisibleStart) >= getMaxScrollAmount()) { 1.2338 + // Item already has enough of it visible, changing selection is good enough 1.2339 + return 0; 1.2340 + } 1.2341 + 1.2342 + int amountToScroll = (viewToMakeVisibleEnd - goalEnd); 1.2343 + 1.2344 + if (mFirstPosition + numChildren == mItemCount) { 1.2345 + final View lastChild = getChildAt(numChildren - 1); 1.2346 + final int lastChildEnd = (mIsVertical ? lastChild.getBottom() : lastChild.getRight()); 1.2347 + 1.2348 + // Last is last in list -> Make sure we don't scroll past it 1.2349 + final int max = lastChildEnd - end; 1.2350 + amountToScroll = Math.min(amountToScroll, max); 1.2351 + } 1.2352 + 1.2353 + return Math.min(amountToScroll, getMaxScrollAmount()); 1.2354 + } else { 1.2355 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.2356 + 1.2357 + int indexToMakeVisible = 0; 1.2358 + if (nextSelectedPosition != INVALID_POSITION) { 1.2359 + indexToMakeVisible = nextSelectedPosition - mFirstPosition; 1.2360 + } 1.2361 + 1.2362 + final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; 1.2363 + final View viewToMakeVisible = getChildAt(indexToMakeVisible); 1.2364 + 1.2365 + int goalStart = start; 1.2366 + if (positionToMakeVisible > 0) { 1.2367 + goalStart += getArrowScrollPreviewLength(); 1.2368 + } 1.2369 + 1.2370 + final int viewToMakeVisibleStart = 1.2371 + (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); 1.2372 + final int viewToMakeVisibleEnd = 1.2373 + (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); 1.2374 + 1.2375 + if (viewToMakeVisibleStart >= goalStart) { 1.2376 + // Item is fully visible 1.2377 + return 0; 1.2378 + } 1.2379 + 1.2380 + if (nextSelectedPosition != INVALID_POSITION && 1.2381 + (viewToMakeVisibleEnd - goalStart) >= getMaxScrollAmount()) { 1.2382 + // Item already has enough of it visible, changing selection is good enough 1.2383 + return 0; 1.2384 + } 1.2385 + 1.2386 + int amountToScroll = (goalStart - viewToMakeVisibleStart); 1.2387 + 1.2388 + if (mFirstPosition == 0) { 1.2389 + final View firstChild = getChildAt(0); 1.2390 + final int firstChildStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); 1.2391 + 1.2392 + // First is first in list -> make sure we don't scroll past it 1.2393 + final int max = start - firstChildStart; 1.2394 + amountToScroll = Math.min(amountToScroll, max); 1.2395 + } 1.2396 + 1.2397 + return Math.min(amountToScroll, getMaxScrollAmount()); 1.2398 + } 1.2399 + } 1.2400 + 1.2401 + /** 1.2402 + * Determine how much we need to scroll in order to get newFocus in view. 1.2403 + * 1.2404 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.2405 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.2406 + * current view orientation. 1.2407 + * @param newFocus The view that would take focus. 1.2408 + * @param positionOfNewFocus The position of the list item containing newFocus 1.2409 + * 1.2410 + * @return The amount to scroll. Note: this is always positive! Direction 1.2411 + * needs to be taken into account when actually scrolling. 1.2412 + */ 1.2413 + private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) { 1.2414 + forceValidFocusDirection(direction); 1.2415 + 1.2416 + int amountToScroll = 0; 1.2417 + 1.2418 + newFocus.getDrawingRect(mTempRect); 1.2419 + offsetDescendantRectToMyCoords(newFocus, mTempRect); 1.2420 + 1.2421 + if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { 1.2422 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.2423 + final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left); 1.2424 + 1.2425 + if (newFocusStart < start) { 1.2426 + amountToScroll = start - newFocusStart; 1.2427 + if (positionOfNewFocus > 0) { 1.2428 + amountToScroll += getArrowScrollPreviewLength(); 1.2429 + } 1.2430 + } 1.2431 + } else { 1.2432 + final int end = (mIsVertical ? getHeight() - getPaddingBottom() : 1.2433 + getWidth() - getPaddingRight()); 1.2434 + final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); 1.2435 + 1.2436 + if (newFocusEnd > end) { 1.2437 + amountToScroll = newFocusEnd - end; 1.2438 + if (positionOfNewFocus < mItemCount - 1) { 1.2439 + amountToScroll += getArrowScrollPreviewLength(); 1.2440 + } 1.2441 + } 1.2442 + } 1.2443 + 1.2444 + return amountToScroll; 1.2445 + } 1.2446 + 1.2447 + /** 1.2448 + * Determine the distance to the nearest edge of a view in a particular 1.2449 + * direction. 1.2450 + * 1.2451 + * @param descendant A descendant of this list. 1.2452 + * @return The distance, or 0 if the nearest edge is already on screen. 1.2453 + */ 1.2454 + private int distanceToView(View descendant) { 1.2455 + descendant.getDrawingRect(mTempRect); 1.2456 + offsetDescendantRectToMyCoords(descendant, mTempRect); 1.2457 + 1.2458 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.2459 + final int end = (mIsVertical ? getHeight() - getPaddingBottom() : 1.2460 + getWidth() - getPaddingRight()); 1.2461 + 1.2462 + final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left); 1.2463 + final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); 1.2464 + 1.2465 + int distance = 0; 1.2466 + if (viewEnd < start) { 1.2467 + distance = start - viewEnd; 1.2468 + } else if (viewStart > end) { 1.2469 + distance = viewStart - end; 1.2470 + } 1.2471 + 1.2472 + return distance; 1.2473 + } 1.2474 + 1.2475 + private boolean handleKeyScroll(KeyEvent event, int count, int direction) { 1.2476 + boolean handled = false; 1.2477 + 1.2478 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2479 + handled = resurrectSelectionIfNeeded(); 1.2480 + if (!handled) { 1.2481 + while (count-- > 0) { 1.2482 + if (arrowScroll(direction)) { 1.2483 + handled = true; 1.2484 + } else { 1.2485 + break; 1.2486 + } 1.2487 + } 1.2488 + } 1.2489 + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { 1.2490 + handled = resurrectSelectionIfNeeded() || fullScroll(direction); 1.2491 + } 1.2492 + 1.2493 + return handled; 1.2494 + } 1.2495 + 1.2496 + private boolean handleKeyEvent(int keyCode, int count, KeyEvent event) { 1.2497 + if (mAdapter == null || !mIsAttached) { 1.2498 + return false; 1.2499 + } 1.2500 + 1.2501 + if (mDataChanged) { 1.2502 + layoutChildren(); 1.2503 + } 1.2504 + 1.2505 + boolean handled = false; 1.2506 + final int action = event.getAction(); 1.2507 + 1.2508 + if (action != KeyEvent.ACTION_UP) { 1.2509 + switch (keyCode) { 1.2510 + case KeyEvent.KEYCODE_DPAD_UP: 1.2511 + if (mIsVertical) { 1.2512 + handled = handleKeyScroll(event, count, View.FOCUS_UP); 1.2513 + } else if (KeyEventCompat.hasNoModifiers(event)) { 1.2514 + handled = handleFocusWithinItem(View.FOCUS_UP); 1.2515 + } 1.2516 + break; 1.2517 + 1.2518 + case KeyEvent.KEYCODE_DPAD_DOWN: { 1.2519 + if (mIsVertical) { 1.2520 + handled = handleKeyScroll(event, count, View.FOCUS_DOWN); 1.2521 + } else if (KeyEventCompat.hasNoModifiers(event)) { 1.2522 + handled = handleFocusWithinItem(View.FOCUS_DOWN); 1.2523 + } 1.2524 + break; 1.2525 + } 1.2526 + 1.2527 + case KeyEvent.KEYCODE_DPAD_LEFT: 1.2528 + if (!mIsVertical) { 1.2529 + handled = handleKeyScroll(event, count, View.FOCUS_LEFT); 1.2530 + } else if (KeyEventCompat.hasNoModifiers(event)) { 1.2531 + handled = handleFocusWithinItem(View.FOCUS_LEFT); 1.2532 + } 1.2533 + break; 1.2534 + 1.2535 + case KeyEvent.KEYCODE_DPAD_RIGHT: 1.2536 + if (!mIsVertical) { 1.2537 + handled = handleKeyScroll(event, count, View.FOCUS_RIGHT); 1.2538 + } else if (KeyEventCompat.hasNoModifiers(event)) { 1.2539 + handled = handleFocusWithinItem(View.FOCUS_RIGHT); 1.2540 + } 1.2541 + break; 1.2542 + 1.2543 + case KeyEvent.KEYCODE_DPAD_CENTER: 1.2544 + case KeyEvent.KEYCODE_ENTER: 1.2545 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2546 + handled = resurrectSelectionIfNeeded(); 1.2547 + if (!handled 1.2548 + && event.getRepeatCount() == 0 && getChildCount() > 0) { 1.2549 + keyPressed(); 1.2550 + handled = true; 1.2551 + } 1.2552 + } 1.2553 + break; 1.2554 + 1.2555 + case KeyEvent.KEYCODE_SPACE: 1.2556 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2557 + handled = resurrectSelectionIfNeeded() || 1.2558 + pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); 1.2559 + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 1.2560 + handled = resurrectSelectionIfNeeded() || 1.2561 + fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); 1.2562 + } 1.2563 + 1.2564 + handled = true; 1.2565 + break; 1.2566 + 1.2567 + case KeyEvent.KEYCODE_PAGE_UP: 1.2568 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2569 + handled = resurrectSelectionIfNeeded() || 1.2570 + pageScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); 1.2571 + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { 1.2572 + handled = resurrectSelectionIfNeeded() || 1.2573 + fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); 1.2574 + } 1.2575 + break; 1.2576 + 1.2577 + case KeyEvent.KEYCODE_PAGE_DOWN: 1.2578 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2579 + handled = resurrectSelectionIfNeeded() || 1.2580 + pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); 1.2581 + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { 1.2582 + handled = resurrectSelectionIfNeeded() || 1.2583 + fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); 1.2584 + } 1.2585 + break; 1.2586 + 1.2587 + case KeyEvent.KEYCODE_MOVE_HOME: 1.2588 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2589 + handled = resurrectSelectionIfNeeded() || 1.2590 + fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); 1.2591 + } 1.2592 + break; 1.2593 + 1.2594 + case KeyEvent.KEYCODE_MOVE_END: 1.2595 + if (KeyEventCompat.hasNoModifiers(event)) { 1.2596 + handled = resurrectSelectionIfNeeded() || 1.2597 + fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); 1.2598 + } 1.2599 + break; 1.2600 + } 1.2601 + } 1.2602 + 1.2603 + if (handled) { 1.2604 + return true; 1.2605 + } 1.2606 + 1.2607 + switch (action) { 1.2608 + case KeyEvent.ACTION_DOWN: 1.2609 + return super.onKeyDown(keyCode, event); 1.2610 + 1.2611 + case KeyEvent.ACTION_UP: 1.2612 + if (!isEnabled()) { 1.2613 + return true; 1.2614 + } 1.2615 + 1.2616 + if (isClickable() && isPressed() && 1.2617 + mSelectedPosition >= 0 && mAdapter != null && 1.2618 + mSelectedPosition < mAdapter.getCount()) { 1.2619 + 1.2620 + final View child = getChildAt(mSelectedPosition - mFirstPosition); 1.2621 + if (child != null) { 1.2622 + performItemClick(child, mSelectedPosition, mSelectedRowId); 1.2623 + child.setPressed(false); 1.2624 + } 1.2625 + 1.2626 + setPressed(false); 1.2627 + return true; 1.2628 + } 1.2629 + 1.2630 + return false; 1.2631 + 1.2632 + case KeyEvent.ACTION_MULTIPLE: 1.2633 + return super.onKeyMultiple(keyCode, count, event); 1.2634 + 1.2635 + default: 1.2636 + return false; 1.2637 + } 1.2638 + } 1.2639 + 1.2640 + private void initOrResetVelocityTracker() { 1.2641 + if (mVelocityTracker == null) { 1.2642 + mVelocityTracker = VelocityTracker.obtain(); 1.2643 + } else { 1.2644 + mVelocityTracker.clear(); 1.2645 + } 1.2646 + } 1.2647 + 1.2648 + private void initVelocityTrackerIfNotExists() { 1.2649 + if (mVelocityTracker == null) { 1.2650 + mVelocityTracker = VelocityTracker.obtain(); 1.2651 + } 1.2652 + } 1.2653 + 1.2654 + private void recycleVelocityTracker() { 1.2655 + if (mVelocityTracker != null) { 1.2656 + mVelocityTracker.recycle(); 1.2657 + mVelocityTracker = null; 1.2658 + } 1.2659 + } 1.2660 + 1.2661 + /** 1.2662 + * Notify our scroll listener (if there is one) of a change in scroll state 1.2663 + */ 1.2664 + private void invokeOnItemScrollListener() { 1.2665 + if (mOnScrollListener != null) { 1.2666 + mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); 1.2667 + } 1.2668 + 1.2669 + // Dummy values, View's implementation does not use these. 1.2670 + onScrollChanged(0, 0, 0, 0); 1.2671 + } 1.2672 + 1.2673 + private void reportScrollStateChange(int newState) { 1.2674 + if (newState == mLastScrollState) { 1.2675 + return; 1.2676 + } 1.2677 + 1.2678 + if (mOnScrollListener != null) { 1.2679 + mLastScrollState = newState; 1.2680 + mOnScrollListener.onScrollStateChanged(this, newState); 1.2681 + } 1.2682 + } 1.2683 + 1.2684 + private boolean maybeStartScrolling(int delta) { 1.2685 + final boolean isOverScroll = (mOverScroll != 0); 1.2686 + if (Math.abs(delta) <= mTouchSlop && !isOverScroll) { 1.2687 + return false; 1.2688 + } 1.2689 + 1.2690 + if (isOverScroll) { 1.2691 + mTouchMode = TOUCH_MODE_OVERSCROLL; 1.2692 + } else { 1.2693 + mTouchMode = TOUCH_MODE_DRAGGING; 1.2694 + } 1.2695 + 1.2696 + // Time to start stealing events! Once we've stolen them, don't 1.2697 + // let anyone steal from us. 1.2698 + final ViewParent parent = getParent(); 1.2699 + if (parent != null) { 1.2700 + parent.requestDisallowInterceptTouchEvent(true); 1.2701 + } 1.2702 + 1.2703 + cancelCheckForLongPress(); 1.2704 + 1.2705 + setPressed(false); 1.2706 + View motionView = getChildAt(mMotionPosition - mFirstPosition); 1.2707 + if (motionView != null) { 1.2708 + motionView.setPressed(false); 1.2709 + } 1.2710 + 1.2711 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 1.2712 + 1.2713 + return true; 1.2714 + } 1.2715 + 1.2716 + private void maybeScroll(int delta) { 1.2717 + if (mTouchMode == TOUCH_MODE_DRAGGING) { 1.2718 + handleDragChange(delta); 1.2719 + } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { 1.2720 + handleOverScrollChange(delta); 1.2721 + } 1.2722 + } 1.2723 + 1.2724 + private void handleDragChange(int delta) { 1.2725 + // Time to start stealing events! Once we've stolen them, don't 1.2726 + // let anyone steal from us. 1.2727 + final ViewParent parent = getParent(); 1.2728 + if (parent != null) { 1.2729 + parent.requestDisallowInterceptTouchEvent(true); 1.2730 + } 1.2731 + 1.2732 + final int motionIndex; 1.2733 + if (mMotionPosition >= 0) { 1.2734 + motionIndex = mMotionPosition - mFirstPosition; 1.2735 + } else { 1.2736 + // If we don't have a motion position that we can reliably track, 1.2737 + // pick something in the middle to make a best guess at things below. 1.2738 + motionIndex = getChildCount() / 2; 1.2739 + } 1.2740 + 1.2741 + int motionViewPrevStart = 0; 1.2742 + View motionView = this.getChildAt(motionIndex); 1.2743 + if (motionView != null) { 1.2744 + motionViewPrevStart = (mIsVertical ? motionView.getTop() : motionView.getLeft()); 1.2745 + } 1.2746 + 1.2747 + boolean atEdge = trackMotionScroll(delta); 1.2748 + 1.2749 + motionView = this.getChildAt(motionIndex); 1.2750 + if (motionView != null) { 1.2751 + final int motionViewRealStart = 1.2752 + (mIsVertical ? motionView.getTop() : motionView.getLeft()); 1.2753 + 1.2754 + if (atEdge) { 1.2755 + final int overscroll = -delta - (motionViewRealStart - motionViewPrevStart); 1.2756 + updateOverScrollState(delta, overscroll); 1.2757 + } 1.2758 + } 1.2759 + } 1.2760 + 1.2761 + private void updateOverScrollState(int delta, int overscroll) { 1.2762 + overScrollByInternal((mIsVertical ? 0 : overscroll), 1.2763 + (mIsVertical ? overscroll : 0), 1.2764 + (mIsVertical ? 0 : mOverScroll), 1.2765 + (mIsVertical ? mOverScroll : 0), 1.2766 + 0, 0, 1.2767 + (mIsVertical ? 0 : mOverscrollDistance), 1.2768 + (mIsVertical ? mOverscrollDistance : 0), 1.2769 + true); 1.2770 + 1.2771 + if (Math.abs(mOverscrollDistance) == Math.abs(mOverScroll)) { 1.2772 + // Break fling velocity if we impacted an edge 1.2773 + if (mVelocityTracker != null) { 1.2774 + mVelocityTracker.clear(); 1.2775 + } 1.2776 + } 1.2777 + 1.2778 + final int overscrollMode = ViewCompat.getOverScrollMode(this); 1.2779 + if (overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 1.2780 + (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { 1.2781 + mTouchMode = TOUCH_MODE_OVERSCROLL; 1.2782 + 1.2783 + float pull = (float) overscroll / (mIsVertical ? getHeight() : getWidth()); 1.2784 + if (delta > 0) { 1.2785 + mStartEdge.onPull(pull); 1.2786 + 1.2787 + if (!mEndEdge.isFinished()) { 1.2788 + mEndEdge.onRelease(); 1.2789 + } 1.2790 + } else if (delta < 0) { 1.2791 + mEndEdge.onPull(pull); 1.2792 + 1.2793 + if (!mStartEdge.isFinished()) { 1.2794 + mStartEdge.onRelease(); 1.2795 + } 1.2796 + } 1.2797 + 1.2798 + if (delta != 0) { 1.2799 + ViewCompat.postInvalidateOnAnimation(this); 1.2800 + } 1.2801 + } 1.2802 + } 1.2803 + 1.2804 + private void handleOverScrollChange(int delta) { 1.2805 + final int oldOverScroll = mOverScroll; 1.2806 + final int newOverScroll = oldOverScroll - delta; 1.2807 + 1.2808 + int overScrollDistance = -delta; 1.2809 + if ((newOverScroll < 0 && oldOverScroll >= 0) || 1.2810 + (newOverScroll > 0 && oldOverScroll <= 0)) { 1.2811 + overScrollDistance = -oldOverScroll; 1.2812 + delta += overScrollDistance; 1.2813 + } else { 1.2814 + delta = 0; 1.2815 + } 1.2816 + 1.2817 + if (overScrollDistance != 0) { 1.2818 + updateOverScrollState(delta, overScrollDistance); 1.2819 + } 1.2820 + 1.2821 + if (delta != 0) { 1.2822 + if (mOverScroll != 0) { 1.2823 + mOverScroll = 0; 1.2824 + ViewCompat.postInvalidateOnAnimation(this); 1.2825 + } 1.2826 + 1.2827 + trackMotionScroll(delta); 1.2828 + mTouchMode = TOUCH_MODE_DRAGGING; 1.2829 + 1.2830 + // We did not scroll the full amount. Treat this essentially like the 1.2831 + // start of a new touch scroll 1.2832 + mMotionPosition = findClosestMotionRowOrColumn((int) mLastTouchPos); 1.2833 + mTouchRemainderPos = 0; 1.2834 + } 1.2835 + } 1.2836 + 1.2837 + /** 1.2838 + * What is the distance between the source and destination rectangles given the direction of 1.2839 + * focus navigation between them? The direction basically helps figure out more quickly what is 1.2840 + * self evident by the relationship between the rects... 1.2841 + * 1.2842 + * @param source the source rectangle 1.2843 + * @param dest the destination rectangle 1.2844 + * @param direction the direction 1.2845 + * @return the distance between the rectangles 1.2846 + */ 1.2847 + private static int getDistance(Rect source, Rect dest, int direction) { 1.2848 + int sX, sY; // source x, y 1.2849 + int dX, dY; // dest x, y 1.2850 + 1.2851 + switch (direction) { 1.2852 + case View.FOCUS_RIGHT: 1.2853 + sX = source.right; 1.2854 + sY = source.top + source.height() / 2; 1.2855 + dX = dest.left; 1.2856 + dY = dest.top + dest.height() / 2; 1.2857 + break; 1.2858 + 1.2859 + case View.FOCUS_DOWN: 1.2860 + sX = source.left + source.width() / 2; 1.2861 + sY = source.bottom; 1.2862 + dX = dest.left + dest.width() / 2; 1.2863 + dY = dest.top; 1.2864 + break; 1.2865 + 1.2866 + case View.FOCUS_LEFT: 1.2867 + sX = source.left; 1.2868 + sY = source.top + source.height() / 2; 1.2869 + dX = dest.right; 1.2870 + dY = dest.top + dest.height() / 2; 1.2871 + break; 1.2872 + 1.2873 + case View.FOCUS_UP: 1.2874 + sX = source.left + source.width() / 2; 1.2875 + sY = source.top; 1.2876 + dX = dest.left + dest.width() / 2; 1.2877 + dY = dest.bottom; 1.2878 + break; 1.2879 + 1.2880 + case View.FOCUS_FORWARD: 1.2881 + case View.FOCUS_BACKWARD: 1.2882 + sX = source.right + source.width() / 2; 1.2883 + sY = source.top + source.height() / 2; 1.2884 + dX = dest.left + dest.width() / 2; 1.2885 + dY = dest.top + dest.height() / 2; 1.2886 + break; 1.2887 + 1.2888 + default: 1.2889 + throw new IllegalArgumentException("direction must be one of " 1.2890 + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, " 1.2891 + + "FOCUS_FORWARD, FOCUS_BACKWARD}."); 1.2892 + } 1.2893 + 1.2894 + int deltaX = dX - sX; 1.2895 + int deltaY = dY - sY; 1.2896 + 1.2897 + return deltaY * deltaY + deltaX * deltaX; 1.2898 + } 1.2899 + 1.2900 + private int findMotionRowOrColumn(int motionPos) { 1.2901 + int childCount = getChildCount(); 1.2902 + if (childCount == 0) { 1.2903 + return INVALID_POSITION; 1.2904 + } 1.2905 + 1.2906 + for (int i = 0; i < childCount; i++) { 1.2907 + View v = getChildAt(i); 1.2908 + 1.2909 + if ((mIsVertical && motionPos <= v.getBottom()) || 1.2910 + (!mIsVertical && motionPos <= v.getRight())) { 1.2911 + return mFirstPosition + i; 1.2912 + } 1.2913 + } 1.2914 + 1.2915 + return INVALID_POSITION; 1.2916 + } 1.2917 + 1.2918 + private int findClosestMotionRowOrColumn(int motionPos) { 1.2919 + final int childCount = getChildCount(); 1.2920 + if (childCount == 0) { 1.2921 + return INVALID_POSITION; 1.2922 + } 1.2923 + 1.2924 + final int motionRow = findMotionRowOrColumn(motionPos); 1.2925 + if (motionRow != INVALID_POSITION) { 1.2926 + return motionRow; 1.2927 + } else { 1.2928 + return mFirstPosition + childCount - 1; 1.2929 + } 1.2930 + } 1.2931 + 1.2932 + @TargetApi(9) 1.2933 + private int getScaledOverscrollDistance(ViewConfiguration vc) { 1.2934 + if (Build.VERSION.SDK_INT < 9) { 1.2935 + return 0; 1.2936 + } 1.2937 + 1.2938 + return vc.getScaledOverscrollDistance(); 1.2939 + } 1.2940 + 1.2941 + private boolean contentFits() { 1.2942 + final int childCount = getChildCount(); 1.2943 + if (childCount == 0) { 1.2944 + return true; 1.2945 + } 1.2946 + 1.2947 + if (childCount != mItemCount) { 1.2948 + return false; 1.2949 + } 1.2950 + 1.2951 + View first = getChildAt(0); 1.2952 + View last = getChildAt(childCount - 1); 1.2953 + 1.2954 + if (mIsVertical) { 1.2955 + return first.getTop() >= getPaddingTop() && 1.2956 + last.getBottom() <= getHeight() - getPaddingBottom(); 1.2957 + } else { 1.2958 + return first.getLeft() >= getPaddingLeft() && 1.2959 + last.getRight() <= getWidth() - getPaddingRight(); 1.2960 + } 1.2961 + } 1.2962 + 1.2963 + private void updateScrollbarsDirection() { 1.2964 + setHorizontalScrollBarEnabled(!mIsVertical); 1.2965 + setVerticalScrollBarEnabled(mIsVertical); 1.2966 + } 1.2967 + 1.2968 + private void triggerCheckForTap() { 1.2969 + if (mPendingCheckForTap == null) { 1.2970 + mPendingCheckForTap = new CheckForTap(); 1.2971 + } 1.2972 + 1.2973 + postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 1.2974 + } 1.2975 + 1.2976 + private void cancelCheckForTap() { 1.2977 + if (mPendingCheckForTap == null) { 1.2978 + return; 1.2979 + } 1.2980 + 1.2981 + removeCallbacks(mPendingCheckForTap); 1.2982 + } 1.2983 + 1.2984 + private void triggerCheckForLongPress() { 1.2985 + if (mPendingCheckForLongPress == null) { 1.2986 + mPendingCheckForLongPress = new CheckForLongPress(); 1.2987 + } 1.2988 + 1.2989 + mPendingCheckForLongPress.rememberWindowAttachCount(); 1.2990 + 1.2991 + postDelayed(mPendingCheckForLongPress, 1.2992 + ViewConfiguration.getLongPressTimeout()); 1.2993 + } 1.2994 + 1.2995 + private void cancelCheckForLongPress() { 1.2996 + if (mPendingCheckForLongPress == null) { 1.2997 + return; 1.2998 + } 1.2999 + 1.3000 + removeCallbacks(mPendingCheckForLongPress); 1.3001 + } 1.3002 + 1.3003 + boolean trackMotionScroll(int incrementalDelta) { 1.3004 + final int childCount = getChildCount(); 1.3005 + if (childCount == 0) { 1.3006 + return true; 1.3007 + } 1.3008 + 1.3009 + final View first = getChildAt(0); 1.3010 + final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); 1.3011 + 1.3012 + final View last = getChildAt(childCount - 1); 1.3013 + final int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); 1.3014 + 1.3015 + final int paddingTop = getPaddingTop(); 1.3016 + final int paddingBottom = getPaddingBottom(); 1.3017 + final int paddingLeft = getPaddingLeft(); 1.3018 + final int paddingRight = getPaddingRight(); 1.3019 + 1.3020 + final int paddingStart = (mIsVertical ? paddingTop : paddingLeft); 1.3021 + 1.3022 + final int spaceBefore = paddingStart - firstStart; 1.3023 + final int end = (mIsVertical ? getHeight() - paddingBottom : 1.3024 + getWidth() - paddingRight); 1.3025 + final int spaceAfter = lastEnd - end; 1.3026 + 1.3027 + final int size; 1.3028 + if (mIsVertical) { 1.3029 + size = getHeight() - paddingBottom - paddingTop; 1.3030 + } else { 1.3031 + size = getWidth() - paddingRight - paddingLeft; 1.3032 + } 1.3033 + 1.3034 + if (incrementalDelta < 0) { 1.3035 + incrementalDelta = Math.max(-(size - 1), incrementalDelta); 1.3036 + } else { 1.3037 + incrementalDelta = Math.min(size - 1, incrementalDelta); 1.3038 + } 1.3039 + 1.3040 + final int firstPosition = mFirstPosition; 1.3041 + 1.3042 + final boolean cannotScrollDown = (firstPosition == 0 && 1.3043 + firstStart >= paddingStart && incrementalDelta >= 0); 1.3044 + final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && 1.3045 + lastEnd <= end && incrementalDelta <= 0); 1.3046 + 1.3047 + if (cannotScrollDown || cannotScrollUp) { 1.3048 + return incrementalDelta != 0; 1.3049 + } 1.3050 + 1.3051 + final boolean inTouchMode = isInTouchMode(); 1.3052 + if (inTouchMode) { 1.3053 + hideSelector(); 1.3054 + } 1.3055 + 1.3056 + int start = 0; 1.3057 + int count = 0; 1.3058 + 1.3059 + final boolean down = (incrementalDelta < 0); 1.3060 + if (down) { 1.3061 + int childrenStart = -incrementalDelta + paddingStart; 1.3062 + 1.3063 + for (int i = 0; i < childCount; i++) { 1.3064 + final View child = getChildAt(i); 1.3065 + final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); 1.3066 + 1.3067 + if (childEnd >= childrenStart) { 1.3068 + break; 1.3069 + } 1.3070 + 1.3071 + count++; 1.3072 + mRecycler.addScrapView(child, firstPosition + i); 1.3073 + } 1.3074 + } else { 1.3075 + int childrenEnd = end - incrementalDelta; 1.3076 + 1.3077 + for (int i = childCount - 1; i >= 0; i--) { 1.3078 + final View child = getChildAt(i); 1.3079 + final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); 1.3080 + 1.3081 + if (childStart <= childrenEnd) { 1.3082 + break; 1.3083 + } 1.3084 + 1.3085 + start = i; 1.3086 + count++; 1.3087 + mRecycler.addScrapView(child, firstPosition + i); 1.3088 + } 1.3089 + } 1.3090 + 1.3091 + mBlockLayoutRequests = true; 1.3092 + 1.3093 + if (count > 0) { 1.3094 + detachViewsFromParent(start, count); 1.3095 + } 1.3096 + 1.3097 + // invalidate before moving the children to avoid unnecessary invalidate 1.3098 + // calls to bubble up from the children all the way to the top 1.3099 + if (!awakenScrollbarsInternal()) { 1.3100 + invalidate(); 1.3101 + } 1.3102 + 1.3103 + offsetChildren(incrementalDelta); 1.3104 + 1.3105 + if (down) { 1.3106 + mFirstPosition += count; 1.3107 + } 1.3108 + 1.3109 + final int absIncrementalDelta = Math.abs(incrementalDelta); 1.3110 + if (spaceBefore < absIncrementalDelta || spaceAfter < absIncrementalDelta) { 1.3111 + fillGap(down); 1.3112 + } 1.3113 + 1.3114 + if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { 1.3115 + final int childIndex = mSelectedPosition - mFirstPosition; 1.3116 + if (childIndex >= 0 && childIndex < getChildCount()) { 1.3117 + positionSelector(mSelectedPosition, getChildAt(childIndex)); 1.3118 + } 1.3119 + } else if (mSelectorPosition != INVALID_POSITION) { 1.3120 + final int childIndex = mSelectorPosition - mFirstPosition; 1.3121 + if (childIndex >= 0 && childIndex < getChildCount()) { 1.3122 + positionSelector(INVALID_POSITION, getChildAt(childIndex)); 1.3123 + } 1.3124 + } else { 1.3125 + mSelectorRect.setEmpty(); 1.3126 + } 1.3127 + 1.3128 + mBlockLayoutRequests = false; 1.3129 + 1.3130 + invokeOnItemScrollListener(); 1.3131 + 1.3132 + return false; 1.3133 + } 1.3134 + 1.3135 + @TargetApi(14) 1.3136 + private final float getCurrVelocity() { 1.3137 + if (Build.VERSION.SDK_INT >= 14) { 1.3138 + return mScroller.getCurrVelocity(); 1.3139 + } 1.3140 + 1.3141 + return 0; 1.3142 + } 1.3143 + 1.3144 + @TargetApi(5) 1.3145 + private boolean awakenScrollbarsInternal() { 1.3146 + if (Build.VERSION.SDK_INT >= 5) { 1.3147 + return super.awakenScrollBars(); 1.3148 + } else { 1.3149 + return false; 1.3150 + } 1.3151 + } 1.3152 + 1.3153 + @Override 1.3154 + public void computeScroll() { 1.3155 + if (!mScroller.computeScrollOffset()) { 1.3156 + return; 1.3157 + } 1.3158 + 1.3159 + final int pos; 1.3160 + if (mIsVertical) { 1.3161 + pos = mScroller.getCurrY(); 1.3162 + } else { 1.3163 + pos = mScroller.getCurrX(); 1.3164 + } 1.3165 + 1.3166 + final int diff = (int) (pos - mLastTouchPos); 1.3167 + mLastTouchPos = pos; 1.3168 + 1.3169 + final boolean stopped = trackMotionScroll(diff); 1.3170 + 1.3171 + if (!stopped && !mScroller.isFinished()) { 1.3172 + ViewCompat.postInvalidateOnAnimation(this); 1.3173 + } else { 1.3174 + if (stopped) { 1.3175 + final int overScrollMode = ViewCompat.getOverScrollMode(this); 1.3176 + if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) { 1.3177 + final EdgeEffectCompat edge = 1.3178 + (diff > 0 ? mStartEdge : mEndEdge); 1.3179 + 1.3180 + boolean needsInvalidate = 1.3181 + edge.onAbsorb(Math.abs((int) getCurrVelocity())); 1.3182 + 1.3183 + if (needsInvalidate) { 1.3184 + ViewCompat.postInvalidateOnAnimation(this); 1.3185 + } 1.3186 + } 1.3187 + 1.3188 + mScroller.abortAnimation(); 1.3189 + } 1.3190 + 1.3191 + mTouchMode = TOUCH_MODE_REST; 1.3192 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.3193 + } 1.3194 + } 1.3195 + 1.3196 + private void finishEdgeGlows() { 1.3197 + if (mStartEdge != null) { 1.3198 + mStartEdge.finish(); 1.3199 + } 1.3200 + 1.3201 + if (mEndEdge != null) { 1.3202 + mEndEdge.finish(); 1.3203 + } 1.3204 + } 1.3205 + 1.3206 + private boolean drawStartEdge(Canvas canvas) { 1.3207 + if (mStartEdge.isFinished()) { 1.3208 + return false; 1.3209 + } 1.3210 + 1.3211 + if (mIsVertical) { 1.3212 + return mStartEdge.draw(canvas); 1.3213 + } 1.3214 + 1.3215 + final int restoreCount = canvas.save(); 1.3216 + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1.3217 + 1.3218 + canvas.translate(0, height); 1.3219 + canvas.rotate(270); 1.3220 + 1.3221 + final boolean needsInvalidate = mStartEdge.draw(canvas); 1.3222 + canvas.restoreToCount(restoreCount); 1.3223 + return needsInvalidate; 1.3224 + } 1.3225 + 1.3226 + private boolean drawEndEdge(Canvas canvas) { 1.3227 + if (mEndEdge.isFinished()) { 1.3228 + return false; 1.3229 + } 1.3230 + 1.3231 + final int restoreCount = canvas.save(); 1.3232 + final int width = getWidth() - getPaddingLeft() - getPaddingRight(); 1.3233 + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1.3234 + 1.3235 + if (mIsVertical) { 1.3236 + canvas.translate(-width, height); 1.3237 + canvas.rotate(180, width, 0); 1.3238 + } else { 1.3239 + canvas.translate(width, 0); 1.3240 + canvas.rotate(90); 1.3241 + } 1.3242 + 1.3243 + final boolean needsInvalidate = mEndEdge.draw(canvas); 1.3244 + canvas.restoreToCount(restoreCount); 1.3245 + return needsInvalidate; 1.3246 + } 1.3247 + 1.3248 + private void drawSelector(Canvas canvas) { 1.3249 + if (!mSelectorRect.isEmpty()) { 1.3250 + final Drawable selector = mSelector; 1.3251 + selector.setBounds(mSelectorRect); 1.3252 + selector.draw(canvas); 1.3253 + } 1.3254 + } 1.3255 + 1.3256 + private void useDefaultSelector() { 1.3257 + setSelector(getResources().getDrawable( 1.3258 + android.R.drawable.list_selector_background)); 1.3259 + } 1.3260 + 1.3261 + private boolean shouldShowSelector() { 1.3262 + return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState(); 1.3263 + } 1.3264 + 1.3265 + private void positionSelector(int position, View selected) { 1.3266 + if (position != INVALID_POSITION) { 1.3267 + mSelectorPosition = position; 1.3268 + } 1.3269 + 1.3270 + mSelectorRect.set(selected.getLeft(), selected.getTop(), selected.getRight(), 1.3271 + selected.getBottom()); 1.3272 + 1.3273 + final boolean isChildViewEnabled = mIsChildViewEnabled; 1.3274 + if (selected.isEnabled() != isChildViewEnabled) { 1.3275 + mIsChildViewEnabled = !isChildViewEnabled; 1.3276 + 1.3277 + if (getSelectedItemPosition() != INVALID_POSITION) { 1.3278 + refreshDrawableState(); 1.3279 + } 1.3280 + } 1.3281 + } 1.3282 + 1.3283 + private void hideSelector() { 1.3284 + if (mSelectedPosition != INVALID_POSITION) { 1.3285 + if (mLayoutMode != LAYOUT_SPECIFIC) { 1.3286 + mResurrectToPosition = mSelectedPosition; 1.3287 + } 1.3288 + 1.3289 + if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) { 1.3290 + mResurrectToPosition = mNextSelectedPosition; 1.3291 + } 1.3292 + 1.3293 + setSelectedPositionInt(INVALID_POSITION); 1.3294 + setNextSelectedPositionInt(INVALID_POSITION); 1.3295 + 1.3296 + mSelectedStart = 0; 1.3297 + } 1.3298 + } 1.3299 + 1.3300 + private void setSelectedPositionInt(int position) { 1.3301 + mSelectedPosition = position; 1.3302 + mSelectedRowId = getItemIdAtPosition(position); 1.3303 + } 1.3304 + 1.3305 + private void setSelectionInt(int position) { 1.3306 + setNextSelectedPositionInt(position); 1.3307 + boolean awakeScrollbars = false; 1.3308 + 1.3309 + final int selectedPosition = mSelectedPosition; 1.3310 + if (selectedPosition >= 0) { 1.3311 + if (position == selectedPosition - 1) { 1.3312 + awakeScrollbars = true; 1.3313 + } else if (position == selectedPosition + 1) { 1.3314 + awakeScrollbars = true; 1.3315 + } 1.3316 + } 1.3317 + 1.3318 + layoutChildren(); 1.3319 + 1.3320 + if (awakeScrollbars) { 1.3321 + awakenScrollbarsInternal(); 1.3322 + } 1.3323 + } 1.3324 + 1.3325 + private void setNextSelectedPositionInt(int position) { 1.3326 + mNextSelectedPosition = position; 1.3327 + mNextSelectedRowId = getItemIdAtPosition(position); 1.3328 + 1.3329 + // If we are trying to sync to the selection, update that too 1.3330 + if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { 1.3331 + mSyncPosition = position; 1.3332 + mSyncRowId = mNextSelectedRowId; 1.3333 + } 1.3334 + } 1.3335 + 1.3336 + private boolean touchModeDrawsInPressedState() { 1.3337 + switch (mTouchMode) { 1.3338 + case TOUCH_MODE_TAP: 1.3339 + case TOUCH_MODE_DONE_WAITING: 1.3340 + return true; 1.3341 + default: 1.3342 + return false; 1.3343 + } 1.3344 + } 1.3345 + 1.3346 + /** 1.3347 + * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if 1.3348 + * this is a long press. 1.3349 + */ 1.3350 + private void keyPressed() { 1.3351 + if (!isEnabled() || !isClickable()) { 1.3352 + return; 1.3353 + } 1.3354 + 1.3355 + final Drawable selector = mSelector; 1.3356 + final Rect selectorRect = mSelectorRect; 1.3357 + 1.3358 + if (selector != null && (isFocused() || touchModeDrawsInPressedState()) 1.3359 + && !selectorRect.isEmpty()) { 1.3360 + 1.3361 + final View child = getChildAt(mSelectedPosition - mFirstPosition); 1.3362 + 1.3363 + if (child != null) { 1.3364 + if (child.hasFocusable()) { 1.3365 + return; 1.3366 + } 1.3367 + 1.3368 + child.setPressed(true); 1.3369 + } 1.3370 + 1.3371 + setPressed(true); 1.3372 + 1.3373 + final boolean longClickable = isLongClickable(); 1.3374 + final Drawable d = selector.getCurrent(); 1.3375 + if (d != null && d instanceof TransitionDrawable) { 1.3376 + if (longClickable) { 1.3377 + ((TransitionDrawable) d).startTransition( 1.3378 + ViewConfiguration.getLongPressTimeout()); 1.3379 + } else { 1.3380 + ((TransitionDrawable) d).resetTransition(); 1.3381 + } 1.3382 + } 1.3383 + 1.3384 + if (longClickable && !mDataChanged) { 1.3385 + if (mPendingCheckForKeyLongPress == null) { 1.3386 + mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); 1.3387 + } 1.3388 + 1.3389 + mPendingCheckForKeyLongPress.rememberWindowAttachCount(); 1.3390 + postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); 1.3391 + } 1.3392 + } 1.3393 + } 1.3394 + 1.3395 + private void updateSelectorState() { 1.3396 + if (mSelector != null) { 1.3397 + if (shouldShowSelector()) { 1.3398 + mSelector.setState(getDrawableState()); 1.3399 + } else { 1.3400 + mSelector.setState(STATE_NOTHING); 1.3401 + } 1.3402 + } 1.3403 + } 1.3404 + 1.3405 + private void checkSelectionChanged() { 1.3406 + if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { 1.3407 + selectionChanged(); 1.3408 + mOldSelectedPosition = mSelectedPosition; 1.3409 + mOldSelectedRowId = mSelectedRowId; 1.3410 + } 1.3411 + } 1.3412 + 1.3413 + private void selectionChanged() { 1.3414 + OnItemSelectedListener listener = getOnItemSelectedListener(); 1.3415 + if (listener == null) { 1.3416 + return; 1.3417 + } 1.3418 + 1.3419 + if (mInLayout || mBlockLayoutRequests) { 1.3420 + // If we are in a layout traversal, defer notification 1.3421 + // by posting. This ensures that the view tree is 1.3422 + // in a consistent state and is able to accommodate 1.3423 + // new layout or invalidate requests. 1.3424 + if (mSelectionNotifier == null) { 1.3425 + mSelectionNotifier = new SelectionNotifier(); 1.3426 + } 1.3427 + 1.3428 + post(mSelectionNotifier); 1.3429 + } else { 1.3430 + fireOnSelected(); 1.3431 + performAccessibilityActionsOnSelected(); 1.3432 + } 1.3433 + } 1.3434 + 1.3435 + private void fireOnSelected() { 1.3436 + OnItemSelectedListener listener = getOnItemSelectedListener(); 1.3437 + if (listener == null) { 1.3438 + return; 1.3439 + } 1.3440 + 1.3441 + final int selection = getSelectedItemPosition(); 1.3442 + if (selection >= 0) { 1.3443 + View v = getSelectedView(); 1.3444 + listener.onItemSelected(this, v, selection, 1.3445 + mAdapter.getItemId(selection)); 1.3446 + } else { 1.3447 + listener.onNothingSelected(this); 1.3448 + } 1.3449 + } 1.3450 + 1.3451 + private void performAccessibilityActionsOnSelected() { 1.3452 + final int position = getSelectedItemPosition(); 1.3453 + if (position >= 0) { 1.3454 + // We fire selection events here not in View 1.3455 + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1.3456 + } 1.3457 + } 1.3458 + 1.3459 + private int lookForSelectablePosition(int position) { 1.3460 + return lookForSelectablePosition(position, true); 1.3461 + } 1.3462 + 1.3463 + private int lookForSelectablePosition(int position, boolean lookDown) { 1.3464 + final ListAdapter adapter = mAdapter; 1.3465 + if (adapter == null || isInTouchMode()) { 1.3466 + return INVALID_POSITION; 1.3467 + } 1.3468 + 1.3469 + final int itemCount = mItemCount; 1.3470 + if (!mAreAllItemsSelectable) { 1.3471 + if (lookDown) { 1.3472 + position = Math.max(0, position); 1.3473 + while (position < itemCount && !adapter.isEnabled(position)) { 1.3474 + position++; 1.3475 + } 1.3476 + } else { 1.3477 + position = Math.min(position, itemCount - 1); 1.3478 + while (position >= 0 && !adapter.isEnabled(position)) { 1.3479 + position--; 1.3480 + } 1.3481 + } 1.3482 + 1.3483 + if (position < 0 || position >= itemCount) { 1.3484 + return INVALID_POSITION; 1.3485 + } 1.3486 + 1.3487 + return position; 1.3488 + } else { 1.3489 + if (position < 0 || position >= itemCount) { 1.3490 + return INVALID_POSITION; 1.3491 + } 1.3492 + 1.3493 + return position; 1.3494 + } 1.3495 + } 1.3496 + 1.3497 + /** 1.3498 + * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or 1.3499 + * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the 1.3500 + * current view orientation. 1.3501 + * 1.3502 + * @return The position of the next selectable position of the views that 1.3503 + * are currently visible, taking into account the fact that there might 1.3504 + * be no selection. Returns {@link #INVALID_POSITION} if there is no 1.3505 + * selectable view on screen in the given direction. 1.3506 + */ 1.3507 + private int lookForSelectablePositionOnScreen(int direction) { 1.3508 + forceValidFocusDirection(direction); 1.3509 + 1.3510 + final int firstPosition = mFirstPosition; 1.3511 + final ListAdapter adapter = getAdapter(); 1.3512 + 1.3513 + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { 1.3514 + int startPos = (mSelectedPosition != INVALID_POSITION ? 1.3515 + mSelectedPosition + 1 : firstPosition); 1.3516 + 1.3517 + if (startPos >= adapter.getCount()) { 1.3518 + return INVALID_POSITION; 1.3519 + } 1.3520 + 1.3521 + if (startPos < firstPosition) { 1.3522 + startPos = firstPosition; 1.3523 + } 1.3524 + 1.3525 + final int lastVisiblePos = getLastVisiblePosition(); 1.3526 + 1.3527 + for (int pos = startPos; pos <= lastVisiblePos; pos++) { 1.3528 + if (adapter.isEnabled(pos) 1.3529 + && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) { 1.3530 + return pos; 1.3531 + } 1.3532 + } 1.3533 + } else { 1.3534 + final int last = firstPosition + getChildCount() - 1; 1.3535 + 1.3536 + int startPos = (mSelectedPosition != INVALID_POSITION) ? 1.3537 + mSelectedPosition - 1 : firstPosition + getChildCount() - 1; 1.3538 + 1.3539 + if (startPos < 0 || startPos >= adapter.getCount()) { 1.3540 + return INVALID_POSITION; 1.3541 + } 1.3542 + 1.3543 + if (startPos > last) { 1.3544 + startPos = last; 1.3545 + } 1.3546 + 1.3547 + for (int pos = startPos; pos >= firstPosition; pos--) { 1.3548 + if (adapter.isEnabled(pos) 1.3549 + && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) { 1.3550 + return pos; 1.3551 + } 1.3552 + } 1.3553 + } 1.3554 + 1.3555 + return INVALID_POSITION; 1.3556 + } 1.3557 + 1.3558 + @Override 1.3559 + protected void drawableStateChanged() { 1.3560 + super.drawableStateChanged(); 1.3561 + updateSelectorState(); 1.3562 + } 1.3563 + 1.3564 + @Override 1.3565 + protected int[] onCreateDrawableState(int extraSpace) { 1.3566 + // If the child view is enabled then do the default behavior. 1.3567 + if (mIsChildViewEnabled) { 1.3568 + // Common case 1.3569 + return super.onCreateDrawableState(extraSpace); 1.3570 + } 1.3571 + 1.3572 + // The selector uses this View's drawable state. The selected child view 1.3573 + // is disabled, so we need to remove the enabled state from the drawable 1.3574 + // states. 1.3575 + final int enabledState = ENABLED_STATE_SET[0]; 1.3576 + 1.3577 + // If we don't have any extra space, it will return one of the static state arrays, 1.3578 + // and clearing the enabled state on those arrays is a bad thing! If we specify 1.3579 + // we need extra space, it will create+copy into a new array that safely mutable. 1.3580 + int[] state = super.onCreateDrawableState(extraSpace + 1); 1.3581 + int enabledPos = -1; 1.3582 + for (int i = state.length - 1; i >= 0; i--) { 1.3583 + if (state[i] == enabledState) { 1.3584 + enabledPos = i; 1.3585 + break; 1.3586 + } 1.3587 + } 1.3588 + 1.3589 + // Remove the enabled state 1.3590 + if (enabledPos >= 0) { 1.3591 + System.arraycopy(state, enabledPos + 1, state, enabledPos, 1.3592 + state.length - enabledPos - 1); 1.3593 + } 1.3594 + 1.3595 + return state; 1.3596 + } 1.3597 + 1.3598 + @Override 1.3599 + protected boolean canAnimate() { 1.3600 + return (super.canAnimate() && mItemCount > 0); 1.3601 + } 1.3602 + 1.3603 + @Override 1.3604 + protected void dispatchDraw(Canvas canvas) { 1.3605 + final boolean drawSelectorOnTop = mDrawSelectorOnTop; 1.3606 + if (!drawSelectorOnTop) { 1.3607 + drawSelector(canvas); 1.3608 + } 1.3609 + 1.3610 + super.dispatchDraw(canvas); 1.3611 + 1.3612 + if (drawSelectorOnTop) { 1.3613 + drawSelector(canvas); 1.3614 + } 1.3615 + } 1.3616 + 1.3617 + @Override 1.3618 + public void draw(Canvas canvas) { 1.3619 + super.draw(canvas); 1.3620 + 1.3621 + boolean needsInvalidate = false; 1.3622 + 1.3623 + if (mStartEdge != null) { 1.3624 + needsInvalidate |= drawStartEdge(canvas); 1.3625 + } 1.3626 + 1.3627 + if (mEndEdge != null) { 1.3628 + needsInvalidate |= drawEndEdge(canvas); 1.3629 + } 1.3630 + 1.3631 + if (needsInvalidate) { 1.3632 + ViewCompat.postInvalidateOnAnimation(this); 1.3633 + } 1.3634 + } 1.3635 + 1.3636 + @Override 1.3637 + public void requestLayout() { 1.3638 + if (!mInLayout && !mBlockLayoutRequests) { 1.3639 + super.requestLayout(); 1.3640 + } 1.3641 + } 1.3642 + 1.3643 + @Override 1.3644 + public View getSelectedView() { 1.3645 + if (mItemCount > 0 && mSelectedPosition >= 0) { 1.3646 + return getChildAt(mSelectedPosition - mFirstPosition); 1.3647 + } else { 1.3648 + return null; 1.3649 + } 1.3650 + } 1.3651 + 1.3652 + @Override 1.3653 + public void setSelection(int position) { 1.3654 + setSelectionFromOffset(position, 0); 1.3655 + } 1.3656 + 1.3657 + public void setSelectionFromOffset(int position, int offset) { 1.3658 + if (mAdapter == null) { 1.3659 + return; 1.3660 + } 1.3661 + 1.3662 + if (!isInTouchMode()) { 1.3663 + position = lookForSelectablePosition(position); 1.3664 + if (position >= 0) { 1.3665 + setNextSelectedPositionInt(position); 1.3666 + } 1.3667 + } else { 1.3668 + mResurrectToPosition = position; 1.3669 + } 1.3670 + 1.3671 + if (position >= 0) { 1.3672 + mLayoutMode = LAYOUT_SPECIFIC; 1.3673 + 1.3674 + if (mIsVertical) { 1.3675 + mSpecificStart = getPaddingTop() + offset; 1.3676 + } else { 1.3677 + mSpecificStart = getPaddingLeft() + offset; 1.3678 + } 1.3679 + 1.3680 + if (mNeedSync) { 1.3681 + mSyncPosition = position; 1.3682 + mSyncRowId = mAdapter.getItemId(position); 1.3683 + } 1.3684 + 1.3685 + requestLayout(); 1.3686 + } 1.3687 + } 1.3688 + 1.3689 + @Override 1.3690 + public boolean dispatchKeyEvent(KeyEvent event) { 1.3691 + // Dispatch in the normal way 1.3692 + boolean handled = super.dispatchKeyEvent(event); 1.3693 + if (!handled) { 1.3694 + // If we didn't handle it... 1.3695 + final View focused = getFocusedChild(); 1.3696 + if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) { 1.3697 + // ... and our focused child didn't handle it 1.3698 + // ... give it to ourselves so we can scroll if necessary 1.3699 + handled = onKeyDown(event.getKeyCode(), event); 1.3700 + } 1.3701 + } 1.3702 + 1.3703 + return handled; 1.3704 + } 1.3705 + 1.3706 + @Override 1.3707 + protected void dispatchSetPressed(boolean pressed) { 1.3708 + // Don't dispatch setPressed to our children. We call setPressed on ourselves to 1.3709 + // get the selector in the right state, but we don't want to press each child. 1.3710 + } 1.3711 + 1.3712 + @Override 1.3713 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1.3714 + if (mSelector == null) { 1.3715 + useDefaultSelector(); 1.3716 + } 1.3717 + 1.3718 + int widthMode = MeasureSpec.getMode(widthMeasureSpec); 1.3719 + int heightMode = MeasureSpec.getMode(heightMeasureSpec); 1.3720 + int widthSize = MeasureSpec.getSize(widthMeasureSpec); 1.3721 + int heightSize = MeasureSpec.getSize(heightMeasureSpec); 1.3722 + 1.3723 + int childWidth = 0; 1.3724 + int childHeight = 0; 1.3725 + 1.3726 + mItemCount = (mAdapter == null ? 0 : mAdapter.getCount()); 1.3727 + if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || 1.3728 + heightMode == MeasureSpec.UNSPECIFIED)) { 1.3729 + final View child = obtainView(0, mIsScrap); 1.3730 + 1.3731 + final int secondaryMeasureSpec = 1.3732 + (mIsVertical ? widthMeasureSpec : heightMeasureSpec); 1.3733 + 1.3734 + measureScrapChild(child, 0, secondaryMeasureSpec); 1.3735 + 1.3736 + childWidth = child.getMeasuredWidth(); 1.3737 + childHeight = child.getMeasuredHeight(); 1.3738 + 1.3739 + if (recycleOnMeasure()) { 1.3740 + mRecycler.addScrapView(child, -1); 1.3741 + } 1.3742 + } 1.3743 + 1.3744 + if (widthMode == MeasureSpec.UNSPECIFIED) { 1.3745 + widthSize = getPaddingLeft() + getPaddingRight() + childWidth; 1.3746 + if (mIsVertical) { 1.3747 + widthSize += getVerticalScrollbarWidth(); 1.3748 + } 1.3749 + } 1.3750 + 1.3751 + if (heightMode == MeasureSpec.UNSPECIFIED) { 1.3752 + heightSize = getPaddingTop() + getPaddingBottom() + childHeight; 1.3753 + if (!mIsVertical) { 1.3754 + heightSize += getHorizontalScrollbarHeight(); 1.3755 + } 1.3756 + } 1.3757 + 1.3758 + if (mIsVertical && heightMode == MeasureSpec.AT_MOST) { 1.3759 + heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); 1.3760 + } 1.3761 + 1.3762 + if (!mIsVertical && widthMode == MeasureSpec.AT_MOST) { 1.3763 + widthSize = measureWidthOfChildren(heightMeasureSpec, 0, NO_POSITION, widthSize, -1); 1.3764 + } 1.3765 + 1.3766 + setMeasuredDimension(widthSize, heightSize); 1.3767 + } 1.3768 + 1.3769 + @Override 1.3770 + protected void onLayout(boolean changed, int l, int t, int r, int b) { 1.3771 + mInLayout = true; 1.3772 + 1.3773 + if (changed) { 1.3774 + final int childCount = getChildCount(); 1.3775 + for (int i = 0; i < childCount; i++) { 1.3776 + getChildAt(i).forceLayout(); 1.3777 + } 1.3778 + 1.3779 + mRecycler.markChildrenDirty(); 1.3780 + } 1.3781 + 1.3782 + layoutChildren(); 1.3783 + 1.3784 + mInLayout = false; 1.3785 + 1.3786 + final int width = r - l - getPaddingLeft() - getPaddingRight(); 1.3787 + final int height = b - t - getPaddingTop() - getPaddingBottom(); 1.3788 + 1.3789 + if (mStartEdge != null && mEndEdge != null) { 1.3790 + if (mIsVertical) { 1.3791 + mStartEdge.setSize(width, height); 1.3792 + mEndEdge.setSize(width, height); 1.3793 + } else { 1.3794 + mStartEdge.setSize(height, width); 1.3795 + mEndEdge.setSize(height, width); 1.3796 + } 1.3797 + } 1.3798 + } 1.3799 + 1.3800 + private void layoutChildren() { 1.3801 + if (getWidth() == 0 || getHeight() == 0) { 1.3802 + return; 1.3803 + } 1.3804 + 1.3805 + final boolean blockLayoutRequests = mBlockLayoutRequests; 1.3806 + if (!blockLayoutRequests) { 1.3807 + mBlockLayoutRequests = true; 1.3808 + } else { 1.3809 + return; 1.3810 + } 1.3811 + 1.3812 + try { 1.3813 + invalidate(); 1.3814 + 1.3815 + if (mAdapter == null) { 1.3816 + resetState(); 1.3817 + return; 1.3818 + } 1.3819 + 1.3820 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.3821 + final int end = 1.3822 + (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); 1.3823 + 1.3824 + int childCount = getChildCount(); 1.3825 + int index = 0; 1.3826 + int delta = 0; 1.3827 + 1.3828 + View focusLayoutRestoreView = null; 1.3829 + 1.3830 + View selected = null; 1.3831 + View oldSelected = null; 1.3832 + View newSelected = null; 1.3833 + View oldFirstChild = null; 1.3834 + 1.3835 + switch (mLayoutMode) { 1.3836 + case LAYOUT_SET_SELECTION: 1.3837 + index = mNextSelectedPosition - mFirstPosition; 1.3838 + if (index >= 0 && index < childCount) { 1.3839 + newSelected = getChildAt(index); 1.3840 + } 1.3841 + 1.3842 + break; 1.3843 + 1.3844 + case LAYOUT_FORCE_TOP: 1.3845 + case LAYOUT_FORCE_BOTTOM: 1.3846 + case LAYOUT_SPECIFIC: 1.3847 + case LAYOUT_SYNC: 1.3848 + break; 1.3849 + 1.3850 + case LAYOUT_MOVE_SELECTION: 1.3851 + default: 1.3852 + // Remember the previously selected view 1.3853 + index = mSelectedPosition - mFirstPosition; 1.3854 + if (index >= 0 && index < childCount) { 1.3855 + oldSelected = getChildAt(index); 1.3856 + } 1.3857 + 1.3858 + // Remember the previous first child 1.3859 + oldFirstChild = getChildAt(0); 1.3860 + 1.3861 + if (mNextSelectedPosition >= 0) { 1.3862 + delta = mNextSelectedPosition - mSelectedPosition; 1.3863 + } 1.3864 + 1.3865 + // Caution: newSelected might be null 1.3866 + newSelected = getChildAt(index + delta); 1.3867 + } 1.3868 + 1.3869 + final boolean dataChanged = mDataChanged; 1.3870 + if (dataChanged) { 1.3871 + handleDataChanged(); 1.3872 + } 1.3873 + 1.3874 + // Handle the empty set by removing all views that are visible 1.3875 + // and calling it a day 1.3876 + if (mItemCount == 0) { 1.3877 + resetState(); 1.3878 + return; 1.3879 + } else if (mItemCount != mAdapter.getCount()) { 1.3880 + throw new IllegalStateException("The content of the adapter has changed but " 1.3881 + + "TwoWayView did not receive a notification. Make sure the content of " 1.3882 + + "your adapter is not modified from a background thread, but only " 1.3883 + + "from the UI thread. [in TwoWayView(" + getId() + ", " + getClass() 1.3884 + + ") with Adapter(" + mAdapter.getClass() + ")]"); 1.3885 + } 1.3886 + 1.3887 + setSelectedPositionInt(mNextSelectedPosition); 1.3888 + 1.3889 + // Reset the focus restoration 1.3890 + View focusLayoutRestoreDirectChild = null; 1.3891 + 1.3892 + // Pull all children into the RecycleBin. 1.3893 + // These views will be reused if possible 1.3894 + final int firstPosition = mFirstPosition; 1.3895 + final RecycleBin recycleBin = mRecycler; 1.3896 + 1.3897 + if (dataChanged) { 1.3898 + for (int i = 0; i < childCount; i++) { 1.3899 + recycleBin.addScrapView(getChildAt(i), firstPosition + i); 1.3900 + } 1.3901 + } else { 1.3902 + recycleBin.fillActiveViews(childCount, firstPosition); 1.3903 + } 1.3904 + 1.3905 + // Take focus back to us temporarily to avoid the eventual 1.3906 + // call to clear focus when removing the focused child below 1.3907 + // from messing things up when ViewAncestor assigns focus back 1.3908 + // to someone else. 1.3909 + final View focusedChild = getFocusedChild(); 1.3910 + if (focusedChild != null) { 1.3911 + // We can remember the focused view to restore after relayout if the 1.3912 + // data hasn't changed, or if the focused position is a header or footer. 1.3913 + if (!dataChanged) { 1.3914 + focusLayoutRestoreDirectChild = focusedChild; 1.3915 + 1.3916 + // Remember the specific view that had focus 1.3917 + focusLayoutRestoreView = findFocus(); 1.3918 + if (focusLayoutRestoreView != null) { 1.3919 + // Tell it we are going to mess with it 1.3920 + focusLayoutRestoreView.onStartTemporaryDetach(); 1.3921 + } 1.3922 + } 1.3923 + 1.3924 + requestFocus(); 1.3925 + } 1.3926 + 1.3927 + // FIXME: We need a way to save current accessibility focus here 1.3928 + // so that it can be restored after we re-attach the children on each 1.3929 + // layout round. 1.3930 + 1.3931 + detachAllViewsFromParent(); 1.3932 + 1.3933 + switch (mLayoutMode) { 1.3934 + case LAYOUT_SET_SELECTION: 1.3935 + if (newSelected != null) { 1.3936 + final int newSelectedStart = 1.3937 + (mIsVertical ? newSelected.getTop() : newSelected.getLeft()); 1.3938 + 1.3939 + selected = fillFromSelection(newSelectedStart, start, end); 1.3940 + } else { 1.3941 + selected = fillFromMiddle(start, end); 1.3942 + } 1.3943 + 1.3944 + break; 1.3945 + 1.3946 + case LAYOUT_SYNC: 1.3947 + selected = fillSpecific(mSyncPosition, mSpecificStart); 1.3948 + break; 1.3949 + 1.3950 + case LAYOUT_FORCE_BOTTOM: 1.3951 + selected = fillBefore(mItemCount - 1, end); 1.3952 + adjustViewsStartOrEnd(); 1.3953 + break; 1.3954 + 1.3955 + case LAYOUT_FORCE_TOP: 1.3956 + mFirstPosition = 0; 1.3957 + selected = fillFromOffset(start); 1.3958 + adjustViewsStartOrEnd(); 1.3959 + break; 1.3960 + 1.3961 + case LAYOUT_SPECIFIC: 1.3962 + selected = fillSpecific(reconcileSelectedPosition(), mSpecificStart); 1.3963 + break; 1.3964 + 1.3965 + case LAYOUT_MOVE_SELECTION: 1.3966 + selected = moveSelection(oldSelected, newSelected, delta, start, end); 1.3967 + break; 1.3968 + 1.3969 + default: 1.3970 + if (childCount == 0) { 1.3971 + final int position = lookForSelectablePosition(0); 1.3972 + setSelectedPositionInt(position); 1.3973 + selected = fillFromOffset(start); 1.3974 + } else { 1.3975 + if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { 1.3976 + int offset = start; 1.3977 + if (oldSelected != null) { 1.3978 + offset = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft()); 1.3979 + } 1.3980 + selected = fillSpecific(mSelectedPosition, offset); 1.3981 + } else if (mFirstPosition < mItemCount) { 1.3982 + int offset = start; 1.3983 + if (oldFirstChild != null) { 1.3984 + offset = (mIsVertical ? oldFirstChild.getTop() : oldFirstChild.getLeft()); 1.3985 + } 1.3986 + 1.3987 + selected = fillSpecific(mFirstPosition, offset); 1.3988 + } else { 1.3989 + selected = fillSpecific(0, start); 1.3990 + } 1.3991 + } 1.3992 + 1.3993 + break; 1.3994 + 1.3995 + } 1.3996 + 1.3997 + recycleBin.scrapActiveViews(); 1.3998 + 1.3999 + if (selected != null) { 1.4000 + if (mItemsCanFocus && hasFocus() && !selected.hasFocus()) { 1.4001 + final boolean focusWasTaken = (selected == focusLayoutRestoreDirectChild && 1.4002 + focusLayoutRestoreView != null && 1.4003 + focusLayoutRestoreView.requestFocus()) || selected.requestFocus(); 1.4004 + 1.4005 + if (!focusWasTaken) { 1.4006 + // Selected item didn't take focus, fine, but still want 1.4007 + // to make sure something else outside of the selected view 1.4008 + // has focus 1.4009 + final View focused = getFocusedChild(); 1.4010 + if (focused != null) { 1.4011 + focused.clearFocus(); 1.4012 + } 1.4013 + 1.4014 + positionSelector(INVALID_POSITION, selected); 1.4015 + } else { 1.4016 + selected.setSelected(false); 1.4017 + mSelectorRect.setEmpty(); 1.4018 + } 1.4019 + } else { 1.4020 + positionSelector(INVALID_POSITION, selected); 1.4021 + } 1.4022 + 1.4023 + mSelectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.4024 + } else { 1.4025 + if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_DRAGGING) { 1.4026 + View child = getChildAt(mMotionPosition - mFirstPosition); 1.4027 + 1.4028 + if (child != null) { 1.4029 + positionSelector(mMotionPosition, child); 1.4030 + } 1.4031 + } else { 1.4032 + mSelectedStart = 0; 1.4033 + mSelectorRect.setEmpty(); 1.4034 + } 1.4035 + 1.4036 + // Even if there is not selected position, we may need to restore 1.4037 + // focus (i.e. something focusable in touch mode) 1.4038 + if (hasFocus() && focusLayoutRestoreView != null) { 1.4039 + focusLayoutRestoreView.requestFocus(); 1.4040 + } 1.4041 + } 1.4042 + 1.4043 + // Tell focus view we are done mucking with it, if it is still in 1.4044 + // our view hierarchy. 1.4045 + if (focusLayoutRestoreView != null 1.4046 + && focusLayoutRestoreView.getWindowToken() != null) { 1.4047 + focusLayoutRestoreView.onFinishTemporaryDetach(); 1.4048 + } 1.4049 + 1.4050 + mLayoutMode = LAYOUT_NORMAL; 1.4051 + mDataChanged = false; 1.4052 + mNeedSync = false; 1.4053 + 1.4054 + setNextSelectedPositionInt(mSelectedPosition); 1.4055 + if (mItemCount > 0) { 1.4056 + checkSelectionChanged(); 1.4057 + } 1.4058 + 1.4059 + invokeOnItemScrollListener(); 1.4060 + } finally { 1.4061 + if (!blockLayoutRequests) { 1.4062 + mBlockLayoutRequests = false; 1.4063 + mDataChanged = false; 1.4064 + } 1.4065 + } 1.4066 + } 1.4067 + 1.4068 + protected boolean recycleOnMeasure() { 1.4069 + return true; 1.4070 + } 1.4071 + 1.4072 + private void offsetChildren(int offset) { 1.4073 + final int childCount = getChildCount(); 1.4074 + 1.4075 + for (int i = 0; i < childCount; i++) { 1.4076 + final View child = getChildAt(i); 1.4077 + 1.4078 + if (mIsVertical) { 1.4079 + child.offsetTopAndBottom(offset); 1.4080 + } else { 1.4081 + child.offsetLeftAndRight(offset); 1.4082 + } 1.4083 + } 1.4084 + } 1.4085 + 1.4086 + private View moveSelection(View oldSelected, View newSelected, int delta, int start, 1.4087 + int end) { 1.4088 + final int selectedPosition = mSelectedPosition; 1.4089 + 1.4090 + final int oldSelectedStart = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft()); 1.4091 + final int oldSelectedEnd = (mIsVertical ? oldSelected.getBottom() : oldSelected.getRight()); 1.4092 + 1.4093 + View selected = null; 1.4094 + 1.4095 + if (delta > 0) { 1.4096 + /* 1.4097 + * Case 1: Scrolling down. 1.4098 + */ 1.4099 + 1.4100 + /* 1.4101 + * Before After 1.4102 + * | | | | 1.4103 + * +-------+ +-------+ 1.4104 + * | A | | A | 1.4105 + * | 1 | => +-------+ 1.4106 + * +-------+ | B | 1.4107 + * | B | | 2 | 1.4108 + * +-------+ +-------+ 1.4109 + * | | | | 1.4110 + * 1.4111 + * Try to keep the top of the previously selected item where it was. 1.4112 + * oldSelected = A 1.4113 + * selected = B 1.4114 + */ 1.4115 + 1.4116 + // Put oldSelected (A) where it belongs 1.4117 + oldSelected = makeAndAddView(selectedPosition - 1, oldSelectedStart, true, false); 1.4118 + 1.4119 + final int itemMargin = mItemMargin; 1.4120 + 1.4121 + // Now put the new selection (B) below that 1.4122 + selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true); 1.4123 + 1.4124 + final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.4125 + final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); 1.4126 + 1.4127 + // Some of the newly selected item extends below the bottom of the list 1.4128 + if (selectedEnd > end) { 1.4129 + // Find space available above the selection into which we can scroll upwards 1.4130 + final int spaceBefore = selectedStart - start; 1.4131 + 1.4132 + // Find space required to bring the bottom of the selected item fully into view 1.4133 + final int spaceAfter = selectedEnd - end; 1.4134 + 1.4135 + // Don't scroll more than half the size of the list 1.4136 + final int halfSpace = (end - start) / 2; 1.4137 + int offset = Math.min(spaceBefore, spaceAfter); 1.4138 + offset = Math.min(offset, halfSpace); 1.4139 + 1.4140 + if (mIsVertical) { 1.4141 + oldSelected.offsetTopAndBottom(-offset); 1.4142 + selected.offsetTopAndBottom(-offset); 1.4143 + } else { 1.4144 + oldSelected.offsetLeftAndRight(-offset); 1.4145 + selected.offsetLeftAndRight(-offset); 1.4146 + } 1.4147 + } 1.4148 + 1.4149 + // Fill in views before and after 1.4150 + fillBefore(mSelectedPosition - 2, selectedStart - itemMargin); 1.4151 + adjustViewsStartOrEnd(); 1.4152 + fillAfter(mSelectedPosition + 1, selectedEnd + itemMargin); 1.4153 + } else if (delta < 0) { 1.4154 + /* 1.4155 + * Case 2: Scrolling up. 1.4156 + */ 1.4157 + 1.4158 + /* 1.4159 + * Before After 1.4160 + * | | | | 1.4161 + * +-------+ +-------+ 1.4162 + * | A | | A | 1.4163 + * +-------+ => | 1 | 1.4164 + * | B | +-------+ 1.4165 + * | 2 | | B | 1.4166 + * +-------+ +-------+ 1.4167 + * | | | | 1.4168 + * 1.4169 + * Try to keep the top of the item about to become selected where it was. 1.4170 + * newSelected = A 1.4171 + * olSelected = B 1.4172 + */ 1.4173 + 1.4174 + if (newSelected != null) { 1.4175 + // Try to position the top of newSel (A) where it was before it was selected 1.4176 + final int newSelectedStart = (mIsVertical ? newSelected.getTop() : newSelected.getLeft()); 1.4177 + selected = makeAndAddView(selectedPosition, newSelectedStart, true, true); 1.4178 + } else { 1.4179 + // If (A) was not on screen and so did not have a view, position 1.4180 + // it above the oldSelected (B) 1.4181 + selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true); 1.4182 + } 1.4183 + 1.4184 + final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.4185 + final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); 1.4186 + 1.4187 + // Some of the newly selected item extends above the top of the list 1.4188 + if (selectedStart < start) { 1.4189 + // Find space required to bring the top of the selected item fully into view 1.4190 + final int spaceBefore = start - selectedStart; 1.4191 + 1.4192 + // Find space available below the selection into which we can scroll downwards 1.4193 + final int spaceAfter = end - selectedEnd; 1.4194 + 1.4195 + // Don't scroll more than half the height of the list 1.4196 + final int halfSpace = (end - start) / 2; 1.4197 + int offset = Math.min(spaceBefore, spaceAfter); 1.4198 + offset = Math.min(offset, halfSpace); 1.4199 + 1.4200 + if (mIsVertical) { 1.4201 + selected.offsetTopAndBottom(offset); 1.4202 + } else { 1.4203 + selected.offsetLeftAndRight(offset); 1.4204 + } 1.4205 + } 1.4206 + 1.4207 + // Fill in views above and below 1.4208 + fillBeforeAndAfter(selected, selectedPosition); 1.4209 + } else { 1.4210 + /* 1.4211 + * Case 3: Staying still 1.4212 + */ 1.4213 + 1.4214 + selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true); 1.4215 + 1.4216 + final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.4217 + final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); 1.4218 + 1.4219 + // We're staying still... 1.4220 + if (oldSelectedStart < start) { 1.4221 + // ... but the top of the old selection was off screen. 1.4222 + // (This can happen if the data changes size out from under us) 1.4223 + int newEnd = selectedEnd; 1.4224 + if (newEnd < start + 20) { 1.4225 + // Not enough visible -- bring it onscreen 1.4226 + if (mIsVertical) { 1.4227 + selected.offsetTopAndBottom(start - selectedStart); 1.4228 + } else { 1.4229 + selected.offsetLeftAndRight(start - selectedStart); 1.4230 + } 1.4231 + } 1.4232 + } 1.4233 + 1.4234 + // Fill in views above and below 1.4235 + fillBeforeAndAfter(selected, selectedPosition); 1.4236 + } 1.4237 + 1.4238 + return selected; 1.4239 + } 1.4240 + 1.4241 + void confirmCheckedPositionsById() { 1.4242 + // Clear out the positional check states, we'll rebuild it below from IDs. 1.4243 + mCheckStates.clear(); 1.4244 + 1.4245 + for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { 1.4246 + final long id = mCheckedIdStates.keyAt(checkedIndex); 1.4247 + final int lastPos = mCheckedIdStates.valueAt(checkedIndex); 1.4248 + 1.4249 + final long lastPosId = mAdapter.getItemId(lastPos); 1.4250 + if (id != lastPosId) { 1.4251 + // Look around to see if the ID is nearby. If not, uncheck it. 1.4252 + final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); 1.4253 + final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); 1.4254 + boolean found = false; 1.4255 + 1.4256 + for (int searchPos = start; searchPos < end; searchPos++) { 1.4257 + final long searchId = mAdapter.getItemId(searchPos); 1.4258 + if (id == searchId) { 1.4259 + found = true; 1.4260 + mCheckStates.put(searchPos, true); 1.4261 + mCheckedIdStates.setValueAt(checkedIndex, searchPos); 1.4262 + break; 1.4263 + } 1.4264 + } 1.4265 + 1.4266 + if (!found) { 1.4267 + mCheckedIdStates.delete(id); 1.4268 + checkedIndex--; 1.4269 + mCheckedItemCount--; 1.4270 + } 1.4271 + } else { 1.4272 + mCheckStates.put(lastPos, true); 1.4273 + } 1.4274 + } 1.4275 + } 1.4276 + 1.4277 + private void handleDataChanged() { 1.4278 + if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mAdapter != null && mAdapter.hasStableIds()) { 1.4279 + confirmCheckedPositionsById(); 1.4280 + } 1.4281 + 1.4282 + mRecycler.clearTransientStateViews(); 1.4283 + 1.4284 + final int itemCount = mItemCount; 1.4285 + if (itemCount > 0) { 1.4286 + int newPos; 1.4287 + int selectablePos; 1.4288 + 1.4289 + // Find the row we are supposed to sync to 1.4290 + if (mNeedSync) { 1.4291 + // Update this first, since setNextSelectedPositionInt inspects it 1.4292 + mNeedSync = false; 1.4293 + mPendingSync = null; 1.4294 + 1.4295 + switch (mSyncMode) { 1.4296 + case SYNC_SELECTED_POSITION: 1.4297 + if (isInTouchMode()) { 1.4298 + // We saved our state when not in touch mode. (We know this because 1.4299 + // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to 1.4300 + // restore in touch mode. Just leave mSyncPosition as it is (possibly 1.4301 + // adjusting if the available range changed) and return. 1.4302 + mLayoutMode = LAYOUT_SYNC; 1.4303 + mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1); 1.4304 + 1.4305 + return; 1.4306 + } else { 1.4307 + // See if we can find a position in the new data with the same 1.4308 + // id as the old selection. This will change mSyncPosition. 1.4309 + newPos = findSyncPosition(); 1.4310 + if (newPos >= 0) { 1.4311 + // Found it. Now verify that new selection is still selectable 1.4312 + selectablePos = lookForSelectablePosition(newPos, true); 1.4313 + if (selectablePos == newPos) { 1.4314 + // Same row id is selected 1.4315 + mSyncPosition = newPos; 1.4316 + 1.4317 + if (mSyncHeight == getHeight()) { 1.4318 + // If we are at the same height as when we saved state, try 1.4319 + // to restore the scroll position too. 1.4320 + mLayoutMode = LAYOUT_SYNC; 1.4321 + } else { 1.4322 + // We are not the same height as when the selection was saved, so 1.4323 + // don't try to restore the exact position 1.4324 + mLayoutMode = LAYOUT_SET_SELECTION; 1.4325 + } 1.4326 + 1.4327 + // Restore selection 1.4328 + setNextSelectedPositionInt(newPos); 1.4329 + return; 1.4330 + } 1.4331 + } 1.4332 + } 1.4333 + break; 1.4334 + 1.4335 + case SYNC_FIRST_POSITION: 1.4336 + // Leave mSyncPosition as it is -- just pin to available range 1.4337 + mLayoutMode = LAYOUT_SYNC; 1.4338 + mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1); 1.4339 + 1.4340 + return; 1.4341 + } 1.4342 + } 1.4343 + 1.4344 + if (!isInTouchMode()) { 1.4345 + // We couldn't find matching data -- try to use the same position 1.4346 + newPos = getSelectedItemPosition(); 1.4347 + 1.4348 + // Pin position to the available range 1.4349 + if (newPos >= itemCount) { 1.4350 + newPos = itemCount - 1; 1.4351 + } 1.4352 + if (newPos < 0) { 1.4353 + newPos = 0; 1.4354 + } 1.4355 + 1.4356 + // Make sure we select something selectable -- first look down 1.4357 + selectablePos = lookForSelectablePosition(newPos, true); 1.4358 + 1.4359 + if (selectablePos >= 0) { 1.4360 + setNextSelectedPositionInt(selectablePos); 1.4361 + return; 1.4362 + } else { 1.4363 + // Looking down didn't work -- try looking up 1.4364 + selectablePos = lookForSelectablePosition(newPos, false); 1.4365 + if (selectablePos >= 0) { 1.4366 + setNextSelectedPositionInt(selectablePos); 1.4367 + return; 1.4368 + } 1.4369 + } 1.4370 + } else { 1.4371 + // We already know where we want to resurrect the selection 1.4372 + if (mResurrectToPosition >= 0) { 1.4373 + return; 1.4374 + } 1.4375 + } 1.4376 + } 1.4377 + 1.4378 + // Nothing is selected. Give up and reset everything. 1.4379 + mLayoutMode = LAYOUT_FORCE_TOP; 1.4380 + mSelectedPosition = INVALID_POSITION; 1.4381 + mSelectedRowId = INVALID_ROW_ID; 1.4382 + mNextSelectedPosition = INVALID_POSITION; 1.4383 + mNextSelectedRowId = INVALID_ROW_ID; 1.4384 + mNeedSync = false; 1.4385 + mPendingSync = null; 1.4386 + mSelectorPosition = INVALID_POSITION; 1.4387 + 1.4388 + checkSelectionChanged(); 1.4389 + } 1.4390 + 1.4391 + private int reconcileSelectedPosition() { 1.4392 + int position = mSelectedPosition; 1.4393 + if (position < 0) { 1.4394 + position = mResurrectToPosition; 1.4395 + } 1.4396 + 1.4397 + position = Math.max(0, position); 1.4398 + position = Math.min(position, mItemCount - 1); 1.4399 + 1.4400 + return position; 1.4401 + } 1.4402 + 1.4403 + boolean resurrectSelection() { 1.4404 + final int childCount = getChildCount(); 1.4405 + if (childCount <= 0) { 1.4406 + return false; 1.4407 + } 1.4408 + 1.4409 + int selectedStart = 0; 1.4410 + int selectedPosition; 1.4411 + 1.4412 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.4413 + final int end = 1.4414 + (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); 1.4415 + 1.4416 + final int firstPosition = mFirstPosition; 1.4417 + final int toPosition = mResurrectToPosition; 1.4418 + boolean down = true; 1.4419 + 1.4420 + if (toPosition >= firstPosition && toPosition < firstPosition + childCount) { 1.4421 + selectedPosition = toPosition; 1.4422 + 1.4423 + final View selected = getChildAt(selectedPosition - mFirstPosition); 1.4424 + selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.4425 + } else if (toPosition < firstPosition) { 1.4426 + // Default to selecting whatever is first 1.4427 + selectedPosition = firstPosition; 1.4428 + 1.4429 + for (int i = 0; i < childCount; i++) { 1.4430 + final View child = getChildAt(i); 1.4431 + final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); 1.4432 + 1.4433 + if (i == 0) { 1.4434 + // Remember the position of the first item 1.4435 + selectedStart = childStart; 1.4436 + } 1.4437 + 1.4438 + if (childStart >= start) { 1.4439 + // Found a view whose top is fully visible 1.4440 + selectedPosition = firstPosition + i; 1.4441 + selectedStart = childStart; 1.4442 + break; 1.4443 + } 1.4444 + } 1.4445 + } else { 1.4446 + selectedPosition = firstPosition + childCount - 1; 1.4447 + down = false; 1.4448 + 1.4449 + for (int i = childCount - 1; i >= 0; i--) { 1.4450 + final View child = getChildAt(i); 1.4451 + final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); 1.4452 + final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); 1.4453 + 1.4454 + if (i == childCount - 1) { 1.4455 + selectedStart = childStart; 1.4456 + } 1.4457 + 1.4458 + if (childEnd <= end) { 1.4459 + selectedPosition = firstPosition + i; 1.4460 + selectedStart = childStart; 1.4461 + break; 1.4462 + } 1.4463 + } 1.4464 + } 1.4465 + 1.4466 + mResurrectToPosition = INVALID_POSITION; 1.4467 + mTouchMode = TOUCH_MODE_REST; 1.4468 + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 1.4469 + 1.4470 + mSpecificStart = selectedStart; 1.4471 + 1.4472 + selectedPosition = lookForSelectablePosition(selectedPosition, down); 1.4473 + if (selectedPosition >= firstPosition && selectedPosition <= getLastVisiblePosition()) { 1.4474 + mLayoutMode = LAYOUT_SPECIFIC; 1.4475 + updateSelectorState(); 1.4476 + setSelectionInt(selectedPosition); 1.4477 + invokeOnItemScrollListener(); 1.4478 + } else { 1.4479 + selectedPosition = INVALID_POSITION; 1.4480 + } 1.4481 + 1.4482 + return selectedPosition >= 0; 1.4483 + } 1.4484 + 1.4485 + /** 1.4486 + * If there is a selection returns false. 1.4487 + * Otherwise resurrects the selection and returns true if resurrected. 1.4488 + */ 1.4489 + boolean resurrectSelectionIfNeeded() { 1.4490 + if (mSelectedPosition < 0 && resurrectSelection()) { 1.4491 + updateSelectorState(); 1.4492 + return true; 1.4493 + } 1.4494 + 1.4495 + return false; 1.4496 + } 1.4497 + 1.4498 + private int getChildWidthMeasureSpec(LayoutParams lp) { 1.4499 + if (!mIsVertical && lp.width == LayoutParams.WRAP_CONTENT) { 1.4500 + return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1.4501 + } else if (mIsVertical) { 1.4502 + final int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight(); 1.4503 + return MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY); 1.4504 + } else { 1.4505 + return MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 1.4506 + } 1.4507 + } 1.4508 + 1.4509 + private int getChildHeightMeasureSpec(LayoutParams lp) { 1.4510 + if (mIsVertical && lp.height == LayoutParams.WRAP_CONTENT) { 1.4511 + return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1.4512 + } else if (!mIsVertical) { 1.4513 + final int maxHeight = getHeight() - getPaddingTop() - getPaddingBottom(); 1.4514 + return MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY); 1.4515 + } else { 1.4516 + return MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); 1.4517 + } 1.4518 + } 1.4519 + 1.4520 + private void measureChild(View child) { 1.4521 + measureChild(child, (LayoutParams) child.getLayoutParams()); 1.4522 + } 1.4523 + 1.4524 + private void measureChild(View child, LayoutParams lp) { 1.4525 + final int widthSpec = getChildWidthMeasureSpec(lp); 1.4526 + final int heightSpec = getChildHeightMeasureSpec(lp); 1.4527 + child.measure(widthSpec, heightSpec); 1.4528 + } 1.4529 + 1.4530 + private void relayoutMeasuredChild(View child) { 1.4531 + final int w = child.getMeasuredWidth(); 1.4532 + final int h = child.getMeasuredHeight(); 1.4533 + 1.4534 + final int childLeft = getPaddingLeft(); 1.4535 + final int childRight = childLeft + w; 1.4536 + final int childTop = child.getTop(); 1.4537 + final int childBottom = childTop + h; 1.4538 + 1.4539 + child.layout(childLeft, childTop, childRight, childBottom); 1.4540 + } 1.4541 + 1.4542 + private void measureScrapChild(View scrapChild, int position, int secondaryMeasureSpec) { 1.4543 + LayoutParams lp = (LayoutParams) scrapChild.getLayoutParams(); 1.4544 + if (lp == null) { 1.4545 + lp = generateDefaultLayoutParams(); 1.4546 + scrapChild.setLayoutParams(lp); 1.4547 + } 1.4548 + 1.4549 + lp.viewType = mAdapter.getItemViewType(position); 1.4550 + lp.forceAdd = true; 1.4551 + 1.4552 + final int widthMeasureSpec; 1.4553 + final int heightMeasureSpec; 1.4554 + if (mIsVertical) { 1.4555 + widthMeasureSpec = secondaryMeasureSpec; 1.4556 + heightMeasureSpec = getChildHeightMeasureSpec(lp); 1.4557 + } else { 1.4558 + widthMeasureSpec = getChildWidthMeasureSpec(lp); 1.4559 + heightMeasureSpec = secondaryMeasureSpec; 1.4560 + } 1.4561 + 1.4562 + scrapChild.measure(widthMeasureSpec, heightMeasureSpec); 1.4563 + } 1.4564 + 1.4565 + /** 1.4566 + * Measures the height of the given range of children (inclusive) and 1.4567 + * returns the height with this TwoWayView's padding and item margin heights 1.4568 + * included. If maxHeight is provided, the measuring will stop when the 1.4569 + * current height reaches maxHeight. 1.4570 + * 1.4571 + * @param widthMeasureSpec The width measure spec to be given to a child's 1.4572 + * {@link View#measure(int, int)}. 1.4573 + * @param startPosition The position of the first child to be shown. 1.4574 + * @param endPosition The (inclusive) position of the last child to be 1.4575 + * shown. Specify {@link #NO_POSITION} if the last child should be 1.4576 + * the last available child from the adapter. 1.4577 + * @param maxHeight The maximum height that will be returned (if all the 1.4578 + * children don't fit in this value, this value will be 1.4579 + * returned). 1.4580 + * @param disallowPartialChildPosition In general, whether the returned 1.4581 + * height should only contain entire children. This is more 1.4582 + * powerful--it is the first inclusive position at which partial 1.4583 + * children will not be allowed. Example: it looks nice to have 1.4584 + * at least 3 completely visible children, and in portrait this 1.4585 + * will most likely fit; but in landscape there could be times 1.4586 + * when even 2 children can not be completely shown, so a value 1.4587 + * of 2 (remember, inclusive) would be good (assuming 1.4588 + * startPosition is 0). 1.4589 + * @return The height of this TwoWayView with the given children. 1.4590 + */ 1.4591 + private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, 1.4592 + final int maxHeight, int disallowPartialChildPosition) { 1.4593 + 1.4594 + final int paddingTop = getPaddingTop(); 1.4595 + final int paddingBottom = getPaddingBottom(); 1.4596 + 1.4597 + final ListAdapter adapter = mAdapter; 1.4598 + if (adapter == null) { 1.4599 + return paddingTop + paddingBottom; 1.4600 + } 1.4601 + 1.4602 + // Include the padding of the list 1.4603 + int returnedHeight = paddingTop + paddingBottom; 1.4604 + final int itemMargin = mItemMargin; 1.4605 + 1.4606 + // The previous height value that was less than maxHeight and contained 1.4607 + // no partial children 1.4608 + int prevHeightWithoutPartialChild = 0; 1.4609 + int i; 1.4610 + View child; 1.4611 + 1.4612 + // mItemCount - 1 since endPosition parameter is inclusive 1.4613 + endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; 1.4614 + final RecycleBin recycleBin = mRecycler; 1.4615 + final boolean shouldRecycle = recycleOnMeasure(); 1.4616 + final boolean[] isScrap = mIsScrap; 1.4617 + 1.4618 + for (i = startPosition; i <= endPosition; ++i) { 1.4619 + child = obtainView(i, isScrap); 1.4620 + 1.4621 + measureScrapChild(child, i, widthMeasureSpec); 1.4622 + 1.4623 + if (i > 0) { 1.4624 + // Count the item margin for all but one child 1.4625 + returnedHeight += itemMargin; 1.4626 + } 1.4627 + 1.4628 + // Recycle the view before we possibly return from the method 1.4629 + if (shouldRecycle) { 1.4630 + recycleBin.addScrapView(child, -1); 1.4631 + } 1.4632 + 1.4633 + returnedHeight += child.getMeasuredHeight(); 1.4634 + 1.4635 + if (returnedHeight >= maxHeight) { 1.4636 + // We went over, figure out which height to return. If returnedHeight > maxHeight, 1.4637 + // then the i'th position did not fit completely. 1.4638 + return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) 1.4639 + && (i > disallowPartialChildPosition) // We've past the min pos 1.4640 + && (prevHeightWithoutPartialChild > 0) // We have a prev height 1.4641 + && (returnedHeight != maxHeight) // i'th child did not fit completely 1.4642 + ? prevHeightWithoutPartialChild 1.4643 + : maxHeight; 1.4644 + } 1.4645 + 1.4646 + if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { 1.4647 + prevHeightWithoutPartialChild = returnedHeight; 1.4648 + } 1.4649 + } 1.4650 + 1.4651 + // At this point, we went through the range of children, and they each 1.4652 + // completely fit, so return the returnedHeight 1.4653 + return returnedHeight; 1.4654 + } 1.4655 + 1.4656 + /** 1.4657 + * Measures the width of the given range of children (inclusive) and 1.4658 + * returns the width with this TwoWayView's padding and item margin widths 1.4659 + * included. If maxWidth is provided, the measuring will stop when the 1.4660 + * current width reaches maxWidth. 1.4661 + * 1.4662 + * @param heightMeasureSpec The height measure spec to be given to a child's 1.4663 + * {@link View#measure(int, int)}. 1.4664 + * @param startPosition The position of the first child to be shown. 1.4665 + * @param endPosition The (inclusive) position of the last child to be 1.4666 + * shown. Specify {@link #NO_POSITION} if the last child should be 1.4667 + * the last available child from the adapter. 1.4668 + * @param maxWidth The maximum width that will be returned (if all the 1.4669 + * children don't fit in this value, this value will be 1.4670 + * returned). 1.4671 + * @param disallowPartialChildPosition In general, whether the returned 1.4672 + * width should only contain entire children. This is more 1.4673 + * powerful--it is the first inclusive position at which partial 1.4674 + * children will not be allowed. Example: it looks nice to have 1.4675 + * at least 3 completely visible children, and in portrait this 1.4676 + * will most likely fit; but in landscape there could be times 1.4677 + * when even 2 children can not be completely shown, so a value 1.4678 + * of 2 (remember, inclusive) would be good (assuming 1.4679 + * startPosition is 0). 1.4680 + * @return The width of this TwoWayView with the given children. 1.4681 + */ 1.4682 + private int measureWidthOfChildren(int heightMeasureSpec, int startPosition, int endPosition, 1.4683 + final int maxWidth, int disallowPartialChildPosition) { 1.4684 + 1.4685 + final int paddingLeft = getPaddingLeft(); 1.4686 + final int paddingRight = getPaddingRight(); 1.4687 + 1.4688 + final ListAdapter adapter = mAdapter; 1.4689 + if (adapter == null) { 1.4690 + return paddingLeft + paddingRight; 1.4691 + } 1.4692 + 1.4693 + // Include the padding of the list 1.4694 + int returnedWidth = paddingLeft + paddingRight; 1.4695 + final int itemMargin = mItemMargin; 1.4696 + 1.4697 + // The previous height value that was less than maxHeight and contained 1.4698 + // no partial children 1.4699 + int prevWidthWithoutPartialChild = 0; 1.4700 + int i; 1.4701 + View child; 1.4702 + 1.4703 + // mItemCount - 1 since endPosition parameter is inclusive 1.4704 + endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; 1.4705 + final RecycleBin recycleBin = mRecycler; 1.4706 + final boolean shouldRecycle = recycleOnMeasure(); 1.4707 + final boolean[] isScrap = mIsScrap; 1.4708 + 1.4709 + for (i = startPosition; i <= endPosition; ++i) { 1.4710 + child = obtainView(i, isScrap); 1.4711 + 1.4712 + measureScrapChild(child, i, heightMeasureSpec); 1.4713 + 1.4714 + if (i > 0) { 1.4715 + // Count the item margin for all but one child 1.4716 + returnedWidth += itemMargin; 1.4717 + } 1.4718 + 1.4719 + // Recycle the view before we possibly return from the method 1.4720 + if (shouldRecycle) { 1.4721 + recycleBin.addScrapView(child, -1); 1.4722 + } 1.4723 + 1.4724 + returnedWidth += child.getMeasuredHeight(); 1.4725 + 1.4726 + if (returnedWidth >= maxWidth) { 1.4727 + // We went over, figure out which width to return. If returnedWidth > maxWidth, 1.4728 + // then the i'th position did not fit completely. 1.4729 + return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) 1.4730 + && (i > disallowPartialChildPosition) // We've past the min pos 1.4731 + && (prevWidthWithoutPartialChild > 0) // We have a prev width 1.4732 + && (returnedWidth != maxWidth) // i'th child did not fit completely 1.4733 + ? prevWidthWithoutPartialChild 1.4734 + : maxWidth; 1.4735 + } 1.4736 + 1.4737 + if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { 1.4738 + prevWidthWithoutPartialChild = returnedWidth; 1.4739 + } 1.4740 + } 1.4741 + 1.4742 + // At this point, we went through the range of children, and they each 1.4743 + // completely fit, so return the returnedWidth 1.4744 + return returnedWidth; 1.4745 + } 1.4746 + 1.4747 + private View makeAndAddView(int position, int offset, boolean flow, boolean selected) { 1.4748 + final int top; 1.4749 + final int left; 1.4750 + 1.4751 + if (mIsVertical) { 1.4752 + top = offset; 1.4753 + left = getPaddingLeft(); 1.4754 + } else { 1.4755 + top = getPaddingTop(); 1.4756 + left = offset; 1.4757 + } 1.4758 + 1.4759 + if (!mDataChanged) { 1.4760 + // Try to use an existing view for this position 1.4761 + final View activeChild = mRecycler.getActiveView(position); 1.4762 + if (activeChild != null) { 1.4763 + // Found it -- we're using an existing child 1.4764 + // This just needs to be positioned 1.4765 + setupChild(activeChild, position, top, left, flow, selected, true); 1.4766 + 1.4767 + return activeChild; 1.4768 + } 1.4769 + } 1.4770 + 1.4771 + // Make a new view for this position, or convert an unused view if possible 1.4772 + final View child = obtainView(position, mIsScrap); 1.4773 + 1.4774 + // This needs to be positioned and measured 1.4775 + setupChild(child, position, top, left, flow, selected, mIsScrap[0]); 1.4776 + 1.4777 + return child; 1.4778 + } 1.4779 + 1.4780 + @TargetApi(11) 1.4781 + private void setupChild(View child, int position, int top, int left, 1.4782 + boolean flow, boolean selected, boolean recycled) { 1.4783 + final boolean isSelected = selected && shouldShowSelector(); 1.4784 + final boolean updateChildSelected = isSelected != child.isSelected(); 1.4785 + final int touchMode = mTouchMode; 1.4786 + 1.4787 + final boolean isPressed = touchMode > TOUCH_MODE_DOWN && touchMode < TOUCH_MODE_DRAGGING && 1.4788 + mMotionPosition == position; 1.4789 + 1.4790 + final boolean updateChildPressed = isPressed != child.isPressed(); 1.4791 + final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); 1.4792 + 1.4793 + // Respect layout params that are already in the view. Otherwise make some up... 1.4794 + LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1.4795 + if (lp == null) { 1.4796 + lp = generateDefaultLayoutParams(); 1.4797 + } 1.4798 + 1.4799 + lp.viewType = mAdapter.getItemViewType(position); 1.4800 + 1.4801 + if (recycled && !lp.forceAdd) { 1.4802 + attachViewToParent(child, (flow ? -1 : 0), lp); 1.4803 + } else { 1.4804 + lp.forceAdd = false; 1.4805 + addViewInLayout(child, (flow ? -1 : 0), lp, true); 1.4806 + } 1.4807 + 1.4808 + if (updateChildSelected) { 1.4809 + child.setSelected(isSelected); 1.4810 + } 1.4811 + 1.4812 + if (updateChildPressed) { 1.4813 + child.setPressed(isPressed); 1.4814 + } 1.4815 + 1.4816 + if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mCheckStates != null) { 1.4817 + if (child instanceof Checkable) { 1.4818 + ((Checkable) child).setChecked(mCheckStates.get(position)); 1.4819 + } else if (getContext().getApplicationInfo().targetSdkVersion 1.4820 + >= Build.VERSION_CODES.HONEYCOMB) { 1.4821 + child.setActivated(mCheckStates.get(position)); 1.4822 + } 1.4823 + } 1.4824 + 1.4825 + if (needToMeasure) { 1.4826 + measureChild(child, lp); 1.4827 + } else { 1.4828 + cleanupLayoutState(child); 1.4829 + } 1.4830 + 1.4831 + final int w = child.getMeasuredWidth(); 1.4832 + final int h = child.getMeasuredHeight(); 1.4833 + 1.4834 + final int childTop = (mIsVertical && !flow ? top - h : top); 1.4835 + final int childLeft = (!mIsVertical && !flow ? left - w : left); 1.4836 + 1.4837 + if (needToMeasure) { 1.4838 + final int childRight = childLeft + w; 1.4839 + final int childBottom = childTop + h; 1.4840 + 1.4841 + child.layout(childLeft, childTop, childRight, childBottom); 1.4842 + } else { 1.4843 + child.offsetLeftAndRight(childLeft - child.getLeft()); 1.4844 + child.offsetTopAndBottom(childTop - child.getTop()); 1.4845 + } 1.4846 + } 1.4847 + 1.4848 + void fillGap(boolean down) { 1.4849 + final int childCount = getChildCount(); 1.4850 + 1.4851 + if (down) { 1.4852 + final int paddingStart = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.4853 + 1.4854 + final int lastEnd; 1.4855 + if (mIsVertical) { 1.4856 + lastEnd = getChildAt(childCount - 1).getBottom(); 1.4857 + } else { 1.4858 + lastEnd = getChildAt(childCount - 1).getRight(); 1.4859 + } 1.4860 + 1.4861 + final int offset = (childCount > 0 ? lastEnd + mItemMargin : paddingStart); 1.4862 + fillAfter(mFirstPosition + childCount, offset); 1.4863 + correctTooHigh(getChildCount()); 1.4864 + } else { 1.4865 + final int end; 1.4866 + final int firstStart; 1.4867 + 1.4868 + if (mIsVertical) { 1.4869 + end = getHeight() - getPaddingBottom(); 1.4870 + firstStart = getChildAt(0).getTop(); 1.4871 + } else { 1.4872 + end = getWidth() - getPaddingRight(); 1.4873 + firstStart = getChildAt(0).getLeft(); 1.4874 + } 1.4875 + 1.4876 + final int offset = (childCount > 0 ? firstStart - mItemMargin : end); 1.4877 + fillBefore(mFirstPosition - 1, offset); 1.4878 + correctTooLow(getChildCount()); 1.4879 + } 1.4880 + } 1.4881 + 1.4882 + private View fillBefore(int pos, int nextOffset) { 1.4883 + View selectedView = null; 1.4884 + 1.4885 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.4886 + 1.4887 + while (nextOffset > start && pos >= 0) { 1.4888 + boolean isSelected = (pos == mSelectedPosition); 1.4889 + View child = makeAndAddView(pos, nextOffset, false, isSelected); 1.4890 + 1.4891 + if (mIsVertical) { 1.4892 + nextOffset = child.getTop() - mItemMargin; 1.4893 + } else { 1.4894 + nextOffset = child.getLeft() - mItemMargin; 1.4895 + } 1.4896 + 1.4897 + if (isSelected) { 1.4898 + selectedView = child; 1.4899 + } 1.4900 + 1.4901 + pos--; 1.4902 + } 1.4903 + 1.4904 + mFirstPosition = pos + 1; 1.4905 + 1.4906 + return selectedView; 1.4907 + } 1.4908 + 1.4909 + private View fillAfter(int pos, int nextOffset) { 1.4910 + View selectedView = null; 1.4911 + 1.4912 + final int end = 1.4913 + (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); 1.4914 + 1.4915 + while (nextOffset < end && pos < mItemCount) { 1.4916 + boolean selected = (pos == mSelectedPosition); 1.4917 + 1.4918 + View child = makeAndAddView(pos, nextOffset, true, selected); 1.4919 + 1.4920 + if (mIsVertical) { 1.4921 + nextOffset = child.getBottom() + mItemMargin; 1.4922 + } else { 1.4923 + nextOffset = child.getRight() + mItemMargin; 1.4924 + } 1.4925 + 1.4926 + if (selected) { 1.4927 + selectedView = child; 1.4928 + } 1.4929 + 1.4930 + pos++; 1.4931 + } 1.4932 + 1.4933 + return selectedView; 1.4934 + } 1.4935 + 1.4936 + private View fillSpecific(int position, int offset) { 1.4937 + final boolean tempIsSelected = (position == mSelectedPosition); 1.4938 + View temp = makeAndAddView(position, offset, true, tempIsSelected); 1.4939 + 1.4940 + // Possibly changed again in fillBefore if we add rows above this one. 1.4941 + mFirstPosition = position; 1.4942 + 1.4943 + final int itemMargin = mItemMargin; 1.4944 + 1.4945 + final int offsetBefore; 1.4946 + if (mIsVertical) { 1.4947 + offsetBefore = temp.getTop() - itemMargin; 1.4948 + } else { 1.4949 + offsetBefore = temp.getLeft() - itemMargin; 1.4950 + } 1.4951 + final View before = fillBefore(position - 1, offsetBefore); 1.4952 + 1.4953 + // This will correct for the top of the first view not touching the top of the list 1.4954 + adjustViewsStartOrEnd(); 1.4955 + 1.4956 + final int offsetAfter; 1.4957 + if (mIsVertical) { 1.4958 + offsetAfter = temp.getBottom() + itemMargin; 1.4959 + } else { 1.4960 + offsetAfter = temp.getRight() + itemMargin; 1.4961 + } 1.4962 + final View after = fillAfter(position + 1, offsetAfter); 1.4963 + 1.4964 + final int childCount = getChildCount(); 1.4965 + if (childCount > 0) { 1.4966 + correctTooHigh(childCount); 1.4967 + } 1.4968 + 1.4969 + if (tempIsSelected) { 1.4970 + return temp; 1.4971 + } else if (before != null) { 1.4972 + return before; 1.4973 + } else { 1.4974 + return after; 1.4975 + } 1.4976 + } 1.4977 + 1.4978 + private View fillFromOffset(int nextOffset) { 1.4979 + mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); 1.4980 + mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); 1.4981 + 1.4982 + if (mFirstPosition < 0) { 1.4983 + mFirstPosition = 0; 1.4984 + } 1.4985 + 1.4986 + return fillAfter(mFirstPosition, nextOffset); 1.4987 + } 1.4988 + 1.4989 + private View fillFromMiddle(int start, int end) { 1.4990 + final int size = end - start; 1.4991 + int position = reconcileSelectedPosition(); 1.4992 + 1.4993 + View selected = makeAndAddView(position, start, true, true); 1.4994 + mFirstPosition = position; 1.4995 + 1.4996 + if (mIsVertical) { 1.4997 + int selectedHeight = selected.getMeasuredHeight(); 1.4998 + if (selectedHeight <= size) { 1.4999 + selected.offsetTopAndBottom((size - selectedHeight) / 2); 1.5000 + } 1.5001 + } else { 1.5002 + int selectedWidth = selected.getMeasuredWidth(); 1.5003 + if (selectedWidth <= size) { 1.5004 + selected.offsetLeftAndRight((size - selectedWidth) / 2); 1.5005 + } 1.5006 + } 1.5007 + 1.5008 + fillBeforeAndAfter(selected, position); 1.5009 + correctTooHigh(getChildCount()); 1.5010 + 1.5011 + return selected; 1.5012 + } 1.5013 + 1.5014 + private void fillBeforeAndAfter(View selected, int position) { 1.5015 + final int itemMargin = mItemMargin; 1.5016 + 1.5017 + final int offsetBefore; 1.5018 + if (mIsVertical) { 1.5019 + offsetBefore = selected.getTop() - itemMargin; 1.5020 + } else { 1.5021 + offsetBefore = selected.getLeft() - itemMargin; 1.5022 + } 1.5023 + 1.5024 + fillBefore(position - 1, offsetBefore); 1.5025 + 1.5026 + adjustViewsStartOrEnd(); 1.5027 + 1.5028 + final int offsetAfter; 1.5029 + if (mIsVertical) { 1.5030 + offsetAfter = selected.getBottom() + itemMargin; 1.5031 + } else { 1.5032 + offsetAfter = selected.getRight() + itemMargin; 1.5033 + } 1.5034 + 1.5035 + fillAfter(position + 1, offsetAfter); 1.5036 + } 1.5037 + 1.5038 + private View fillFromSelection(int selectedTop, int start, int end) { 1.5039 + final int selectedPosition = mSelectedPosition; 1.5040 + View selected; 1.5041 + 1.5042 + selected = makeAndAddView(selectedPosition, selectedTop, true, true); 1.5043 + 1.5044 + final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); 1.5045 + final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); 1.5046 + 1.5047 + // Some of the newly selected item extends below the bottom of the list 1.5048 + if (selectedEnd > end) { 1.5049 + // Find space available above the selection into which we can scroll 1.5050 + // upwards 1.5051 + final int spaceAbove = selectedStart - start; 1.5052 + 1.5053 + // Find space required to bring the bottom of the selected item 1.5054 + // fully into view 1.5055 + final int spaceBelow = selectedEnd - end; 1.5056 + 1.5057 + final int offset = Math.min(spaceAbove, spaceBelow); 1.5058 + 1.5059 + // Now offset the selected item to get it into view 1.5060 + selected.offsetTopAndBottom(-offset); 1.5061 + } else if (selectedStart < start) { 1.5062 + // Find space required to bring the top of the selected item fully 1.5063 + // into view 1.5064 + final int spaceAbove = start - selectedStart; 1.5065 + 1.5066 + // Find space available below the selection into which we can scroll 1.5067 + // downwards 1.5068 + final int spaceBelow = end - selectedEnd; 1.5069 + 1.5070 + final int offset = Math.min(spaceAbove, spaceBelow); 1.5071 + 1.5072 + // Offset the selected item to get it into view 1.5073 + selected.offsetTopAndBottom(offset); 1.5074 + } 1.5075 + 1.5076 + // Fill in views above and below 1.5077 + fillBeforeAndAfter(selected, selectedPosition); 1.5078 + correctTooHigh(getChildCount()); 1.5079 + 1.5080 + return selected; 1.5081 + } 1.5082 + 1.5083 + private void correctTooHigh(int childCount) { 1.5084 + // First see if the last item is visible. If it is not, it is OK for the 1.5085 + // top of the list to be pushed up. 1.5086 + final int lastPosition = mFirstPosition + childCount - 1; 1.5087 + if (lastPosition != mItemCount - 1 || childCount == 0) { 1.5088 + return; 1.5089 + } 1.5090 + 1.5091 + // Get the last child ... 1.5092 + final View lastChild = getChildAt(childCount - 1); 1.5093 + 1.5094 + // ... and its end edge 1.5095 + final int lastEnd; 1.5096 + if (mIsVertical) { 1.5097 + lastEnd = lastChild.getBottom(); 1.5098 + } else { 1.5099 + lastEnd = lastChild.getRight(); 1.5100 + } 1.5101 + 1.5102 + // This is bottom of our drawable area 1.5103 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.5104 + final int end = 1.5105 + (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); 1.5106 + 1.5107 + // This is how far the end edge of the last view is from the end of the 1.5108 + // drawable area 1.5109 + int endOffset = end - lastEnd; 1.5110 + 1.5111 + View firstChild = getChildAt(0); 1.5112 + int firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); 1.5113 + 1.5114 + // Make sure we are 1) Too high, and 2) Either there are more rows above the 1.5115 + // first row or the first row is scrolled off the top of the drawable area 1.5116 + if (endOffset > 0 && (mFirstPosition > 0 || firstStart < start)) { 1.5117 + if (mFirstPosition == 0) { 1.5118 + // Don't pull the top too far down 1.5119 + endOffset = Math.min(endOffset, start - firstStart); 1.5120 + } 1.5121 + 1.5122 + // Move everything down 1.5123 + offsetChildren(endOffset); 1.5124 + 1.5125 + if (mFirstPosition > 0) { 1.5126 + firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); 1.5127 + 1.5128 + // Fill the gap that was opened above mFirstPosition with more rows, if 1.5129 + // possible 1.5130 + fillBefore(mFirstPosition - 1, firstStart - mItemMargin); 1.5131 + 1.5132 + // Close up the remaining gap 1.5133 + adjustViewsStartOrEnd(); 1.5134 + } 1.5135 + } 1.5136 + } 1.5137 + 1.5138 + private void correctTooLow(int childCount) { 1.5139 + // First see if the first item is visible. If it is not, it is OK for the 1.5140 + // bottom of the list to be pushed down. 1.5141 + if (mFirstPosition != 0 || childCount == 0) { 1.5142 + return; 1.5143 + } 1.5144 + 1.5145 + final View first = getChildAt(0); 1.5146 + final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); 1.5147 + 1.5148 + final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); 1.5149 + 1.5150 + final int end; 1.5151 + if (mIsVertical) { 1.5152 + end = getHeight() - getPaddingBottom(); 1.5153 + } else { 1.5154 + end = getWidth() - getPaddingRight(); 1.5155 + } 1.5156 + 1.5157 + // This is how far the start edge of the first view is from the start of the 1.5158 + // drawable area 1.5159 + int startOffset = firstStart - start; 1.5160 + 1.5161 + View last = getChildAt(childCount - 1); 1.5162 + int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); 1.5163 + 1.5164 + int lastPosition = mFirstPosition + childCount - 1; 1.5165 + 1.5166 + // Make sure we are 1) Too low, and 2) Either there are more columns/rows below the 1.5167 + // last column/row or the last column/row is scrolled off the end of the 1.5168 + // drawable area 1.5169 + if (startOffset > 0) { 1.5170 + if (lastPosition < mItemCount - 1 || lastEnd > end) { 1.5171 + if (lastPosition == mItemCount - 1) { 1.5172 + // Don't pull the bottom too far up 1.5173 + startOffset = Math.min(startOffset, lastEnd - end); 1.5174 + } 1.5175 + 1.5176 + // Move everything up 1.5177 + offsetChildren(-startOffset); 1.5178 + 1.5179 + if (lastPosition < mItemCount - 1) { 1.5180 + lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); 1.5181 + 1.5182 + // Fill the gap that was opened below the last position with more rows, if 1.5183 + // possible 1.5184 + fillAfter(lastPosition + 1, lastEnd + mItemMargin); 1.5185 + 1.5186 + // Close up the remaining gap 1.5187 + adjustViewsStartOrEnd(); 1.5188 + } 1.5189 + } else if (lastPosition == mItemCount - 1) { 1.5190 + adjustViewsStartOrEnd(); 1.5191 + } 1.5192 + } 1.5193 + } 1.5194 + 1.5195 + private void adjustViewsStartOrEnd() { 1.5196 + if (getChildCount() == 0) { 1.5197 + return; 1.5198 + } 1.5199 + 1.5200 + final View firstChild = getChildAt(0); 1.5201 + 1.5202 + int delta; 1.5203 + if (mIsVertical) { 1.5204 + delta = firstChild.getTop() - getPaddingTop() - mItemMargin; 1.5205 + } else { 1.5206 + delta = firstChild.getLeft() - getPaddingLeft() - mItemMargin; 1.5207 + } 1.5208 + 1.5209 + if (delta < 0) { 1.5210 + // We only are looking to see if we are too low, not too high 1.5211 + delta = 0; 1.5212 + } 1.5213 + 1.5214 + if (delta != 0) { 1.5215 + offsetChildren(-delta); 1.5216 + } 1.5217 + } 1.5218 + 1.5219 + @TargetApi(14) 1.5220 + private SparseBooleanArray cloneCheckStates() { 1.5221 + if (mCheckStates == null) { 1.5222 + return null; 1.5223 + } 1.5224 + 1.5225 + SparseBooleanArray checkedStates; 1.5226 + 1.5227 + if (Build.VERSION.SDK_INT >= 14) { 1.5228 + checkedStates = mCheckStates.clone(); 1.5229 + } else { 1.5230 + checkedStates = new SparseBooleanArray(); 1.5231 + 1.5232 + for (int i = 0; i < mCheckStates.size(); i++) { 1.5233 + checkedStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i)); 1.5234 + } 1.5235 + } 1.5236 + 1.5237 + return checkedStates; 1.5238 + } 1.5239 + 1.5240 + private int findSyncPosition() { 1.5241 + int itemCount = mItemCount; 1.5242 + 1.5243 + if (itemCount == 0) { 1.5244 + return INVALID_POSITION; 1.5245 + } 1.5246 + 1.5247 + final long idToMatch = mSyncRowId; 1.5248 + 1.5249 + // If there isn't a selection don't hunt for it 1.5250 + if (idToMatch == INVALID_ROW_ID) { 1.5251 + return INVALID_POSITION; 1.5252 + } 1.5253 + 1.5254 + // Pin seed to reasonable values 1.5255 + int seed = mSyncPosition; 1.5256 + seed = Math.max(0, seed); 1.5257 + seed = Math.min(itemCount - 1, seed); 1.5258 + 1.5259 + long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; 1.5260 + 1.5261 + long rowId; 1.5262 + 1.5263 + // first position scanned so far 1.5264 + int first = seed; 1.5265 + 1.5266 + // last position scanned so far 1.5267 + int last = seed; 1.5268 + 1.5269 + // True if we should move down on the next iteration 1.5270 + boolean next = false; 1.5271 + 1.5272 + // True when we have looked at the first item in the data 1.5273 + boolean hitFirst; 1.5274 + 1.5275 + // True when we have looked at the last item in the data 1.5276 + boolean hitLast; 1.5277 + 1.5278 + // Get the item ID locally (instead of getItemIdAtPosition), so 1.5279 + // we need the adapter 1.5280 + final ListAdapter adapter = mAdapter; 1.5281 + if (adapter == null) { 1.5282 + return INVALID_POSITION; 1.5283 + } 1.5284 + 1.5285 + while (SystemClock.uptimeMillis() <= endTime) { 1.5286 + rowId = adapter.getItemId(seed); 1.5287 + if (rowId == idToMatch) { 1.5288 + // Found it! 1.5289 + return seed; 1.5290 + } 1.5291 + 1.5292 + hitLast = (last == itemCount - 1); 1.5293 + hitFirst = (first == 0); 1.5294 + 1.5295 + if (hitLast && hitFirst) { 1.5296 + // Looked at everything 1.5297 + break; 1.5298 + } 1.5299 + 1.5300 + if (hitFirst || (next && !hitLast)) { 1.5301 + // Either we hit the top, or we are trying to move down 1.5302 + last++; 1.5303 + seed = last; 1.5304 + 1.5305 + // Try going up next time 1.5306 + next = false; 1.5307 + } else if (hitLast || (!next && !hitFirst)) { 1.5308 + // Either we hit the bottom, or we are trying to move up 1.5309 + first--; 1.5310 + seed = first; 1.5311 + 1.5312 + // Try going down next time 1.5313 + next = true; 1.5314 + } 1.5315 + } 1.5316 + 1.5317 + return INVALID_POSITION; 1.5318 + } 1.5319 + 1.5320 + @TargetApi(16) 1.5321 + private View obtainView(int position, boolean[] isScrap) { 1.5322 + isScrap[0] = false; 1.5323 + 1.5324 + View scrapView = mRecycler.getTransientStateView(position); 1.5325 + if (scrapView != null) { 1.5326 + return scrapView; 1.5327 + } 1.5328 + 1.5329 + scrapView = mRecycler.getScrapView(position); 1.5330 + 1.5331 + final View child; 1.5332 + if (scrapView != null) { 1.5333 + child = mAdapter.getView(position, scrapView, this); 1.5334 + 1.5335 + if (child != scrapView) { 1.5336 + mRecycler.addScrapView(scrapView, position); 1.5337 + } else { 1.5338 + isScrap[0] = true; 1.5339 + } 1.5340 + } else { 1.5341 + child = mAdapter.getView(position, null, this); 1.5342 + } 1.5343 + 1.5344 + if (ViewCompat.getImportantForAccessibility(child) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1.5345 + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 1.5346 + } 1.5347 + 1.5348 + if (mHasStableIds) { 1.5349 + LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1.5350 + 1.5351 + if (lp == null) { 1.5352 + lp = generateDefaultLayoutParams(); 1.5353 + } else if (!checkLayoutParams(lp)) { 1.5354 + lp = generateLayoutParams(lp); 1.5355 + } 1.5356 + 1.5357 + lp.id = mAdapter.getItemId(position); 1.5358 + 1.5359 + child.setLayoutParams(lp); 1.5360 + } 1.5361 + 1.5362 + if (mAccessibilityDelegate == null) { 1.5363 + mAccessibilityDelegate = new ListItemAccessibilityDelegate(); 1.5364 + } 1.5365 + 1.5366 + ViewCompat.setAccessibilityDelegate(child, mAccessibilityDelegate); 1.5367 + 1.5368 + return child; 1.5369 + } 1.5370 + 1.5371 + void resetState() { 1.5372 + removeAllViewsInLayout(); 1.5373 + 1.5374 + mSelectedStart = 0; 1.5375 + mFirstPosition = 0; 1.5376 + mDataChanged = false; 1.5377 + mNeedSync = false; 1.5378 + mPendingSync = null; 1.5379 + mOldSelectedPosition = INVALID_POSITION; 1.5380 + mOldSelectedRowId = INVALID_ROW_ID; 1.5381 + 1.5382 + mOverScroll = 0; 1.5383 + 1.5384 + setSelectedPositionInt(INVALID_POSITION); 1.5385 + setNextSelectedPositionInt(INVALID_POSITION); 1.5386 + 1.5387 + mSelectorPosition = INVALID_POSITION; 1.5388 + mSelectorRect.setEmpty(); 1.5389 + 1.5390 + invalidate(); 1.5391 + } 1.5392 + 1.5393 + private void rememberSyncState() { 1.5394 + if (getChildCount() == 0) { 1.5395 + return; 1.5396 + } 1.5397 + 1.5398 + mNeedSync = true; 1.5399 + 1.5400 + if (mSelectedPosition >= 0) { 1.5401 + View child = getChildAt(mSelectedPosition - mFirstPosition); 1.5402 + 1.5403 + mSyncRowId = mNextSelectedRowId; 1.5404 + mSyncPosition = mNextSelectedPosition; 1.5405 + 1.5406 + if (child != null) { 1.5407 + mSpecificStart = (mIsVertical ? child.getTop() : child.getLeft()); 1.5408 + } 1.5409 + 1.5410 + mSyncMode = SYNC_SELECTED_POSITION; 1.5411 + } else { 1.5412 + // Sync the based on the offset of the first view 1.5413 + View child = getChildAt(0); 1.5414 + ListAdapter adapter = getAdapter(); 1.5415 + 1.5416 + if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { 1.5417 + mSyncRowId = adapter.getItemId(mFirstPosition); 1.5418 + } else { 1.5419 + mSyncRowId = NO_ID; 1.5420 + } 1.5421 + 1.5422 + mSyncPosition = mFirstPosition; 1.5423 + 1.5424 + if (child != null) { 1.5425 + mSpecificStart = child.getTop(); 1.5426 + } 1.5427 + 1.5428 + mSyncMode = SYNC_FIRST_POSITION; 1.5429 + } 1.5430 + } 1.5431 + 1.5432 + private ContextMenuInfo createContextMenuInfo(View view, int position, long id) { 1.5433 + return new AdapterContextMenuInfo(view, position, id); 1.5434 + } 1.5435 + 1.5436 + @TargetApi(11) 1.5437 + private void updateOnScreenCheckedViews() { 1.5438 + final int firstPos = mFirstPosition; 1.5439 + final int count = getChildCount(); 1.5440 + 1.5441 + final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion 1.5442 + >= Build.VERSION_CODES.HONEYCOMB; 1.5443 + 1.5444 + for (int i = 0; i < count; i++) { 1.5445 + final View child = getChildAt(i); 1.5446 + final int position = firstPos + i; 1.5447 + 1.5448 + if (child instanceof Checkable) { 1.5449 + ((Checkable) child).setChecked(mCheckStates.get(position)); 1.5450 + } else if (useActivated) { 1.5451 + child.setActivated(mCheckStates.get(position)); 1.5452 + } 1.5453 + } 1.5454 + } 1.5455 + 1.5456 + @Override 1.5457 + public boolean performItemClick(View view, int position, long id) { 1.5458 + boolean checkedStateChanged = false; 1.5459 + 1.5460 + if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { 1.5461 + boolean checked = !mCheckStates.get(position, false); 1.5462 + mCheckStates.put(position, checked); 1.5463 + 1.5464 + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1.5465 + if (checked) { 1.5466 + mCheckedIdStates.put(mAdapter.getItemId(position), position); 1.5467 + } else { 1.5468 + mCheckedIdStates.delete(mAdapter.getItemId(position)); 1.5469 + } 1.5470 + } 1.5471 + 1.5472 + if (checked) { 1.5473 + mCheckedItemCount++; 1.5474 + } else { 1.5475 + mCheckedItemCount--; 1.5476 + } 1.5477 + 1.5478 + checkedStateChanged = true; 1.5479 + } else if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0) { 1.5480 + boolean checked = !mCheckStates.get(position, false); 1.5481 + if (checked) { 1.5482 + mCheckStates.clear(); 1.5483 + mCheckStates.put(position, true); 1.5484 + 1.5485 + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1.5486 + mCheckedIdStates.clear(); 1.5487 + mCheckedIdStates.put(mAdapter.getItemId(position), position); 1.5488 + } 1.5489 + 1.5490 + mCheckedItemCount = 1; 1.5491 + } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { 1.5492 + mCheckedItemCount = 0; 1.5493 + } 1.5494 + 1.5495 + checkedStateChanged = true; 1.5496 + } 1.5497 + 1.5498 + if (checkedStateChanged) { 1.5499 + updateOnScreenCheckedViews(); 1.5500 + } 1.5501 + 1.5502 + return super.performItemClick(view, position, id); 1.5503 + } 1.5504 + 1.5505 + private boolean performLongPress(final View child, 1.5506 + final int longPressPosition, final long longPressId) { 1.5507 + // CHOICE_MODE_MULTIPLE_MODAL takes over long press. 1.5508 + boolean handled = false; 1.5509 + 1.5510 + OnItemLongClickListener listener = getOnItemLongClickListener(); 1.5511 + if (listener != null) { 1.5512 + handled = listener.onItemLongClick(TwoWayView.this, child, 1.5513 + longPressPosition, longPressId); 1.5514 + } 1.5515 + 1.5516 + if (!handled) { 1.5517 + mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); 1.5518 + handled = super.showContextMenuForChild(TwoWayView.this); 1.5519 + } 1.5520 + 1.5521 + if (handled) { 1.5522 + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 1.5523 + } 1.5524 + 1.5525 + return handled; 1.5526 + } 1.5527 + 1.5528 + @Override 1.5529 + protected LayoutParams generateDefaultLayoutParams() { 1.5530 + if (mIsVertical) { 1.5531 + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 1.5532 + } else { 1.5533 + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 1.5534 + } 1.5535 + } 1.5536 + 1.5537 + @Override 1.5538 + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 1.5539 + return new LayoutParams(lp); 1.5540 + } 1.5541 + 1.5542 + @Override 1.5543 + protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) { 1.5544 + return lp instanceof LayoutParams; 1.5545 + } 1.5546 + 1.5547 + @Override 1.5548 + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1.5549 + return new LayoutParams(getContext(), attrs); 1.5550 + } 1.5551 + 1.5552 + @Override 1.5553 + protected ContextMenuInfo getContextMenuInfo() { 1.5554 + return mContextMenuInfo; 1.5555 + } 1.5556 + 1.5557 + @Override 1.5558 + public Parcelable onSaveInstanceState() { 1.5559 + Parcelable superState = super.onSaveInstanceState(); 1.5560 + SavedState ss = new SavedState(superState); 1.5561 + 1.5562 + if (mPendingSync != null) { 1.5563 + ss.selectedId = mPendingSync.selectedId; 1.5564 + ss.firstId = mPendingSync.firstId; 1.5565 + ss.viewStart = mPendingSync.viewStart; 1.5566 + ss.position = mPendingSync.position; 1.5567 + ss.height = mPendingSync.height; 1.5568 + 1.5569 + return ss; 1.5570 + } 1.5571 + 1.5572 + boolean haveChildren = (getChildCount() > 0 && mItemCount > 0); 1.5573 + long selectedId = getSelectedItemId(); 1.5574 + ss.selectedId = selectedId; 1.5575 + ss.height = getHeight(); 1.5576 + 1.5577 + if (selectedId >= 0) { 1.5578 + ss.viewStart = mSelectedStart; 1.5579 + ss.position = getSelectedItemPosition(); 1.5580 + ss.firstId = INVALID_POSITION; 1.5581 + } else if (haveChildren && mFirstPosition > 0) { 1.5582 + // Remember the position of the first child. 1.5583 + // We only do this if we are not currently at the top of 1.5584 + // the list, for two reasons: 1.5585 + // 1.5586 + // (1) The list may be in the process of becoming empty, in 1.5587 + // which case mItemCount may not be 0, but if we try to 1.5588 + // ask for any information about position 0 we will crash. 1.5589 + // 1.5590 + // (2) Being "at the top" seems like a special case, anyway, 1.5591 + // and the user wouldn't expect to end up somewhere else when 1.5592 + // they revisit the list even if its content has changed. 1.5593 + 1.5594 + View child = getChildAt(0); 1.5595 + ss.viewStart = (mIsVertical ? child.getTop() : child.getLeft()); 1.5596 + 1.5597 + int firstPos = mFirstPosition; 1.5598 + if (firstPos >= mItemCount) { 1.5599 + firstPos = mItemCount - 1; 1.5600 + } 1.5601 + 1.5602 + ss.position = firstPos; 1.5603 + ss.firstId = mAdapter.getItemId(firstPos); 1.5604 + } else { 1.5605 + ss.viewStart = 0; 1.5606 + ss.firstId = INVALID_POSITION; 1.5607 + ss.position = 0; 1.5608 + } 1.5609 + 1.5610 + if (mCheckStates != null) { 1.5611 + ss.checkState = cloneCheckStates(); 1.5612 + } 1.5613 + 1.5614 + if (mCheckedIdStates != null) { 1.5615 + final LongSparseArray<Integer> idState = new LongSparseArray<Integer>(); 1.5616 + 1.5617 + final int count = mCheckedIdStates.size(); 1.5618 + for (int i = 0; i < count; i++) { 1.5619 + idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i)); 1.5620 + } 1.5621 + 1.5622 + ss.checkIdState = idState; 1.5623 + } 1.5624 + 1.5625 + ss.checkedItemCount = mCheckedItemCount; 1.5626 + 1.5627 + return ss; 1.5628 + } 1.5629 + 1.5630 + @Override 1.5631 + public void onRestoreInstanceState(Parcelable state) { 1.5632 + SavedState ss = (SavedState) state; 1.5633 + super.onRestoreInstanceState(ss.getSuperState()); 1.5634 + 1.5635 + mDataChanged = true; 1.5636 + mSyncHeight = ss.height; 1.5637 + 1.5638 + if (ss.selectedId >= 0) { 1.5639 + mNeedSync = true; 1.5640 + mPendingSync = ss; 1.5641 + mSyncRowId = ss.selectedId; 1.5642 + mSyncPosition = ss.position; 1.5643 + mSpecificStart = ss.viewStart; 1.5644 + mSyncMode = SYNC_SELECTED_POSITION; 1.5645 + } else if (ss.firstId >= 0) { 1.5646 + setSelectedPositionInt(INVALID_POSITION); 1.5647 + 1.5648 + // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync 1.5649 + setNextSelectedPositionInt(INVALID_POSITION); 1.5650 + 1.5651 + mSelectorPosition = INVALID_POSITION; 1.5652 + mNeedSync = true; 1.5653 + mPendingSync = ss; 1.5654 + mSyncRowId = ss.firstId; 1.5655 + mSyncPosition = ss.position; 1.5656 + mSpecificStart = ss.viewStart; 1.5657 + mSyncMode = SYNC_FIRST_POSITION; 1.5658 + } 1.5659 + 1.5660 + if (ss.checkState != null) { 1.5661 + mCheckStates = ss.checkState; 1.5662 + } 1.5663 + 1.5664 + if (ss.checkIdState != null) { 1.5665 + mCheckedIdStates = ss.checkIdState; 1.5666 + } 1.5667 + 1.5668 + mCheckedItemCount = ss.checkedItemCount; 1.5669 + 1.5670 + requestLayout(); 1.5671 + } 1.5672 + 1.5673 + public static class LayoutParams extends ViewGroup.LayoutParams { 1.5674 + /** 1.5675 + * Type of this view as reported by the adapter 1.5676 + */ 1.5677 + int viewType; 1.5678 + 1.5679 + /** 1.5680 + * The stable ID of the item this view displays 1.5681 + */ 1.5682 + long id = -1; 1.5683 + 1.5684 + /** 1.5685 + * The position the view was removed from when pulled out of the 1.5686 + * scrap heap. 1.5687 + * @hide 1.5688 + */ 1.5689 + int scrappedFromPosition; 1.5690 + 1.5691 + /** 1.5692 + * When a TwoWayView is measured with an AT_MOST measure spec, it needs 1.5693 + * to obtain children views to measure itself. When doing so, the children 1.5694 + * are not attached to the window, but put in the recycler which assumes 1.5695 + * they've been attached before. Setting this flag will force the reused 1.5696 + * view to be attached to the window rather than just attached to the 1.5697 + * parent. 1.5698 + */ 1.5699 + boolean forceAdd; 1.5700 + 1.5701 + public LayoutParams(int width, int height) { 1.5702 + super(width, height); 1.5703 + 1.5704 + if (this.width == MATCH_PARENT) { 1.5705 + Log.w(LOGTAG, "Constructing LayoutParams with width FILL_PARENT " + 1.5706 + "does not make much sense as the view might change orientation. " + 1.5707 + "Falling back to WRAP_CONTENT"); 1.5708 + this.width = WRAP_CONTENT; 1.5709 + } 1.5710 + 1.5711 + if (this.height == MATCH_PARENT) { 1.5712 + Log.w(LOGTAG, "Constructing LayoutParams with height FILL_PARENT " + 1.5713 + "does not make much sense as the view might change orientation. " + 1.5714 + "Falling back to WRAP_CONTENT"); 1.5715 + this.height = WRAP_CONTENT; 1.5716 + } 1.5717 + } 1.5718 + 1.5719 + public LayoutParams(Context c, AttributeSet attrs) { 1.5720 + super(c, attrs); 1.5721 + 1.5722 + if (this.width == MATCH_PARENT) { 1.5723 + Log.w(LOGTAG, "Inflation setting LayoutParams width to MATCH_PARENT - " + 1.5724 + "does not make much sense as the view might change orientation. " + 1.5725 + "Falling back to WRAP_CONTENT"); 1.5726 + this.width = MATCH_PARENT; 1.5727 + } 1.5728 + 1.5729 + if (this.height == MATCH_PARENT) { 1.5730 + Log.w(LOGTAG, "Inflation setting LayoutParams height to MATCH_PARENT - " + 1.5731 + "does not make much sense as the view might change orientation. " + 1.5732 + "Falling back to WRAP_CONTENT"); 1.5733 + this.height = WRAP_CONTENT; 1.5734 + } 1.5735 + } 1.5736 + 1.5737 + public LayoutParams(ViewGroup.LayoutParams other) { 1.5738 + super(other); 1.5739 + 1.5740 + if (this.width == MATCH_PARENT) { 1.5741 + Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " + 1.5742 + "does not make much sense as the view might change orientation. " + 1.5743 + "Falling back to WRAP_CONTENT"); 1.5744 + this.width = WRAP_CONTENT; 1.5745 + } 1.5746 + 1.5747 + if (this.height == MATCH_PARENT) { 1.5748 + Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " + 1.5749 + "does not make much sense as the view might change orientation. " + 1.5750 + "Falling back to WRAP_CONTENT"); 1.5751 + this.height = WRAP_CONTENT; 1.5752 + } 1.5753 + } 1.5754 + } 1.5755 + 1.5756 + class RecycleBin { 1.5757 + private RecyclerListener mRecyclerListener; 1.5758 + private int mFirstActivePosition; 1.5759 + private View[] mActiveViews = new View[0]; 1.5760 + private ArrayList<View>[] mScrapViews; 1.5761 + private int mViewTypeCount; 1.5762 + private ArrayList<View> mCurrentScrap; 1.5763 + private SparseArrayCompat<View> mTransientStateViews; 1.5764 + 1.5765 + public void setViewTypeCount(int viewTypeCount) { 1.5766 + if (viewTypeCount < 1) { 1.5767 + throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); 1.5768 + } 1.5769 + 1.5770 + @SuppressWarnings({"unchecked", "rawtypes"}) 1.5771 + ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; 1.5772 + for (int i = 0; i < viewTypeCount; i++) { 1.5773 + scrapViews[i] = new ArrayList<View>(); 1.5774 + } 1.5775 + 1.5776 + mViewTypeCount = viewTypeCount; 1.5777 + mCurrentScrap = scrapViews[0]; 1.5778 + mScrapViews = scrapViews; 1.5779 + } 1.5780 + 1.5781 + public void markChildrenDirty() { 1.5782 + if (mViewTypeCount == 1) { 1.5783 + final ArrayList<View> scrap = mCurrentScrap; 1.5784 + final int scrapCount = scrap.size(); 1.5785 + 1.5786 + for (int i = 0; i < scrapCount; i++) { 1.5787 + scrap.get(i).forceLayout(); 1.5788 + } 1.5789 + } else { 1.5790 + final int typeCount = mViewTypeCount; 1.5791 + for (int i = 0; i < typeCount; i++) { 1.5792 + final ArrayList<View> scrap = mScrapViews[i]; 1.5793 + final int scrapCount = scrap.size(); 1.5794 + 1.5795 + for (int j = 0; j < scrapCount; j++) { 1.5796 + scrap.get(j).forceLayout(); 1.5797 + } 1.5798 + } 1.5799 + } 1.5800 + 1.5801 + if (mTransientStateViews != null) { 1.5802 + final int count = mTransientStateViews.size(); 1.5803 + for (int i = 0; i < count; i++) { 1.5804 + mTransientStateViews.valueAt(i).forceLayout(); 1.5805 + } 1.5806 + } 1.5807 + } 1.5808 + 1.5809 + public boolean shouldRecycleViewType(int viewType) { 1.5810 + return viewType >= 0; 1.5811 + } 1.5812 + 1.5813 + void clear() { 1.5814 + if (mViewTypeCount == 1) { 1.5815 + final ArrayList<View> scrap = mCurrentScrap; 1.5816 + final int scrapCount = scrap.size(); 1.5817 + 1.5818 + for (int i = 0; i < scrapCount; i++) { 1.5819 + removeDetachedView(scrap.remove(scrapCount - 1 - i), false); 1.5820 + } 1.5821 + } else { 1.5822 + final int typeCount = mViewTypeCount; 1.5823 + for (int i = 0; i < typeCount; i++) { 1.5824 + final ArrayList<View> scrap = mScrapViews[i]; 1.5825 + final int scrapCount = scrap.size(); 1.5826 + 1.5827 + for (int j = 0; j < scrapCount; j++) { 1.5828 + removeDetachedView(scrap.remove(scrapCount - 1 - j), false); 1.5829 + } 1.5830 + } 1.5831 + } 1.5832 + 1.5833 + if (mTransientStateViews != null) { 1.5834 + mTransientStateViews.clear(); 1.5835 + } 1.5836 + } 1.5837 + 1.5838 + void fillActiveViews(int childCount, int firstActivePosition) { 1.5839 + if (mActiveViews.length < childCount) { 1.5840 + mActiveViews = new View[childCount]; 1.5841 + } 1.5842 + 1.5843 + mFirstActivePosition = firstActivePosition; 1.5844 + 1.5845 + final View[] activeViews = mActiveViews; 1.5846 + for (int i = 0; i < childCount; i++) { 1.5847 + View child = getChildAt(i); 1.5848 + 1.5849 + // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. 1.5850 + // However, we will NOT place them into scrap views. 1.5851 + activeViews[i] = child; 1.5852 + } 1.5853 + } 1.5854 + 1.5855 + View getActiveView(int position) { 1.5856 + final int index = position - mFirstActivePosition; 1.5857 + final View[] activeViews = mActiveViews; 1.5858 + 1.5859 + if (index >= 0 && index < activeViews.length) { 1.5860 + final View match = activeViews[index]; 1.5861 + activeViews[index] = null; 1.5862 + 1.5863 + return match; 1.5864 + } 1.5865 + 1.5866 + return null; 1.5867 + } 1.5868 + 1.5869 + View getTransientStateView(int position) { 1.5870 + if (mTransientStateViews == null) { 1.5871 + return null; 1.5872 + } 1.5873 + 1.5874 + final int index = mTransientStateViews.indexOfKey(position); 1.5875 + if (index < 0) { 1.5876 + return null; 1.5877 + } 1.5878 + 1.5879 + final View result = mTransientStateViews.valueAt(index); 1.5880 + mTransientStateViews.removeAt(index); 1.5881 + 1.5882 + return result; 1.5883 + } 1.5884 + 1.5885 + void clearTransientStateViews() { 1.5886 + if (mTransientStateViews != null) { 1.5887 + mTransientStateViews.clear(); 1.5888 + } 1.5889 + } 1.5890 + 1.5891 + View getScrapView(int position) { 1.5892 + if (mViewTypeCount == 1) { 1.5893 + return retrieveFromScrap(mCurrentScrap, position); 1.5894 + } else { 1.5895 + int whichScrap = mAdapter.getItemViewType(position); 1.5896 + if (whichScrap >= 0 && whichScrap < mScrapViews.length) { 1.5897 + return retrieveFromScrap(mScrapViews[whichScrap], position); 1.5898 + } 1.5899 + } 1.5900 + 1.5901 + return null; 1.5902 + } 1.5903 + 1.5904 + @TargetApi(14) 1.5905 + void addScrapView(View scrap, int position) { 1.5906 + LayoutParams lp = (LayoutParams) scrap.getLayoutParams(); 1.5907 + if (lp == null) { 1.5908 + return; 1.5909 + } 1.5910 + 1.5911 + lp.scrappedFromPosition = position; 1.5912 + 1.5913 + final int viewType = lp.viewType; 1.5914 + final boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap); 1.5915 + 1.5916 + // Don't put views that should be ignored into the scrap heap 1.5917 + if (!shouldRecycleViewType(viewType) || scrapHasTransientState) { 1.5918 + if (scrapHasTransientState) { 1.5919 + if (mTransientStateViews == null) { 1.5920 + mTransientStateViews = new SparseArrayCompat<View>(); 1.5921 + } 1.5922 + 1.5923 + mTransientStateViews.put(position, scrap); 1.5924 + } 1.5925 + 1.5926 + return; 1.5927 + } 1.5928 + 1.5929 + if (mViewTypeCount == 1) { 1.5930 + mCurrentScrap.add(scrap); 1.5931 + } else { 1.5932 + mScrapViews[viewType].add(scrap); 1.5933 + } 1.5934 + 1.5935 + // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept 1.5936 + // null delegates. 1.5937 + if (Build.VERSION.SDK_INT >= 14) { 1.5938 + scrap.setAccessibilityDelegate(null); 1.5939 + } 1.5940 + 1.5941 + if (mRecyclerListener != null) { 1.5942 + mRecyclerListener.onMovedToScrapHeap(scrap); 1.5943 + } 1.5944 + } 1.5945 + 1.5946 + @TargetApi(14) 1.5947 + void scrapActiveViews() { 1.5948 + final View[] activeViews = mActiveViews; 1.5949 + final boolean multipleScraps = (mViewTypeCount > 1); 1.5950 + 1.5951 + ArrayList<View> scrapViews = mCurrentScrap; 1.5952 + final int count = activeViews.length; 1.5953 + 1.5954 + for (int i = count - 1; i >= 0; i--) { 1.5955 + final View victim = activeViews[i]; 1.5956 + if (victim != null) { 1.5957 + final LayoutParams lp = (LayoutParams) victim.getLayoutParams(); 1.5958 + int whichScrap = lp.viewType; 1.5959 + 1.5960 + activeViews[i] = null; 1.5961 + 1.5962 + final boolean scrapHasTransientState = ViewCompat.hasTransientState(victim); 1.5963 + if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) { 1.5964 + if (scrapHasTransientState) { 1.5965 + removeDetachedView(victim, false); 1.5966 + 1.5967 + if (mTransientStateViews == null) { 1.5968 + mTransientStateViews = new SparseArrayCompat<View>(); 1.5969 + } 1.5970 + 1.5971 + mTransientStateViews.put(mFirstActivePosition + i, victim); 1.5972 + } 1.5973 + 1.5974 + continue; 1.5975 + } 1.5976 + 1.5977 + if (multipleScraps) { 1.5978 + scrapViews = mScrapViews[whichScrap]; 1.5979 + } 1.5980 + 1.5981 + lp.scrappedFromPosition = mFirstActivePosition + i; 1.5982 + scrapViews.add(victim); 1.5983 + 1.5984 + // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept 1.5985 + // null delegates. 1.5986 + if (Build.VERSION.SDK_INT >= 14) { 1.5987 + victim.setAccessibilityDelegate(null); 1.5988 + } 1.5989 + 1.5990 + if (mRecyclerListener != null) { 1.5991 + mRecyclerListener.onMovedToScrapHeap(victim); 1.5992 + } 1.5993 + } 1.5994 + } 1.5995 + 1.5996 + pruneScrapViews(); 1.5997 + } 1.5998 + 1.5999 + private void pruneScrapViews() { 1.6000 + final int maxViews = mActiveViews.length; 1.6001 + final int viewTypeCount = mViewTypeCount; 1.6002 + final ArrayList<View>[] scrapViews = mScrapViews; 1.6003 + 1.6004 + for (int i = 0; i < viewTypeCount; ++i) { 1.6005 + final ArrayList<View> scrapPile = scrapViews[i]; 1.6006 + int size = scrapPile.size(); 1.6007 + final int extras = size - maxViews; 1.6008 + 1.6009 + size--; 1.6010 + 1.6011 + for (int j = 0; j < extras; j++) { 1.6012 + removeDetachedView(scrapPile.remove(size--), false); 1.6013 + } 1.6014 + } 1.6015 + 1.6016 + if (mTransientStateViews != null) { 1.6017 + for (int i = 0; i < mTransientStateViews.size(); i++) { 1.6018 + final View v = mTransientStateViews.valueAt(i); 1.6019 + if (!ViewCompat.hasTransientState(v)) { 1.6020 + mTransientStateViews.removeAt(i); 1.6021 + i--; 1.6022 + } 1.6023 + } 1.6024 + } 1.6025 + } 1.6026 + 1.6027 + void reclaimScrapViews(List<View> views) { 1.6028 + if (mViewTypeCount == 1) { 1.6029 + views.addAll(mCurrentScrap); 1.6030 + } else { 1.6031 + final int viewTypeCount = mViewTypeCount; 1.6032 + final ArrayList<View>[] scrapViews = mScrapViews; 1.6033 + 1.6034 + for (int i = 0; i < viewTypeCount; ++i) { 1.6035 + final ArrayList<View> scrapPile = scrapViews[i]; 1.6036 + views.addAll(scrapPile); 1.6037 + } 1.6038 + } 1.6039 + } 1.6040 + 1.6041 + View retrieveFromScrap(ArrayList<View> scrapViews, int position) { 1.6042 + int size = scrapViews.size(); 1.6043 + if (size <= 0) { 1.6044 + return null; 1.6045 + } 1.6046 + 1.6047 + for (int i = 0; i < size; i++) { 1.6048 + final View scrapView = scrapViews.get(i); 1.6049 + final LayoutParams lp = (LayoutParams) scrapView.getLayoutParams(); 1.6050 + 1.6051 + if (lp.scrappedFromPosition == position) { 1.6052 + scrapViews.remove(i); 1.6053 + return scrapView; 1.6054 + } 1.6055 + } 1.6056 + 1.6057 + return scrapViews.remove(size - 1); 1.6058 + } 1.6059 + } 1.6060 + 1.6061 + @Override 1.6062 + public void setEmptyView(View emptyView) { 1.6063 + super.setEmptyView(emptyView); 1.6064 + mEmptyView = emptyView; 1.6065 + updateEmptyStatus(); 1.6066 + } 1.6067 + 1.6068 + @Override 1.6069 + public void setFocusable(boolean focusable) { 1.6070 + final ListAdapter adapter = getAdapter(); 1.6071 + final boolean empty = (adapter == null || adapter.getCount() == 0); 1.6072 + 1.6073 + mDesiredFocusableState = focusable; 1.6074 + if (!focusable) { 1.6075 + mDesiredFocusableInTouchModeState = false; 1.6076 + } 1.6077 + 1.6078 + super.setFocusable(focusable && !empty); 1.6079 + } 1.6080 + 1.6081 + @Override 1.6082 + public void setFocusableInTouchMode(boolean focusable) { 1.6083 + final ListAdapter adapter = getAdapter(); 1.6084 + final boolean empty = (adapter == null || adapter.getCount() == 0); 1.6085 + 1.6086 + mDesiredFocusableInTouchModeState = focusable; 1.6087 + if (focusable) { 1.6088 + mDesiredFocusableState = true; 1.6089 + } 1.6090 + 1.6091 + super.setFocusableInTouchMode(focusable && !empty); 1.6092 + } 1.6093 + 1.6094 + private void checkFocus() { 1.6095 + final ListAdapter adapter = getAdapter(); 1.6096 + final boolean focusable = (adapter != null && adapter.getCount() > 0); 1.6097 + 1.6098 + // The order in which we set focusable in touch mode/focusable may matter 1.6099 + // for the client, see View.setFocusableInTouchMode() comments for more 1.6100 + // details 1.6101 + super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); 1.6102 + super.setFocusable(focusable && mDesiredFocusableState); 1.6103 + 1.6104 + if (mEmptyView != null) { 1.6105 + updateEmptyStatus(); 1.6106 + } 1.6107 + } 1.6108 + 1.6109 + private void updateEmptyStatus() { 1.6110 + final boolean isEmpty = (mAdapter == null || mAdapter.isEmpty()); 1.6111 + 1.6112 + if (isEmpty) { 1.6113 + if (mEmptyView != null) { 1.6114 + mEmptyView.setVisibility(View.VISIBLE); 1.6115 + setVisibility(View.GONE); 1.6116 + } else { 1.6117 + // If the caller just removed our empty view, make sure the list 1.6118 + // view is visible 1.6119 + setVisibility(View.VISIBLE); 1.6120 + } 1.6121 + 1.6122 + // We are now GONE, so pending layouts will not be dispatched. 1.6123 + // Force one here to make sure that the state of the list matches 1.6124 + // the state of the adapter. 1.6125 + if (mDataChanged) { 1.6126 + onLayout(false, getLeft(), getTop(), getRight(), getBottom()); 1.6127 + } 1.6128 + } else { 1.6129 + if (mEmptyView != null) { 1.6130 + mEmptyView.setVisibility(View.GONE); 1.6131 + } 1.6132 + 1.6133 + setVisibility(View.VISIBLE); 1.6134 + } 1.6135 + } 1.6136 + 1.6137 + private class AdapterDataSetObserver extends DataSetObserver { 1.6138 + private Parcelable mInstanceState = null; 1.6139 + 1.6140 + @Override 1.6141 + public void onChanged() { 1.6142 + mDataChanged = true; 1.6143 + mOldItemCount = mItemCount; 1.6144 + mItemCount = getAdapter().getCount(); 1.6145 + 1.6146 + // Detect the case where a cursor that was previously invalidated has 1.6147 + // been re-populated with new data. 1.6148 + if (TwoWayView.this.mHasStableIds && mInstanceState != null 1.6149 + && mOldItemCount == 0 && mItemCount > 0) { 1.6150 + TwoWayView.this.onRestoreInstanceState(mInstanceState); 1.6151 + mInstanceState = null; 1.6152 + } else { 1.6153 + rememberSyncState(); 1.6154 + } 1.6155 + 1.6156 + checkFocus(); 1.6157 + requestLayout(); 1.6158 + } 1.6159 + 1.6160 + @Override 1.6161 + public void onInvalidated() { 1.6162 + mDataChanged = true; 1.6163 + 1.6164 + if (TwoWayView.this.mHasStableIds) { 1.6165 + // Remember the current state for the case where our hosting activity is being 1.6166 + // stopped and later restarted 1.6167 + mInstanceState = TwoWayView.this.onSaveInstanceState(); 1.6168 + } 1.6169 + 1.6170 + // Data is invalid so we should reset our state 1.6171 + mOldItemCount = mItemCount; 1.6172 + mItemCount = 0; 1.6173 + 1.6174 + mSelectedPosition = INVALID_POSITION; 1.6175 + mSelectedRowId = INVALID_ROW_ID; 1.6176 + 1.6177 + mNextSelectedPosition = INVALID_POSITION; 1.6178 + mNextSelectedRowId = INVALID_ROW_ID; 1.6179 + 1.6180 + mNeedSync = false; 1.6181 + 1.6182 + checkFocus(); 1.6183 + requestLayout(); 1.6184 + } 1.6185 + } 1.6186 + 1.6187 + static class SavedState extends BaseSavedState { 1.6188 + long selectedId; 1.6189 + long firstId; 1.6190 + int viewStart; 1.6191 + int position; 1.6192 + int height; 1.6193 + int checkedItemCount; 1.6194 + SparseBooleanArray checkState; 1.6195 + LongSparseArray<Integer> checkIdState; 1.6196 + 1.6197 + /** 1.6198 + * Constructor called from {@link TwoWayView#onSaveInstanceState()} 1.6199 + */ 1.6200 + SavedState(Parcelable superState) { 1.6201 + super(superState); 1.6202 + } 1.6203 + 1.6204 + /** 1.6205 + * Constructor called from {@link #CREATOR} 1.6206 + */ 1.6207 + private SavedState(Parcel in) { 1.6208 + super(in); 1.6209 + 1.6210 + selectedId = in.readLong(); 1.6211 + firstId = in.readLong(); 1.6212 + viewStart = in.readInt(); 1.6213 + position = in.readInt(); 1.6214 + height = in.readInt(); 1.6215 + 1.6216 + checkedItemCount = in.readInt(); 1.6217 + checkState = in.readSparseBooleanArray(); 1.6218 + 1.6219 + final int N = in.readInt(); 1.6220 + if (N > 0) { 1.6221 + checkIdState = new LongSparseArray<Integer>(); 1.6222 + for (int i = 0; i < N; i++) { 1.6223 + final long key = in.readLong(); 1.6224 + final int value = in.readInt(); 1.6225 + checkIdState.put(key, value); 1.6226 + } 1.6227 + } 1.6228 + } 1.6229 + 1.6230 + @Override 1.6231 + public void writeToParcel(Parcel out, int flags) { 1.6232 + super.writeToParcel(out, flags); 1.6233 + 1.6234 + out.writeLong(selectedId); 1.6235 + out.writeLong(firstId); 1.6236 + out.writeInt(viewStart); 1.6237 + out.writeInt(position); 1.6238 + out.writeInt(height); 1.6239 + 1.6240 + out.writeInt(checkedItemCount); 1.6241 + out.writeSparseBooleanArray(checkState); 1.6242 + 1.6243 + final int N = checkIdState != null ? checkIdState.size() : 0; 1.6244 + out.writeInt(N); 1.6245 + 1.6246 + for (int i = 0; i < N; i++) { 1.6247 + out.writeLong(checkIdState.keyAt(i)); 1.6248 + out.writeInt(checkIdState.valueAt(i)); 1.6249 + } 1.6250 + } 1.6251 + 1.6252 + @Override 1.6253 + public String toString() { 1.6254 + return "TwoWayView.SavedState{" 1.6255 + + Integer.toHexString(System.identityHashCode(this)) 1.6256 + + " selectedId=" + selectedId 1.6257 + + " firstId=" + firstId 1.6258 + + " viewStart=" + viewStart 1.6259 + + " height=" + height 1.6260 + + " position=" + position 1.6261 + + " checkState=" + checkState + "}"; 1.6262 + } 1.6263 + 1.6264 + public static final Parcelable.Creator<SavedState> CREATOR 1.6265 + = new Parcelable.Creator<SavedState>() { 1.6266 + @Override 1.6267 + public SavedState createFromParcel(Parcel in) { 1.6268 + return new SavedState(in); 1.6269 + } 1.6270 + 1.6271 + @Override 1.6272 + public SavedState[] newArray(int size) { 1.6273 + return new SavedState[size]; 1.6274 + } 1.6275 + }; 1.6276 + } 1.6277 + 1.6278 + private class SelectionNotifier implements Runnable { 1.6279 + @Override 1.6280 + public void run() { 1.6281 + if (mDataChanged) { 1.6282 + // Data has changed between when this SelectionNotifier 1.6283 + // was posted and now. We need to wait until the AdapterView 1.6284 + // has been synched to the new data. 1.6285 + if (mAdapter != null) { 1.6286 + post(this); 1.6287 + } 1.6288 + } else { 1.6289 + fireOnSelected(); 1.6290 + performAccessibilityActionsOnSelected(); 1.6291 + } 1.6292 + } 1.6293 + } 1.6294 + 1.6295 + private class WindowRunnnable { 1.6296 + private int mOriginalAttachCount; 1.6297 + 1.6298 + public void rememberWindowAttachCount() { 1.6299 + mOriginalAttachCount = getWindowAttachCount(); 1.6300 + } 1.6301 + 1.6302 + public boolean sameWindow() { 1.6303 + return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount; 1.6304 + } 1.6305 + } 1.6306 + 1.6307 + private class PerformClick extends WindowRunnnable implements Runnable { 1.6308 + int mClickMotionPosition; 1.6309 + 1.6310 + @Override 1.6311 + public void run() { 1.6312 + if (mDataChanged) { 1.6313 + return; 1.6314 + } 1.6315 + 1.6316 + final ListAdapter adapter = mAdapter; 1.6317 + final int motionPosition = mClickMotionPosition; 1.6318 + 1.6319 + if (adapter != null && mItemCount > 0 && 1.6320 + motionPosition != INVALID_POSITION && 1.6321 + motionPosition < adapter.getCount() && sameWindow()) { 1.6322 + 1.6323 + final View child = getChildAt(motionPosition - mFirstPosition); 1.6324 + if (child != null) { 1.6325 + performItemClick(child, motionPosition, adapter.getItemId(motionPosition)); 1.6326 + } 1.6327 + } 1.6328 + } 1.6329 + } 1.6330 + 1.6331 + private final class CheckForTap implements Runnable { 1.6332 + @Override 1.6333 + public void run() { 1.6334 + if (mTouchMode != TOUCH_MODE_DOWN) { 1.6335 + return; 1.6336 + } 1.6337 + 1.6338 + mTouchMode = TOUCH_MODE_TAP; 1.6339 + 1.6340 + final View child = getChildAt(mMotionPosition - mFirstPosition); 1.6341 + if (child != null && !child.hasFocusable()) { 1.6342 + mLayoutMode = LAYOUT_NORMAL; 1.6343 + 1.6344 + if (!mDataChanged) { 1.6345 + setPressed(true); 1.6346 + child.setPressed(true); 1.6347 + 1.6348 + layoutChildren(); 1.6349 + positionSelector(mMotionPosition, child); 1.6350 + refreshDrawableState(); 1.6351 + 1.6352 + positionSelector(mMotionPosition, child); 1.6353 + refreshDrawableState(); 1.6354 + 1.6355 + final boolean longClickable = isLongClickable(); 1.6356 + 1.6357 + if (mSelector != null) { 1.6358 + Drawable d = mSelector.getCurrent(); 1.6359 + 1.6360 + if (d != null && d instanceof TransitionDrawable) { 1.6361 + if (longClickable) { 1.6362 + final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); 1.6363 + ((TransitionDrawable) d).startTransition(longPressTimeout); 1.6364 + } else { 1.6365 + ((TransitionDrawable) d).resetTransition(); 1.6366 + } 1.6367 + } 1.6368 + } 1.6369 + 1.6370 + if (longClickable) { 1.6371 + triggerCheckForLongPress(); 1.6372 + } else { 1.6373 + mTouchMode = TOUCH_MODE_DONE_WAITING; 1.6374 + } 1.6375 + } else { 1.6376 + mTouchMode = TOUCH_MODE_DONE_WAITING; 1.6377 + } 1.6378 + } 1.6379 + } 1.6380 + } 1.6381 + 1.6382 + private class CheckForLongPress extends WindowRunnnable implements Runnable { 1.6383 + @Override 1.6384 + public void run() { 1.6385 + final int motionPosition = mMotionPosition; 1.6386 + final View child = getChildAt(motionPosition - mFirstPosition); 1.6387 + 1.6388 + if (child != null) { 1.6389 + final long longPressId = mAdapter.getItemId(mMotionPosition); 1.6390 + 1.6391 + boolean handled = false; 1.6392 + if (sameWindow() && !mDataChanged) { 1.6393 + handled = performLongPress(child, motionPosition, longPressId); 1.6394 + } 1.6395 + 1.6396 + if (handled) { 1.6397 + mTouchMode = TOUCH_MODE_REST; 1.6398 + setPressed(false); 1.6399 + child.setPressed(false); 1.6400 + } else { 1.6401 + mTouchMode = TOUCH_MODE_DONE_WAITING; 1.6402 + } 1.6403 + } 1.6404 + } 1.6405 + } 1.6406 + 1.6407 + private class CheckForKeyLongPress extends WindowRunnnable implements Runnable { 1.6408 + public void run() { 1.6409 + if (!isPressed() || mSelectedPosition < 0) { 1.6410 + return; 1.6411 + } 1.6412 + 1.6413 + final int index = mSelectedPosition - mFirstPosition; 1.6414 + final View v = getChildAt(index); 1.6415 + 1.6416 + if (!mDataChanged) { 1.6417 + boolean handled = false; 1.6418 + 1.6419 + if (sameWindow()) { 1.6420 + handled = performLongPress(v, mSelectedPosition, mSelectedRowId); 1.6421 + } 1.6422 + 1.6423 + if (handled) { 1.6424 + setPressed(false); 1.6425 + v.setPressed(false); 1.6426 + } 1.6427 + } else { 1.6428 + setPressed(false); 1.6429 + 1.6430 + if (v != null) { 1.6431 + v.setPressed(false); 1.6432 + } 1.6433 + } 1.6434 + } 1.6435 + } 1.6436 + 1.6437 + private static class ArrowScrollFocusResult { 1.6438 + private int mSelectedPosition; 1.6439 + private int mAmountToScroll; 1.6440 + 1.6441 + /** 1.6442 + * How {@link TwoWayView#arrowScrollFocused} returns its values. 1.6443 + */ 1.6444 + void populate(int selectedPosition, int amountToScroll) { 1.6445 + mSelectedPosition = selectedPosition; 1.6446 + mAmountToScroll = amountToScroll; 1.6447 + } 1.6448 + 1.6449 + public int getSelectedPosition() { 1.6450 + return mSelectedPosition; 1.6451 + } 1.6452 + 1.6453 + public int getAmountToScroll() { 1.6454 + return mAmountToScroll; 1.6455 + } 1.6456 + } 1.6457 + 1.6458 + private class ListItemAccessibilityDelegate extends AccessibilityDelegateCompat { 1.6459 + @Override 1.6460 + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 1.6461 + super.onInitializeAccessibilityNodeInfo(host, info); 1.6462 + 1.6463 + final int position = getPositionForView(host); 1.6464 + final ListAdapter adapter = getAdapter(); 1.6465 + 1.6466 + // Cannot perform actions on invalid items 1.6467 + if (position == INVALID_POSITION || adapter == null) { 1.6468 + return; 1.6469 + } 1.6470 + 1.6471 + // Cannot perform actions on disabled items 1.6472 + if (!isEnabled() || !adapter.isEnabled(position)) { 1.6473 + return; 1.6474 + } 1.6475 + 1.6476 + if (position == getSelectedItemPosition()) { 1.6477 + info.setSelected(true); 1.6478 + info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION); 1.6479 + } else { 1.6480 + info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT); 1.6481 + } 1.6482 + 1.6483 + if (isClickable()) { 1.6484 + info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 1.6485 + info.setClickable(true); 1.6486 + } 1.6487 + 1.6488 + if (isLongClickable()) { 1.6489 + info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK); 1.6490 + info.setLongClickable(true); 1.6491 + } 1.6492 + } 1.6493 + 1.6494 + @Override 1.6495 + public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 1.6496 + if (super.performAccessibilityAction(host, action, arguments)) { 1.6497 + return true; 1.6498 + } 1.6499 + 1.6500 + final int position = getPositionForView(host); 1.6501 + final ListAdapter adapter = getAdapter(); 1.6502 + 1.6503 + // Cannot perform actions on invalid items 1.6504 + if (position == INVALID_POSITION || adapter == null) { 1.6505 + return false; 1.6506 + } 1.6507 + 1.6508 + // Cannot perform actions on disabled items 1.6509 + if (!isEnabled() || !adapter.isEnabled(position)) { 1.6510 + return false; 1.6511 + } 1.6512 + 1.6513 + final long id = getItemIdAtPosition(position); 1.6514 + 1.6515 + switch (action) { 1.6516 + case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION: 1.6517 + if (getSelectedItemPosition() == position) { 1.6518 + setSelection(INVALID_POSITION); 1.6519 + return true; 1.6520 + } 1.6521 + return false; 1.6522 + 1.6523 + case AccessibilityNodeInfoCompat.ACTION_SELECT: 1.6524 + if (getSelectedItemPosition() != position) { 1.6525 + setSelection(position); 1.6526 + return true; 1.6527 + } 1.6528 + return false; 1.6529 + 1.6530 + case AccessibilityNodeInfoCompat.ACTION_CLICK: 1.6531 + if (isClickable()) { 1.6532 + return performItemClick(host, position, id); 1.6533 + } 1.6534 + return false; 1.6535 + 1.6536 + case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK: 1.6537 + if (isLongClickable()) { 1.6538 + return performLongPress(host, position, id); 1.6539 + } 1.6540 + return false; 1.6541 + } 1.6542 + 1.6543 + return false; 1.6544 + } 1.6545 + } 1.6546 +}