Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2013 Lucas Rocha |
michael@0 | 3 | * |
michael@0 | 4 | * This code is based on bits and pieces of Android's AbsListView, |
michael@0 | 5 | * Listview, and StaggeredGridView. |
michael@0 | 6 | * |
michael@0 | 7 | * Copyright (C) 2012 The Android Open Source Project |
michael@0 | 8 | * |
michael@0 | 9 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 10 | * you may not use this file except in compliance with the License. |
michael@0 | 11 | * You may obtain a copy of the License at |
michael@0 | 12 | * |
michael@0 | 13 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 14 | * |
michael@0 | 15 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 16 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 18 | * See the License for the specific language governing permissions and |
michael@0 | 19 | * limitations under the License. |
michael@0 | 20 | */ |
michael@0 | 21 | |
michael@0 | 22 | package org.mozilla.gecko.widget; |
michael@0 | 23 | |
michael@0 | 24 | import org.mozilla.gecko.R; |
michael@0 | 25 | |
michael@0 | 26 | import android.annotation.TargetApi; |
michael@0 | 27 | import android.content.Context; |
michael@0 | 28 | import android.content.res.TypedArray; |
michael@0 | 29 | import android.database.DataSetObserver; |
michael@0 | 30 | import android.graphics.Canvas; |
michael@0 | 31 | import android.graphics.Rect; |
michael@0 | 32 | import android.graphics.drawable.Drawable; |
michael@0 | 33 | import android.graphics.drawable.TransitionDrawable; |
michael@0 | 34 | import android.os.Build; |
michael@0 | 35 | import android.os.Bundle; |
michael@0 | 36 | import android.os.Parcel; |
michael@0 | 37 | import android.os.Parcelable; |
michael@0 | 38 | import android.os.SystemClock; |
michael@0 | 39 | import android.support.v4.util.LongSparseArray; |
michael@0 | 40 | import android.support.v4.util.SparseArrayCompat; |
michael@0 | 41 | import android.support.v4.view.AccessibilityDelegateCompat; |
michael@0 | 42 | import android.support.v4.view.KeyEventCompat; |
michael@0 | 43 | import android.support.v4.view.MotionEventCompat; |
michael@0 | 44 | import android.support.v4.view.VelocityTrackerCompat; |
michael@0 | 45 | import android.support.v4.view.ViewCompat; |
michael@0 | 46 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; |
michael@0 | 47 | import android.support.v4.widget.EdgeEffectCompat; |
michael@0 | 48 | import android.util.AttributeSet; |
michael@0 | 49 | import android.util.Log; |
michael@0 | 50 | import android.util.SparseBooleanArray; |
michael@0 | 51 | import android.view.ContextMenu.ContextMenuInfo; |
michael@0 | 52 | import android.view.FocusFinder; |
michael@0 | 53 | import android.view.HapticFeedbackConstants; |
michael@0 | 54 | import android.view.KeyEvent; |
michael@0 | 55 | import android.view.MotionEvent; |
michael@0 | 56 | import android.view.SoundEffectConstants; |
michael@0 | 57 | import android.view.VelocityTracker; |
michael@0 | 58 | import android.view.View; |
michael@0 | 59 | import android.view.ViewConfiguration; |
michael@0 | 60 | import android.view.ViewGroup; |
michael@0 | 61 | import android.view.ViewParent; |
michael@0 | 62 | import android.view.ViewTreeObserver; |
michael@0 | 63 | import android.view.accessibility.AccessibilityEvent; |
michael@0 | 64 | import android.view.accessibility.AccessibilityNodeInfo; |
michael@0 | 65 | import android.widget.AdapterView; |
michael@0 | 66 | import android.widget.Checkable; |
michael@0 | 67 | import android.widget.ListAdapter; |
michael@0 | 68 | import android.widget.Scroller; |
michael@0 | 69 | |
michael@0 | 70 | import java.util.ArrayList; |
michael@0 | 71 | import java.util.List; |
michael@0 | 72 | |
michael@0 | 73 | /* |
michael@0 | 74 | * Implementation Notes: |
michael@0 | 75 | * |
michael@0 | 76 | * Some terminology: |
michael@0 | 77 | * |
michael@0 | 78 | * index - index of the items that are currently visible |
michael@0 | 79 | * position - index of the items in the cursor |
michael@0 | 80 | * |
michael@0 | 81 | * Given the bi-directional nature of this view, the source code |
michael@0 | 82 | * usually names variables with 'start' to mean 'top' or 'left'; and |
michael@0 | 83 | * 'end' to mean 'bottom' or 'right', depending on the current |
michael@0 | 84 | * orientation of the widget. |
michael@0 | 85 | */ |
michael@0 | 86 | |
michael@0 | 87 | /** |
michael@0 | 88 | * A view that shows items in a vertical or horizontal scrolling list. |
michael@0 | 89 | * The items come from the {@link ListAdapter} associated with this view. |
michael@0 | 90 | */ |
michael@0 | 91 | public class TwoWayView extends AdapterView<ListAdapter> implements |
michael@0 | 92 | ViewTreeObserver.OnTouchModeChangeListener { |
michael@0 | 93 | private static final String LOGTAG = "TwoWayView"; |
michael@0 | 94 | |
michael@0 | 95 | private static final int NO_POSITION = -1; |
michael@0 | 96 | private static final int INVALID_POINTER = -1; |
michael@0 | 97 | |
michael@0 | 98 | public static final int[] STATE_NOTHING = new int[] { 0 }; |
michael@0 | 99 | |
michael@0 | 100 | private static final int TOUCH_MODE_REST = -1; |
michael@0 | 101 | private static final int TOUCH_MODE_DOWN = 0; |
michael@0 | 102 | private static final int TOUCH_MODE_TAP = 1; |
michael@0 | 103 | private static final int TOUCH_MODE_DONE_WAITING = 2; |
michael@0 | 104 | private static final int TOUCH_MODE_DRAGGING = 3; |
michael@0 | 105 | private static final int TOUCH_MODE_FLINGING = 4; |
michael@0 | 106 | private static final int TOUCH_MODE_OVERSCROLL = 5; |
michael@0 | 107 | |
michael@0 | 108 | private static final int TOUCH_MODE_UNKNOWN = -1; |
michael@0 | 109 | private static final int TOUCH_MODE_ON = 0; |
michael@0 | 110 | private static final int TOUCH_MODE_OFF = 1; |
michael@0 | 111 | |
michael@0 | 112 | private static final int LAYOUT_NORMAL = 0; |
michael@0 | 113 | private static final int LAYOUT_FORCE_TOP = 1; |
michael@0 | 114 | private static final int LAYOUT_SET_SELECTION = 2; |
michael@0 | 115 | private static final int LAYOUT_FORCE_BOTTOM = 3; |
michael@0 | 116 | private static final int LAYOUT_SPECIFIC = 4; |
michael@0 | 117 | private static final int LAYOUT_SYNC = 5; |
michael@0 | 118 | private static final int LAYOUT_MOVE_SELECTION = 6; |
michael@0 | 119 | |
michael@0 | 120 | private static final int SYNC_SELECTED_POSITION = 0; |
michael@0 | 121 | private static final int SYNC_FIRST_POSITION = 1; |
michael@0 | 122 | |
michael@0 | 123 | private static final int SYNC_MAX_DURATION_MILLIS = 100; |
michael@0 | 124 | |
michael@0 | 125 | private static final int CHECK_POSITION_SEARCH_DISTANCE = 20; |
michael@0 | 126 | |
michael@0 | 127 | private static final float MAX_SCROLL_FACTOR = 0.33f; |
michael@0 | 128 | |
michael@0 | 129 | private static final int MIN_SCROLL_PREVIEW_PIXELS = 10; |
michael@0 | 130 | |
michael@0 | 131 | public static enum ChoiceMode { |
michael@0 | 132 | NONE, |
michael@0 | 133 | SINGLE, |
michael@0 | 134 | MULTIPLE |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | public static enum Orientation { |
michael@0 | 138 | HORIZONTAL, |
michael@0 | 139 | VERTICAL; |
michael@0 | 140 | }; |
michael@0 | 141 | |
michael@0 | 142 | private ListAdapter mAdapter; |
michael@0 | 143 | |
michael@0 | 144 | private boolean mIsVertical; |
michael@0 | 145 | |
michael@0 | 146 | private int mItemMargin; |
michael@0 | 147 | |
michael@0 | 148 | private boolean mInLayout; |
michael@0 | 149 | private boolean mBlockLayoutRequests; |
michael@0 | 150 | |
michael@0 | 151 | private boolean mIsAttached; |
michael@0 | 152 | |
michael@0 | 153 | private final RecycleBin mRecycler; |
michael@0 | 154 | private AdapterDataSetObserver mDataSetObserver; |
michael@0 | 155 | |
michael@0 | 156 | private boolean mItemsCanFocus; |
michael@0 | 157 | |
michael@0 | 158 | final boolean[] mIsScrap = new boolean[1]; |
michael@0 | 159 | |
michael@0 | 160 | private boolean mDataChanged; |
michael@0 | 161 | private int mItemCount; |
michael@0 | 162 | private int mOldItemCount; |
michael@0 | 163 | private boolean mHasStableIds; |
michael@0 | 164 | private boolean mAreAllItemsSelectable; |
michael@0 | 165 | |
michael@0 | 166 | private int mFirstPosition; |
michael@0 | 167 | private int mSpecificStart; |
michael@0 | 168 | |
michael@0 | 169 | private SavedState mPendingSync; |
michael@0 | 170 | |
michael@0 | 171 | private final int mTouchSlop; |
michael@0 | 172 | private final int mMaximumVelocity; |
michael@0 | 173 | private final int mFlingVelocity; |
michael@0 | 174 | private float mLastTouchPos; |
michael@0 | 175 | private float mTouchRemainderPos; |
michael@0 | 176 | private int mActivePointerId; |
michael@0 | 177 | |
michael@0 | 178 | private final Rect mTempRect; |
michael@0 | 179 | |
michael@0 | 180 | private final ArrowScrollFocusResult mArrowScrollFocusResult; |
michael@0 | 181 | |
michael@0 | 182 | private Rect mTouchFrame; |
michael@0 | 183 | private int mMotionPosition; |
michael@0 | 184 | private CheckForTap mPendingCheckForTap; |
michael@0 | 185 | private CheckForLongPress mPendingCheckForLongPress; |
michael@0 | 186 | private CheckForKeyLongPress mPendingCheckForKeyLongPress; |
michael@0 | 187 | private PerformClick mPerformClick; |
michael@0 | 188 | private Runnable mTouchModeReset; |
michael@0 | 189 | private int mResurrectToPosition; |
michael@0 | 190 | |
michael@0 | 191 | private boolean mIsChildViewEnabled; |
michael@0 | 192 | |
michael@0 | 193 | private boolean mDrawSelectorOnTop; |
michael@0 | 194 | private Drawable mSelector; |
michael@0 | 195 | private int mSelectorPosition; |
michael@0 | 196 | private final Rect mSelectorRect; |
michael@0 | 197 | |
michael@0 | 198 | private int mOverScroll; |
michael@0 | 199 | private final int mOverscrollDistance; |
michael@0 | 200 | |
michael@0 | 201 | private boolean mDesiredFocusableState; |
michael@0 | 202 | private boolean mDesiredFocusableInTouchModeState; |
michael@0 | 203 | |
michael@0 | 204 | private SelectionNotifier mSelectionNotifier; |
michael@0 | 205 | |
michael@0 | 206 | private boolean mNeedSync; |
michael@0 | 207 | private int mSyncMode; |
michael@0 | 208 | private int mSyncPosition; |
michael@0 | 209 | private long mSyncRowId; |
michael@0 | 210 | private long mSyncHeight; |
michael@0 | 211 | private int mSelectedStart; |
michael@0 | 212 | |
michael@0 | 213 | private int mNextSelectedPosition; |
michael@0 | 214 | private long mNextSelectedRowId; |
michael@0 | 215 | private int mSelectedPosition; |
michael@0 | 216 | private long mSelectedRowId; |
michael@0 | 217 | private int mOldSelectedPosition; |
michael@0 | 218 | private long mOldSelectedRowId; |
michael@0 | 219 | |
michael@0 | 220 | private ChoiceMode mChoiceMode; |
michael@0 | 221 | private int mCheckedItemCount; |
michael@0 | 222 | private SparseBooleanArray mCheckStates; |
michael@0 | 223 | LongSparseArray<Integer> mCheckedIdStates; |
michael@0 | 224 | |
michael@0 | 225 | private ContextMenuInfo mContextMenuInfo; |
michael@0 | 226 | |
michael@0 | 227 | private int mLayoutMode; |
michael@0 | 228 | private int mTouchMode; |
michael@0 | 229 | private int mLastTouchMode; |
michael@0 | 230 | private VelocityTracker mVelocityTracker; |
michael@0 | 231 | private final Scroller mScroller; |
michael@0 | 232 | |
michael@0 | 233 | private EdgeEffectCompat mStartEdge; |
michael@0 | 234 | private EdgeEffectCompat mEndEdge; |
michael@0 | 235 | |
michael@0 | 236 | private OnScrollListener mOnScrollListener; |
michael@0 | 237 | private int mLastScrollState; |
michael@0 | 238 | |
michael@0 | 239 | private View mEmptyView; |
michael@0 | 240 | |
michael@0 | 241 | private ListItemAccessibilityDelegate mAccessibilityDelegate; |
michael@0 | 242 | |
michael@0 | 243 | private int mLastAccessibilityScrollEventFromIndex; |
michael@0 | 244 | private int mLastAccessibilityScrollEventToIndex; |
michael@0 | 245 | |
michael@0 | 246 | public interface OnScrollListener { |
michael@0 | 247 | |
michael@0 | 248 | /** |
michael@0 | 249 | * The view is not scrolling. Note navigating the list using the trackball counts as |
michael@0 | 250 | * being in the idle state since these transitions are not animated. |
michael@0 | 251 | */ |
michael@0 | 252 | public static int SCROLL_STATE_IDLE = 0; |
michael@0 | 253 | |
michael@0 | 254 | /** |
michael@0 | 255 | * The user is scrolling using touch, and their finger is still on the screen |
michael@0 | 256 | */ |
michael@0 | 257 | public static int SCROLL_STATE_TOUCH_SCROLL = 1; |
michael@0 | 258 | |
michael@0 | 259 | /** |
michael@0 | 260 | * The user had previously been scrolling using touch and had performed a fling. The |
michael@0 | 261 | * animation is now coasting to a stop |
michael@0 | 262 | */ |
michael@0 | 263 | public static int SCROLL_STATE_FLING = 2; |
michael@0 | 264 | |
michael@0 | 265 | /** |
michael@0 | 266 | * Callback method to be invoked while the list view or grid view is being scrolled. If the |
michael@0 | 267 | * view is being scrolled, this method will be called before the next frame of the scroll is |
michael@0 | 268 | * rendered. In particular, it will be called before any calls to |
michael@0 | 269 | * {@link Adapter#getView(int, View, ViewGroup)}. |
michael@0 | 270 | * |
michael@0 | 271 | * @param view The view whose scroll state is being reported |
michael@0 | 272 | * |
michael@0 | 273 | * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE}, |
michael@0 | 274 | * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. |
michael@0 | 275 | */ |
michael@0 | 276 | public void onScrollStateChanged(TwoWayView view, int scrollState); |
michael@0 | 277 | |
michael@0 | 278 | /** |
michael@0 | 279 | * Callback method to be invoked when the list or grid has been scrolled. This will be |
michael@0 | 280 | * called after the scroll has completed |
michael@0 | 281 | * @param view The view whose scroll state is being reported |
michael@0 | 282 | * @param firstVisibleItem the index of the first visible cell (ignore if |
michael@0 | 283 | * visibleItemCount == 0) |
michael@0 | 284 | * @param visibleItemCount the number of visible cells |
michael@0 | 285 | * @param totalItemCount the number of items in the list adaptor |
michael@0 | 286 | */ |
michael@0 | 287 | public void onScroll(TwoWayView view, int firstVisibleItem, int visibleItemCount, |
michael@0 | 288 | int totalItemCount); |
michael@0 | 289 | } |
michael@0 | 290 | |
michael@0 | 291 | /** |
michael@0 | 292 | * A RecyclerListener is used to receive a notification whenever a View is placed |
michael@0 | 293 | * inside the RecycleBin's scrap heap. This listener is used to free resources |
michael@0 | 294 | * associated to Views placed in the RecycleBin. |
michael@0 | 295 | * |
michael@0 | 296 | * @see TwoWayView.RecycleBin |
michael@0 | 297 | * @see TwoWayView#setRecyclerListener(TwoWayView.RecyclerListener) |
michael@0 | 298 | */ |
michael@0 | 299 | public static interface RecyclerListener { |
michael@0 | 300 | /** |
michael@0 | 301 | * Indicates that the specified View was moved into the recycler's scrap heap. |
michael@0 | 302 | * The view is not displayed on screen any more and any expensive resource |
michael@0 | 303 | * associated with the view should be discarded. |
michael@0 | 304 | * |
michael@0 | 305 | * @param view |
michael@0 | 306 | */ |
michael@0 | 307 | void onMovedToScrapHeap(View view); |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | public TwoWayView(Context context) { |
michael@0 | 311 | this(context, null); |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | public TwoWayView(Context context, AttributeSet attrs) { |
michael@0 | 315 | this(context, attrs, 0); |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | public TwoWayView(Context context, AttributeSet attrs, int defStyle) { |
michael@0 | 319 | super(context, attrs, defStyle); |
michael@0 | 320 | |
michael@0 | 321 | mNeedSync = false; |
michael@0 | 322 | mVelocityTracker = null; |
michael@0 | 323 | |
michael@0 | 324 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 325 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 326 | mLastTouchMode = TOUCH_MODE_UNKNOWN; |
michael@0 | 327 | |
michael@0 | 328 | mIsAttached = false; |
michael@0 | 329 | |
michael@0 | 330 | mContextMenuInfo = null; |
michael@0 | 331 | |
michael@0 | 332 | mOnScrollListener = null; |
michael@0 | 333 | mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; |
michael@0 | 334 | |
michael@0 | 335 | final ViewConfiguration vc = ViewConfiguration.get(context); |
michael@0 | 336 | mTouchSlop = vc.getScaledTouchSlop(); |
michael@0 | 337 | mMaximumVelocity = vc.getScaledMaximumFlingVelocity(); |
michael@0 | 338 | mFlingVelocity = vc.getScaledMinimumFlingVelocity(); |
michael@0 | 339 | mOverscrollDistance = getScaledOverscrollDistance(vc); |
michael@0 | 340 | |
michael@0 | 341 | mOverScroll = 0; |
michael@0 | 342 | |
michael@0 | 343 | mScroller = new Scroller(context); |
michael@0 | 344 | |
michael@0 | 345 | mIsVertical = true; |
michael@0 | 346 | |
michael@0 | 347 | mItemsCanFocus = false; |
michael@0 | 348 | |
michael@0 | 349 | mTempRect = new Rect(); |
michael@0 | 350 | |
michael@0 | 351 | mArrowScrollFocusResult = new ArrowScrollFocusResult(); |
michael@0 | 352 | |
michael@0 | 353 | mSelectorPosition = INVALID_POSITION; |
michael@0 | 354 | |
michael@0 | 355 | mSelectorRect = new Rect(); |
michael@0 | 356 | mSelectedStart = 0; |
michael@0 | 357 | |
michael@0 | 358 | mResurrectToPosition = INVALID_POSITION; |
michael@0 | 359 | |
michael@0 | 360 | mSelectedStart = 0; |
michael@0 | 361 | mNextSelectedPosition = INVALID_POSITION; |
michael@0 | 362 | mNextSelectedRowId = INVALID_ROW_ID; |
michael@0 | 363 | mSelectedPosition = INVALID_POSITION; |
michael@0 | 364 | mSelectedRowId = INVALID_ROW_ID; |
michael@0 | 365 | mOldSelectedPosition = INVALID_POSITION; |
michael@0 | 366 | mOldSelectedRowId = INVALID_ROW_ID; |
michael@0 | 367 | |
michael@0 | 368 | mChoiceMode = ChoiceMode.NONE; |
michael@0 | 369 | mCheckedItemCount = 0; |
michael@0 | 370 | mCheckedIdStates = null; |
michael@0 | 371 | mCheckStates = null; |
michael@0 | 372 | |
michael@0 | 373 | mRecycler = new RecycleBin(); |
michael@0 | 374 | mDataSetObserver = null; |
michael@0 | 375 | |
michael@0 | 376 | mAreAllItemsSelectable = true; |
michael@0 | 377 | |
michael@0 | 378 | mStartEdge = null; |
michael@0 | 379 | mEndEdge = null; |
michael@0 | 380 | |
michael@0 | 381 | setClickable(true); |
michael@0 | 382 | setFocusableInTouchMode(true); |
michael@0 | 383 | setWillNotDraw(false); |
michael@0 | 384 | setAlwaysDrawnWithCacheEnabled(false); |
michael@0 | 385 | setWillNotDraw(false); |
michael@0 | 386 | setClipToPadding(false); |
michael@0 | 387 | |
michael@0 | 388 | ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS); |
michael@0 | 389 | |
michael@0 | 390 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoWayView, defStyle, 0); |
michael@0 | 391 | initializeScrollbars(a); |
michael@0 | 392 | |
michael@0 | 393 | mDrawSelectorOnTop = a.getBoolean( |
michael@0 | 394 | R.styleable.TwoWayView_android_drawSelectorOnTop, false); |
michael@0 | 395 | |
michael@0 | 396 | Drawable d = a.getDrawable(R.styleable.TwoWayView_android_listSelector); |
michael@0 | 397 | if (d != null) { |
michael@0 | 398 | setSelector(d); |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | int orientation = a.getInt(R.styleable.TwoWayView_android_orientation, -1); |
michael@0 | 402 | if (orientation >= 0) { |
michael@0 | 403 | setOrientation(Orientation.values()[orientation]); |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | int choiceMode = a.getInt(R.styleable.TwoWayView_android_choiceMode, -1); |
michael@0 | 407 | if (choiceMode >= 0) { |
michael@0 | 408 | setChoiceMode(ChoiceMode.values()[choiceMode]); |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | a.recycle(); |
michael@0 | 412 | |
michael@0 | 413 | updateScrollbarsDirection(); |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | public void setOrientation(Orientation orientation) { |
michael@0 | 417 | final boolean isVertical = (orientation.compareTo(Orientation.VERTICAL) == 0); |
michael@0 | 418 | if (mIsVertical == isVertical) { |
michael@0 | 419 | return; |
michael@0 | 420 | } |
michael@0 | 421 | |
michael@0 | 422 | mIsVertical = isVertical; |
michael@0 | 423 | |
michael@0 | 424 | updateScrollbarsDirection(); |
michael@0 | 425 | resetState(); |
michael@0 | 426 | mRecycler.clear(); |
michael@0 | 427 | |
michael@0 | 428 | requestLayout(); |
michael@0 | 429 | } |
michael@0 | 430 | |
michael@0 | 431 | public Orientation getOrientation() { |
michael@0 | 432 | return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL); |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | public void setItemMargin(int itemMargin) { |
michael@0 | 436 | if (mItemMargin == itemMargin) { |
michael@0 | 437 | return; |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | mItemMargin = itemMargin; |
michael@0 | 441 | requestLayout(); |
michael@0 | 442 | } |
michael@0 | 443 | |
michael@0 | 444 | public int getItemMargin() { |
michael@0 | 445 | return mItemMargin; |
michael@0 | 446 | } |
michael@0 | 447 | |
michael@0 | 448 | /** |
michael@0 | 449 | * Indicates that the views created by the ListAdapter can contain focusable |
michael@0 | 450 | * items. |
michael@0 | 451 | * |
michael@0 | 452 | * @param itemsCanFocus true if items can get focus, false otherwise |
michael@0 | 453 | */ |
michael@0 | 454 | public void setItemsCanFocus(boolean itemsCanFocus) { |
michael@0 | 455 | mItemsCanFocus = itemsCanFocus; |
michael@0 | 456 | if (!itemsCanFocus) { |
michael@0 | 457 | setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); |
michael@0 | 458 | } |
michael@0 | 459 | } |
michael@0 | 460 | |
michael@0 | 461 | /** |
michael@0 | 462 | * @return Whether the views created by the ListAdapter can contain focusable |
michael@0 | 463 | * items. |
michael@0 | 464 | */ |
michael@0 | 465 | public boolean getItemsCanFocus() { |
michael@0 | 466 | return mItemsCanFocus; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | /** |
michael@0 | 470 | * Set the listener that will receive notifications every time the list scrolls. |
michael@0 | 471 | * |
michael@0 | 472 | * @param l the scroll listener |
michael@0 | 473 | */ |
michael@0 | 474 | public void setOnScrollListener(OnScrollListener l) { |
michael@0 | 475 | mOnScrollListener = l; |
michael@0 | 476 | invokeOnItemScrollListener(); |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | /** |
michael@0 | 480 | * Sets the recycler listener to be notified whenever a View is set aside in |
michael@0 | 481 | * the recycler for later reuse. This listener can be used to free resources |
michael@0 | 482 | * associated to the View. |
michael@0 | 483 | * |
michael@0 | 484 | * @param listener The recycler listener to be notified of views set aside |
michael@0 | 485 | * in the recycler. |
michael@0 | 486 | * |
michael@0 | 487 | * @see TwoWayView.RecycleBin |
michael@0 | 488 | * @see TwoWayView.RecyclerListener |
michael@0 | 489 | */ |
michael@0 | 490 | public void setRecyclerListener(RecyclerListener l) { |
michael@0 | 491 | mRecycler.mRecyclerListener = l; |
michael@0 | 492 | } |
michael@0 | 493 | |
michael@0 | 494 | /** |
michael@0 | 495 | * Controls whether the selection highlight drawable should be drawn on top of the item or |
michael@0 | 496 | * behind it. |
michael@0 | 497 | * |
michael@0 | 498 | * @param onTop If true, the selector will be drawn on the item it is highlighting. The default |
michael@0 | 499 | * is false. |
michael@0 | 500 | * |
michael@0 | 501 | * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop |
michael@0 | 502 | */ |
michael@0 | 503 | public void setDrawSelectorOnTop(boolean drawSelectorOnTop) { |
michael@0 | 504 | mDrawSelectorOnTop = drawSelectorOnTop; |
michael@0 | 505 | } |
michael@0 | 506 | |
michael@0 | 507 | /** |
michael@0 | 508 | * Set a Drawable that should be used to highlight the currently selected item. |
michael@0 | 509 | * |
michael@0 | 510 | * @param resID A Drawable resource to use as the selection highlight. |
michael@0 | 511 | * |
michael@0 | 512 | * @attr ref android.R.styleable#AbsListView_listSelector |
michael@0 | 513 | */ |
michael@0 | 514 | public void setSelector(int resID) { |
michael@0 | 515 | setSelector(getResources().getDrawable(resID)); |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | /** |
michael@0 | 519 | * Set a Drawable that should be used to highlight the currently selected item. |
michael@0 | 520 | * |
michael@0 | 521 | * @param selector A Drawable to use as the selection highlight. |
michael@0 | 522 | * |
michael@0 | 523 | * @attr ref android.R.styleable#AbsListView_listSelector |
michael@0 | 524 | */ |
michael@0 | 525 | public void setSelector(Drawable selector) { |
michael@0 | 526 | if (mSelector != null) { |
michael@0 | 527 | mSelector.setCallback(null); |
michael@0 | 528 | unscheduleDrawable(mSelector); |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | mSelector = selector; |
michael@0 | 532 | Rect padding = new Rect(); |
michael@0 | 533 | selector.getPadding(padding); |
michael@0 | 534 | |
michael@0 | 535 | selector.setCallback(this); |
michael@0 | 536 | updateSelectorState(); |
michael@0 | 537 | } |
michael@0 | 538 | |
michael@0 | 539 | /** |
michael@0 | 540 | * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the |
michael@0 | 541 | * selection in the list. |
michael@0 | 542 | * |
michael@0 | 543 | * @return the drawable used to display the selector |
michael@0 | 544 | */ |
michael@0 | 545 | public Drawable getSelector() { |
michael@0 | 546 | return mSelector; |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | /** |
michael@0 | 550 | * {@inheritDoc} |
michael@0 | 551 | */ |
michael@0 | 552 | @Override |
michael@0 | 553 | public int getSelectedItemPosition() { |
michael@0 | 554 | return mNextSelectedPosition; |
michael@0 | 555 | } |
michael@0 | 556 | |
michael@0 | 557 | /** |
michael@0 | 558 | * {@inheritDoc} |
michael@0 | 559 | */ |
michael@0 | 560 | @Override |
michael@0 | 561 | public long getSelectedItemId() { |
michael@0 | 562 | return mNextSelectedRowId; |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | /** |
michael@0 | 566 | * Returns the number of items currently selected. This will only be valid |
michael@0 | 567 | * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). |
michael@0 | 568 | * |
michael@0 | 569 | * <p>To determine the specific items that are currently selected, use one of |
michael@0 | 570 | * the <code>getChecked*</code> methods. |
michael@0 | 571 | * |
michael@0 | 572 | * @return The number of items currently selected |
michael@0 | 573 | * |
michael@0 | 574 | * @see #getCheckedItemPosition() |
michael@0 | 575 | * @see #getCheckedItemPositions() |
michael@0 | 576 | * @see #getCheckedItemIds() |
michael@0 | 577 | */ |
michael@0 | 578 | public int getCheckedItemCount() { |
michael@0 | 579 | return mCheckedItemCount; |
michael@0 | 580 | } |
michael@0 | 581 | |
michael@0 | 582 | /** |
michael@0 | 583 | * Returns the checked state of the specified position. The result is only |
michael@0 | 584 | * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} |
michael@0 | 585 | * or {@link #CHOICE_MODE_MULTIPLE}. |
michael@0 | 586 | * |
michael@0 | 587 | * @param position The item whose checked state to return |
michael@0 | 588 | * @return The item's checked state or <code>false</code> if choice mode |
michael@0 | 589 | * is invalid |
michael@0 | 590 | * |
michael@0 | 591 | * @see #setChoiceMode(int) |
michael@0 | 592 | */ |
michael@0 | 593 | public boolean isItemChecked(int position) { |
michael@0 | 594 | if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 && mCheckStates != null) { |
michael@0 | 595 | return mCheckStates.get(position); |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | return false; |
michael@0 | 599 | } |
michael@0 | 600 | |
michael@0 | 601 | /** |
michael@0 | 602 | * Returns the currently checked item. The result is only valid if the choice |
michael@0 | 603 | * mode has been set to {@link #CHOICE_MODE_SINGLE}. |
michael@0 | 604 | * |
michael@0 | 605 | * @return The position of the currently checked item or |
michael@0 | 606 | * {@link #INVALID_POSITION} if nothing is selected |
michael@0 | 607 | * |
michael@0 | 608 | * @see #setChoiceMode(int) |
michael@0 | 609 | */ |
michael@0 | 610 | public int getCheckedItemPosition() { |
michael@0 | 611 | if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0 && |
michael@0 | 612 | mCheckStates != null && mCheckStates.size() == 1) { |
michael@0 | 613 | return mCheckStates.keyAt(0); |
michael@0 | 614 | } |
michael@0 | 615 | |
michael@0 | 616 | return INVALID_POSITION; |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | /** |
michael@0 | 620 | * Returns the set of checked items in the list. The result is only valid if |
michael@0 | 621 | * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. |
michael@0 | 622 | * |
michael@0 | 623 | * @return A SparseBooleanArray which will return true for each call to |
michael@0 | 624 | * get(int position) where position is a position in the list, |
michael@0 | 625 | * or <code>null</code> if the choice mode is set to |
michael@0 | 626 | * {@link #CHOICE_MODE_NONE}. |
michael@0 | 627 | */ |
michael@0 | 628 | public SparseBooleanArray getCheckedItemPositions() { |
michael@0 | 629 | if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { |
michael@0 | 630 | return mCheckStates; |
michael@0 | 631 | } |
michael@0 | 632 | |
michael@0 | 633 | return null; |
michael@0 | 634 | } |
michael@0 | 635 | |
michael@0 | 636 | /** |
michael@0 | 637 | * Returns the set of checked items ids. The result is only valid if the |
michael@0 | 638 | * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter |
michael@0 | 639 | * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) |
michael@0 | 640 | * |
michael@0 | 641 | * @return A new array which contains the id of each checked item in the |
michael@0 | 642 | * list. |
michael@0 | 643 | */ |
michael@0 | 644 | public long[] getCheckedItemIds() { |
michael@0 | 645 | if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 || |
michael@0 | 646 | mCheckedIdStates == null || mAdapter == null) { |
michael@0 | 647 | return new long[0]; |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | final LongSparseArray<Integer> idStates = mCheckedIdStates; |
michael@0 | 651 | final int count = idStates.size(); |
michael@0 | 652 | final long[] ids = new long[count]; |
michael@0 | 653 | |
michael@0 | 654 | for (int i = 0; i < count; i++) { |
michael@0 | 655 | ids[i] = idStates.keyAt(i); |
michael@0 | 656 | } |
michael@0 | 657 | |
michael@0 | 658 | return ids; |
michael@0 | 659 | } |
michael@0 | 660 | |
michael@0 | 661 | /** |
michael@0 | 662 | * Sets the checked state of the specified position. The is only valid if |
michael@0 | 663 | * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or |
michael@0 | 664 | * {@link #CHOICE_MODE_MULTIPLE}. |
michael@0 | 665 | * |
michael@0 | 666 | * @param position The item whose checked state is to be checked |
michael@0 | 667 | * @param value The new checked state for the item |
michael@0 | 668 | */ |
michael@0 | 669 | public void setItemChecked(int position, boolean value) { |
michael@0 | 670 | if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0) { |
michael@0 | 671 | return; |
michael@0 | 672 | } |
michael@0 | 673 | |
michael@0 | 674 | if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { |
michael@0 | 675 | boolean oldValue = mCheckStates.get(position); |
michael@0 | 676 | mCheckStates.put(position, value); |
michael@0 | 677 | |
michael@0 | 678 | if (mCheckedIdStates != null && mAdapter.hasStableIds()) { |
michael@0 | 679 | if (value) { |
michael@0 | 680 | mCheckedIdStates.put(mAdapter.getItemId(position), position); |
michael@0 | 681 | } else { |
michael@0 | 682 | mCheckedIdStates.delete(mAdapter.getItemId(position)); |
michael@0 | 683 | } |
michael@0 | 684 | } |
michael@0 | 685 | |
michael@0 | 686 | if (oldValue != value) { |
michael@0 | 687 | if (value) { |
michael@0 | 688 | mCheckedItemCount++; |
michael@0 | 689 | } else { |
michael@0 | 690 | mCheckedItemCount--; |
michael@0 | 691 | } |
michael@0 | 692 | } |
michael@0 | 693 | } else { |
michael@0 | 694 | boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds(); |
michael@0 | 695 | |
michael@0 | 696 | // Clear all values if we're checking something, or unchecking the currently |
michael@0 | 697 | // selected item |
michael@0 | 698 | if (value || isItemChecked(position)) { |
michael@0 | 699 | mCheckStates.clear(); |
michael@0 | 700 | |
michael@0 | 701 | if (updateIds) { |
michael@0 | 702 | mCheckedIdStates.clear(); |
michael@0 | 703 | } |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | // This may end up selecting the value we just cleared but this way |
michael@0 | 707 | // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on |
michael@0 | 708 | if (value) { |
michael@0 | 709 | mCheckStates.put(position, true); |
michael@0 | 710 | |
michael@0 | 711 | if (updateIds) { |
michael@0 | 712 | mCheckedIdStates.put(mAdapter.getItemId(position), position); |
michael@0 | 713 | } |
michael@0 | 714 | |
michael@0 | 715 | mCheckedItemCount = 1; |
michael@0 | 716 | } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { |
michael@0 | 717 | mCheckedItemCount = 0; |
michael@0 | 718 | } |
michael@0 | 719 | } |
michael@0 | 720 | |
michael@0 | 721 | // Do not generate a data change while we are in the layout phase |
michael@0 | 722 | if (!mInLayout && !mBlockLayoutRequests) { |
michael@0 | 723 | mDataChanged = true; |
michael@0 | 724 | rememberSyncState(); |
michael@0 | 725 | requestLayout(); |
michael@0 | 726 | } |
michael@0 | 727 | } |
michael@0 | 728 | |
michael@0 | 729 | /** |
michael@0 | 730 | * Clear any choices previously set |
michael@0 | 731 | */ |
michael@0 | 732 | public void clearChoices() { |
michael@0 | 733 | if (mCheckStates != null) { |
michael@0 | 734 | mCheckStates.clear(); |
michael@0 | 735 | } |
michael@0 | 736 | |
michael@0 | 737 | if (mCheckedIdStates != null) { |
michael@0 | 738 | mCheckedIdStates.clear(); |
michael@0 | 739 | } |
michael@0 | 740 | |
michael@0 | 741 | mCheckedItemCount = 0; |
michael@0 | 742 | } |
michael@0 | 743 | |
michael@0 | 744 | /** |
michael@0 | 745 | * @see #setChoiceMode(int) |
michael@0 | 746 | * |
michael@0 | 747 | * @return The current choice mode |
michael@0 | 748 | */ |
michael@0 | 749 | public ChoiceMode getChoiceMode() { |
michael@0 | 750 | return mChoiceMode; |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | /** |
michael@0 | 754 | * Defines the choice behavior for the List. By default, Lists do not have any choice behavior |
michael@0 | 755 | * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the |
michael@0 | 756 | * List allows up to one item to be in a chosen state. By setting the choiceMode to |
michael@0 | 757 | * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. |
michael@0 | 758 | * |
michael@0 | 759 | * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or |
michael@0 | 760 | * {@link #CHOICE_MODE_MULTIPLE} |
michael@0 | 761 | */ |
michael@0 | 762 | public void setChoiceMode(ChoiceMode choiceMode) { |
michael@0 | 763 | mChoiceMode = choiceMode; |
michael@0 | 764 | |
michael@0 | 765 | if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { |
michael@0 | 766 | if (mCheckStates == null) { |
michael@0 | 767 | mCheckStates = new SparseBooleanArray(); |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { |
michael@0 | 771 | mCheckedIdStates = new LongSparseArray<Integer>(); |
michael@0 | 772 | } |
michael@0 | 773 | } |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | @Override |
michael@0 | 777 | public ListAdapter getAdapter() { |
michael@0 | 778 | return mAdapter; |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | @Override |
michael@0 | 782 | public void setAdapter(ListAdapter adapter) { |
michael@0 | 783 | if (mAdapter != null && mDataSetObserver != null) { |
michael@0 | 784 | mAdapter.unregisterDataSetObserver(mDataSetObserver); |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | resetState(); |
michael@0 | 788 | mRecycler.clear(); |
michael@0 | 789 | |
michael@0 | 790 | mAdapter = adapter; |
michael@0 | 791 | mDataChanged = true; |
michael@0 | 792 | |
michael@0 | 793 | mOldSelectedPosition = INVALID_POSITION; |
michael@0 | 794 | mOldSelectedRowId = INVALID_ROW_ID; |
michael@0 | 795 | |
michael@0 | 796 | if (mCheckStates != null) { |
michael@0 | 797 | mCheckStates.clear(); |
michael@0 | 798 | } |
michael@0 | 799 | |
michael@0 | 800 | if (mCheckedIdStates != null) { |
michael@0 | 801 | mCheckedIdStates.clear(); |
michael@0 | 802 | } |
michael@0 | 803 | |
michael@0 | 804 | if (mAdapter != null) { |
michael@0 | 805 | mOldItemCount = mItemCount; |
michael@0 | 806 | mItemCount = adapter.getCount(); |
michael@0 | 807 | |
michael@0 | 808 | mDataSetObserver = new AdapterDataSetObserver(); |
michael@0 | 809 | mAdapter.registerDataSetObserver(mDataSetObserver); |
michael@0 | 810 | |
michael@0 | 811 | mRecycler.setViewTypeCount(adapter.getViewTypeCount()); |
michael@0 | 812 | |
michael@0 | 813 | mHasStableIds = adapter.hasStableIds(); |
michael@0 | 814 | mAreAllItemsSelectable = adapter.areAllItemsEnabled(); |
michael@0 | 815 | |
michael@0 | 816 | if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mHasStableIds && |
michael@0 | 817 | mCheckedIdStates == null) { |
michael@0 | 818 | mCheckedIdStates = new LongSparseArray<Integer>(); |
michael@0 | 819 | } |
michael@0 | 820 | |
michael@0 | 821 | final int position = lookForSelectablePosition(0); |
michael@0 | 822 | setSelectedPositionInt(position); |
michael@0 | 823 | setNextSelectedPositionInt(position); |
michael@0 | 824 | |
michael@0 | 825 | if (mItemCount == 0) { |
michael@0 | 826 | checkSelectionChanged(); |
michael@0 | 827 | } |
michael@0 | 828 | } else { |
michael@0 | 829 | mItemCount = 0; |
michael@0 | 830 | mHasStableIds = false; |
michael@0 | 831 | mAreAllItemsSelectable = true; |
michael@0 | 832 | |
michael@0 | 833 | checkSelectionChanged(); |
michael@0 | 834 | } |
michael@0 | 835 | |
michael@0 | 836 | checkFocus(); |
michael@0 | 837 | requestLayout(); |
michael@0 | 838 | } |
michael@0 | 839 | |
michael@0 | 840 | @Override |
michael@0 | 841 | public int getFirstVisiblePosition() { |
michael@0 | 842 | return mFirstPosition; |
michael@0 | 843 | } |
michael@0 | 844 | |
michael@0 | 845 | @Override |
michael@0 | 846 | public int getLastVisiblePosition() { |
michael@0 | 847 | return mFirstPosition + getChildCount() - 1; |
michael@0 | 848 | } |
michael@0 | 849 | |
michael@0 | 850 | @Override |
michael@0 | 851 | public int getCount() { |
michael@0 | 852 | return mItemCount; |
michael@0 | 853 | } |
michael@0 | 854 | |
michael@0 | 855 | @Override |
michael@0 | 856 | public int getPositionForView(View view) { |
michael@0 | 857 | View child = view; |
michael@0 | 858 | try { |
michael@0 | 859 | View v; |
michael@0 | 860 | while (!(v = (View) child.getParent()).equals(this)) { |
michael@0 | 861 | child = v; |
michael@0 | 862 | } |
michael@0 | 863 | } catch (ClassCastException e) { |
michael@0 | 864 | // We made it up to the window without find this list view |
michael@0 | 865 | return INVALID_POSITION; |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | // Search the children for the list item |
michael@0 | 869 | final int childCount = getChildCount(); |
michael@0 | 870 | for (int i = 0; i < childCount; i++) { |
michael@0 | 871 | if (getChildAt(i).equals(child)) { |
michael@0 | 872 | return mFirstPosition + i; |
michael@0 | 873 | } |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | // Child not found! |
michael@0 | 877 | return INVALID_POSITION; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | @Override |
michael@0 | 881 | public void getFocusedRect(Rect r) { |
michael@0 | 882 | View view = getSelectedView(); |
michael@0 | 883 | |
michael@0 | 884 | if (view != null && view.getParent() == this) { |
michael@0 | 885 | // The focused rectangle of the selected view offset into the |
michael@0 | 886 | // coordinate space of this view. |
michael@0 | 887 | view.getFocusedRect(r); |
michael@0 | 888 | offsetDescendantRectToMyCoords(view, r); |
michael@0 | 889 | } else { |
michael@0 | 890 | super.getFocusedRect(r); |
michael@0 | 891 | } |
michael@0 | 892 | } |
michael@0 | 893 | |
michael@0 | 894 | @Override |
michael@0 | 895 | protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { |
michael@0 | 896 | super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); |
michael@0 | 897 | |
michael@0 | 898 | if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) { |
michael@0 | 899 | if (!mIsAttached && mAdapter != null) { |
michael@0 | 900 | // Data may have changed while we were detached and it's valid |
michael@0 | 901 | // to change focus while detached. Refresh so we don't die. |
michael@0 | 902 | mDataChanged = true; |
michael@0 | 903 | mOldItemCount = mItemCount; |
michael@0 | 904 | mItemCount = mAdapter.getCount(); |
michael@0 | 905 | } |
michael@0 | 906 | |
michael@0 | 907 | resurrectSelection(); |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | final ListAdapter adapter = mAdapter; |
michael@0 | 911 | int closetChildIndex = INVALID_POSITION; |
michael@0 | 912 | int closestChildStart = 0; |
michael@0 | 913 | |
michael@0 | 914 | if (adapter != null && gainFocus && previouslyFocusedRect != null) { |
michael@0 | 915 | previouslyFocusedRect.offset(getScrollX(), getScrollY()); |
michael@0 | 916 | |
michael@0 | 917 | // Don't cache the result of getChildCount or mFirstPosition here, |
michael@0 | 918 | // it could change in layoutChildren. |
michael@0 | 919 | if (adapter.getCount() < getChildCount() + mFirstPosition) { |
michael@0 | 920 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 921 | layoutChildren(); |
michael@0 | 922 | } |
michael@0 | 923 | |
michael@0 | 924 | // Figure out which item should be selected based on previously |
michael@0 | 925 | // focused rect. |
michael@0 | 926 | Rect otherRect = mTempRect; |
michael@0 | 927 | int minDistance = Integer.MAX_VALUE; |
michael@0 | 928 | final int childCount = getChildCount(); |
michael@0 | 929 | final int firstPosition = mFirstPosition; |
michael@0 | 930 | |
michael@0 | 931 | for (int i = 0; i < childCount; i++) { |
michael@0 | 932 | // Only consider selectable views |
michael@0 | 933 | if (!adapter.isEnabled(firstPosition + i)) { |
michael@0 | 934 | continue; |
michael@0 | 935 | } |
michael@0 | 936 | |
michael@0 | 937 | View other = getChildAt(i); |
michael@0 | 938 | other.getDrawingRect(otherRect); |
michael@0 | 939 | offsetDescendantRectToMyCoords(other, otherRect); |
michael@0 | 940 | int distance = getDistance(previouslyFocusedRect, otherRect, direction); |
michael@0 | 941 | |
michael@0 | 942 | if (distance < minDistance) { |
michael@0 | 943 | minDistance = distance; |
michael@0 | 944 | closetChildIndex = i; |
michael@0 | 945 | closestChildStart = (mIsVertical ? other.getTop() : other.getLeft()); |
michael@0 | 946 | } |
michael@0 | 947 | } |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | if (closetChildIndex >= 0) { |
michael@0 | 951 | setSelectionFromOffset(closetChildIndex + mFirstPosition, closestChildStart); |
michael@0 | 952 | } else { |
michael@0 | 953 | requestLayout(); |
michael@0 | 954 | } |
michael@0 | 955 | } |
michael@0 | 956 | |
michael@0 | 957 | @Override |
michael@0 | 958 | protected void onAttachedToWindow() { |
michael@0 | 959 | super.onAttachedToWindow(); |
michael@0 | 960 | |
michael@0 | 961 | final ViewTreeObserver treeObserver = getViewTreeObserver(); |
michael@0 | 962 | treeObserver.addOnTouchModeChangeListener(this); |
michael@0 | 963 | |
michael@0 | 964 | if (mAdapter != null && mDataSetObserver == null) { |
michael@0 | 965 | mDataSetObserver = new AdapterDataSetObserver(); |
michael@0 | 966 | mAdapter.registerDataSetObserver(mDataSetObserver); |
michael@0 | 967 | |
michael@0 | 968 | // Data may have changed while we were detached. Refresh. |
michael@0 | 969 | mDataChanged = true; |
michael@0 | 970 | mOldItemCount = mItemCount; |
michael@0 | 971 | mItemCount = mAdapter.getCount(); |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | mIsAttached = true; |
michael@0 | 975 | } |
michael@0 | 976 | |
michael@0 | 977 | @Override |
michael@0 | 978 | protected void onDetachedFromWindow() { |
michael@0 | 979 | super.onDetachedFromWindow(); |
michael@0 | 980 | |
michael@0 | 981 | // Detach any view left in the scrap heap |
michael@0 | 982 | mRecycler.clear(); |
michael@0 | 983 | |
michael@0 | 984 | final ViewTreeObserver treeObserver = getViewTreeObserver(); |
michael@0 | 985 | treeObserver.removeOnTouchModeChangeListener(this); |
michael@0 | 986 | |
michael@0 | 987 | if (mAdapter != null) { |
michael@0 | 988 | mAdapter.unregisterDataSetObserver(mDataSetObserver); |
michael@0 | 989 | mDataSetObserver = null; |
michael@0 | 990 | } |
michael@0 | 991 | |
michael@0 | 992 | if (mPerformClick != null) { |
michael@0 | 993 | removeCallbacks(mPerformClick); |
michael@0 | 994 | } |
michael@0 | 995 | |
michael@0 | 996 | if (mTouchModeReset != null) { |
michael@0 | 997 | removeCallbacks(mTouchModeReset); |
michael@0 | 998 | mTouchModeReset.run(); |
michael@0 | 999 | } |
michael@0 | 1000 | |
michael@0 | 1001 | mIsAttached = false; |
michael@0 | 1002 | } |
michael@0 | 1003 | |
michael@0 | 1004 | @Override |
michael@0 | 1005 | public void onWindowFocusChanged(boolean hasWindowFocus) { |
michael@0 | 1006 | super.onWindowFocusChanged(hasWindowFocus); |
michael@0 | 1007 | |
michael@0 | 1008 | final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF; |
michael@0 | 1009 | |
michael@0 | 1010 | if (!hasWindowFocus) { |
michael@0 | 1011 | if (touchMode == TOUCH_MODE_OFF) { |
michael@0 | 1012 | // Remember the last selected element |
michael@0 | 1013 | mResurrectToPosition = mSelectedPosition; |
michael@0 | 1014 | } |
michael@0 | 1015 | } else { |
michael@0 | 1016 | // If we changed touch mode since the last time we had focus |
michael@0 | 1017 | if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { |
michael@0 | 1018 | // If we come back in trackball mode, we bring the selection back |
michael@0 | 1019 | if (touchMode == TOUCH_MODE_OFF) { |
michael@0 | 1020 | // This will trigger a layout |
michael@0 | 1021 | resurrectSelection(); |
michael@0 | 1022 | |
michael@0 | 1023 | // If we come back in touch mode, then we want to hide the selector |
michael@0 | 1024 | } else { |
michael@0 | 1025 | hideSelector(); |
michael@0 | 1026 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 1027 | layoutChildren(); |
michael@0 | 1028 | } |
michael@0 | 1029 | } |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | mLastTouchMode = touchMode; |
michael@0 | 1033 | } |
michael@0 | 1034 | |
michael@0 | 1035 | @Override |
michael@0 | 1036 | protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { |
michael@0 | 1037 | boolean needsInvalidate = false; |
michael@0 | 1038 | |
michael@0 | 1039 | if (mIsVertical && mOverScroll != scrollY) { |
michael@0 | 1040 | onScrollChanged(getScrollX(), scrollY, getScrollX(), mOverScroll); |
michael@0 | 1041 | mOverScroll = scrollY; |
michael@0 | 1042 | needsInvalidate = true; |
michael@0 | 1043 | } else if (!mIsVertical && mOverScroll != scrollX) { |
michael@0 | 1044 | onScrollChanged(scrollX, getScrollY(), mOverScroll, getScrollY()); |
michael@0 | 1045 | mOverScroll = scrollX; |
michael@0 | 1046 | needsInvalidate = true; |
michael@0 | 1047 | } |
michael@0 | 1048 | |
michael@0 | 1049 | if (needsInvalidate) { |
michael@0 | 1050 | invalidate(); |
michael@0 | 1051 | awakenScrollbarsInternal(); |
michael@0 | 1052 | } |
michael@0 | 1053 | } |
michael@0 | 1054 | |
michael@0 | 1055 | @TargetApi(9) |
michael@0 | 1056 | private boolean overScrollByInternal(int deltaX, int deltaY, |
michael@0 | 1057 | int scrollX, int scrollY, |
michael@0 | 1058 | int scrollRangeX, int scrollRangeY, |
michael@0 | 1059 | int maxOverScrollX, int maxOverScrollY, |
michael@0 | 1060 | boolean isTouchEvent) { |
michael@0 | 1061 | if (Build.VERSION.SDK_INT < 9) { |
michael@0 | 1062 | return false; |
michael@0 | 1063 | } |
michael@0 | 1064 | |
michael@0 | 1065 | return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, |
michael@0 | 1066 | scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); |
michael@0 | 1067 | } |
michael@0 | 1068 | |
michael@0 | 1069 | @Override |
michael@0 | 1070 | @TargetApi(9) |
michael@0 | 1071 | public void setOverScrollMode(int mode) { |
michael@0 | 1072 | if (Build.VERSION.SDK_INT < 9) { |
michael@0 | 1073 | return; |
michael@0 | 1074 | } |
michael@0 | 1075 | |
michael@0 | 1076 | if (mode != ViewCompat.OVER_SCROLL_NEVER) { |
michael@0 | 1077 | if (mStartEdge == null) { |
michael@0 | 1078 | Context context = getContext(); |
michael@0 | 1079 | |
michael@0 | 1080 | mStartEdge = new EdgeEffectCompat(context); |
michael@0 | 1081 | mEndEdge = new EdgeEffectCompat(context); |
michael@0 | 1082 | } |
michael@0 | 1083 | } else { |
michael@0 | 1084 | mStartEdge = null; |
michael@0 | 1085 | mEndEdge = null; |
michael@0 | 1086 | } |
michael@0 | 1087 | |
michael@0 | 1088 | super.setOverScrollMode(mode); |
michael@0 | 1089 | } |
michael@0 | 1090 | |
michael@0 | 1091 | public int pointToPosition(int x, int y) { |
michael@0 | 1092 | Rect frame = mTouchFrame; |
michael@0 | 1093 | if (frame == null) { |
michael@0 | 1094 | mTouchFrame = new Rect(); |
michael@0 | 1095 | frame = mTouchFrame; |
michael@0 | 1096 | } |
michael@0 | 1097 | |
michael@0 | 1098 | final int count = getChildCount(); |
michael@0 | 1099 | for (int i = count - 1; i >= 0; i--) { |
michael@0 | 1100 | final View child = getChildAt(i); |
michael@0 | 1101 | |
michael@0 | 1102 | if (child.getVisibility() == View.VISIBLE) { |
michael@0 | 1103 | child.getHitRect(frame); |
michael@0 | 1104 | |
michael@0 | 1105 | if (frame.contains(x, y)) { |
michael@0 | 1106 | return mFirstPosition + i; |
michael@0 | 1107 | } |
michael@0 | 1108 | } |
michael@0 | 1109 | } |
michael@0 | 1110 | return INVALID_POSITION; |
michael@0 | 1111 | } |
michael@0 | 1112 | |
michael@0 | 1113 | @Override |
michael@0 | 1114 | protected int computeVerticalScrollExtent() { |
michael@0 | 1115 | final int count = getChildCount(); |
michael@0 | 1116 | if (count == 0) { |
michael@0 | 1117 | return 0; |
michael@0 | 1118 | } |
michael@0 | 1119 | |
michael@0 | 1120 | int extent = count * 100; |
michael@0 | 1121 | |
michael@0 | 1122 | View child = getChildAt(0); |
michael@0 | 1123 | final int childTop = child.getTop(); |
michael@0 | 1124 | |
michael@0 | 1125 | int childHeight = child.getHeight(); |
michael@0 | 1126 | if (childHeight > 0) { |
michael@0 | 1127 | extent += (childTop * 100) / childHeight; |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | child = getChildAt(count - 1); |
michael@0 | 1131 | final int childBottom = child.getBottom(); |
michael@0 | 1132 | |
michael@0 | 1133 | childHeight = child.getHeight(); |
michael@0 | 1134 | if (childHeight > 0) { |
michael@0 | 1135 | extent -= ((childBottom - getHeight()) * 100) / childHeight; |
michael@0 | 1136 | } |
michael@0 | 1137 | |
michael@0 | 1138 | return extent; |
michael@0 | 1139 | } |
michael@0 | 1140 | |
michael@0 | 1141 | @Override |
michael@0 | 1142 | protected int computeHorizontalScrollExtent() { |
michael@0 | 1143 | final int count = getChildCount(); |
michael@0 | 1144 | if (count == 0) { |
michael@0 | 1145 | return 0; |
michael@0 | 1146 | } |
michael@0 | 1147 | |
michael@0 | 1148 | int extent = count * 100; |
michael@0 | 1149 | |
michael@0 | 1150 | View child = getChildAt(0); |
michael@0 | 1151 | final int childLeft = child.getLeft(); |
michael@0 | 1152 | |
michael@0 | 1153 | int childWidth = child.getWidth(); |
michael@0 | 1154 | if (childWidth > 0) { |
michael@0 | 1155 | extent += (childLeft * 100) / childWidth; |
michael@0 | 1156 | } |
michael@0 | 1157 | |
michael@0 | 1158 | child = getChildAt(count - 1); |
michael@0 | 1159 | final int childRight = child.getRight(); |
michael@0 | 1160 | |
michael@0 | 1161 | childWidth = child.getWidth(); |
michael@0 | 1162 | if (childWidth > 0) { |
michael@0 | 1163 | extent -= ((childRight - getWidth()) * 100) / childWidth; |
michael@0 | 1164 | } |
michael@0 | 1165 | |
michael@0 | 1166 | return extent; |
michael@0 | 1167 | } |
michael@0 | 1168 | |
michael@0 | 1169 | @Override |
michael@0 | 1170 | protected int computeVerticalScrollOffset() { |
michael@0 | 1171 | final int firstPosition = mFirstPosition; |
michael@0 | 1172 | final int childCount = getChildCount(); |
michael@0 | 1173 | |
michael@0 | 1174 | if (firstPosition < 0 || childCount == 0) { |
michael@0 | 1175 | return 0; |
michael@0 | 1176 | } |
michael@0 | 1177 | |
michael@0 | 1178 | final View child = getChildAt(0); |
michael@0 | 1179 | final int childTop = child.getTop(); |
michael@0 | 1180 | |
michael@0 | 1181 | int childHeight = child.getHeight(); |
michael@0 | 1182 | if (childHeight > 0) { |
michael@0 | 1183 | return Math.max(firstPosition * 100 - (childTop * 100) / childHeight, 0); |
michael@0 | 1184 | } |
michael@0 | 1185 | |
michael@0 | 1186 | return 0; |
michael@0 | 1187 | } |
michael@0 | 1188 | |
michael@0 | 1189 | @Override |
michael@0 | 1190 | protected int computeHorizontalScrollOffset() { |
michael@0 | 1191 | final int firstPosition = mFirstPosition; |
michael@0 | 1192 | final int childCount = getChildCount(); |
michael@0 | 1193 | |
michael@0 | 1194 | if (firstPosition < 0 || childCount == 0) { |
michael@0 | 1195 | return 0; |
michael@0 | 1196 | } |
michael@0 | 1197 | |
michael@0 | 1198 | final View child = getChildAt(0); |
michael@0 | 1199 | final int childLeft = child.getLeft(); |
michael@0 | 1200 | |
michael@0 | 1201 | int childWidth = child.getWidth(); |
michael@0 | 1202 | if (childWidth > 0) { |
michael@0 | 1203 | return Math.max(firstPosition * 100 - (childLeft * 100) / childWidth, 0); |
michael@0 | 1204 | } |
michael@0 | 1205 | |
michael@0 | 1206 | return 0; |
michael@0 | 1207 | } |
michael@0 | 1208 | |
michael@0 | 1209 | @Override |
michael@0 | 1210 | protected int computeVerticalScrollRange() { |
michael@0 | 1211 | int result = Math.max(mItemCount * 100, 0); |
michael@0 | 1212 | |
michael@0 | 1213 | if (mIsVertical && mOverScroll != 0) { |
michael@0 | 1214 | // Compensate for overscroll |
michael@0 | 1215 | result += Math.abs((int) ((float) mOverScroll / getHeight() * mItemCount * 100)); |
michael@0 | 1216 | } |
michael@0 | 1217 | |
michael@0 | 1218 | return result; |
michael@0 | 1219 | } |
michael@0 | 1220 | |
michael@0 | 1221 | @Override |
michael@0 | 1222 | protected int computeHorizontalScrollRange() { |
michael@0 | 1223 | int result = Math.max(mItemCount * 100, 0); |
michael@0 | 1224 | |
michael@0 | 1225 | if (!mIsVertical && mOverScroll != 0) { |
michael@0 | 1226 | // Compensate for overscroll |
michael@0 | 1227 | result += Math.abs((int) ((float) mOverScroll / getWidth() * mItemCount * 100)); |
michael@0 | 1228 | } |
michael@0 | 1229 | |
michael@0 | 1230 | return result; |
michael@0 | 1231 | } |
michael@0 | 1232 | |
michael@0 | 1233 | @Override |
michael@0 | 1234 | public boolean showContextMenuForChild(View originalView) { |
michael@0 | 1235 | final int longPressPosition = getPositionForView(originalView); |
michael@0 | 1236 | if (longPressPosition >= 0) { |
michael@0 | 1237 | final long longPressId = mAdapter.getItemId(longPressPosition); |
michael@0 | 1238 | boolean handled = false; |
michael@0 | 1239 | |
michael@0 | 1240 | OnItemLongClickListener listener = getOnItemLongClickListener(); |
michael@0 | 1241 | if (listener != null) { |
michael@0 | 1242 | handled = listener.onItemLongClick(TwoWayView.this, originalView, |
michael@0 | 1243 | longPressPosition, longPressId); |
michael@0 | 1244 | } |
michael@0 | 1245 | |
michael@0 | 1246 | if (!handled) { |
michael@0 | 1247 | mContextMenuInfo = createContextMenuInfo( |
michael@0 | 1248 | getChildAt(longPressPosition - mFirstPosition), |
michael@0 | 1249 | longPressPosition, longPressId); |
michael@0 | 1250 | |
michael@0 | 1251 | handled = super.showContextMenuForChild(originalView); |
michael@0 | 1252 | } |
michael@0 | 1253 | |
michael@0 | 1254 | return handled; |
michael@0 | 1255 | } |
michael@0 | 1256 | |
michael@0 | 1257 | return false; |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | @Override |
michael@0 | 1261 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
michael@0 | 1262 | if (disallowIntercept) { |
michael@0 | 1263 | recycleVelocityTracker(); |
michael@0 | 1264 | } |
michael@0 | 1265 | |
michael@0 | 1266 | super.requestDisallowInterceptTouchEvent(disallowIntercept); |
michael@0 | 1267 | } |
michael@0 | 1268 | |
michael@0 | 1269 | @Override |
michael@0 | 1270 | public boolean onInterceptTouchEvent(MotionEvent ev) { |
michael@0 | 1271 | if (!mIsAttached) { |
michael@0 | 1272 | return false; |
michael@0 | 1273 | } |
michael@0 | 1274 | |
michael@0 | 1275 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; |
michael@0 | 1276 | switch (action) { |
michael@0 | 1277 | case MotionEvent.ACTION_DOWN: |
michael@0 | 1278 | initOrResetVelocityTracker(); |
michael@0 | 1279 | mVelocityTracker.addMovement(ev); |
michael@0 | 1280 | |
michael@0 | 1281 | mScroller.abortAnimation(); |
michael@0 | 1282 | |
michael@0 | 1283 | final float x = ev.getX(); |
michael@0 | 1284 | final float y = ev.getY(); |
michael@0 | 1285 | |
michael@0 | 1286 | mLastTouchPos = (mIsVertical ? y : x); |
michael@0 | 1287 | |
michael@0 | 1288 | final int motionPosition = findMotionRowOrColumn((int) mLastTouchPos); |
michael@0 | 1289 | |
michael@0 | 1290 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); |
michael@0 | 1291 | mTouchRemainderPos = 0; |
michael@0 | 1292 | |
michael@0 | 1293 | if (mTouchMode == TOUCH_MODE_FLINGING) { |
michael@0 | 1294 | return true; |
michael@0 | 1295 | } else if (motionPosition >= 0) { |
michael@0 | 1296 | mMotionPosition = motionPosition; |
michael@0 | 1297 | mTouchMode = TOUCH_MODE_DOWN; |
michael@0 | 1298 | } |
michael@0 | 1299 | |
michael@0 | 1300 | break; |
michael@0 | 1301 | |
michael@0 | 1302 | case MotionEvent.ACTION_MOVE: { |
michael@0 | 1303 | if (mTouchMode != TOUCH_MODE_DOWN) { |
michael@0 | 1304 | break; |
michael@0 | 1305 | } |
michael@0 | 1306 | |
michael@0 | 1307 | initVelocityTrackerIfNotExists(); |
michael@0 | 1308 | mVelocityTracker.addMovement(ev); |
michael@0 | 1309 | |
michael@0 | 1310 | final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); |
michael@0 | 1311 | if (index < 0) { |
michael@0 | 1312 | Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " + |
michael@0 | 1313 | mActivePointerId + " - did TwoWayView receive an inconsistent " + |
michael@0 | 1314 | "event stream?"); |
michael@0 | 1315 | return false; |
michael@0 | 1316 | } |
michael@0 | 1317 | |
michael@0 | 1318 | final float pos; |
michael@0 | 1319 | if (mIsVertical) { |
michael@0 | 1320 | pos = MotionEventCompat.getY(ev, index); |
michael@0 | 1321 | } else { |
michael@0 | 1322 | pos = MotionEventCompat.getX(ev, index); |
michael@0 | 1323 | } |
michael@0 | 1324 | |
michael@0 | 1325 | final float diff = pos - mLastTouchPos + mTouchRemainderPos; |
michael@0 | 1326 | final int delta = (int) diff; |
michael@0 | 1327 | mTouchRemainderPos = diff - delta; |
michael@0 | 1328 | |
michael@0 | 1329 | if (maybeStartScrolling(delta)) { |
michael@0 | 1330 | return true; |
michael@0 | 1331 | } |
michael@0 | 1332 | |
michael@0 | 1333 | break; |
michael@0 | 1334 | } |
michael@0 | 1335 | |
michael@0 | 1336 | case MotionEvent.ACTION_CANCEL: |
michael@0 | 1337 | case MotionEvent.ACTION_UP: |
michael@0 | 1338 | mActivePointerId = INVALID_POINTER; |
michael@0 | 1339 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1340 | recycleVelocityTracker(); |
michael@0 | 1341 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 1342 | |
michael@0 | 1343 | break; |
michael@0 | 1344 | } |
michael@0 | 1345 | |
michael@0 | 1346 | return false; |
michael@0 | 1347 | } |
michael@0 | 1348 | |
michael@0 | 1349 | @Override |
michael@0 | 1350 | public boolean onTouchEvent(MotionEvent ev) { |
michael@0 | 1351 | if (!isEnabled()) { |
michael@0 | 1352 | // A disabled view that is clickable still consumes the touch |
michael@0 | 1353 | // events, it just doesn't respond to them. |
michael@0 | 1354 | return isClickable() || isLongClickable(); |
michael@0 | 1355 | } |
michael@0 | 1356 | |
michael@0 | 1357 | if (!mIsAttached) { |
michael@0 | 1358 | return false; |
michael@0 | 1359 | } |
michael@0 | 1360 | |
michael@0 | 1361 | boolean needsInvalidate = false; |
michael@0 | 1362 | |
michael@0 | 1363 | initVelocityTrackerIfNotExists(); |
michael@0 | 1364 | mVelocityTracker.addMovement(ev); |
michael@0 | 1365 | |
michael@0 | 1366 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; |
michael@0 | 1367 | switch (action) { |
michael@0 | 1368 | case MotionEvent.ACTION_DOWN: { |
michael@0 | 1369 | if (mDataChanged) { |
michael@0 | 1370 | break; |
michael@0 | 1371 | } |
michael@0 | 1372 | |
michael@0 | 1373 | mVelocityTracker.clear(); |
michael@0 | 1374 | mScroller.abortAnimation(); |
michael@0 | 1375 | |
michael@0 | 1376 | final float x = ev.getX(); |
michael@0 | 1377 | final float y = ev.getY(); |
michael@0 | 1378 | |
michael@0 | 1379 | mLastTouchPos = (mIsVertical ? y : x); |
michael@0 | 1380 | |
michael@0 | 1381 | int motionPosition = pointToPosition((int) x, (int) y); |
michael@0 | 1382 | |
michael@0 | 1383 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0); |
michael@0 | 1384 | mTouchRemainderPos = 0; |
michael@0 | 1385 | |
michael@0 | 1386 | if (mDataChanged) { |
michael@0 | 1387 | break; |
michael@0 | 1388 | } |
michael@0 | 1389 | |
michael@0 | 1390 | if (mTouchMode == TOUCH_MODE_FLINGING) { |
michael@0 | 1391 | mTouchMode = TOUCH_MODE_DRAGGING; |
michael@0 | 1392 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); |
michael@0 | 1393 | motionPosition = findMotionRowOrColumn((int) mLastTouchPos); |
michael@0 | 1394 | return true; |
michael@0 | 1395 | } else if (mMotionPosition >= 0 && mAdapter.isEnabled(mMotionPosition)) { |
michael@0 | 1396 | mTouchMode = TOUCH_MODE_DOWN; |
michael@0 | 1397 | triggerCheckForTap(); |
michael@0 | 1398 | } |
michael@0 | 1399 | |
michael@0 | 1400 | mMotionPosition = motionPosition; |
michael@0 | 1401 | |
michael@0 | 1402 | break; |
michael@0 | 1403 | } |
michael@0 | 1404 | |
michael@0 | 1405 | case MotionEvent.ACTION_MOVE: { |
michael@0 | 1406 | final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); |
michael@0 | 1407 | if (index < 0) { |
michael@0 | 1408 | Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " + |
michael@0 | 1409 | mActivePointerId + " - did TwoWayView receive an inconsistent " + |
michael@0 | 1410 | "event stream?"); |
michael@0 | 1411 | return false; |
michael@0 | 1412 | } |
michael@0 | 1413 | |
michael@0 | 1414 | final float pos; |
michael@0 | 1415 | if (mIsVertical) { |
michael@0 | 1416 | pos = MotionEventCompat.getY(ev, index); |
michael@0 | 1417 | } else { |
michael@0 | 1418 | pos = MotionEventCompat.getX(ev, index); |
michael@0 | 1419 | } |
michael@0 | 1420 | |
michael@0 | 1421 | if (mDataChanged) { |
michael@0 | 1422 | // Re-sync everything if data has been changed |
michael@0 | 1423 | // since the scroll operation can query the adapter. |
michael@0 | 1424 | layoutChildren(); |
michael@0 | 1425 | } |
michael@0 | 1426 | |
michael@0 | 1427 | final float diff = pos - mLastTouchPos + mTouchRemainderPos; |
michael@0 | 1428 | final int delta = (int) diff; |
michael@0 | 1429 | mTouchRemainderPos = diff - delta; |
michael@0 | 1430 | |
michael@0 | 1431 | switch (mTouchMode) { |
michael@0 | 1432 | case TOUCH_MODE_DOWN: |
michael@0 | 1433 | case TOUCH_MODE_TAP: |
michael@0 | 1434 | case TOUCH_MODE_DONE_WAITING: |
michael@0 | 1435 | // Check if we have moved far enough that it looks more like a |
michael@0 | 1436 | // scroll than a tap |
michael@0 | 1437 | maybeStartScrolling(delta); |
michael@0 | 1438 | break; |
michael@0 | 1439 | |
michael@0 | 1440 | case TOUCH_MODE_DRAGGING: |
michael@0 | 1441 | case TOUCH_MODE_OVERSCROLL: |
michael@0 | 1442 | mLastTouchPos = pos; |
michael@0 | 1443 | maybeScroll(delta); |
michael@0 | 1444 | break; |
michael@0 | 1445 | } |
michael@0 | 1446 | |
michael@0 | 1447 | break; |
michael@0 | 1448 | } |
michael@0 | 1449 | |
michael@0 | 1450 | case MotionEvent.ACTION_CANCEL: |
michael@0 | 1451 | cancelCheckForTap(); |
michael@0 | 1452 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1453 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 1454 | |
michael@0 | 1455 | setPressed(false); |
michael@0 | 1456 | View motionView = this.getChildAt(mMotionPosition - mFirstPosition); |
michael@0 | 1457 | if (motionView != null) { |
michael@0 | 1458 | motionView.setPressed(false); |
michael@0 | 1459 | } |
michael@0 | 1460 | |
michael@0 | 1461 | if (mStartEdge != null && mEndEdge != null) { |
michael@0 | 1462 | needsInvalidate = mStartEdge.onRelease() | mEndEdge.onRelease(); |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | recycleVelocityTracker(); |
michael@0 | 1466 | |
michael@0 | 1467 | break; |
michael@0 | 1468 | |
michael@0 | 1469 | case MotionEvent.ACTION_UP: { |
michael@0 | 1470 | switch (mTouchMode) { |
michael@0 | 1471 | case TOUCH_MODE_DOWN: |
michael@0 | 1472 | case TOUCH_MODE_TAP: |
michael@0 | 1473 | case TOUCH_MODE_DONE_WAITING: { |
michael@0 | 1474 | final int motionPosition = mMotionPosition; |
michael@0 | 1475 | final View child = getChildAt(motionPosition - mFirstPosition); |
michael@0 | 1476 | |
michael@0 | 1477 | final float x = ev.getX(); |
michael@0 | 1478 | final float y = ev.getY(); |
michael@0 | 1479 | |
michael@0 | 1480 | boolean inList = false; |
michael@0 | 1481 | if (mIsVertical) { |
michael@0 | 1482 | inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight(); |
michael@0 | 1483 | } else { |
michael@0 | 1484 | inList = y > getPaddingTop() && y < getHeight() - getPaddingBottom(); |
michael@0 | 1485 | } |
michael@0 | 1486 | |
michael@0 | 1487 | if (child != null && !child.hasFocusable() && inList) { |
michael@0 | 1488 | if (mTouchMode != TOUCH_MODE_DOWN) { |
michael@0 | 1489 | child.setPressed(false); |
michael@0 | 1490 | } |
michael@0 | 1491 | |
michael@0 | 1492 | if (mPerformClick == null) { |
michael@0 | 1493 | mPerformClick = new PerformClick(); |
michael@0 | 1494 | } |
michael@0 | 1495 | |
michael@0 | 1496 | final PerformClick performClick = mPerformClick; |
michael@0 | 1497 | performClick.mClickMotionPosition = motionPosition; |
michael@0 | 1498 | performClick.rememberWindowAttachCount(); |
michael@0 | 1499 | |
michael@0 | 1500 | mResurrectToPosition = motionPosition; |
michael@0 | 1501 | |
michael@0 | 1502 | if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { |
michael@0 | 1503 | if (mTouchMode == TOUCH_MODE_DOWN) { |
michael@0 | 1504 | cancelCheckForTap(); |
michael@0 | 1505 | } else { |
michael@0 | 1506 | cancelCheckForLongPress(); |
michael@0 | 1507 | } |
michael@0 | 1508 | |
michael@0 | 1509 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 1510 | |
michael@0 | 1511 | if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
michael@0 | 1512 | mTouchMode = TOUCH_MODE_TAP; |
michael@0 | 1513 | |
michael@0 | 1514 | setPressed(true); |
michael@0 | 1515 | positionSelector(mMotionPosition, child); |
michael@0 | 1516 | child.setPressed(true); |
michael@0 | 1517 | |
michael@0 | 1518 | if (mSelector != null) { |
michael@0 | 1519 | Drawable d = mSelector.getCurrent(); |
michael@0 | 1520 | if (d != null && d instanceof TransitionDrawable) { |
michael@0 | 1521 | ((TransitionDrawable) d).resetTransition(); |
michael@0 | 1522 | } |
michael@0 | 1523 | } |
michael@0 | 1524 | |
michael@0 | 1525 | if (mTouchModeReset != null) { |
michael@0 | 1526 | removeCallbacks(mTouchModeReset); |
michael@0 | 1527 | } |
michael@0 | 1528 | |
michael@0 | 1529 | mTouchModeReset = new Runnable() { |
michael@0 | 1530 | @Override |
michael@0 | 1531 | public void run() { |
michael@0 | 1532 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1533 | |
michael@0 | 1534 | setPressed(false); |
michael@0 | 1535 | child.setPressed(false); |
michael@0 | 1536 | |
michael@0 | 1537 | if (!mDataChanged) { |
michael@0 | 1538 | performClick.run(); |
michael@0 | 1539 | } |
michael@0 | 1540 | |
michael@0 | 1541 | mTouchModeReset = null; |
michael@0 | 1542 | } |
michael@0 | 1543 | }; |
michael@0 | 1544 | |
michael@0 | 1545 | postDelayed(mTouchModeReset, |
michael@0 | 1546 | ViewConfiguration.getPressedStateDuration()); |
michael@0 | 1547 | } else { |
michael@0 | 1548 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1549 | updateSelectorState(); |
michael@0 | 1550 | } |
michael@0 | 1551 | } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
michael@0 | 1552 | performClick.run(); |
michael@0 | 1553 | } |
michael@0 | 1554 | } |
michael@0 | 1555 | |
michael@0 | 1556 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1557 | updateSelectorState(); |
michael@0 | 1558 | |
michael@0 | 1559 | break; |
michael@0 | 1560 | } |
michael@0 | 1561 | |
michael@0 | 1562 | case TOUCH_MODE_DRAGGING: |
michael@0 | 1563 | if (contentFits()) { |
michael@0 | 1564 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1565 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 1566 | break; |
michael@0 | 1567 | } |
michael@0 | 1568 | |
michael@0 | 1569 | mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); |
michael@0 | 1570 | |
michael@0 | 1571 | final float velocity; |
michael@0 | 1572 | if (mIsVertical) { |
michael@0 | 1573 | velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker, |
michael@0 | 1574 | mActivePointerId); |
michael@0 | 1575 | } else { |
michael@0 | 1576 | velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker, |
michael@0 | 1577 | mActivePointerId); |
michael@0 | 1578 | } |
michael@0 | 1579 | |
michael@0 | 1580 | if (Math.abs(velocity) >= mFlingVelocity) { |
michael@0 | 1581 | mTouchMode = TOUCH_MODE_FLINGING; |
michael@0 | 1582 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); |
michael@0 | 1583 | |
michael@0 | 1584 | mScroller.fling(0, 0, |
michael@0 | 1585 | (int) (mIsVertical ? 0 : velocity), |
michael@0 | 1586 | (int) (mIsVertical ? velocity : 0), |
michael@0 | 1587 | (mIsVertical ? 0 : Integer.MIN_VALUE), |
michael@0 | 1588 | (mIsVertical ? 0 : Integer.MAX_VALUE), |
michael@0 | 1589 | (mIsVertical ? Integer.MIN_VALUE : 0), |
michael@0 | 1590 | (mIsVertical ? Integer.MAX_VALUE : 0)); |
michael@0 | 1591 | |
michael@0 | 1592 | mLastTouchPos = 0; |
michael@0 | 1593 | needsInvalidate = true; |
michael@0 | 1594 | } else { |
michael@0 | 1595 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1596 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 1597 | } |
michael@0 | 1598 | |
michael@0 | 1599 | break; |
michael@0 | 1600 | |
michael@0 | 1601 | case TOUCH_MODE_OVERSCROLL: |
michael@0 | 1602 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 1603 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 1604 | break; |
michael@0 | 1605 | } |
michael@0 | 1606 | |
michael@0 | 1607 | cancelCheckForTap(); |
michael@0 | 1608 | cancelCheckForLongPress(); |
michael@0 | 1609 | setPressed(false); |
michael@0 | 1610 | |
michael@0 | 1611 | if (mStartEdge != null && mEndEdge != null) { |
michael@0 | 1612 | needsInvalidate |= mStartEdge.onRelease() | mEndEdge.onRelease(); |
michael@0 | 1613 | } |
michael@0 | 1614 | |
michael@0 | 1615 | recycleVelocityTracker(); |
michael@0 | 1616 | |
michael@0 | 1617 | break; |
michael@0 | 1618 | } |
michael@0 | 1619 | } |
michael@0 | 1620 | |
michael@0 | 1621 | if (needsInvalidate) { |
michael@0 | 1622 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 1623 | } |
michael@0 | 1624 | |
michael@0 | 1625 | return true; |
michael@0 | 1626 | } |
michael@0 | 1627 | |
michael@0 | 1628 | @Override |
michael@0 | 1629 | public void onTouchModeChanged(boolean isInTouchMode) { |
michael@0 | 1630 | if (isInTouchMode) { |
michael@0 | 1631 | // Get rid of the selection when we enter touch mode |
michael@0 | 1632 | hideSelector(); |
michael@0 | 1633 | |
michael@0 | 1634 | // Layout, but only if we already have done so previously. |
michael@0 | 1635 | // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore |
michael@0 | 1636 | // state.) |
michael@0 | 1637 | if (getWidth() > 0 && getHeight() > 0 && getChildCount() > 0) { |
michael@0 | 1638 | layoutChildren(); |
michael@0 | 1639 | } |
michael@0 | 1640 | |
michael@0 | 1641 | updateSelectorState(); |
michael@0 | 1642 | } else { |
michael@0 | 1643 | final int touchMode = mTouchMode; |
michael@0 | 1644 | if (touchMode == TOUCH_MODE_OVERSCROLL) { |
michael@0 | 1645 | if (mOverScroll != 0) { |
michael@0 | 1646 | mOverScroll = 0; |
michael@0 | 1647 | finishEdgeGlows(); |
michael@0 | 1648 | invalidate(); |
michael@0 | 1649 | } |
michael@0 | 1650 | } |
michael@0 | 1651 | } |
michael@0 | 1652 | } |
michael@0 | 1653 | |
michael@0 | 1654 | @Override |
michael@0 | 1655 | public boolean onKeyDown(int keyCode, KeyEvent event) { |
michael@0 | 1656 | return handleKeyEvent(keyCode, 1, event); |
michael@0 | 1657 | } |
michael@0 | 1658 | |
michael@0 | 1659 | @Override |
michael@0 | 1660 | public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { |
michael@0 | 1661 | return handleKeyEvent(keyCode, repeatCount, event); |
michael@0 | 1662 | } |
michael@0 | 1663 | |
michael@0 | 1664 | @Override |
michael@0 | 1665 | public boolean onKeyUp(int keyCode, KeyEvent event) { |
michael@0 | 1666 | return handleKeyEvent(keyCode, 1, event); |
michael@0 | 1667 | } |
michael@0 | 1668 | |
michael@0 | 1669 | @Override |
michael@0 | 1670 | public void sendAccessibilityEvent(int eventType) { |
michael@0 | 1671 | // Since this class calls onScrollChanged even if the mFirstPosition and the |
michael@0 | 1672 | // child count have not changed we will avoid sending duplicate accessibility |
michael@0 | 1673 | // events. |
michael@0 | 1674 | if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { |
michael@0 | 1675 | final int firstVisiblePosition = getFirstVisiblePosition(); |
michael@0 | 1676 | final int lastVisiblePosition = getLastVisiblePosition(); |
michael@0 | 1677 | |
michael@0 | 1678 | if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition |
michael@0 | 1679 | && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { |
michael@0 | 1680 | return; |
michael@0 | 1681 | } else { |
michael@0 | 1682 | mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; |
michael@0 | 1683 | mLastAccessibilityScrollEventToIndex = lastVisiblePosition; |
michael@0 | 1684 | } |
michael@0 | 1685 | } |
michael@0 | 1686 | |
michael@0 | 1687 | super.sendAccessibilityEvent(eventType); |
michael@0 | 1688 | } |
michael@0 | 1689 | |
michael@0 | 1690 | @Override |
michael@0 | 1691 | @TargetApi(14) |
michael@0 | 1692 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { |
michael@0 | 1693 | super.onInitializeAccessibilityEvent(event); |
michael@0 | 1694 | event.setClassName(TwoWayView.class.getName()); |
michael@0 | 1695 | } |
michael@0 | 1696 | |
michael@0 | 1697 | @Override |
michael@0 | 1698 | @TargetApi(14) |
michael@0 | 1699 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { |
michael@0 | 1700 | super.onInitializeAccessibilityNodeInfo(info); |
michael@0 | 1701 | info.setClassName(TwoWayView.class.getName()); |
michael@0 | 1702 | |
michael@0 | 1703 | AccessibilityNodeInfoCompat infoCompat = new AccessibilityNodeInfoCompat(info); |
michael@0 | 1704 | |
michael@0 | 1705 | if (isEnabled()) { |
michael@0 | 1706 | if (getFirstVisiblePosition() > 0) { |
michael@0 | 1707 | infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); |
michael@0 | 1708 | } |
michael@0 | 1709 | |
michael@0 | 1710 | if (getLastVisiblePosition() < getCount() - 1) { |
michael@0 | 1711 | infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); |
michael@0 | 1712 | } |
michael@0 | 1713 | } |
michael@0 | 1714 | } |
michael@0 | 1715 | |
michael@0 | 1716 | @Override |
michael@0 | 1717 | @TargetApi(16) |
michael@0 | 1718 | public boolean performAccessibilityAction(int action, Bundle arguments) { |
michael@0 | 1719 | if (super.performAccessibilityAction(action, arguments)) { |
michael@0 | 1720 | return true; |
michael@0 | 1721 | } |
michael@0 | 1722 | |
michael@0 | 1723 | switch (action) { |
michael@0 | 1724 | case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: |
michael@0 | 1725 | if (isEnabled() && getLastVisiblePosition() < getCount() - 1) { |
michael@0 | 1726 | final int viewportSize; |
michael@0 | 1727 | if (mIsVertical) { |
michael@0 | 1728 | viewportSize = getHeight() - getPaddingTop() - getPaddingBottom(); |
michael@0 | 1729 | } else { |
michael@0 | 1730 | viewportSize = getWidth() - getPaddingLeft() - getPaddingRight(); |
michael@0 | 1731 | } |
michael@0 | 1732 | |
michael@0 | 1733 | // TODO: Use some form of smooth scroll instead |
michael@0 | 1734 | trackMotionScroll(viewportSize); |
michael@0 | 1735 | return true; |
michael@0 | 1736 | } |
michael@0 | 1737 | return false; |
michael@0 | 1738 | |
michael@0 | 1739 | case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: |
michael@0 | 1740 | if (isEnabled() && mFirstPosition > 0) { |
michael@0 | 1741 | final int viewportSize; |
michael@0 | 1742 | if (mIsVertical) { |
michael@0 | 1743 | viewportSize = getHeight() - getPaddingTop() - getPaddingBottom(); |
michael@0 | 1744 | } else { |
michael@0 | 1745 | viewportSize = getWidth() - getPaddingLeft() - getPaddingRight(); |
michael@0 | 1746 | } |
michael@0 | 1747 | |
michael@0 | 1748 | // TODO: Use some form of smooth scroll instead |
michael@0 | 1749 | trackMotionScroll(-viewportSize); |
michael@0 | 1750 | return true; |
michael@0 | 1751 | } |
michael@0 | 1752 | return false; |
michael@0 | 1753 | } |
michael@0 | 1754 | |
michael@0 | 1755 | return false; |
michael@0 | 1756 | } |
michael@0 | 1757 | |
michael@0 | 1758 | /** |
michael@0 | 1759 | * Return true if child is an ancestor of parent, (or equal to the parent). |
michael@0 | 1760 | */ |
michael@0 | 1761 | private boolean isViewAncestorOf(View child, View parent) { |
michael@0 | 1762 | if (child == parent) { |
michael@0 | 1763 | return true; |
michael@0 | 1764 | } |
michael@0 | 1765 | |
michael@0 | 1766 | final ViewParent theParent = child.getParent(); |
michael@0 | 1767 | |
michael@0 | 1768 | return (theParent instanceof ViewGroup) && |
michael@0 | 1769 | isViewAncestorOf((View) theParent, parent); |
michael@0 | 1770 | } |
michael@0 | 1771 | |
michael@0 | 1772 | private void forceValidFocusDirection(int direction) { |
michael@0 | 1773 | if (mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) { |
michael@0 | 1774 | throw new IllegalArgumentException("Focus direction must be one of" |
michael@0 | 1775 | + " {View.FOCUS_UP, View.FOCUS_DOWN} for vertical orientation"); |
michael@0 | 1776 | } else if (!mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) { |
michael@0 | 1777 | throw new IllegalArgumentException("Focus direction must be one of" |
michael@0 | 1778 | + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation"); |
michael@0 | 1779 | } |
michael@0 | 1780 | } |
michael@0 | 1781 | |
michael@0 | 1782 | private void forceValidInnerFocusDirection(int direction) { |
michael@0 | 1783 | if (mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) { |
michael@0 | 1784 | throw new IllegalArgumentException("Direction must be one of" |
michael@0 | 1785 | + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation"); |
michael@0 | 1786 | } else if (!mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) { |
michael@0 | 1787 | throw new IllegalArgumentException("direction must be one of" |
michael@0 | 1788 | + " {View.FOCUS_UP, View.FOCUS_DOWN} for horizontal orientation"); |
michael@0 | 1789 | } |
michael@0 | 1790 | } |
michael@0 | 1791 | |
michael@0 | 1792 | /** |
michael@0 | 1793 | * Scrolls up or down by the number of items currently present on screen. |
michael@0 | 1794 | * |
michael@0 | 1795 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 1796 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 1797 | * current view orientation. |
michael@0 | 1798 | * |
michael@0 | 1799 | * @return whether selection was moved |
michael@0 | 1800 | */ |
michael@0 | 1801 | boolean pageScroll(int direction) { |
michael@0 | 1802 | forceValidFocusDirection(direction); |
michael@0 | 1803 | |
michael@0 | 1804 | boolean forward = false; |
michael@0 | 1805 | int nextPage = -1; |
michael@0 | 1806 | |
michael@0 | 1807 | if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { |
michael@0 | 1808 | nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1); |
michael@0 | 1809 | } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { |
michael@0 | 1810 | nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1); |
michael@0 | 1811 | forward = true; |
michael@0 | 1812 | } |
michael@0 | 1813 | |
michael@0 | 1814 | if (nextPage < 0) { |
michael@0 | 1815 | return false; |
michael@0 | 1816 | } |
michael@0 | 1817 | |
michael@0 | 1818 | final int position = lookForSelectablePosition(nextPage, forward); |
michael@0 | 1819 | if (position >= 0) { |
michael@0 | 1820 | mLayoutMode = LAYOUT_SPECIFIC; |
michael@0 | 1821 | mSpecificStart = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 1822 | |
michael@0 | 1823 | if (forward && position > mItemCount - getChildCount()) { |
michael@0 | 1824 | mLayoutMode = LAYOUT_FORCE_BOTTOM; |
michael@0 | 1825 | } |
michael@0 | 1826 | |
michael@0 | 1827 | if (!forward && position < getChildCount()) { |
michael@0 | 1828 | mLayoutMode = LAYOUT_FORCE_TOP; |
michael@0 | 1829 | } |
michael@0 | 1830 | |
michael@0 | 1831 | setSelectionInt(position); |
michael@0 | 1832 | invokeOnItemScrollListener(); |
michael@0 | 1833 | |
michael@0 | 1834 | if (!awakenScrollbarsInternal()) { |
michael@0 | 1835 | invalidate(); |
michael@0 | 1836 | } |
michael@0 | 1837 | |
michael@0 | 1838 | return true; |
michael@0 | 1839 | } |
michael@0 | 1840 | |
michael@0 | 1841 | return false; |
michael@0 | 1842 | } |
michael@0 | 1843 | |
michael@0 | 1844 | /** |
michael@0 | 1845 | * Go to the last or first item if possible (not worrying about panning across or navigating |
michael@0 | 1846 | * within the internal focus of the currently selected item.) |
michael@0 | 1847 | * |
michael@0 | 1848 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 1849 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 1850 | * current view orientation. |
michael@0 | 1851 | * |
michael@0 | 1852 | * @return whether selection was moved |
michael@0 | 1853 | */ |
michael@0 | 1854 | boolean fullScroll(int direction) { |
michael@0 | 1855 | forceValidFocusDirection(direction); |
michael@0 | 1856 | |
michael@0 | 1857 | boolean moved = false; |
michael@0 | 1858 | if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { |
michael@0 | 1859 | if (mSelectedPosition != 0) { |
michael@0 | 1860 | int position = lookForSelectablePosition(0, true); |
michael@0 | 1861 | if (position >= 0) { |
michael@0 | 1862 | mLayoutMode = LAYOUT_FORCE_TOP; |
michael@0 | 1863 | setSelectionInt(position); |
michael@0 | 1864 | invokeOnItemScrollListener(); |
michael@0 | 1865 | } |
michael@0 | 1866 | |
michael@0 | 1867 | moved = true; |
michael@0 | 1868 | } |
michael@0 | 1869 | } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { |
michael@0 | 1870 | if (mSelectedPosition < mItemCount - 1) { |
michael@0 | 1871 | int position = lookForSelectablePosition(mItemCount - 1, true); |
michael@0 | 1872 | if (position >= 0) { |
michael@0 | 1873 | mLayoutMode = LAYOUT_FORCE_BOTTOM; |
michael@0 | 1874 | setSelectionInt(position); |
michael@0 | 1875 | invokeOnItemScrollListener(); |
michael@0 | 1876 | } |
michael@0 | 1877 | |
michael@0 | 1878 | moved = true; |
michael@0 | 1879 | } |
michael@0 | 1880 | } |
michael@0 | 1881 | |
michael@0 | 1882 | if (moved && !awakenScrollbarsInternal()) { |
michael@0 | 1883 | awakenScrollbarsInternal(); |
michael@0 | 1884 | invalidate(); |
michael@0 | 1885 | } |
michael@0 | 1886 | |
michael@0 | 1887 | return moved; |
michael@0 | 1888 | } |
michael@0 | 1889 | |
michael@0 | 1890 | /** |
michael@0 | 1891 | * To avoid horizontal/vertical focus searches changing the selected item, |
michael@0 | 1892 | * we manually focus search within the selected item (as applicable), and |
michael@0 | 1893 | * prevent focus from jumping to something within another item. |
michael@0 | 1894 | * |
michael@0 | 1895 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 1896 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 1897 | * current view orientation. |
michael@0 | 1898 | * |
michael@0 | 1899 | * @return Whether this consumes the key event. |
michael@0 | 1900 | */ |
michael@0 | 1901 | private boolean handleFocusWithinItem(int direction) { |
michael@0 | 1902 | forceValidInnerFocusDirection(direction); |
michael@0 | 1903 | |
michael@0 | 1904 | final int numChildren = getChildCount(); |
michael@0 | 1905 | |
michael@0 | 1906 | if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) { |
michael@0 | 1907 | final View selectedView = getSelectedView(); |
michael@0 | 1908 | |
michael@0 | 1909 | if (selectedView != null && selectedView.hasFocus() && |
michael@0 | 1910 | selectedView instanceof ViewGroup) { |
michael@0 | 1911 | |
michael@0 | 1912 | final View currentFocus = selectedView.findFocus(); |
michael@0 | 1913 | final View nextFocus = FocusFinder.getInstance().findNextFocus( |
michael@0 | 1914 | (ViewGroup) selectedView, currentFocus, direction); |
michael@0 | 1915 | |
michael@0 | 1916 | if (nextFocus != null) { |
michael@0 | 1917 | // Do the math to get interesting rect in next focus' coordinates |
michael@0 | 1918 | currentFocus.getFocusedRect(mTempRect); |
michael@0 | 1919 | offsetDescendantRectToMyCoords(currentFocus, mTempRect); |
michael@0 | 1920 | offsetRectIntoDescendantCoords(nextFocus, mTempRect); |
michael@0 | 1921 | |
michael@0 | 1922 | if (nextFocus.requestFocus(direction, mTempRect)) { |
michael@0 | 1923 | return true; |
michael@0 | 1924 | } |
michael@0 | 1925 | } |
michael@0 | 1926 | |
michael@0 | 1927 | // We are blocking the key from being handled (by returning true) |
michael@0 | 1928 | // if the global result is going to be some other view within this |
michael@0 | 1929 | // list. This is to achieve the overall goal of having horizontal/vertical |
michael@0 | 1930 | // d-pad navigation remain in the current item depending on the current |
michael@0 | 1931 | // orientation in this view. |
michael@0 | 1932 | final View globalNextFocus = FocusFinder.getInstance().findNextFocus( |
michael@0 | 1933 | (ViewGroup) getRootView(), currentFocus, direction); |
michael@0 | 1934 | |
michael@0 | 1935 | if (globalNextFocus != null) { |
michael@0 | 1936 | return isViewAncestorOf(globalNextFocus, this); |
michael@0 | 1937 | } |
michael@0 | 1938 | } |
michael@0 | 1939 | } |
michael@0 | 1940 | |
michael@0 | 1941 | return false; |
michael@0 | 1942 | } |
michael@0 | 1943 | |
michael@0 | 1944 | /** |
michael@0 | 1945 | * Scrolls to the next or previous item if possible. |
michael@0 | 1946 | * |
michael@0 | 1947 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 1948 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 1949 | * current view orientation. |
michael@0 | 1950 | * |
michael@0 | 1951 | * @return whether selection was moved |
michael@0 | 1952 | */ |
michael@0 | 1953 | private boolean arrowScroll(int direction) { |
michael@0 | 1954 | forceValidFocusDirection(direction); |
michael@0 | 1955 | |
michael@0 | 1956 | try { |
michael@0 | 1957 | mInLayout = true; |
michael@0 | 1958 | |
michael@0 | 1959 | final boolean handled = arrowScrollImpl(direction); |
michael@0 | 1960 | if (handled) { |
michael@0 | 1961 | playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); |
michael@0 | 1962 | } |
michael@0 | 1963 | |
michael@0 | 1964 | return handled; |
michael@0 | 1965 | } finally { |
michael@0 | 1966 | mInLayout = false; |
michael@0 | 1967 | } |
michael@0 | 1968 | } |
michael@0 | 1969 | |
michael@0 | 1970 | /** |
michael@0 | 1971 | * When selection changes, it is possible that the previously selected or the |
michael@0 | 1972 | * next selected item will change its size. If so, we need to offset some folks, |
michael@0 | 1973 | * and re-layout the items as appropriate. |
michael@0 | 1974 | * |
michael@0 | 1975 | * @param selectedView The currently selected view (before changing selection). |
michael@0 | 1976 | * should be <code>null</code> if there was no previous selection. |
michael@0 | 1977 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 1978 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 1979 | * current view orientation. |
michael@0 | 1980 | * @param newSelectedPosition The position of the next selection. |
michael@0 | 1981 | * @param newFocusAssigned whether new focus was assigned. This matters because |
michael@0 | 1982 | * when something has focus, we don't want to show selection (ugh). |
michael@0 | 1983 | */ |
michael@0 | 1984 | private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition, |
michael@0 | 1985 | boolean newFocusAssigned) { |
michael@0 | 1986 | forceValidFocusDirection(direction); |
michael@0 | 1987 | |
michael@0 | 1988 | if (newSelectedPosition == INVALID_POSITION) { |
michael@0 | 1989 | throw new IllegalArgumentException("newSelectedPosition needs to be valid"); |
michael@0 | 1990 | } |
michael@0 | 1991 | |
michael@0 | 1992 | // Whether or not we are moving down/right or up/left, we want to preserve the |
michael@0 | 1993 | // top/left of whatever view is at the start: |
michael@0 | 1994 | // - moving down/right: the view that had selection |
michael@0 | 1995 | // - moving up/left: the view that is getting selection |
michael@0 | 1996 | final int selectedIndex = mSelectedPosition - mFirstPosition; |
michael@0 | 1997 | final int nextSelectedIndex = newSelectedPosition - mFirstPosition; |
michael@0 | 1998 | int startViewIndex, endViewIndex; |
michael@0 | 1999 | boolean topSelected = false; |
michael@0 | 2000 | View startView; |
michael@0 | 2001 | View endView; |
michael@0 | 2002 | |
michael@0 | 2003 | if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { |
michael@0 | 2004 | startViewIndex = nextSelectedIndex; |
michael@0 | 2005 | endViewIndex = selectedIndex; |
michael@0 | 2006 | startView = getChildAt(startViewIndex); |
michael@0 | 2007 | endView = selectedView; |
michael@0 | 2008 | topSelected = true; |
michael@0 | 2009 | } else { |
michael@0 | 2010 | startViewIndex = selectedIndex; |
michael@0 | 2011 | endViewIndex = nextSelectedIndex; |
michael@0 | 2012 | startView = selectedView; |
michael@0 | 2013 | endView = getChildAt(endViewIndex); |
michael@0 | 2014 | } |
michael@0 | 2015 | |
michael@0 | 2016 | final int numChildren = getChildCount(); |
michael@0 | 2017 | |
michael@0 | 2018 | // start with top view: is it changing size? |
michael@0 | 2019 | if (startView != null) { |
michael@0 | 2020 | startView.setSelected(!newFocusAssigned && topSelected); |
michael@0 | 2021 | measureAndAdjustDown(startView, startViewIndex, numChildren); |
michael@0 | 2022 | } |
michael@0 | 2023 | |
michael@0 | 2024 | // is the bottom view changing size? |
michael@0 | 2025 | if (endView != null) { |
michael@0 | 2026 | endView.setSelected(!newFocusAssigned && !topSelected); |
michael@0 | 2027 | measureAndAdjustDown(endView, endViewIndex, numChildren); |
michael@0 | 2028 | } |
michael@0 | 2029 | } |
michael@0 | 2030 | |
michael@0 | 2031 | /** |
michael@0 | 2032 | * Re-measure a child, and if its height changes, lay it out preserving its |
michael@0 | 2033 | * top, and adjust the children below it appropriately. |
michael@0 | 2034 | * |
michael@0 | 2035 | * @param child The child |
michael@0 | 2036 | * @param childIndex The view group index of the child. |
michael@0 | 2037 | * @param numChildren The number of children in the view group. |
michael@0 | 2038 | */ |
michael@0 | 2039 | private void measureAndAdjustDown(View child, int childIndex, int numChildren) { |
michael@0 | 2040 | int oldHeight = child.getHeight(); |
michael@0 | 2041 | measureChild(child); |
michael@0 | 2042 | |
michael@0 | 2043 | if (child.getMeasuredHeight() == oldHeight) { |
michael@0 | 2044 | return; |
michael@0 | 2045 | } |
michael@0 | 2046 | |
michael@0 | 2047 | // lay out the view, preserving its top |
michael@0 | 2048 | relayoutMeasuredChild(child); |
michael@0 | 2049 | |
michael@0 | 2050 | // adjust views below appropriately |
michael@0 | 2051 | final int heightDelta = child.getMeasuredHeight() - oldHeight; |
michael@0 | 2052 | for (int i = childIndex + 1; i < numChildren; i++) { |
michael@0 | 2053 | getChildAt(i).offsetTopAndBottom(heightDelta); |
michael@0 | 2054 | } |
michael@0 | 2055 | } |
michael@0 | 2056 | |
michael@0 | 2057 | /** |
michael@0 | 2058 | * Do an arrow scroll based on focus searching. If a new view is |
michael@0 | 2059 | * given focus, return the selection delta and amount to scroll via |
michael@0 | 2060 | * an {@link ArrowScrollFocusResult}, otherwise, return null. |
michael@0 | 2061 | * |
michael@0 | 2062 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 2063 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 2064 | * current view orientation. |
michael@0 | 2065 | * |
michael@0 | 2066 | * @return The result if focus has changed, or <code>null</code>. |
michael@0 | 2067 | */ |
michael@0 | 2068 | private ArrowScrollFocusResult arrowScrollFocused(final int direction) { |
michael@0 | 2069 | forceValidFocusDirection(direction); |
michael@0 | 2070 | |
michael@0 | 2071 | final View selectedView = getSelectedView(); |
michael@0 | 2072 | final View newFocus; |
michael@0 | 2073 | final int searchPoint; |
michael@0 | 2074 | |
michael@0 | 2075 | if (selectedView != null && selectedView.hasFocus()) { |
michael@0 | 2076 | View oldFocus = selectedView.findFocus(); |
michael@0 | 2077 | newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction); |
michael@0 | 2078 | } else { |
michael@0 | 2079 | if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { |
michael@0 | 2080 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 2081 | |
michael@0 | 2082 | final int selectedStart; |
michael@0 | 2083 | if (selectedView != null) { |
michael@0 | 2084 | selectedStart = (mIsVertical ? selectedView.getTop() : selectedView.getLeft()); |
michael@0 | 2085 | } else { |
michael@0 | 2086 | selectedStart = start; |
michael@0 | 2087 | } |
michael@0 | 2088 | |
michael@0 | 2089 | searchPoint = Math.max(selectedStart, start); |
michael@0 | 2090 | } else { |
michael@0 | 2091 | final int end = (mIsVertical ? getHeight() - getPaddingBottom() : |
michael@0 | 2092 | getWidth() - getPaddingRight()); |
michael@0 | 2093 | |
michael@0 | 2094 | final int selectedEnd; |
michael@0 | 2095 | if (selectedView != null) { |
michael@0 | 2096 | selectedEnd = (mIsVertical ? selectedView.getBottom() : selectedView.getRight()); |
michael@0 | 2097 | } else { |
michael@0 | 2098 | selectedEnd = end; |
michael@0 | 2099 | } |
michael@0 | 2100 | |
michael@0 | 2101 | searchPoint = Math.min(selectedEnd, end); |
michael@0 | 2102 | } |
michael@0 | 2103 | |
michael@0 | 2104 | final int x = (mIsVertical ? 0 : searchPoint); |
michael@0 | 2105 | final int y = (mIsVertical ? searchPoint : 0); |
michael@0 | 2106 | mTempRect.set(x, y, x, y); |
michael@0 | 2107 | |
michael@0 | 2108 | newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction); |
michael@0 | 2109 | } |
michael@0 | 2110 | |
michael@0 | 2111 | if (newFocus != null) { |
michael@0 | 2112 | final int positionOfNewFocus = positionOfNewFocus(newFocus); |
michael@0 | 2113 | |
michael@0 | 2114 | // If the focus change is in a different new position, make sure |
michael@0 | 2115 | // we aren't jumping over another selectable position. |
michael@0 | 2116 | if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) { |
michael@0 | 2117 | final int selectablePosition = lookForSelectablePositionOnScreen(direction); |
michael@0 | 2118 | |
michael@0 | 2119 | final boolean movingForward = |
michael@0 | 2120 | (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT); |
michael@0 | 2121 | final boolean movingBackward = |
michael@0 | 2122 | (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT); |
michael@0 | 2123 | |
michael@0 | 2124 | if (selectablePosition != INVALID_POSITION && |
michael@0 | 2125 | ((movingForward && selectablePosition < positionOfNewFocus) || |
michael@0 | 2126 | (movingBackward && selectablePosition > positionOfNewFocus))) { |
michael@0 | 2127 | return null; |
michael@0 | 2128 | } |
michael@0 | 2129 | } |
michael@0 | 2130 | |
michael@0 | 2131 | int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus); |
michael@0 | 2132 | |
michael@0 | 2133 | final int maxScrollAmount = getMaxScrollAmount(); |
michael@0 | 2134 | if (focusScroll < maxScrollAmount) { |
michael@0 | 2135 | // Not moving too far, safe to give next view focus |
michael@0 | 2136 | newFocus.requestFocus(direction); |
michael@0 | 2137 | mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll); |
michael@0 | 2138 | return mArrowScrollFocusResult; |
michael@0 | 2139 | } else if (distanceToView(newFocus) < maxScrollAmount){ |
michael@0 | 2140 | // Case to consider: |
michael@0 | 2141 | // Too far to get entire next focusable on screen, but by going |
michael@0 | 2142 | // max scroll amount, we are getting it at least partially in view, |
michael@0 | 2143 | // so give it focus and scroll the max amount. |
michael@0 | 2144 | newFocus.requestFocus(direction); |
michael@0 | 2145 | mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount); |
michael@0 | 2146 | return mArrowScrollFocusResult; |
michael@0 | 2147 | } |
michael@0 | 2148 | } |
michael@0 | 2149 | |
michael@0 | 2150 | return null; |
michael@0 | 2151 | } |
michael@0 | 2152 | |
michael@0 | 2153 | /** |
michael@0 | 2154 | * @return The maximum amount a list view will scroll in response to |
michael@0 | 2155 | * an arrow event. |
michael@0 | 2156 | */ |
michael@0 | 2157 | public int getMaxScrollAmount() { |
michael@0 | 2158 | return (int) (MAX_SCROLL_FACTOR * getHeight()); |
michael@0 | 2159 | } |
michael@0 | 2160 | |
michael@0 | 2161 | /** |
michael@0 | 2162 | * @return The amount to preview next items when arrow scrolling. |
michael@0 | 2163 | */ |
michael@0 | 2164 | private int getArrowScrollPreviewLength() { |
michael@0 | 2165 | // FIXME: TwoWayView has no fading edge support just yet but using it |
michael@0 | 2166 | // makes it convenient for defining the next item's previous length. |
michael@0 | 2167 | int fadingEdgeLength = |
michael@0 | 2168 | (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength()); |
michael@0 | 2169 | |
michael@0 | 2170 | return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, fadingEdgeLength); |
michael@0 | 2171 | } |
michael@0 | 2172 | |
michael@0 | 2173 | /** |
michael@0 | 2174 | * @param newFocus The view that would have focus. |
michael@0 | 2175 | * @return the position that contains newFocus |
michael@0 | 2176 | */ |
michael@0 | 2177 | private int positionOfNewFocus(View newFocus) { |
michael@0 | 2178 | final int numChildren = getChildCount(); |
michael@0 | 2179 | |
michael@0 | 2180 | for (int i = 0; i < numChildren; i++) { |
michael@0 | 2181 | final View child = getChildAt(i); |
michael@0 | 2182 | if (isViewAncestorOf(newFocus, child)) { |
michael@0 | 2183 | return mFirstPosition + i; |
michael@0 | 2184 | } |
michael@0 | 2185 | } |
michael@0 | 2186 | |
michael@0 | 2187 | throw new IllegalArgumentException("newFocus is not a child of any of the" |
michael@0 | 2188 | + " children of the list!"); |
michael@0 | 2189 | } |
michael@0 | 2190 | |
michael@0 | 2191 | /** |
michael@0 | 2192 | * Handle an arrow scroll going up or down. Take into account whether items are selectable, |
michael@0 | 2193 | * whether there are focusable items, etc. |
michael@0 | 2194 | * |
michael@0 | 2195 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 2196 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 2197 | * current view orientation. |
michael@0 | 2198 | * |
michael@0 | 2199 | * @return Whether any scrolling, selection or focus change occurred. |
michael@0 | 2200 | */ |
michael@0 | 2201 | private boolean arrowScrollImpl(int direction) { |
michael@0 | 2202 | forceValidFocusDirection(direction); |
michael@0 | 2203 | |
michael@0 | 2204 | if (getChildCount() <= 0) { |
michael@0 | 2205 | return false; |
michael@0 | 2206 | } |
michael@0 | 2207 | |
michael@0 | 2208 | View selectedView = getSelectedView(); |
michael@0 | 2209 | int selectedPos = mSelectedPosition; |
michael@0 | 2210 | |
michael@0 | 2211 | int nextSelectedPosition = lookForSelectablePositionOnScreen(direction); |
michael@0 | 2212 | int amountToScroll = amountToScroll(direction, nextSelectedPosition); |
michael@0 | 2213 | |
michael@0 | 2214 | // If we are moving focus, we may OVERRIDE the default behaviour |
michael@0 | 2215 | final ArrowScrollFocusResult focusResult = (mItemsCanFocus ? arrowScrollFocused(direction) : null); |
michael@0 | 2216 | if (focusResult != null) { |
michael@0 | 2217 | nextSelectedPosition = focusResult.getSelectedPosition(); |
michael@0 | 2218 | amountToScroll = focusResult.getAmountToScroll(); |
michael@0 | 2219 | } |
michael@0 | 2220 | |
michael@0 | 2221 | boolean needToRedraw = (focusResult != null); |
michael@0 | 2222 | if (nextSelectedPosition != INVALID_POSITION) { |
michael@0 | 2223 | handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null); |
michael@0 | 2224 | |
michael@0 | 2225 | setSelectedPositionInt(nextSelectedPosition); |
michael@0 | 2226 | setNextSelectedPositionInt(nextSelectedPosition); |
michael@0 | 2227 | |
michael@0 | 2228 | selectedView = getSelectedView(); |
michael@0 | 2229 | selectedPos = nextSelectedPosition; |
michael@0 | 2230 | |
michael@0 | 2231 | if (mItemsCanFocus && focusResult == null) { |
michael@0 | 2232 | // There was no new view found to take focus, make sure we |
michael@0 | 2233 | // don't leave focus with the old selection. |
michael@0 | 2234 | final View focused = getFocusedChild(); |
michael@0 | 2235 | if (focused != null) { |
michael@0 | 2236 | focused.clearFocus(); |
michael@0 | 2237 | } |
michael@0 | 2238 | } |
michael@0 | 2239 | |
michael@0 | 2240 | needToRedraw = true; |
michael@0 | 2241 | checkSelectionChanged(); |
michael@0 | 2242 | } |
michael@0 | 2243 | |
michael@0 | 2244 | if (amountToScroll > 0) { |
michael@0 | 2245 | trackMotionScroll(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ? |
michael@0 | 2246 | amountToScroll : -amountToScroll); |
michael@0 | 2247 | needToRedraw = true; |
michael@0 | 2248 | } |
michael@0 | 2249 | |
michael@0 | 2250 | // If we didn't find a new focusable, make sure any existing focused |
michael@0 | 2251 | // item that was panned off screen gives up focus. |
michael@0 | 2252 | if (mItemsCanFocus && focusResult == null && |
michael@0 | 2253 | selectedView != null && selectedView.hasFocus()) { |
michael@0 | 2254 | final View focused = selectedView.findFocus(); |
michael@0 | 2255 | if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) { |
michael@0 | 2256 | focused.clearFocus(); |
michael@0 | 2257 | } |
michael@0 | 2258 | } |
michael@0 | 2259 | |
michael@0 | 2260 | // If the current selection is panned off, we need to remove the selection |
michael@0 | 2261 | if (nextSelectedPosition == INVALID_POSITION && selectedView != null |
michael@0 | 2262 | && !isViewAncestorOf(selectedView, this)) { |
michael@0 | 2263 | selectedView = null; |
michael@0 | 2264 | hideSelector(); |
michael@0 | 2265 | |
michael@0 | 2266 | // But we don't want to set the ressurect position (that would make subsequent |
michael@0 | 2267 | // unhandled key events bring back the item we just scrolled off) |
michael@0 | 2268 | mResurrectToPosition = INVALID_POSITION; |
michael@0 | 2269 | } |
michael@0 | 2270 | |
michael@0 | 2271 | if (needToRedraw) { |
michael@0 | 2272 | if (selectedView != null) { |
michael@0 | 2273 | positionSelector(selectedPos, selectedView); |
michael@0 | 2274 | mSelectedStart = selectedView.getTop(); |
michael@0 | 2275 | } |
michael@0 | 2276 | |
michael@0 | 2277 | if (!awakenScrollbarsInternal()) { |
michael@0 | 2278 | invalidate(); |
michael@0 | 2279 | } |
michael@0 | 2280 | |
michael@0 | 2281 | invokeOnItemScrollListener(); |
michael@0 | 2282 | return true; |
michael@0 | 2283 | } |
michael@0 | 2284 | |
michael@0 | 2285 | return false; |
michael@0 | 2286 | } |
michael@0 | 2287 | |
michael@0 | 2288 | /** |
michael@0 | 2289 | * Determine how much we need to scroll in order to get the next selected view |
michael@0 | 2290 | * visible. The amount is capped at {@link #getMaxScrollAmount()}. |
michael@0 | 2291 | * |
michael@0 | 2292 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 2293 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 2294 | * current view orientation. |
michael@0 | 2295 | * @param nextSelectedPosition The position of the next selection, or |
michael@0 | 2296 | * {@link #INVALID_POSITION} if there is no next selectable position |
michael@0 | 2297 | * |
michael@0 | 2298 | * @return The amount to scroll. Note: this is always positive! Direction |
michael@0 | 2299 | * needs to be taken into account when actually scrolling. |
michael@0 | 2300 | */ |
michael@0 | 2301 | private int amountToScroll(int direction, int nextSelectedPosition) { |
michael@0 | 2302 | forceValidFocusDirection(direction); |
michael@0 | 2303 | |
michael@0 | 2304 | final int numChildren = getChildCount(); |
michael@0 | 2305 | |
michael@0 | 2306 | if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { |
michael@0 | 2307 | final int end = (mIsVertical ? getHeight() - getPaddingBottom() : |
michael@0 | 2308 | getWidth() - getPaddingRight()); |
michael@0 | 2309 | |
michael@0 | 2310 | int indexToMakeVisible = numChildren - 1; |
michael@0 | 2311 | if (nextSelectedPosition != INVALID_POSITION) { |
michael@0 | 2312 | indexToMakeVisible = nextSelectedPosition - mFirstPosition; |
michael@0 | 2313 | } |
michael@0 | 2314 | |
michael@0 | 2315 | final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; |
michael@0 | 2316 | final View viewToMakeVisible = getChildAt(indexToMakeVisible); |
michael@0 | 2317 | |
michael@0 | 2318 | int goalEnd = end; |
michael@0 | 2319 | if (positionToMakeVisible < mItemCount - 1) { |
michael@0 | 2320 | goalEnd -= getArrowScrollPreviewLength(); |
michael@0 | 2321 | } |
michael@0 | 2322 | |
michael@0 | 2323 | final int viewToMakeVisibleStart = |
michael@0 | 2324 | (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); |
michael@0 | 2325 | final int viewToMakeVisibleEnd = |
michael@0 | 2326 | (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); |
michael@0 | 2327 | |
michael@0 | 2328 | if (viewToMakeVisibleEnd <= goalEnd) { |
michael@0 | 2329 | // Target item is fully visible |
michael@0 | 2330 | return 0; |
michael@0 | 2331 | } |
michael@0 | 2332 | |
michael@0 | 2333 | if (nextSelectedPosition != INVALID_POSITION && |
michael@0 | 2334 | (goalEnd - viewToMakeVisibleStart) >= getMaxScrollAmount()) { |
michael@0 | 2335 | // Item already has enough of it visible, changing selection is good enough |
michael@0 | 2336 | return 0; |
michael@0 | 2337 | } |
michael@0 | 2338 | |
michael@0 | 2339 | int amountToScroll = (viewToMakeVisibleEnd - goalEnd); |
michael@0 | 2340 | |
michael@0 | 2341 | if (mFirstPosition + numChildren == mItemCount) { |
michael@0 | 2342 | final View lastChild = getChildAt(numChildren - 1); |
michael@0 | 2343 | final int lastChildEnd = (mIsVertical ? lastChild.getBottom() : lastChild.getRight()); |
michael@0 | 2344 | |
michael@0 | 2345 | // Last is last in list -> Make sure we don't scroll past it |
michael@0 | 2346 | final int max = lastChildEnd - end; |
michael@0 | 2347 | amountToScroll = Math.min(amountToScroll, max); |
michael@0 | 2348 | } |
michael@0 | 2349 | |
michael@0 | 2350 | return Math.min(amountToScroll, getMaxScrollAmount()); |
michael@0 | 2351 | } else { |
michael@0 | 2352 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 2353 | |
michael@0 | 2354 | int indexToMakeVisible = 0; |
michael@0 | 2355 | if (nextSelectedPosition != INVALID_POSITION) { |
michael@0 | 2356 | indexToMakeVisible = nextSelectedPosition - mFirstPosition; |
michael@0 | 2357 | } |
michael@0 | 2358 | |
michael@0 | 2359 | final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; |
michael@0 | 2360 | final View viewToMakeVisible = getChildAt(indexToMakeVisible); |
michael@0 | 2361 | |
michael@0 | 2362 | int goalStart = start; |
michael@0 | 2363 | if (positionToMakeVisible > 0) { |
michael@0 | 2364 | goalStart += getArrowScrollPreviewLength(); |
michael@0 | 2365 | } |
michael@0 | 2366 | |
michael@0 | 2367 | final int viewToMakeVisibleStart = |
michael@0 | 2368 | (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); |
michael@0 | 2369 | final int viewToMakeVisibleEnd = |
michael@0 | 2370 | (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); |
michael@0 | 2371 | |
michael@0 | 2372 | if (viewToMakeVisibleStart >= goalStart) { |
michael@0 | 2373 | // Item is fully visible |
michael@0 | 2374 | return 0; |
michael@0 | 2375 | } |
michael@0 | 2376 | |
michael@0 | 2377 | if (nextSelectedPosition != INVALID_POSITION && |
michael@0 | 2378 | (viewToMakeVisibleEnd - goalStart) >= getMaxScrollAmount()) { |
michael@0 | 2379 | // Item already has enough of it visible, changing selection is good enough |
michael@0 | 2380 | return 0; |
michael@0 | 2381 | } |
michael@0 | 2382 | |
michael@0 | 2383 | int amountToScroll = (goalStart - viewToMakeVisibleStart); |
michael@0 | 2384 | |
michael@0 | 2385 | if (mFirstPosition == 0) { |
michael@0 | 2386 | final View firstChild = getChildAt(0); |
michael@0 | 2387 | final int firstChildStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); |
michael@0 | 2388 | |
michael@0 | 2389 | // First is first in list -> make sure we don't scroll past it |
michael@0 | 2390 | final int max = start - firstChildStart; |
michael@0 | 2391 | amountToScroll = Math.min(amountToScroll, max); |
michael@0 | 2392 | } |
michael@0 | 2393 | |
michael@0 | 2394 | return Math.min(amountToScroll, getMaxScrollAmount()); |
michael@0 | 2395 | } |
michael@0 | 2396 | } |
michael@0 | 2397 | |
michael@0 | 2398 | /** |
michael@0 | 2399 | * Determine how much we need to scroll in order to get newFocus in view. |
michael@0 | 2400 | * |
michael@0 | 2401 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 2402 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 2403 | * current view orientation. |
michael@0 | 2404 | * @param newFocus The view that would take focus. |
michael@0 | 2405 | * @param positionOfNewFocus The position of the list item containing newFocus |
michael@0 | 2406 | * |
michael@0 | 2407 | * @return The amount to scroll. Note: this is always positive! Direction |
michael@0 | 2408 | * needs to be taken into account when actually scrolling. |
michael@0 | 2409 | */ |
michael@0 | 2410 | private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) { |
michael@0 | 2411 | forceValidFocusDirection(direction); |
michael@0 | 2412 | |
michael@0 | 2413 | int amountToScroll = 0; |
michael@0 | 2414 | |
michael@0 | 2415 | newFocus.getDrawingRect(mTempRect); |
michael@0 | 2416 | offsetDescendantRectToMyCoords(newFocus, mTempRect); |
michael@0 | 2417 | |
michael@0 | 2418 | if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { |
michael@0 | 2419 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 2420 | final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left); |
michael@0 | 2421 | |
michael@0 | 2422 | if (newFocusStart < start) { |
michael@0 | 2423 | amountToScroll = start - newFocusStart; |
michael@0 | 2424 | if (positionOfNewFocus > 0) { |
michael@0 | 2425 | amountToScroll += getArrowScrollPreviewLength(); |
michael@0 | 2426 | } |
michael@0 | 2427 | } |
michael@0 | 2428 | } else { |
michael@0 | 2429 | final int end = (mIsVertical ? getHeight() - getPaddingBottom() : |
michael@0 | 2430 | getWidth() - getPaddingRight()); |
michael@0 | 2431 | final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); |
michael@0 | 2432 | |
michael@0 | 2433 | if (newFocusEnd > end) { |
michael@0 | 2434 | amountToScroll = newFocusEnd - end; |
michael@0 | 2435 | if (positionOfNewFocus < mItemCount - 1) { |
michael@0 | 2436 | amountToScroll += getArrowScrollPreviewLength(); |
michael@0 | 2437 | } |
michael@0 | 2438 | } |
michael@0 | 2439 | } |
michael@0 | 2440 | |
michael@0 | 2441 | return amountToScroll; |
michael@0 | 2442 | } |
michael@0 | 2443 | |
michael@0 | 2444 | /** |
michael@0 | 2445 | * Determine the distance to the nearest edge of a view in a particular |
michael@0 | 2446 | * direction. |
michael@0 | 2447 | * |
michael@0 | 2448 | * @param descendant A descendant of this list. |
michael@0 | 2449 | * @return The distance, or 0 if the nearest edge is already on screen. |
michael@0 | 2450 | */ |
michael@0 | 2451 | private int distanceToView(View descendant) { |
michael@0 | 2452 | descendant.getDrawingRect(mTempRect); |
michael@0 | 2453 | offsetDescendantRectToMyCoords(descendant, mTempRect); |
michael@0 | 2454 | |
michael@0 | 2455 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 2456 | final int end = (mIsVertical ? getHeight() - getPaddingBottom() : |
michael@0 | 2457 | getWidth() - getPaddingRight()); |
michael@0 | 2458 | |
michael@0 | 2459 | final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left); |
michael@0 | 2460 | final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); |
michael@0 | 2461 | |
michael@0 | 2462 | int distance = 0; |
michael@0 | 2463 | if (viewEnd < start) { |
michael@0 | 2464 | distance = start - viewEnd; |
michael@0 | 2465 | } else if (viewStart > end) { |
michael@0 | 2466 | distance = viewStart - end; |
michael@0 | 2467 | } |
michael@0 | 2468 | |
michael@0 | 2469 | return distance; |
michael@0 | 2470 | } |
michael@0 | 2471 | |
michael@0 | 2472 | private boolean handleKeyScroll(KeyEvent event, int count, int direction) { |
michael@0 | 2473 | boolean handled = false; |
michael@0 | 2474 | |
michael@0 | 2475 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2476 | handled = resurrectSelectionIfNeeded(); |
michael@0 | 2477 | if (!handled) { |
michael@0 | 2478 | while (count-- > 0) { |
michael@0 | 2479 | if (arrowScroll(direction)) { |
michael@0 | 2480 | handled = true; |
michael@0 | 2481 | } else { |
michael@0 | 2482 | break; |
michael@0 | 2483 | } |
michael@0 | 2484 | } |
michael@0 | 2485 | } |
michael@0 | 2486 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { |
michael@0 | 2487 | handled = resurrectSelectionIfNeeded() || fullScroll(direction); |
michael@0 | 2488 | } |
michael@0 | 2489 | |
michael@0 | 2490 | return handled; |
michael@0 | 2491 | } |
michael@0 | 2492 | |
michael@0 | 2493 | private boolean handleKeyEvent(int keyCode, int count, KeyEvent event) { |
michael@0 | 2494 | if (mAdapter == null || !mIsAttached) { |
michael@0 | 2495 | return false; |
michael@0 | 2496 | } |
michael@0 | 2497 | |
michael@0 | 2498 | if (mDataChanged) { |
michael@0 | 2499 | layoutChildren(); |
michael@0 | 2500 | } |
michael@0 | 2501 | |
michael@0 | 2502 | boolean handled = false; |
michael@0 | 2503 | final int action = event.getAction(); |
michael@0 | 2504 | |
michael@0 | 2505 | if (action != KeyEvent.ACTION_UP) { |
michael@0 | 2506 | switch (keyCode) { |
michael@0 | 2507 | case KeyEvent.KEYCODE_DPAD_UP: |
michael@0 | 2508 | if (mIsVertical) { |
michael@0 | 2509 | handled = handleKeyScroll(event, count, View.FOCUS_UP); |
michael@0 | 2510 | } else if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2511 | handled = handleFocusWithinItem(View.FOCUS_UP); |
michael@0 | 2512 | } |
michael@0 | 2513 | break; |
michael@0 | 2514 | |
michael@0 | 2515 | case KeyEvent.KEYCODE_DPAD_DOWN: { |
michael@0 | 2516 | if (mIsVertical) { |
michael@0 | 2517 | handled = handleKeyScroll(event, count, View.FOCUS_DOWN); |
michael@0 | 2518 | } else if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2519 | handled = handleFocusWithinItem(View.FOCUS_DOWN); |
michael@0 | 2520 | } |
michael@0 | 2521 | break; |
michael@0 | 2522 | } |
michael@0 | 2523 | |
michael@0 | 2524 | case KeyEvent.KEYCODE_DPAD_LEFT: |
michael@0 | 2525 | if (!mIsVertical) { |
michael@0 | 2526 | handled = handleKeyScroll(event, count, View.FOCUS_LEFT); |
michael@0 | 2527 | } else if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2528 | handled = handleFocusWithinItem(View.FOCUS_LEFT); |
michael@0 | 2529 | } |
michael@0 | 2530 | break; |
michael@0 | 2531 | |
michael@0 | 2532 | case KeyEvent.KEYCODE_DPAD_RIGHT: |
michael@0 | 2533 | if (!mIsVertical) { |
michael@0 | 2534 | handled = handleKeyScroll(event, count, View.FOCUS_RIGHT); |
michael@0 | 2535 | } else if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2536 | handled = handleFocusWithinItem(View.FOCUS_RIGHT); |
michael@0 | 2537 | } |
michael@0 | 2538 | break; |
michael@0 | 2539 | |
michael@0 | 2540 | case KeyEvent.KEYCODE_DPAD_CENTER: |
michael@0 | 2541 | case KeyEvent.KEYCODE_ENTER: |
michael@0 | 2542 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2543 | handled = resurrectSelectionIfNeeded(); |
michael@0 | 2544 | if (!handled |
michael@0 | 2545 | && event.getRepeatCount() == 0 && getChildCount() > 0) { |
michael@0 | 2546 | keyPressed(); |
michael@0 | 2547 | handled = true; |
michael@0 | 2548 | } |
michael@0 | 2549 | } |
michael@0 | 2550 | break; |
michael@0 | 2551 | |
michael@0 | 2552 | case KeyEvent.KEYCODE_SPACE: |
michael@0 | 2553 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2554 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2555 | pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); |
michael@0 | 2556 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { |
michael@0 | 2557 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2558 | fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); |
michael@0 | 2559 | } |
michael@0 | 2560 | |
michael@0 | 2561 | handled = true; |
michael@0 | 2562 | break; |
michael@0 | 2563 | |
michael@0 | 2564 | case KeyEvent.KEYCODE_PAGE_UP: |
michael@0 | 2565 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2566 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2567 | pageScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); |
michael@0 | 2568 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { |
michael@0 | 2569 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2570 | fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); |
michael@0 | 2571 | } |
michael@0 | 2572 | break; |
michael@0 | 2573 | |
michael@0 | 2574 | case KeyEvent.KEYCODE_PAGE_DOWN: |
michael@0 | 2575 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2576 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2577 | pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); |
michael@0 | 2578 | } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) { |
michael@0 | 2579 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2580 | fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); |
michael@0 | 2581 | } |
michael@0 | 2582 | break; |
michael@0 | 2583 | |
michael@0 | 2584 | case KeyEvent.KEYCODE_MOVE_HOME: |
michael@0 | 2585 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2586 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2587 | fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT); |
michael@0 | 2588 | } |
michael@0 | 2589 | break; |
michael@0 | 2590 | |
michael@0 | 2591 | case KeyEvent.KEYCODE_MOVE_END: |
michael@0 | 2592 | if (KeyEventCompat.hasNoModifiers(event)) { |
michael@0 | 2593 | handled = resurrectSelectionIfNeeded() || |
michael@0 | 2594 | fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT); |
michael@0 | 2595 | } |
michael@0 | 2596 | break; |
michael@0 | 2597 | } |
michael@0 | 2598 | } |
michael@0 | 2599 | |
michael@0 | 2600 | if (handled) { |
michael@0 | 2601 | return true; |
michael@0 | 2602 | } |
michael@0 | 2603 | |
michael@0 | 2604 | switch (action) { |
michael@0 | 2605 | case KeyEvent.ACTION_DOWN: |
michael@0 | 2606 | return super.onKeyDown(keyCode, event); |
michael@0 | 2607 | |
michael@0 | 2608 | case KeyEvent.ACTION_UP: |
michael@0 | 2609 | if (!isEnabled()) { |
michael@0 | 2610 | return true; |
michael@0 | 2611 | } |
michael@0 | 2612 | |
michael@0 | 2613 | if (isClickable() && isPressed() && |
michael@0 | 2614 | mSelectedPosition >= 0 && mAdapter != null && |
michael@0 | 2615 | mSelectedPosition < mAdapter.getCount()) { |
michael@0 | 2616 | |
michael@0 | 2617 | final View child = getChildAt(mSelectedPosition - mFirstPosition); |
michael@0 | 2618 | if (child != null) { |
michael@0 | 2619 | performItemClick(child, mSelectedPosition, mSelectedRowId); |
michael@0 | 2620 | child.setPressed(false); |
michael@0 | 2621 | } |
michael@0 | 2622 | |
michael@0 | 2623 | setPressed(false); |
michael@0 | 2624 | return true; |
michael@0 | 2625 | } |
michael@0 | 2626 | |
michael@0 | 2627 | return false; |
michael@0 | 2628 | |
michael@0 | 2629 | case KeyEvent.ACTION_MULTIPLE: |
michael@0 | 2630 | return super.onKeyMultiple(keyCode, count, event); |
michael@0 | 2631 | |
michael@0 | 2632 | default: |
michael@0 | 2633 | return false; |
michael@0 | 2634 | } |
michael@0 | 2635 | } |
michael@0 | 2636 | |
michael@0 | 2637 | private void initOrResetVelocityTracker() { |
michael@0 | 2638 | if (mVelocityTracker == null) { |
michael@0 | 2639 | mVelocityTracker = VelocityTracker.obtain(); |
michael@0 | 2640 | } else { |
michael@0 | 2641 | mVelocityTracker.clear(); |
michael@0 | 2642 | } |
michael@0 | 2643 | } |
michael@0 | 2644 | |
michael@0 | 2645 | private void initVelocityTrackerIfNotExists() { |
michael@0 | 2646 | if (mVelocityTracker == null) { |
michael@0 | 2647 | mVelocityTracker = VelocityTracker.obtain(); |
michael@0 | 2648 | } |
michael@0 | 2649 | } |
michael@0 | 2650 | |
michael@0 | 2651 | private void recycleVelocityTracker() { |
michael@0 | 2652 | if (mVelocityTracker != null) { |
michael@0 | 2653 | mVelocityTracker.recycle(); |
michael@0 | 2654 | mVelocityTracker = null; |
michael@0 | 2655 | } |
michael@0 | 2656 | } |
michael@0 | 2657 | |
michael@0 | 2658 | /** |
michael@0 | 2659 | * Notify our scroll listener (if there is one) of a change in scroll state |
michael@0 | 2660 | */ |
michael@0 | 2661 | private void invokeOnItemScrollListener() { |
michael@0 | 2662 | if (mOnScrollListener != null) { |
michael@0 | 2663 | mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); |
michael@0 | 2664 | } |
michael@0 | 2665 | |
michael@0 | 2666 | // Dummy values, View's implementation does not use these. |
michael@0 | 2667 | onScrollChanged(0, 0, 0, 0); |
michael@0 | 2668 | } |
michael@0 | 2669 | |
michael@0 | 2670 | private void reportScrollStateChange(int newState) { |
michael@0 | 2671 | if (newState == mLastScrollState) { |
michael@0 | 2672 | return; |
michael@0 | 2673 | } |
michael@0 | 2674 | |
michael@0 | 2675 | if (mOnScrollListener != null) { |
michael@0 | 2676 | mLastScrollState = newState; |
michael@0 | 2677 | mOnScrollListener.onScrollStateChanged(this, newState); |
michael@0 | 2678 | } |
michael@0 | 2679 | } |
michael@0 | 2680 | |
michael@0 | 2681 | private boolean maybeStartScrolling(int delta) { |
michael@0 | 2682 | final boolean isOverScroll = (mOverScroll != 0); |
michael@0 | 2683 | if (Math.abs(delta) <= mTouchSlop && !isOverScroll) { |
michael@0 | 2684 | return false; |
michael@0 | 2685 | } |
michael@0 | 2686 | |
michael@0 | 2687 | if (isOverScroll) { |
michael@0 | 2688 | mTouchMode = TOUCH_MODE_OVERSCROLL; |
michael@0 | 2689 | } else { |
michael@0 | 2690 | mTouchMode = TOUCH_MODE_DRAGGING; |
michael@0 | 2691 | } |
michael@0 | 2692 | |
michael@0 | 2693 | // Time to start stealing events! Once we've stolen them, don't |
michael@0 | 2694 | // let anyone steal from us. |
michael@0 | 2695 | final ViewParent parent = getParent(); |
michael@0 | 2696 | if (parent != null) { |
michael@0 | 2697 | parent.requestDisallowInterceptTouchEvent(true); |
michael@0 | 2698 | } |
michael@0 | 2699 | |
michael@0 | 2700 | cancelCheckForLongPress(); |
michael@0 | 2701 | |
michael@0 | 2702 | setPressed(false); |
michael@0 | 2703 | View motionView = getChildAt(mMotionPosition - mFirstPosition); |
michael@0 | 2704 | if (motionView != null) { |
michael@0 | 2705 | motionView.setPressed(false); |
michael@0 | 2706 | } |
michael@0 | 2707 | |
michael@0 | 2708 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); |
michael@0 | 2709 | |
michael@0 | 2710 | return true; |
michael@0 | 2711 | } |
michael@0 | 2712 | |
michael@0 | 2713 | private void maybeScroll(int delta) { |
michael@0 | 2714 | if (mTouchMode == TOUCH_MODE_DRAGGING) { |
michael@0 | 2715 | handleDragChange(delta); |
michael@0 | 2716 | } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { |
michael@0 | 2717 | handleOverScrollChange(delta); |
michael@0 | 2718 | } |
michael@0 | 2719 | } |
michael@0 | 2720 | |
michael@0 | 2721 | private void handleDragChange(int delta) { |
michael@0 | 2722 | // Time to start stealing events! Once we've stolen them, don't |
michael@0 | 2723 | // let anyone steal from us. |
michael@0 | 2724 | final ViewParent parent = getParent(); |
michael@0 | 2725 | if (parent != null) { |
michael@0 | 2726 | parent.requestDisallowInterceptTouchEvent(true); |
michael@0 | 2727 | } |
michael@0 | 2728 | |
michael@0 | 2729 | final int motionIndex; |
michael@0 | 2730 | if (mMotionPosition >= 0) { |
michael@0 | 2731 | motionIndex = mMotionPosition - mFirstPosition; |
michael@0 | 2732 | } else { |
michael@0 | 2733 | // If we don't have a motion position that we can reliably track, |
michael@0 | 2734 | // pick something in the middle to make a best guess at things below. |
michael@0 | 2735 | motionIndex = getChildCount() / 2; |
michael@0 | 2736 | } |
michael@0 | 2737 | |
michael@0 | 2738 | int motionViewPrevStart = 0; |
michael@0 | 2739 | View motionView = this.getChildAt(motionIndex); |
michael@0 | 2740 | if (motionView != null) { |
michael@0 | 2741 | motionViewPrevStart = (mIsVertical ? motionView.getTop() : motionView.getLeft()); |
michael@0 | 2742 | } |
michael@0 | 2743 | |
michael@0 | 2744 | boolean atEdge = trackMotionScroll(delta); |
michael@0 | 2745 | |
michael@0 | 2746 | motionView = this.getChildAt(motionIndex); |
michael@0 | 2747 | if (motionView != null) { |
michael@0 | 2748 | final int motionViewRealStart = |
michael@0 | 2749 | (mIsVertical ? motionView.getTop() : motionView.getLeft()); |
michael@0 | 2750 | |
michael@0 | 2751 | if (atEdge) { |
michael@0 | 2752 | final int overscroll = -delta - (motionViewRealStart - motionViewPrevStart); |
michael@0 | 2753 | updateOverScrollState(delta, overscroll); |
michael@0 | 2754 | } |
michael@0 | 2755 | } |
michael@0 | 2756 | } |
michael@0 | 2757 | |
michael@0 | 2758 | private void updateOverScrollState(int delta, int overscroll) { |
michael@0 | 2759 | overScrollByInternal((mIsVertical ? 0 : overscroll), |
michael@0 | 2760 | (mIsVertical ? overscroll : 0), |
michael@0 | 2761 | (mIsVertical ? 0 : mOverScroll), |
michael@0 | 2762 | (mIsVertical ? mOverScroll : 0), |
michael@0 | 2763 | 0, 0, |
michael@0 | 2764 | (mIsVertical ? 0 : mOverscrollDistance), |
michael@0 | 2765 | (mIsVertical ? mOverscrollDistance : 0), |
michael@0 | 2766 | true); |
michael@0 | 2767 | |
michael@0 | 2768 | if (Math.abs(mOverscrollDistance) == Math.abs(mOverScroll)) { |
michael@0 | 2769 | // Break fling velocity if we impacted an edge |
michael@0 | 2770 | if (mVelocityTracker != null) { |
michael@0 | 2771 | mVelocityTracker.clear(); |
michael@0 | 2772 | } |
michael@0 | 2773 | } |
michael@0 | 2774 | |
michael@0 | 2775 | final int overscrollMode = ViewCompat.getOverScrollMode(this); |
michael@0 | 2776 | if (overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || |
michael@0 | 2777 | (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { |
michael@0 | 2778 | mTouchMode = TOUCH_MODE_OVERSCROLL; |
michael@0 | 2779 | |
michael@0 | 2780 | float pull = (float) overscroll / (mIsVertical ? getHeight() : getWidth()); |
michael@0 | 2781 | if (delta > 0) { |
michael@0 | 2782 | mStartEdge.onPull(pull); |
michael@0 | 2783 | |
michael@0 | 2784 | if (!mEndEdge.isFinished()) { |
michael@0 | 2785 | mEndEdge.onRelease(); |
michael@0 | 2786 | } |
michael@0 | 2787 | } else if (delta < 0) { |
michael@0 | 2788 | mEndEdge.onPull(pull); |
michael@0 | 2789 | |
michael@0 | 2790 | if (!mStartEdge.isFinished()) { |
michael@0 | 2791 | mStartEdge.onRelease(); |
michael@0 | 2792 | } |
michael@0 | 2793 | } |
michael@0 | 2794 | |
michael@0 | 2795 | if (delta != 0) { |
michael@0 | 2796 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 2797 | } |
michael@0 | 2798 | } |
michael@0 | 2799 | } |
michael@0 | 2800 | |
michael@0 | 2801 | private void handleOverScrollChange(int delta) { |
michael@0 | 2802 | final int oldOverScroll = mOverScroll; |
michael@0 | 2803 | final int newOverScroll = oldOverScroll - delta; |
michael@0 | 2804 | |
michael@0 | 2805 | int overScrollDistance = -delta; |
michael@0 | 2806 | if ((newOverScroll < 0 && oldOverScroll >= 0) || |
michael@0 | 2807 | (newOverScroll > 0 && oldOverScroll <= 0)) { |
michael@0 | 2808 | overScrollDistance = -oldOverScroll; |
michael@0 | 2809 | delta += overScrollDistance; |
michael@0 | 2810 | } else { |
michael@0 | 2811 | delta = 0; |
michael@0 | 2812 | } |
michael@0 | 2813 | |
michael@0 | 2814 | if (overScrollDistance != 0) { |
michael@0 | 2815 | updateOverScrollState(delta, overScrollDistance); |
michael@0 | 2816 | } |
michael@0 | 2817 | |
michael@0 | 2818 | if (delta != 0) { |
michael@0 | 2819 | if (mOverScroll != 0) { |
michael@0 | 2820 | mOverScroll = 0; |
michael@0 | 2821 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 2822 | } |
michael@0 | 2823 | |
michael@0 | 2824 | trackMotionScroll(delta); |
michael@0 | 2825 | mTouchMode = TOUCH_MODE_DRAGGING; |
michael@0 | 2826 | |
michael@0 | 2827 | // We did not scroll the full amount. Treat this essentially like the |
michael@0 | 2828 | // start of a new touch scroll |
michael@0 | 2829 | mMotionPosition = findClosestMotionRowOrColumn((int) mLastTouchPos); |
michael@0 | 2830 | mTouchRemainderPos = 0; |
michael@0 | 2831 | } |
michael@0 | 2832 | } |
michael@0 | 2833 | |
michael@0 | 2834 | /** |
michael@0 | 2835 | * What is the distance between the source and destination rectangles given the direction of |
michael@0 | 2836 | * focus navigation between them? The direction basically helps figure out more quickly what is |
michael@0 | 2837 | * self evident by the relationship between the rects... |
michael@0 | 2838 | * |
michael@0 | 2839 | * @param source the source rectangle |
michael@0 | 2840 | * @param dest the destination rectangle |
michael@0 | 2841 | * @param direction the direction |
michael@0 | 2842 | * @return the distance between the rectangles |
michael@0 | 2843 | */ |
michael@0 | 2844 | private static int getDistance(Rect source, Rect dest, int direction) { |
michael@0 | 2845 | int sX, sY; // source x, y |
michael@0 | 2846 | int dX, dY; // dest x, y |
michael@0 | 2847 | |
michael@0 | 2848 | switch (direction) { |
michael@0 | 2849 | case View.FOCUS_RIGHT: |
michael@0 | 2850 | sX = source.right; |
michael@0 | 2851 | sY = source.top + source.height() / 2; |
michael@0 | 2852 | dX = dest.left; |
michael@0 | 2853 | dY = dest.top + dest.height() / 2; |
michael@0 | 2854 | break; |
michael@0 | 2855 | |
michael@0 | 2856 | case View.FOCUS_DOWN: |
michael@0 | 2857 | sX = source.left + source.width() / 2; |
michael@0 | 2858 | sY = source.bottom; |
michael@0 | 2859 | dX = dest.left + dest.width() / 2; |
michael@0 | 2860 | dY = dest.top; |
michael@0 | 2861 | break; |
michael@0 | 2862 | |
michael@0 | 2863 | case View.FOCUS_LEFT: |
michael@0 | 2864 | sX = source.left; |
michael@0 | 2865 | sY = source.top + source.height() / 2; |
michael@0 | 2866 | dX = dest.right; |
michael@0 | 2867 | dY = dest.top + dest.height() / 2; |
michael@0 | 2868 | break; |
michael@0 | 2869 | |
michael@0 | 2870 | case View.FOCUS_UP: |
michael@0 | 2871 | sX = source.left + source.width() / 2; |
michael@0 | 2872 | sY = source.top; |
michael@0 | 2873 | dX = dest.left + dest.width() / 2; |
michael@0 | 2874 | dY = dest.bottom; |
michael@0 | 2875 | break; |
michael@0 | 2876 | |
michael@0 | 2877 | case View.FOCUS_FORWARD: |
michael@0 | 2878 | case View.FOCUS_BACKWARD: |
michael@0 | 2879 | sX = source.right + source.width() / 2; |
michael@0 | 2880 | sY = source.top + source.height() / 2; |
michael@0 | 2881 | dX = dest.left + dest.width() / 2; |
michael@0 | 2882 | dY = dest.top + dest.height() / 2; |
michael@0 | 2883 | break; |
michael@0 | 2884 | |
michael@0 | 2885 | default: |
michael@0 | 2886 | throw new IllegalArgumentException("direction must be one of " |
michael@0 | 2887 | + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, " |
michael@0 | 2888 | + "FOCUS_FORWARD, FOCUS_BACKWARD}."); |
michael@0 | 2889 | } |
michael@0 | 2890 | |
michael@0 | 2891 | int deltaX = dX - sX; |
michael@0 | 2892 | int deltaY = dY - sY; |
michael@0 | 2893 | |
michael@0 | 2894 | return deltaY * deltaY + deltaX * deltaX; |
michael@0 | 2895 | } |
michael@0 | 2896 | |
michael@0 | 2897 | private int findMotionRowOrColumn(int motionPos) { |
michael@0 | 2898 | int childCount = getChildCount(); |
michael@0 | 2899 | if (childCount == 0) { |
michael@0 | 2900 | return INVALID_POSITION; |
michael@0 | 2901 | } |
michael@0 | 2902 | |
michael@0 | 2903 | for (int i = 0; i < childCount; i++) { |
michael@0 | 2904 | View v = getChildAt(i); |
michael@0 | 2905 | |
michael@0 | 2906 | if ((mIsVertical && motionPos <= v.getBottom()) || |
michael@0 | 2907 | (!mIsVertical && motionPos <= v.getRight())) { |
michael@0 | 2908 | return mFirstPosition + i; |
michael@0 | 2909 | } |
michael@0 | 2910 | } |
michael@0 | 2911 | |
michael@0 | 2912 | return INVALID_POSITION; |
michael@0 | 2913 | } |
michael@0 | 2914 | |
michael@0 | 2915 | private int findClosestMotionRowOrColumn(int motionPos) { |
michael@0 | 2916 | final int childCount = getChildCount(); |
michael@0 | 2917 | if (childCount == 0) { |
michael@0 | 2918 | return INVALID_POSITION; |
michael@0 | 2919 | } |
michael@0 | 2920 | |
michael@0 | 2921 | final int motionRow = findMotionRowOrColumn(motionPos); |
michael@0 | 2922 | if (motionRow != INVALID_POSITION) { |
michael@0 | 2923 | return motionRow; |
michael@0 | 2924 | } else { |
michael@0 | 2925 | return mFirstPosition + childCount - 1; |
michael@0 | 2926 | } |
michael@0 | 2927 | } |
michael@0 | 2928 | |
michael@0 | 2929 | @TargetApi(9) |
michael@0 | 2930 | private int getScaledOverscrollDistance(ViewConfiguration vc) { |
michael@0 | 2931 | if (Build.VERSION.SDK_INT < 9) { |
michael@0 | 2932 | return 0; |
michael@0 | 2933 | } |
michael@0 | 2934 | |
michael@0 | 2935 | return vc.getScaledOverscrollDistance(); |
michael@0 | 2936 | } |
michael@0 | 2937 | |
michael@0 | 2938 | private boolean contentFits() { |
michael@0 | 2939 | final int childCount = getChildCount(); |
michael@0 | 2940 | if (childCount == 0) { |
michael@0 | 2941 | return true; |
michael@0 | 2942 | } |
michael@0 | 2943 | |
michael@0 | 2944 | if (childCount != mItemCount) { |
michael@0 | 2945 | return false; |
michael@0 | 2946 | } |
michael@0 | 2947 | |
michael@0 | 2948 | View first = getChildAt(0); |
michael@0 | 2949 | View last = getChildAt(childCount - 1); |
michael@0 | 2950 | |
michael@0 | 2951 | if (mIsVertical) { |
michael@0 | 2952 | return first.getTop() >= getPaddingTop() && |
michael@0 | 2953 | last.getBottom() <= getHeight() - getPaddingBottom(); |
michael@0 | 2954 | } else { |
michael@0 | 2955 | return first.getLeft() >= getPaddingLeft() && |
michael@0 | 2956 | last.getRight() <= getWidth() - getPaddingRight(); |
michael@0 | 2957 | } |
michael@0 | 2958 | } |
michael@0 | 2959 | |
michael@0 | 2960 | private void updateScrollbarsDirection() { |
michael@0 | 2961 | setHorizontalScrollBarEnabled(!mIsVertical); |
michael@0 | 2962 | setVerticalScrollBarEnabled(mIsVertical); |
michael@0 | 2963 | } |
michael@0 | 2964 | |
michael@0 | 2965 | private void triggerCheckForTap() { |
michael@0 | 2966 | if (mPendingCheckForTap == null) { |
michael@0 | 2967 | mPendingCheckForTap = new CheckForTap(); |
michael@0 | 2968 | } |
michael@0 | 2969 | |
michael@0 | 2970 | postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); |
michael@0 | 2971 | } |
michael@0 | 2972 | |
michael@0 | 2973 | private void cancelCheckForTap() { |
michael@0 | 2974 | if (mPendingCheckForTap == null) { |
michael@0 | 2975 | return; |
michael@0 | 2976 | } |
michael@0 | 2977 | |
michael@0 | 2978 | removeCallbacks(mPendingCheckForTap); |
michael@0 | 2979 | } |
michael@0 | 2980 | |
michael@0 | 2981 | private void triggerCheckForLongPress() { |
michael@0 | 2982 | if (mPendingCheckForLongPress == null) { |
michael@0 | 2983 | mPendingCheckForLongPress = new CheckForLongPress(); |
michael@0 | 2984 | } |
michael@0 | 2985 | |
michael@0 | 2986 | mPendingCheckForLongPress.rememberWindowAttachCount(); |
michael@0 | 2987 | |
michael@0 | 2988 | postDelayed(mPendingCheckForLongPress, |
michael@0 | 2989 | ViewConfiguration.getLongPressTimeout()); |
michael@0 | 2990 | } |
michael@0 | 2991 | |
michael@0 | 2992 | private void cancelCheckForLongPress() { |
michael@0 | 2993 | if (mPendingCheckForLongPress == null) { |
michael@0 | 2994 | return; |
michael@0 | 2995 | } |
michael@0 | 2996 | |
michael@0 | 2997 | removeCallbacks(mPendingCheckForLongPress); |
michael@0 | 2998 | } |
michael@0 | 2999 | |
michael@0 | 3000 | boolean trackMotionScroll(int incrementalDelta) { |
michael@0 | 3001 | final int childCount = getChildCount(); |
michael@0 | 3002 | if (childCount == 0) { |
michael@0 | 3003 | return true; |
michael@0 | 3004 | } |
michael@0 | 3005 | |
michael@0 | 3006 | final View first = getChildAt(0); |
michael@0 | 3007 | final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); |
michael@0 | 3008 | |
michael@0 | 3009 | final View last = getChildAt(childCount - 1); |
michael@0 | 3010 | final int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); |
michael@0 | 3011 | |
michael@0 | 3012 | final int paddingTop = getPaddingTop(); |
michael@0 | 3013 | final int paddingBottom = getPaddingBottom(); |
michael@0 | 3014 | final int paddingLeft = getPaddingLeft(); |
michael@0 | 3015 | final int paddingRight = getPaddingRight(); |
michael@0 | 3016 | |
michael@0 | 3017 | final int paddingStart = (mIsVertical ? paddingTop : paddingLeft); |
michael@0 | 3018 | |
michael@0 | 3019 | final int spaceBefore = paddingStart - firstStart; |
michael@0 | 3020 | final int end = (mIsVertical ? getHeight() - paddingBottom : |
michael@0 | 3021 | getWidth() - paddingRight); |
michael@0 | 3022 | final int spaceAfter = lastEnd - end; |
michael@0 | 3023 | |
michael@0 | 3024 | final int size; |
michael@0 | 3025 | if (mIsVertical) { |
michael@0 | 3026 | size = getHeight() - paddingBottom - paddingTop; |
michael@0 | 3027 | } else { |
michael@0 | 3028 | size = getWidth() - paddingRight - paddingLeft; |
michael@0 | 3029 | } |
michael@0 | 3030 | |
michael@0 | 3031 | if (incrementalDelta < 0) { |
michael@0 | 3032 | incrementalDelta = Math.max(-(size - 1), incrementalDelta); |
michael@0 | 3033 | } else { |
michael@0 | 3034 | incrementalDelta = Math.min(size - 1, incrementalDelta); |
michael@0 | 3035 | } |
michael@0 | 3036 | |
michael@0 | 3037 | final int firstPosition = mFirstPosition; |
michael@0 | 3038 | |
michael@0 | 3039 | final boolean cannotScrollDown = (firstPosition == 0 && |
michael@0 | 3040 | firstStart >= paddingStart && incrementalDelta >= 0); |
michael@0 | 3041 | final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && |
michael@0 | 3042 | lastEnd <= end && incrementalDelta <= 0); |
michael@0 | 3043 | |
michael@0 | 3044 | if (cannotScrollDown || cannotScrollUp) { |
michael@0 | 3045 | return incrementalDelta != 0; |
michael@0 | 3046 | } |
michael@0 | 3047 | |
michael@0 | 3048 | final boolean inTouchMode = isInTouchMode(); |
michael@0 | 3049 | if (inTouchMode) { |
michael@0 | 3050 | hideSelector(); |
michael@0 | 3051 | } |
michael@0 | 3052 | |
michael@0 | 3053 | int start = 0; |
michael@0 | 3054 | int count = 0; |
michael@0 | 3055 | |
michael@0 | 3056 | final boolean down = (incrementalDelta < 0); |
michael@0 | 3057 | if (down) { |
michael@0 | 3058 | int childrenStart = -incrementalDelta + paddingStart; |
michael@0 | 3059 | |
michael@0 | 3060 | for (int i = 0; i < childCount; i++) { |
michael@0 | 3061 | final View child = getChildAt(i); |
michael@0 | 3062 | final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); |
michael@0 | 3063 | |
michael@0 | 3064 | if (childEnd >= childrenStart) { |
michael@0 | 3065 | break; |
michael@0 | 3066 | } |
michael@0 | 3067 | |
michael@0 | 3068 | count++; |
michael@0 | 3069 | mRecycler.addScrapView(child, firstPosition + i); |
michael@0 | 3070 | } |
michael@0 | 3071 | } else { |
michael@0 | 3072 | int childrenEnd = end - incrementalDelta; |
michael@0 | 3073 | |
michael@0 | 3074 | for (int i = childCount - 1; i >= 0; i--) { |
michael@0 | 3075 | final View child = getChildAt(i); |
michael@0 | 3076 | final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); |
michael@0 | 3077 | |
michael@0 | 3078 | if (childStart <= childrenEnd) { |
michael@0 | 3079 | break; |
michael@0 | 3080 | } |
michael@0 | 3081 | |
michael@0 | 3082 | start = i; |
michael@0 | 3083 | count++; |
michael@0 | 3084 | mRecycler.addScrapView(child, firstPosition + i); |
michael@0 | 3085 | } |
michael@0 | 3086 | } |
michael@0 | 3087 | |
michael@0 | 3088 | mBlockLayoutRequests = true; |
michael@0 | 3089 | |
michael@0 | 3090 | if (count > 0) { |
michael@0 | 3091 | detachViewsFromParent(start, count); |
michael@0 | 3092 | } |
michael@0 | 3093 | |
michael@0 | 3094 | // invalidate before moving the children to avoid unnecessary invalidate |
michael@0 | 3095 | // calls to bubble up from the children all the way to the top |
michael@0 | 3096 | if (!awakenScrollbarsInternal()) { |
michael@0 | 3097 | invalidate(); |
michael@0 | 3098 | } |
michael@0 | 3099 | |
michael@0 | 3100 | offsetChildren(incrementalDelta); |
michael@0 | 3101 | |
michael@0 | 3102 | if (down) { |
michael@0 | 3103 | mFirstPosition += count; |
michael@0 | 3104 | } |
michael@0 | 3105 | |
michael@0 | 3106 | final int absIncrementalDelta = Math.abs(incrementalDelta); |
michael@0 | 3107 | if (spaceBefore < absIncrementalDelta || spaceAfter < absIncrementalDelta) { |
michael@0 | 3108 | fillGap(down); |
michael@0 | 3109 | } |
michael@0 | 3110 | |
michael@0 | 3111 | if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { |
michael@0 | 3112 | final int childIndex = mSelectedPosition - mFirstPosition; |
michael@0 | 3113 | if (childIndex >= 0 && childIndex < getChildCount()) { |
michael@0 | 3114 | positionSelector(mSelectedPosition, getChildAt(childIndex)); |
michael@0 | 3115 | } |
michael@0 | 3116 | } else if (mSelectorPosition != INVALID_POSITION) { |
michael@0 | 3117 | final int childIndex = mSelectorPosition - mFirstPosition; |
michael@0 | 3118 | if (childIndex >= 0 && childIndex < getChildCount()) { |
michael@0 | 3119 | positionSelector(INVALID_POSITION, getChildAt(childIndex)); |
michael@0 | 3120 | } |
michael@0 | 3121 | } else { |
michael@0 | 3122 | mSelectorRect.setEmpty(); |
michael@0 | 3123 | } |
michael@0 | 3124 | |
michael@0 | 3125 | mBlockLayoutRequests = false; |
michael@0 | 3126 | |
michael@0 | 3127 | invokeOnItemScrollListener(); |
michael@0 | 3128 | |
michael@0 | 3129 | return false; |
michael@0 | 3130 | } |
michael@0 | 3131 | |
michael@0 | 3132 | @TargetApi(14) |
michael@0 | 3133 | private final float getCurrVelocity() { |
michael@0 | 3134 | if (Build.VERSION.SDK_INT >= 14) { |
michael@0 | 3135 | return mScroller.getCurrVelocity(); |
michael@0 | 3136 | } |
michael@0 | 3137 | |
michael@0 | 3138 | return 0; |
michael@0 | 3139 | } |
michael@0 | 3140 | |
michael@0 | 3141 | @TargetApi(5) |
michael@0 | 3142 | private boolean awakenScrollbarsInternal() { |
michael@0 | 3143 | if (Build.VERSION.SDK_INT >= 5) { |
michael@0 | 3144 | return super.awakenScrollBars(); |
michael@0 | 3145 | } else { |
michael@0 | 3146 | return false; |
michael@0 | 3147 | } |
michael@0 | 3148 | } |
michael@0 | 3149 | |
michael@0 | 3150 | @Override |
michael@0 | 3151 | public void computeScroll() { |
michael@0 | 3152 | if (!mScroller.computeScrollOffset()) { |
michael@0 | 3153 | return; |
michael@0 | 3154 | } |
michael@0 | 3155 | |
michael@0 | 3156 | final int pos; |
michael@0 | 3157 | if (mIsVertical) { |
michael@0 | 3158 | pos = mScroller.getCurrY(); |
michael@0 | 3159 | } else { |
michael@0 | 3160 | pos = mScroller.getCurrX(); |
michael@0 | 3161 | } |
michael@0 | 3162 | |
michael@0 | 3163 | final int diff = (int) (pos - mLastTouchPos); |
michael@0 | 3164 | mLastTouchPos = pos; |
michael@0 | 3165 | |
michael@0 | 3166 | final boolean stopped = trackMotionScroll(diff); |
michael@0 | 3167 | |
michael@0 | 3168 | if (!stopped && !mScroller.isFinished()) { |
michael@0 | 3169 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 3170 | } else { |
michael@0 | 3171 | if (stopped) { |
michael@0 | 3172 | final int overScrollMode = ViewCompat.getOverScrollMode(this); |
michael@0 | 3173 | if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) { |
michael@0 | 3174 | final EdgeEffectCompat edge = |
michael@0 | 3175 | (diff > 0 ? mStartEdge : mEndEdge); |
michael@0 | 3176 | |
michael@0 | 3177 | boolean needsInvalidate = |
michael@0 | 3178 | edge.onAbsorb(Math.abs((int) getCurrVelocity())); |
michael@0 | 3179 | |
michael@0 | 3180 | if (needsInvalidate) { |
michael@0 | 3181 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 3182 | } |
michael@0 | 3183 | } |
michael@0 | 3184 | |
michael@0 | 3185 | mScroller.abortAnimation(); |
michael@0 | 3186 | } |
michael@0 | 3187 | |
michael@0 | 3188 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 3189 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 3190 | } |
michael@0 | 3191 | } |
michael@0 | 3192 | |
michael@0 | 3193 | private void finishEdgeGlows() { |
michael@0 | 3194 | if (mStartEdge != null) { |
michael@0 | 3195 | mStartEdge.finish(); |
michael@0 | 3196 | } |
michael@0 | 3197 | |
michael@0 | 3198 | if (mEndEdge != null) { |
michael@0 | 3199 | mEndEdge.finish(); |
michael@0 | 3200 | } |
michael@0 | 3201 | } |
michael@0 | 3202 | |
michael@0 | 3203 | private boolean drawStartEdge(Canvas canvas) { |
michael@0 | 3204 | if (mStartEdge.isFinished()) { |
michael@0 | 3205 | return false; |
michael@0 | 3206 | } |
michael@0 | 3207 | |
michael@0 | 3208 | if (mIsVertical) { |
michael@0 | 3209 | return mStartEdge.draw(canvas); |
michael@0 | 3210 | } |
michael@0 | 3211 | |
michael@0 | 3212 | final int restoreCount = canvas.save(); |
michael@0 | 3213 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); |
michael@0 | 3214 | |
michael@0 | 3215 | canvas.translate(0, height); |
michael@0 | 3216 | canvas.rotate(270); |
michael@0 | 3217 | |
michael@0 | 3218 | final boolean needsInvalidate = mStartEdge.draw(canvas); |
michael@0 | 3219 | canvas.restoreToCount(restoreCount); |
michael@0 | 3220 | return needsInvalidate; |
michael@0 | 3221 | } |
michael@0 | 3222 | |
michael@0 | 3223 | private boolean drawEndEdge(Canvas canvas) { |
michael@0 | 3224 | if (mEndEdge.isFinished()) { |
michael@0 | 3225 | return false; |
michael@0 | 3226 | } |
michael@0 | 3227 | |
michael@0 | 3228 | final int restoreCount = canvas.save(); |
michael@0 | 3229 | final int width = getWidth() - getPaddingLeft() - getPaddingRight(); |
michael@0 | 3230 | final int height = getHeight() - getPaddingTop() - getPaddingBottom(); |
michael@0 | 3231 | |
michael@0 | 3232 | if (mIsVertical) { |
michael@0 | 3233 | canvas.translate(-width, height); |
michael@0 | 3234 | canvas.rotate(180, width, 0); |
michael@0 | 3235 | } else { |
michael@0 | 3236 | canvas.translate(width, 0); |
michael@0 | 3237 | canvas.rotate(90); |
michael@0 | 3238 | } |
michael@0 | 3239 | |
michael@0 | 3240 | final boolean needsInvalidate = mEndEdge.draw(canvas); |
michael@0 | 3241 | canvas.restoreToCount(restoreCount); |
michael@0 | 3242 | return needsInvalidate; |
michael@0 | 3243 | } |
michael@0 | 3244 | |
michael@0 | 3245 | private void drawSelector(Canvas canvas) { |
michael@0 | 3246 | if (!mSelectorRect.isEmpty()) { |
michael@0 | 3247 | final Drawable selector = mSelector; |
michael@0 | 3248 | selector.setBounds(mSelectorRect); |
michael@0 | 3249 | selector.draw(canvas); |
michael@0 | 3250 | } |
michael@0 | 3251 | } |
michael@0 | 3252 | |
michael@0 | 3253 | private void useDefaultSelector() { |
michael@0 | 3254 | setSelector(getResources().getDrawable( |
michael@0 | 3255 | android.R.drawable.list_selector_background)); |
michael@0 | 3256 | } |
michael@0 | 3257 | |
michael@0 | 3258 | private boolean shouldShowSelector() { |
michael@0 | 3259 | return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState(); |
michael@0 | 3260 | } |
michael@0 | 3261 | |
michael@0 | 3262 | private void positionSelector(int position, View selected) { |
michael@0 | 3263 | if (position != INVALID_POSITION) { |
michael@0 | 3264 | mSelectorPosition = position; |
michael@0 | 3265 | } |
michael@0 | 3266 | |
michael@0 | 3267 | mSelectorRect.set(selected.getLeft(), selected.getTop(), selected.getRight(), |
michael@0 | 3268 | selected.getBottom()); |
michael@0 | 3269 | |
michael@0 | 3270 | final boolean isChildViewEnabled = mIsChildViewEnabled; |
michael@0 | 3271 | if (selected.isEnabled() != isChildViewEnabled) { |
michael@0 | 3272 | mIsChildViewEnabled = !isChildViewEnabled; |
michael@0 | 3273 | |
michael@0 | 3274 | if (getSelectedItemPosition() != INVALID_POSITION) { |
michael@0 | 3275 | refreshDrawableState(); |
michael@0 | 3276 | } |
michael@0 | 3277 | } |
michael@0 | 3278 | } |
michael@0 | 3279 | |
michael@0 | 3280 | private void hideSelector() { |
michael@0 | 3281 | if (mSelectedPosition != INVALID_POSITION) { |
michael@0 | 3282 | if (mLayoutMode != LAYOUT_SPECIFIC) { |
michael@0 | 3283 | mResurrectToPosition = mSelectedPosition; |
michael@0 | 3284 | } |
michael@0 | 3285 | |
michael@0 | 3286 | if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) { |
michael@0 | 3287 | mResurrectToPosition = mNextSelectedPosition; |
michael@0 | 3288 | } |
michael@0 | 3289 | |
michael@0 | 3290 | setSelectedPositionInt(INVALID_POSITION); |
michael@0 | 3291 | setNextSelectedPositionInt(INVALID_POSITION); |
michael@0 | 3292 | |
michael@0 | 3293 | mSelectedStart = 0; |
michael@0 | 3294 | } |
michael@0 | 3295 | } |
michael@0 | 3296 | |
michael@0 | 3297 | private void setSelectedPositionInt(int position) { |
michael@0 | 3298 | mSelectedPosition = position; |
michael@0 | 3299 | mSelectedRowId = getItemIdAtPosition(position); |
michael@0 | 3300 | } |
michael@0 | 3301 | |
michael@0 | 3302 | private void setSelectionInt(int position) { |
michael@0 | 3303 | setNextSelectedPositionInt(position); |
michael@0 | 3304 | boolean awakeScrollbars = false; |
michael@0 | 3305 | |
michael@0 | 3306 | final int selectedPosition = mSelectedPosition; |
michael@0 | 3307 | if (selectedPosition >= 0) { |
michael@0 | 3308 | if (position == selectedPosition - 1) { |
michael@0 | 3309 | awakeScrollbars = true; |
michael@0 | 3310 | } else if (position == selectedPosition + 1) { |
michael@0 | 3311 | awakeScrollbars = true; |
michael@0 | 3312 | } |
michael@0 | 3313 | } |
michael@0 | 3314 | |
michael@0 | 3315 | layoutChildren(); |
michael@0 | 3316 | |
michael@0 | 3317 | if (awakeScrollbars) { |
michael@0 | 3318 | awakenScrollbarsInternal(); |
michael@0 | 3319 | } |
michael@0 | 3320 | } |
michael@0 | 3321 | |
michael@0 | 3322 | private void setNextSelectedPositionInt(int position) { |
michael@0 | 3323 | mNextSelectedPosition = position; |
michael@0 | 3324 | mNextSelectedRowId = getItemIdAtPosition(position); |
michael@0 | 3325 | |
michael@0 | 3326 | // If we are trying to sync to the selection, update that too |
michael@0 | 3327 | if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { |
michael@0 | 3328 | mSyncPosition = position; |
michael@0 | 3329 | mSyncRowId = mNextSelectedRowId; |
michael@0 | 3330 | } |
michael@0 | 3331 | } |
michael@0 | 3332 | |
michael@0 | 3333 | private boolean touchModeDrawsInPressedState() { |
michael@0 | 3334 | switch (mTouchMode) { |
michael@0 | 3335 | case TOUCH_MODE_TAP: |
michael@0 | 3336 | case TOUCH_MODE_DONE_WAITING: |
michael@0 | 3337 | return true; |
michael@0 | 3338 | default: |
michael@0 | 3339 | return false; |
michael@0 | 3340 | } |
michael@0 | 3341 | } |
michael@0 | 3342 | |
michael@0 | 3343 | /** |
michael@0 | 3344 | * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if |
michael@0 | 3345 | * this is a long press. |
michael@0 | 3346 | */ |
michael@0 | 3347 | private void keyPressed() { |
michael@0 | 3348 | if (!isEnabled() || !isClickable()) { |
michael@0 | 3349 | return; |
michael@0 | 3350 | } |
michael@0 | 3351 | |
michael@0 | 3352 | final Drawable selector = mSelector; |
michael@0 | 3353 | final Rect selectorRect = mSelectorRect; |
michael@0 | 3354 | |
michael@0 | 3355 | if (selector != null && (isFocused() || touchModeDrawsInPressedState()) |
michael@0 | 3356 | && !selectorRect.isEmpty()) { |
michael@0 | 3357 | |
michael@0 | 3358 | final View child = getChildAt(mSelectedPosition - mFirstPosition); |
michael@0 | 3359 | |
michael@0 | 3360 | if (child != null) { |
michael@0 | 3361 | if (child.hasFocusable()) { |
michael@0 | 3362 | return; |
michael@0 | 3363 | } |
michael@0 | 3364 | |
michael@0 | 3365 | child.setPressed(true); |
michael@0 | 3366 | } |
michael@0 | 3367 | |
michael@0 | 3368 | setPressed(true); |
michael@0 | 3369 | |
michael@0 | 3370 | final boolean longClickable = isLongClickable(); |
michael@0 | 3371 | final Drawable d = selector.getCurrent(); |
michael@0 | 3372 | if (d != null && d instanceof TransitionDrawable) { |
michael@0 | 3373 | if (longClickable) { |
michael@0 | 3374 | ((TransitionDrawable) d).startTransition( |
michael@0 | 3375 | ViewConfiguration.getLongPressTimeout()); |
michael@0 | 3376 | } else { |
michael@0 | 3377 | ((TransitionDrawable) d).resetTransition(); |
michael@0 | 3378 | } |
michael@0 | 3379 | } |
michael@0 | 3380 | |
michael@0 | 3381 | if (longClickable && !mDataChanged) { |
michael@0 | 3382 | if (mPendingCheckForKeyLongPress == null) { |
michael@0 | 3383 | mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); |
michael@0 | 3384 | } |
michael@0 | 3385 | |
michael@0 | 3386 | mPendingCheckForKeyLongPress.rememberWindowAttachCount(); |
michael@0 | 3387 | postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); |
michael@0 | 3388 | } |
michael@0 | 3389 | } |
michael@0 | 3390 | } |
michael@0 | 3391 | |
michael@0 | 3392 | private void updateSelectorState() { |
michael@0 | 3393 | if (mSelector != null) { |
michael@0 | 3394 | if (shouldShowSelector()) { |
michael@0 | 3395 | mSelector.setState(getDrawableState()); |
michael@0 | 3396 | } else { |
michael@0 | 3397 | mSelector.setState(STATE_NOTHING); |
michael@0 | 3398 | } |
michael@0 | 3399 | } |
michael@0 | 3400 | } |
michael@0 | 3401 | |
michael@0 | 3402 | private void checkSelectionChanged() { |
michael@0 | 3403 | if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { |
michael@0 | 3404 | selectionChanged(); |
michael@0 | 3405 | mOldSelectedPosition = mSelectedPosition; |
michael@0 | 3406 | mOldSelectedRowId = mSelectedRowId; |
michael@0 | 3407 | } |
michael@0 | 3408 | } |
michael@0 | 3409 | |
michael@0 | 3410 | private void selectionChanged() { |
michael@0 | 3411 | OnItemSelectedListener listener = getOnItemSelectedListener(); |
michael@0 | 3412 | if (listener == null) { |
michael@0 | 3413 | return; |
michael@0 | 3414 | } |
michael@0 | 3415 | |
michael@0 | 3416 | if (mInLayout || mBlockLayoutRequests) { |
michael@0 | 3417 | // If we are in a layout traversal, defer notification |
michael@0 | 3418 | // by posting. This ensures that the view tree is |
michael@0 | 3419 | // in a consistent state and is able to accommodate |
michael@0 | 3420 | // new layout or invalidate requests. |
michael@0 | 3421 | if (mSelectionNotifier == null) { |
michael@0 | 3422 | mSelectionNotifier = new SelectionNotifier(); |
michael@0 | 3423 | } |
michael@0 | 3424 | |
michael@0 | 3425 | post(mSelectionNotifier); |
michael@0 | 3426 | } else { |
michael@0 | 3427 | fireOnSelected(); |
michael@0 | 3428 | performAccessibilityActionsOnSelected(); |
michael@0 | 3429 | } |
michael@0 | 3430 | } |
michael@0 | 3431 | |
michael@0 | 3432 | private void fireOnSelected() { |
michael@0 | 3433 | OnItemSelectedListener listener = getOnItemSelectedListener(); |
michael@0 | 3434 | if (listener == null) { |
michael@0 | 3435 | return; |
michael@0 | 3436 | } |
michael@0 | 3437 | |
michael@0 | 3438 | final int selection = getSelectedItemPosition(); |
michael@0 | 3439 | if (selection >= 0) { |
michael@0 | 3440 | View v = getSelectedView(); |
michael@0 | 3441 | listener.onItemSelected(this, v, selection, |
michael@0 | 3442 | mAdapter.getItemId(selection)); |
michael@0 | 3443 | } else { |
michael@0 | 3444 | listener.onNothingSelected(this); |
michael@0 | 3445 | } |
michael@0 | 3446 | } |
michael@0 | 3447 | |
michael@0 | 3448 | private void performAccessibilityActionsOnSelected() { |
michael@0 | 3449 | final int position = getSelectedItemPosition(); |
michael@0 | 3450 | if (position >= 0) { |
michael@0 | 3451 | // We fire selection events here not in View |
michael@0 | 3452 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); |
michael@0 | 3453 | } |
michael@0 | 3454 | } |
michael@0 | 3455 | |
michael@0 | 3456 | private int lookForSelectablePosition(int position) { |
michael@0 | 3457 | return lookForSelectablePosition(position, true); |
michael@0 | 3458 | } |
michael@0 | 3459 | |
michael@0 | 3460 | private int lookForSelectablePosition(int position, boolean lookDown) { |
michael@0 | 3461 | final ListAdapter adapter = mAdapter; |
michael@0 | 3462 | if (adapter == null || isInTouchMode()) { |
michael@0 | 3463 | return INVALID_POSITION; |
michael@0 | 3464 | } |
michael@0 | 3465 | |
michael@0 | 3466 | final int itemCount = mItemCount; |
michael@0 | 3467 | if (!mAreAllItemsSelectable) { |
michael@0 | 3468 | if (lookDown) { |
michael@0 | 3469 | position = Math.max(0, position); |
michael@0 | 3470 | while (position < itemCount && !adapter.isEnabled(position)) { |
michael@0 | 3471 | position++; |
michael@0 | 3472 | } |
michael@0 | 3473 | } else { |
michael@0 | 3474 | position = Math.min(position, itemCount - 1); |
michael@0 | 3475 | while (position >= 0 && !adapter.isEnabled(position)) { |
michael@0 | 3476 | position--; |
michael@0 | 3477 | } |
michael@0 | 3478 | } |
michael@0 | 3479 | |
michael@0 | 3480 | if (position < 0 || position >= itemCount) { |
michael@0 | 3481 | return INVALID_POSITION; |
michael@0 | 3482 | } |
michael@0 | 3483 | |
michael@0 | 3484 | return position; |
michael@0 | 3485 | } else { |
michael@0 | 3486 | if (position < 0 || position >= itemCount) { |
michael@0 | 3487 | return INVALID_POSITION; |
michael@0 | 3488 | } |
michael@0 | 3489 | |
michael@0 | 3490 | return position; |
michael@0 | 3491 | } |
michael@0 | 3492 | } |
michael@0 | 3493 | |
michael@0 | 3494 | /** |
michael@0 | 3495 | * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or |
michael@0 | 3496 | * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the |
michael@0 | 3497 | * current view orientation. |
michael@0 | 3498 | * |
michael@0 | 3499 | * @return The position of the next selectable position of the views that |
michael@0 | 3500 | * are currently visible, taking into account the fact that there might |
michael@0 | 3501 | * be no selection. Returns {@link #INVALID_POSITION} if there is no |
michael@0 | 3502 | * selectable view on screen in the given direction. |
michael@0 | 3503 | */ |
michael@0 | 3504 | private int lookForSelectablePositionOnScreen(int direction) { |
michael@0 | 3505 | forceValidFocusDirection(direction); |
michael@0 | 3506 | |
michael@0 | 3507 | final int firstPosition = mFirstPosition; |
michael@0 | 3508 | final ListAdapter adapter = getAdapter(); |
michael@0 | 3509 | |
michael@0 | 3510 | if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { |
michael@0 | 3511 | int startPos = (mSelectedPosition != INVALID_POSITION ? |
michael@0 | 3512 | mSelectedPosition + 1 : firstPosition); |
michael@0 | 3513 | |
michael@0 | 3514 | if (startPos >= adapter.getCount()) { |
michael@0 | 3515 | return INVALID_POSITION; |
michael@0 | 3516 | } |
michael@0 | 3517 | |
michael@0 | 3518 | if (startPos < firstPosition) { |
michael@0 | 3519 | startPos = firstPosition; |
michael@0 | 3520 | } |
michael@0 | 3521 | |
michael@0 | 3522 | final int lastVisiblePos = getLastVisiblePosition(); |
michael@0 | 3523 | |
michael@0 | 3524 | for (int pos = startPos; pos <= lastVisiblePos; pos++) { |
michael@0 | 3525 | if (adapter.isEnabled(pos) |
michael@0 | 3526 | && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) { |
michael@0 | 3527 | return pos; |
michael@0 | 3528 | } |
michael@0 | 3529 | } |
michael@0 | 3530 | } else { |
michael@0 | 3531 | final int last = firstPosition + getChildCount() - 1; |
michael@0 | 3532 | |
michael@0 | 3533 | int startPos = (mSelectedPosition != INVALID_POSITION) ? |
michael@0 | 3534 | mSelectedPosition - 1 : firstPosition + getChildCount() - 1; |
michael@0 | 3535 | |
michael@0 | 3536 | if (startPos < 0 || startPos >= adapter.getCount()) { |
michael@0 | 3537 | return INVALID_POSITION; |
michael@0 | 3538 | } |
michael@0 | 3539 | |
michael@0 | 3540 | if (startPos > last) { |
michael@0 | 3541 | startPos = last; |
michael@0 | 3542 | } |
michael@0 | 3543 | |
michael@0 | 3544 | for (int pos = startPos; pos >= firstPosition; pos--) { |
michael@0 | 3545 | if (adapter.isEnabled(pos) |
michael@0 | 3546 | && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) { |
michael@0 | 3547 | return pos; |
michael@0 | 3548 | } |
michael@0 | 3549 | } |
michael@0 | 3550 | } |
michael@0 | 3551 | |
michael@0 | 3552 | return INVALID_POSITION; |
michael@0 | 3553 | } |
michael@0 | 3554 | |
michael@0 | 3555 | @Override |
michael@0 | 3556 | protected void drawableStateChanged() { |
michael@0 | 3557 | super.drawableStateChanged(); |
michael@0 | 3558 | updateSelectorState(); |
michael@0 | 3559 | } |
michael@0 | 3560 | |
michael@0 | 3561 | @Override |
michael@0 | 3562 | protected int[] onCreateDrawableState(int extraSpace) { |
michael@0 | 3563 | // If the child view is enabled then do the default behavior. |
michael@0 | 3564 | if (mIsChildViewEnabled) { |
michael@0 | 3565 | // Common case |
michael@0 | 3566 | return super.onCreateDrawableState(extraSpace); |
michael@0 | 3567 | } |
michael@0 | 3568 | |
michael@0 | 3569 | // The selector uses this View's drawable state. The selected child view |
michael@0 | 3570 | // is disabled, so we need to remove the enabled state from the drawable |
michael@0 | 3571 | // states. |
michael@0 | 3572 | final int enabledState = ENABLED_STATE_SET[0]; |
michael@0 | 3573 | |
michael@0 | 3574 | // If we don't have any extra space, it will return one of the static state arrays, |
michael@0 | 3575 | // and clearing the enabled state on those arrays is a bad thing! If we specify |
michael@0 | 3576 | // we need extra space, it will create+copy into a new array that safely mutable. |
michael@0 | 3577 | int[] state = super.onCreateDrawableState(extraSpace + 1); |
michael@0 | 3578 | int enabledPos = -1; |
michael@0 | 3579 | for (int i = state.length - 1; i >= 0; i--) { |
michael@0 | 3580 | if (state[i] == enabledState) { |
michael@0 | 3581 | enabledPos = i; |
michael@0 | 3582 | break; |
michael@0 | 3583 | } |
michael@0 | 3584 | } |
michael@0 | 3585 | |
michael@0 | 3586 | // Remove the enabled state |
michael@0 | 3587 | if (enabledPos >= 0) { |
michael@0 | 3588 | System.arraycopy(state, enabledPos + 1, state, enabledPos, |
michael@0 | 3589 | state.length - enabledPos - 1); |
michael@0 | 3590 | } |
michael@0 | 3591 | |
michael@0 | 3592 | return state; |
michael@0 | 3593 | } |
michael@0 | 3594 | |
michael@0 | 3595 | @Override |
michael@0 | 3596 | protected boolean canAnimate() { |
michael@0 | 3597 | return (super.canAnimate() && mItemCount > 0); |
michael@0 | 3598 | } |
michael@0 | 3599 | |
michael@0 | 3600 | @Override |
michael@0 | 3601 | protected void dispatchDraw(Canvas canvas) { |
michael@0 | 3602 | final boolean drawSelectorOnTop = mDrawSelectorOnTop; |
michael@0 | 3603 | if (!drawSelectorOnTop) { |
michael@0 | 3604 | drawSelector(canvas); |
michael@0 | 3605 | } |
michael@0 | 3606 | |
michael@0 | 3607 | super.dispatchDraw(canvas); |
michael@0 | 3608 | |
michael@0 | 3609 | if (drawSelectorOnTop) { |
michael@0 | 3610 | drawSelector(canvas); |
michael@0 | 3611 | } |
michael@0 | 3612 | } |
michael@0 | 3613 | |
michael@0 | 3614 | @Override |
michael@0 | 3615 | public void draw(Canvas canvas) { |
michael@0 | 3616 | super.draw(canvas); |
michael@0 | 3617 | |
michael@0 | 3618 | boolean needsInvalidate = false; |
michael@0 | 3619 | |
michael@0 | 3620 | if (mStartEdge != null) { |
michael@0 | 3621 | needsInvalidate |= drawStartEdge(canvas); |
michael@0 | 3622 | } |
michael@0 | 3623 | |
michael@0 | 3624 | if (mEndEdge != null) { |
michael@0 | 3625 | needsInvalidate |= drawEndEdge(canvas); |
michael@0 | 3626 | } |
michael@0 | 3627 | |
michael@0 | 3628 | if (needsInvalidate) { |
michael@0 | 3629 | ViewCompat.postInvalidateOnAnimation(this); |
michael@0 | 3630 | } |
michael@0 | 3631 | } |
michael@0 | 3632 | |
michael@0 | 3633 | @Override |
michael@0 | 3634 | public void requestLayout() { |
michael@0 | 3635 | if (!mInLayout && !mBlockLayoutRequests) { |
michael@0 | 3636 | super.requestLayout(); |
michael@0 | 3637 | } |
michael@0 | 3638 | } |
michael@0 | 3639 | |
michael@0 | 3640 | @Override |
michael@0 | 3641 | public View getSelectedView() { |
michael@0 | 3642 | if (mItemCount > 0 && mSelectedPosition >= 0) { |
michael@0 | 3643 | return getChildAt(mSelectedPosition - mFirstPosition); |
michael@0 | 3644 | } else { |
michael@0 | 3645 | return null; |
michael@0 | 3646 | } |
michael@0 | 3647 | } |
michael@0 | 3648 | |
michael@0 | 3649 | @Override |
michael@0 | 3650 | public void setSelection(int position) { |
michael@0 | 3651 | setSelectionFromOffset(position, 0); |
michael@0 | 3652 | } |
michael@0 | 3653 | |
michael@0 | 3654 | public void setSelectionFromOffset(int position, int offset) { |
michael@0 | 3655 | if (mAdapter == null) { |
michael@0 | 3656 | return; |
michael@0 | 3657 | } |
michael@0 | 3658 | |
michael@0 | 3659 | if (!isInTouchMode()) { |
michael@0 | 3660 | position = lookForSelectablePosition(position); |
michael@0 | 3661 | if (position >= 0) { |
michael@0 | 3662 | setNextSelectedPositionInt(position); |
michael@0 | 3663 | } |
michael@0 | 3664 | } else { |
michael@0 | 3665 | mResurrectToPosition = position; |
michael@0 | 3666 | } |
michael@0 | 3667 | |
michael@0 | 3668 | if (position >= 0) { |
michael@0 | 3669 | mLayoutMode = LAYOUT_SPECIFIC; |
michael@0 | 3670 | |
michael@0 | 3671 | if (mIsVertical) { |
michael@0 | 3672 | mSpecificStart = getPaddingTop() + offset; |
michael@0 | 3673 | } else { |
michael@0 | 3674 | mSpecificStart = getPaddingLeft() + offset; |
michael@0 | 3675 | } |
michael@0 | 3676 | |
michael@0 | 3677 | if (mNeedSync) { |
michael@0 | 3678 | mSyncPosition = position; |
michael@0 | 3679 | mSyncRowId = mAdapter.getItemId(position); |
michael@0 | 3680 | } |
michael@0 | 3681 | |
michael@0 | 3682 | requestLayout(); |
michael@0 | 3683 | } |
michael@0 | 3684 | } |
michael@0 | 3685 | |
michael@0 | 3686 | @Override |
michael@0 | 3687 | public boolean dispatchKeyEvent(KeyEvent event) { |
michael@0 | 3688 | // Dispatch in the normal way |
michael@0 | 3689 | boolean handled = super.dispatchKeyEvent(event); |
michael@0 | 3690 | if (!handled) { |
michael@0 | 3691 | // If we didn't handle it... |
michael@0 | 3692 | final View focused = getFocusedChild(); |
michael@0 | 3693 | if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) { |
michael@0 | 3694 | // ... and our focused child didn't handle it |
michael@0 | 3695 | // ... give it to ourselves so we can scroll if necessary |
michael@0 | 3696 | handled = onKeyDown(event.getKeyCode(), event); |
michael@0 | 3697 | } |
michael@0 | 3698 | } |
michael@0 | 3699 | |
michael@0 | 3700 | return handled; |
michael@0 | 3701 | } |
michael@0 | 3702 | |
michael@0 | 3703 | @Override |
michael@0 | 3704 | protected void dispatchSetPressed(boolean pressed) { |
michael@0 | 3705 | // Don't dispatch setPressed to our children. We call setPressed on ourselves to |
michael@0 | 3706 | // get the selector in the right state, but we don't want to press each child. |
michael@0 | 3707 | } |
michael@0 | 3708 | |
michael@0 | 3709 | @Override |
michael@0 | 3710 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
michael@0 | 3711 | if (mSelector == null) { |
michael@0 | 3712 | useDefaultSelector(); |
michael@0 | 3713 | } |
michael@0 | 3714 | |
michael@0 | 3715 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); |
michael@0 | 3716 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); |
michael@0 | 3717 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); |
michael@0 | 3718 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); |
michael@0 | 3719 | |
michael@0 | 3720 | int childWidth = 0; |
michael@0 | 3721 | int childHeight = 0; |
michael@0 | 3722 | |
michael@0 | 3723 | mItemCount = (mAdapter == null ? 0 : mAdapter.getCount()); |
michael@0 | 3724 | if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || |
michael@0 | 3725 | heightMode == MeasureSpec.UNSPECIFIED)) { |
michael@0 | 3726 | final View child = obtainView(0, mIsScrap); |
michael@0 | 3727 | |
michael@0 | 3728 | final int secondaryMeasureSpec = |
michael@0 | 3729 | (mIsVertical ? widthMeasureSpec : heightMeasureSpec); |
michael@0 | 3730 | |
michael@0 | 3731 | measureScrapChild(child, 0, secondaryMeasureSpec); |
michael@0 | 3732 | |
michael@0 | 3733 | childWidth = child.getMeasuredWidth(); |
michael@0 | 3734 | childHeight = child.getMeasuredHeight(); |
michael@0 | 3735 | |
michael@0 | 3736 | if (recycleOnMeasure()) { |
michael@0 | 3737 | mRecycler.addScrapView(child, -1); |
michael@0 | 3738 | } |
michael@0 | 3739 | } |
michael@0 | 3740 | |
michael@0 | 3741 | if (widthMode == MeasureSpec.UNSPECIFIED) { |
michael@0 | 3742 | widthSize = getPaddingLeft() + getPaddingRight() + childWidth; |
michael@0 | 3743 | if (mIsVertical) { |
michael@0 | 3744 | widthSize += getVerticalScrollbarWidth(); |
michael@0 | 3745 | } |
michael@0 | 3746 | } |
michael@0 | 3747 | |
michael@0 | 3748 | if (heightMode == MeasureSpec.UNSPECIFIED) { |
michael@0 | 3749 | heightSize = getPaddingTop() + getPaddingBottom() + childHeight; |
michael@0 | 3750 | if (!mIsVertical) { |
michael@0 | 3751 | heightSize += getHorizontalScrollbarHeight(); |
michael@0 | 3752 | } |
michael@0 | 3753 | } |
michael@0 | 3754 | |
michael@0 | 3755 | if (mIsVertical && heightMode == MeasureSpec.AT_MOST) { |
michael@0 | 3756 | heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); |
michael@0 | 3757 | } |
michael@0 | 3758 | |
michael@0 | 3759 | if (!mIsVertical && widthMode == MeasureSpec.AT_MOST) { |
michael@0 | 3760 | widthSize = measureWidthOfChildren(heightMeasureSpec, 0, NO_POSITION, widthSize, -1); |
michael@0 | 3761 | } |
michael@0 | 3762 | |
michael@0 | 3763 | setMeasuredDimension(widthSize, heightSize); |
michael@0 | 3764 | } |
michael@0 | 3765 | |
michael@0 | 3766 | @Override |
michael@0 | 3767 | protected void onLayout(boolean changed, int l, int t, int r, int b) { |
michael@0 | 3768 | mInLayout = true; |
michael@0 | 3769 | |
michael@0 | 3770 | if (changed) { |
michael@0 | 3771 | final int childCount = getChildCount(); |
michael@0 | 3772 | for (int i = 0; i < childCount; i++) { |
michael@0 | 3773 | getChildAt(i).forceLayout(); |
michael@0 | 3774 | } |
michael@0 | 3775 | |
michael@0 | 3776 | mRecycler.markChildrenDirty(); |
michael@0 | 3777 | } |
michael@0 | 3778 | |
michael@0 | 3779 | layoutChildren(); |
michael@0 | 3780 | |
michael@0 | 3781 | mInLayout = false; |
michael@0 | 3782 | |
michael@0 | 3783 | final int width = r - l - getPaddingLeft() - getPaddingRight(); |
michael@0 | 3784 | final int height = b - t - getPaddingTop() - getPaddingBottom(); |
michael@0 | 3785 | |
michael@0 | 3786 | if (mStartEdge != null && mEndEdge != null) { |
michael@0 | 3787 | if (mIsVertical) { |
michael@0 | 3788 | mStartEdge.setSize(width, height); |
michael@0 | 3789 | mEndEdge.setSize(width, height); |
michael@0 | 3790 | } else { |
michael@0 | 3791 | mStartEdge.setSize(height, width); |
michael@0 | 3792 | mEndEdge.setSize(height, width); |
michael@0 | 3793 | } |
michael@0 | 3794 | } |
michael@0 | 3795 | } |
michael@0 | 3796 | |
michael@0 | 3797 | private void layoutChildren() { |
michael@0 | 3798 | if (getWidth() == 0 || getHeight() == 0) { |
michael@0 | 3799 | return; |
michael@0 | 3800 | } |
michael@0 | 3801 | |
michael@0 | 3802 | final boolean blockLayoutRequests = mBlockLayoutRequests; |
michael@0 | 3803 | if (!blockLayoutRequests) { |
michael@0 | 3804 | mBlockLayoutRequests = true; |
michael@0 | 3805 | } else { |
michael@0 | 3806 | return; |
michael@0 | 3807 | } |
michael@0 | 3808 | |
michael@0 | 3809 | try { |
michael@0 | 3810 | invalidate(); |
michael@0 | 3811 | |
michael@0 | 3812 | if (mAdapter == null) { |
michael@0 | 3813 | resetState(); |
michael@0 | 3814 | return; |
michael@0 | 3815 | } |
michael@0 | 3816 | |
michael@0 | 3817 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 3818 | final int end = |
michael@0 | 3819 | (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); |
michael@0 | 3820 | |
michael@0 | 3821 | int childCount = getChildCount(); |
michael@0 | 3822 | int index = 0; |
michael@0 | 3823 | int delta = 0; |
michael@0 | 3824 | |
michael@0 | 3825 | View focusLayoutRestoreView = null; |
michael@0 | 3826 | |
michael@0 | 3827 | View selected = null; |
michael@0 | 3828 | View oldSelected = null; |
michael@0 | 3829 | View newSelected = null; |
michael@0 | 3830 | View oldFirstChild = null; |
michael@0 | 3831 | |
michael@0 | 3832 | switch (mLayoutMode) { |
michael@0 | 3833 | case LAYOUT_SET_SELECTION: |
michael@0 | 3834 | index = mNextSelectedPosition - mFirstPosition; |
michael@0 | 3835 | if (index >= 0 && index < childCount) { |
michael@0 | 3836 | newSelected = getChildAt(index); |
michael@0 | 3837 | } |
michael@0 | 3838 | |
michael@0 | 3839 | break; |
michael@0 | 3840 | |
michael@0 | 3841 | case LAYOUT_FORCE_TOP: |
michael@0 | 3842 | case LAYOUT_FORCE_BOTTOM: |
michael@0 | 3843 | case LAYOUT_SPECIFIC: |
michael@0 | 3844 | case LAYOUT_SYNC: |
michael@0 | 3845 | break; |
michael@0 | 3846 | |
michael@0 | 3847 | case LAYOUT_MOVE_SELECTION: |
michael@0 | 3848 | default: |
michael@0 | 3849 | // Remember the previously selected view |
michael@0 | 3850 | index = mSelectedPosition - mFirstPosition; |
michael@0 | 3851 | if (index >= 0 && index < childCount) { |
michael@0 | 3852 | oldSelected = getChildAt(index); |
michael@0 | 3853 | } |
michael@0 | 3854 | |
michael@0 | 3855 | // Remember the previous first child |
michael@0 | 3856 | oldFirstChild = getChildAt(0); |
michael@0 | 3857 | |
michael@0 | 3858 | if (mNextSelectedPosition >= 0) { |
michael@0 | 3859 | delta = mNextSelectedPosition - mSelectedPosition; |
michael@0 | 3860 | } |
michael@0 | 3861 | |
michael@0 | 3862 | // Caution: newSelected might be null |
michael@0 | 3863 | newSelected = getChildAt(index + delta); |
michael@0 | 3864 | } |
michael@0 | 3865 | |
michael@0 | 3866 | final boolean dataChanged = mDataChanged; |
michael@0 | 3867 | if (dataChanged) { |
michael@0 | 3868 | handleDataChanged(); |
michael@0 | 3869 | } |
michael@0 | 3870 | |
michael@0 | 3871 | // Handle the empty set by removing all views that are visible |
michael@0 | 3872 | // and calling it a day |
michael@0 | 3873 | if (mItemCount == 0) { |
michael@0 | 3874 | resetState(); |
michael@0 | 3875 | return; |
michael@0 | 3876 | } else if (mItemCount != mAdapter.getCount()) { |
michael@0 | 3877 | throw new IllegalStateException("The content of the adapter has changed but " |
michael@0 | 3878 | + "TwoWayView did not receive a notification. Make sure the content of " |
michael@0 | 3879 | + "your adapter is not modified from a background thread, but only " |
michael@0 | 3880 | + "from the UI thread. [in TwoWayView(" + getId() + ", " + getClass() |
michael@0 | 3881 | + ") with Adapter(" + mAdapter.getClass() + ")]"); |
michael@0 | 3882 | } |
michael@0 | 3883 | |
michael@0 | 3884 | setSelectedPositionInt(mNextSelectedPosition); |
michael@0 | 3885 | |
michael@0 | 3886 | // Reset the focus restoration |
michael@0 | 3887 | View focusLayoutRestoreDirectChild = null; |
michael@0 | 3888 | |
michael@0 | 3889 | // Pull all children into the RecycleBin. |
michael@0 | 3890 | // These views will be reused if possible |
michael@0 | 3891 | final int firstPosition = mFirstPosition; |
michael@0 | 3892 | final RecycleBin recycleBin = mRecycler; |
michael@0 | 3893 | |
michael@0 | 3894 | if (dataChanged) { |
michael@0 | 3895 | for (int i = 0; i < childCount; i++) { |
michael@0 | 3896 | recycleBin.addScrapView(getChildAt(i), firstPosition + i); |
michael@0 | 3897 | } |
michael@0 | 3898 | } else { |
michael@0 | 3899 | recycleBin.fillActiveViews(childCount, firstPosition); |
michael@0 | 3900 | } |
michael@0 | 3901 | |
michael@0 | 3902 | // Take focus back to us temporarily to avoid the eventual |
michael@0 | 3903 | // call to clear focus when removing the focused child below |
michael@0 | 3904 | // from messing things up when ViewAncestor assigns focus back |
michael@0 | 3905 | // to someone else. |
michael@0 | 3906 | final View focusedChild = getFocusedChild(); |
michael@0 | 3907 | if (focusedChild != null) { |
michael@0 | 3908 | // We can remember the focused view to restore after relayout if the |
michael@0 | 3909 | // data hasn't changed, or if the focused position is a header or footer. |
michael@0 | 3910 | if (!dataChanged) { |
michael@0 | 3911 | focusLayoutRestoreDirectChild = focusedChild; |
michael@0 | 3912 | |
michael@0 | 3913 | // Remember the specific view that had focus |
michael@0 | 3914 | focusLayoutRestoreView = findFocus(); |
michael@0 | 3915 | if (focusLayoutRestoreView != null) { |
michael@0 | 3916 | // Tell it we are going to mess with it |
michael@0 | 3917 | focusLayoutRestoreView.onStartTemporaryDetach(); |
michael@0 | 3918 | } |
michael@0 | 3919 | } |
michael@0 | 3920 | |
michael@0 | 3921 | requestFocus(); |
michael@0 | 3922 | } |
michael@0 | 3923 | |
michael@0 | 3924 | // FIXME: We need a way to save current accessibility focus here |
michael@0 | 3925 | // so that it can be restored after we re-attach the children on each |
michael@0 | 3926 | // layout round. |
michael@0 | 3927 | |
michael@0 | 3928 | detachAllViewsFromParent(); |
michael@0 | 3929 | |
michael@0 | 3930 | switch (mLayoutMode) { |
michael@0 | 3931 | case LAYOUT_SET_SELECTION: |
michael@0 | 3932 | if (newSelected != null) { |
michael@0 | 3933 | final int newSelectedStart = |
michael@0 | 3934 | (mIsVertical ? newSelected.getTop() : newSelected.getLeft()); |
michael@0 | 3935 | |
michael@0 | 3936 | selected = fillFromSelection(newSelectedStart, start, end); |
michael@0 | 3937 | } else { |
michael@0 | 3938 | selected = fillFromMiddle(start, end); |
michael@0 | 3939 | } |
michael@0 | 3940 | |
michael@0 | 3941 | break; |
michael@0 | 3942 | |
michael@0 | 3943 | case LAYOUT_SYNC: |
michael@0 | 3944 | selected = fillSpecific(mSyncPosition, mSpecificStart); |
michael@0 | 3945 | break; |
michael@0 | 3946 | |
michael@0 | 3947 | case LAYOUT_FORCE_BOTTOM: |
michael@0 | 3948 | selected = fillBefore(mItemCount - 1, end); |
michael@0 | 3949 | adjustViewsStartOrEnd(); |
michael@0 | 3950 | break; |
michael@0 | 3951 | |
michael@0 | 3952 | case LAYOUT_FORCE_TOP: |
michael@0 | 3953 | mFirstPosition = 0; |
michael@0 | 3954 | selected = fillFromOffset(start); |
michael@0 | 3955 | adjustViewsStartOrEnd(); |
michael@0 | 3956 | break; |
michael@0 | 3957 | |
michael@0 | 3958 | case LAYOUT_SPECIFIC: |
michael@0 | 3959 | selected = fillSpecific(reconcileSelectedPosition(), mSpecificStart); |
michael@0 | 3960 | break; |
michael@0 | 3961 | |
michael@0 | 3962 | case LAYOUT_MOVE_SELECTION: |
michael@0 | 3963 | selected = moveSelection(oldSelected, newSelected, delta, start, end); |
michael@0 | 3964 | break; |
michael@0 | 3965 | |
michael@0 | 3966 | default: |
michael@0 | 3967 | if (childCount == 0) { |
michael@0 | 3968 | final int position = lookForSelectablePosition(0); |
michael@0 | 3969 | setSelectedPositionInt(position); |
michael@0 | 3970 | selected = fillFromOffset(start); |
michael@0 | 3971 | } else { |
michael@0 | 3972 | if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { |
michael@0 | 3973 | int offset = start; |
michael@0 | 3974 | if (oldSelected != null) { |
michael@0 | 3975 | offset = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft()); |
michael@0 | 3976 | } |
michael@0 | 3977 | selected = fillSpecific(mSelectedPosition, offset); |
michael@0 | 3978 | } else if (mFirstPosition < mItemCount) { |
michael@0 | 3979 | int offset = start; |
michael@0 | 3980 | if (oldFirstChild != null) { |
michael@0 | 3981 | offset = (mIsVertical ? oldFirstChild.getTop() : oldFirstChild.getLeft()); |
michael@0 | 3982 | } |
michael@0 | 3983 | |
michael@0 | 3984 | selected = fillSpecific(mFirstPosition, offset); |
michael@0 | 3985 | } else { |
michael@0 | 3986 | selected = fillSpecific(0, start); |
michael@0 | 3987 | } |
michael@0 | 3988 | } |
michael@0 | 3989 | |
michael@0 | 3990 | break; |
michael@0 | 3991 | |
michael@0 | 3992 | } |
michael@0 | 3993 | |
michael@0 | 3994 | recycleBin.scrapActiveViews(); |
michael@0 | 3995 | |
michael@0 | 3996 | if (selected != null) { |
michael@0 | 3997 | if (mItemsCanFocus && hasFocus() && !selected.hasFocus()) { |
michael@0 | 3998 | final boolean focusWasTaken = (selected == focusLayoutRestoreDirectChild && |
michael@0 | 3999 | focusLayoutRestoreView != null && |
michael@0 | 4000 | focusLayoutRestoreView.requestFocus()) || selected.requestFocus(); |
michael@0 | 4001 | |
michael@0 | 4002 | if (!focusWasTaken) { |
michael@0 | 4003 | // Selected item didn't take focus, fine, but still want |
michael@0 | 4004 | // to make sure something else outside of the selected view |
michael@0 | 4005 | // has focus |
michael@0 | 4006 | final View focused = getFocusedChild(); |
michael@0 | 4007 | if (focused != null) { |
michael@0 | 4008 | focused.clearFocus(); |
michael@0 | 4009 | } |
michael@0 | 4010 | |
michael@0 | 4011 | positionSelector(INVALID_POSITION, selected); |
michael@0 | 4012 | } else { |
michael@0 | 4013 | selected.setSelected(false); |
michael@0 | 4014 | mSelectorRect.setEmpty(); |
michael@0 | 4015 | } |
michael@0 | 4016 | } else { |
michael@0 | 4017 | positionSelector(INVALID_POSITION, selected); |
michael@0 | 4018 | } |
michael@0 | 4019 | |
michael@0 | 4020 | mSelectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 4021 | } else { |
michael@0 | 4022 | if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_DRAGGING) { |
michael@0 | 4023 | View child = getChildAt(mMotionPosition - mFirstPosition); |
michael@0 | 4024 | |
michael@0 | 4025 | if (child != null) { |
michael@0 | 4026 | positionSelector(mMotionPosition, child); |
michael@0 | 4027 | } |
michael@0 | 4028 | } else { |
michael@0 | 4029 | mSelectedStart = 0; |
michael@0 | 4030 | mSelectorRect.setEmpty(); |
michael@0 | 4031 | } |
michael@0 | 4032 | |
michael@0 | 4033 | // Even if there is not selected position, we may need to restore |
michael@0 | 4034 | // focus (i.e. something focusable in touch mode) |
michael@0 | 4035 | if (hasFocus() && focusLayoutRestoreView != null) { |
michael@0 | 4036 | focusLayoutRestoreView.requestFocus(); |
michael@0 | 4037 | } |
michael@0 | 4038 | } |
michael@0 | 4039 | |
michael@0 | 4040 | // Tell focus view we are done mucking with it, if it is still in |
michael@0 | 4041 | // our view hierarchy. |
michael@0 | 4042 | if (focusLayoutRestoreView != null |
michael@0 | 4043 | && focusLayoutRestoreView.getWindowToken() != null) { |
michael@0 | 4044 | focusLayoutRestoreView.onFinishTemporaryDetach(); |
michael@0 | 4045 | } |
michael@0 | 4046 | |
michael@0 | 4047 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 4048 | mDataChanged = false; |
michael@0 | 4049 | mNeedSync = false; |
michael@0 | 4050 | |
michael@0 | 4051 | setNextSelectedPositionInt(mSelectedPosition); |
michael@0 | 4052 | if (mItemCount > 0) { |
michael@0 | 4053 | checkSelectionChanged(); |
michael@0 | 4054 | } |
michael@0 | 4055 | |
michael@0 | 4056 | invokeOnItemScrollListener(); |
michael@0 | 4057 | } finally { |
michael@0 | 4058 | if (!blockLayoutRequests) { |
michael@0 | 4059 | mBlockLayoutRequests = false; |
michael@0 | 4060 | mDataChanged = false; |
michael@0 | 4061 | } |
michael@0 | 4062 | } |
michael@0 | 4063 | } |
michael@0 | 4064 | |
michael@0 | 4065 | protected boolean recycleOnMeasure() { |
michael@0 | 4066 | return true; |
michael@0 | 4067 | } |
michael@0 | 4068 | |
michael@0 | 4069 | private void offsetChildren(int offset) { |
michael@0 | 4070 | final int childCount = getChildCount(); |
michael@0 | 4071 | |
michael@0 | 4072 | for (int i = 0; i < childCount; i++) { |
michael@0 | 4073 | final View child = getChildAt(i); |
michael@0 | 4074 | |
michael@0 | 4075 | if (mIsVertical) { |
michael@0 | 4076 | child.offsetTopAndBottom(offset); |
michael@0 | 4077 | } else { |
michael@0 | 4078 | child.offsetLeftAndRight(offset); |
michael@0 | 4079 | } |
michael@0 | 4080 | } |
michael@0 | 4081 | } |
michael@0 | 4082 | |
michael@0 | 4083 | private View moveSelection(View oldSelected, View newSelected, int delta, int start, |
michael@0 | 4084 | int end) { |
michael@0 | 4085 | final int selectedPosition = mSelectedPosition; |
michael@0 | 4086 | |
michael@0 | 4087 | final int oldSelectedStart = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft()); |
michael@0 | 4088 | final int oldSelectedEnd = (mIsVertical ? oldSelected.getBottom() : oldSelected.getRight()); |
michael@0 | 4089 | |
michael@0 | 4090 | View selected = null; |
michael@0 | 4091 | |
michael@0 | 4092 | if (delta > 0) { |
michael@0 | 4093 | /* |
michael@0 | 4094 | * Case 1: Scrolling down. |
michael@0 | 4095 | */ |
michael@0 | 4096 | |
michael@0 | 4097 | /* |
michael@0 | 4098 | * Before After |
michael@0 | 4099 | * | | | | |
michael@0 | 4100 | * +-------+ +-------+ |
michael@0 | 4101 | * | A | | A | |
michael@0 | 4102 | * | 1 | => +-------+ |
michael@0 | 4103 | * +-------+ | B | |
michael@0 | 4104 | * | B | | 2 | |
michael@0 | 4105 | * +-------+ +-------+ |
michael@0 | 4106 | * | | | | |
michael@0 | 4107 | * |
michael@0 | 4108 | * Try to keep the top of the previously selected item where it was. |
michael@0 | 4109 | * oldSelected = A |
michael@0 | 4110 | * selected = B |
michael@0 | 4111 | */ |
michael@0 | 4112 | |
michael@0 | 4113 | // Put oldSelected (A) where it belongs |
michael@0 | 4114 | oldSelected = makeAndAddView(selectedPosition - 1, oldSelectedStart, true, false); |
michael@0 | 4115 | |
michael@0 | 4116 | final int itemMargin = mItemMargin; |
michael@0 | 4117 | |
michael@0 | 4118 | // Now put the new selection (B) below that |
michael@0 | 4119 | selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true); |
michael@0 | 4120 | |
michael@0 | 4121 | final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 4122 | final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); |
michael@0 | 4123 | |
michael@0 | 4124 | // Some of the newly selected item extends below the bottom of the list |
michael@0 | 4125 | if (selectedEnd > end) { |
michael@0 | 4126 | // Find space available above the selection into which we can scroll upwards |
michael@0 | 4127 | final int spaceBefore = selectedStart - start; |
michael@0 | 4128 | |
michael@0 | 4129 | // Find space required to bring the bottom of the selected item fully into view |
michael@0 | 4130 | final int spaceAfter = selectedEnd - end; |
michael@0 | 4131 | |
michael@0 | 4132 | // Don't scroll more than half the size of the list |
michael@0 | 4133 | final int halfSpace = (end - start) / 2; |
michael@0 | 4134 | int offset = Math.min(spaceBefore, spaceAfter); |
michael@0 | 4135 | offset = Math.min(offset, halfSpace); |
michael@0 | 4136 | |
michael@0 | 4137 | if (mIsVertical) { |
michael@0 | 4138 | oldSelected.offsetTopAndBottom(-offset); |
michael@0 | 4139 | selected.offsetTopAndBottom(-offset); |
michael@0 | 4140 | } else { |
michael@0 | 4141 | oldSelected.offsetLeftAndRight(-offset); |
michael@0 | 4142 | selected.offsetLeftAndRight(-offset); |
michael@0 | 4143 | } |
michael@0 | 4144 | } |
michael@0 | 4145 | |
michael@0 | 4146 | // Fill in views before and after |
michael@0 | 4147 | fillBefore(mSelectedPosition - 2, selectedStart - itemMargin); |
michael@0 | 4148 | adjustViewsStartOrEnd(); |
michael@0 | 4149 | fillAfter(mSelectedPosition + 1, selectedEnd + itemMargin); |
michael@0 | 4150 | } else if (delta < 0) { |
michael@0 | 4151 | /* |
michael@0 | 4152 | * Case 2: Scrolling up. |
michael@0 | 4153 | */ |
michael@0 | 4154 | |
michael@0 | 4155 | /* |
michael@0 | 4156 | * Before After |
michael@0 | 4157 | * | | | | |
michael@0 | 4158 | * +-------+ +-------+ |
michael@0 | 4159 | * | A | | A | |
michael@0 | 4160 | * +-------+ => | 1 | |
michael@0 | 4161 | * | B | +-------+ |
michael@0 | 4162 | * | 2 | | B | |
michael@0 | 4163 | * +-------+ +-------+ |
michael@0 | 4164 | * | | | | |
michael@0 | 4165 | * |
michael@0 | 4166 | * Try to keep the top of the item about to become selected where it was. |
michael@0 | 4167 | * newSelected = A |
michael@0 | 4168 | * olSelected = B |
michael@0 | 4169 | */ |
michael@0 | 4170 | |
michael@0 | 4171 | if (newSelected != null) { |
michael@0 | 4172 | // Try to position the top of newSel (A) where it was before it was selected |
michael@0 | 4173 | final int newSelectedStart = (mIsVertical ? newSelected.getTop() : newSelected.getLeft()); |
michael@0 | 4174 | selected = makeAndAddView(selectedPosition, newSelectedStart, true, true); |
michael@0 | 4175 | } else { |
michael@0 | 4176 | // If (A) was not on screen and so did not have a view, position |
michael@0 | 4177 | // it above the oldSelected (B) |
michael@0 | 4178 | selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true); |
michael@0 | 4179 | } |
michael@0 | 4180 | |
michael@0 | 4181 | final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 4182 | final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); |
michael@0 | 4183 | |
michael@0 | 4184 | // Some of the newly selected item extends above the top of the list |
michael@0 | 4185 | if (selectedStart < start) { |
michael@0 | 4186 | // Find space required to bring the top of the selected item fully into view |
michael@0 | 4187 | final int spaceBefore = start - selectedStart; |
michael@0 | 4188 | |
michael@0 | 4189 | // Find space available below the selection into which we can scroll downwards |
michael@0 | 4190 | final int spaceAfter = end - selectedEnd; |
michael@0 | 4191 | |
michael@0 | 4192 | // Don't scroll more than half the height of the list |
michael@0 | 4193 | final int halfSpace = (end - start) / 2; |
michael@0 | 4194 | int offset = Math.min(spaceBefore, spaceAfter); |
michael@0 | 4195 | offset = Math.min(offset, halfSpace); |
michael@0 | 4196 | |
michael@0 | 4197 | if (mIsVertical) { |
michael@0 | 4198 | selected.offsetTopAndBottom(offset); |
michael@0 | 4199 | } else { |
michael@0 | 4200 | selected.offsetLeftAndRight(offset); |
michael@0 | 4201 | } |
michael@0 | 4202 | } |
michael@0 | 4203 | |
michael@0 | 4204 | // Fill in views above and below |
michael@0 | 4205 | fillBeforeAndAfter(selected, selectedPosition); |
michael@0 | 4206 | } else { |
michael@0 | 4207 | /* |
michael@0 | 4208 | * Case 3: Staying still |
michael@0 | 4209 | */ |
michael@0 | 4210 | |
michael@0 | 4211 | selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true); |
michael@0 | 4212 | |
michael@0 | 4213 | final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 4214 | final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); |
michael@0 | 4215 | |
michael@0 | 4216 | // We're staying still... |
michael@0 | 4217 | if (oldSelectedStart < start) { |
michael@0 | 4218 | // ... but the top of the old selection was off screen. |
michael@0 | 4219 | // (This can happen if the data changes size out from under us) |
michael@0 | 4220 | int newEnd = selectedEnd; |
michael@0 | 4221 | if (newEnd < start + 20) { |
michael@0 | 4222 | // Not enough visible -- bring it onscreen |
michael@0 | 4223 | if (mIsVertical) { |
michael@0 | 4224 | selected.offsetTopAndBottom(start - selectedStart); |
michael@0 | 4225 | } else { |
michael@0 | 4226 | selected.offsetLeftAndRight(start - selectedStart); |
michael@0 | 4227 | } |
michael@0 | 4228 | } |
michael@0 | 4229 | } |
michael@0 | 4230 | |
michael@0 | 4231 | // Fill in views above and below |
michael@0 | 4232 | fillBeforeAndAfter(selected, selectedPosition); |
michael@0 | 4233 | } |
michael@0 | 4234 | |
michael@0 | 4235 | return selected; |
michael@0 | 4236 | } |
michael@0 | 4237 | |
michael@0 | 4238 | void confirmCheckedPositionsById() { |
michael@0 | 4239 | // Clear out the positional check states, we'll rebuild it below from IDs. |
michael@0 | 4240 | mCheckStates.clear(); |
michael@0 | 4241 | |
michael@0 | 4242 | for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { |
michael@0 | 4243 | final long id = mCheckedIdStates.keyAt(checkedIndex); |
michael@0 | 4244 | final int lastPos = mCheckedIdStates.valueAt(checkedIndex); |
michael@0 | 4245 | |
michael@0 | 4246 | final long lastPosId = mAdapter.getItemId(lastPos); |
michael@0 | 4247 | if (id != lastPosId) { |
michael@0 | 4248 | // Look around to see if the ID is nearby. If not, uncheck it. |
michael@0 | 4249 | final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); |
michael@0 | 4250 | final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); |
michael@0 | 4251 | boolean found = false; |
michael@0 | 4252 | |
michael@0 | 4253 | for (int searchPos = start; searchPos < end; searchPos++) { |
michael@0 | 4254 | final long searchId = mAdapter.getItemId(searchPos); |
michael@0 | 4255 | if (id == searchId) { |
michael@0 | 4256 | found = true; |
michael@0 | 4257 | mCheckStates.put(searchPos, true); |
michael@0 | 4258 | mCheckedIdStates.setValueAt(checkedIndex, searchPos); |
michael@0 | 4259 | break; |
michael@0 | 4260 | } |
michael@0 | 4261 | } |
michael@0 | 4262 | |
michael@0 | 4263 | if (!found) { |
michael@0 | 4264 | mCheckedIdStates.delete(id); |
michael@0 | 4265 | checkedIndex--; |
michael@0 | 4266 | mCheckedItemCount--; |
michael@0 | 4267 | } |
michael@0 | 4268 | } else { |
michael@0 | 4269 | mCheckStates.put(lastPos, true); |
michael@0 | 4270 | } |
michael@0 | 4271 | } |
michael@0 | 4272 | } |
michael@0 | 4273 | |
michael@0 | 4274 | private void handleDataChanged() { |
michael@0 | 4275 | if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mAdapter != null && mAdapter.hasStableIds()) { |
michael@0 | 4276 | confirmCheckedPositionsById(); |
michael@0 | 4277 | } |
michael@0 | 4278 | |
michael@0 | 4279 | mRecycler.clearTransientStateViews(); |
michael@0 | 4280 | |
michael@0 | 4281 | final int itemCount = mItemCount; |
michael@0 | 4282 | if (itemCount > 0) { |
michael@0 | 4283 | int newPos; |
michael@0 | 4284 | int selectablePos; |
michael@0 | 4285 | |
michael@0 | 4286 | // Find the row we are supposed to sync to |
michael@0 | 4287 | if (mNeedSync) { |
michael@0 | 4288 | // Update this first, since setNextSelectedPositionInt inspects it |
michael@0 | 4289 | mNeedSync = false; |
michael@0 | 4290 | mPendingSync = null; |
michael@0 | 4291 | |
michael@0 | 4292 | switch (mSyncMode) { |
michael@0 | 4293 | case SYNC_SELECTED_POSITION: |
michael@0 | 4294 | if (isInTouchMode()) { |
michael@0 | 4295 | // We saved our state when not in touch mode. (We know this because |
michael@0 | 4296 | // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to |
michael@0 | 4297 | // restore in touch mode. Just leave mSyncPosition as it is (possibly |
michael@0 | 4298 | // adjusting if the available range changed) and return. |
michael@0 | 4299 | mLayoutMode = LAYOUT_SYNC; |
michael@0 | 4300 | mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1); |
michael@0 | 4301 | |
michael@0 | 4302 | return; |
michael@0 | 4303 | } else { |
michael@0 | 4304 | // See if we can find a position in the new data with the same |
michael@0 | 4305 | // id as the old selection. This will change mSyncPosition. |
michael@0 | 4306 | newPos = findSyncPosition(); |
michael@0 | 4307 | if (newPos >= 0) { |
michael@0 | 4308 | // Found it. Now verify that new selection is still selectable |
michael@0 | 4309 | selectablePos = lookForSelectablePosition(newPos, true); |
michael@0 | 4310 | if (selectablePos == newPos) { |
michael@0 | 4311 | // Same row id is selected |
michael@0 | 4312 | mSyncPosition = newPos; |
michael@0 | 4313 | |
michael@0 | 4314 | if (mSyncHeight == getHeight()) { |
michael@0 | 4315 | // If we are at the same height as when we saved state, try |
michael@0 | 4316 | // to restore the scroll position too. |
michael@0 | 4317 | mLayoutMode = LAYOUT_SYNC; |
michael@0 | 4318 | } else { |
michael@0 | 4319 | // We are not the same height as when the selection was saved, so |
michael@0 | 4320 | // don't try to restore the exact position |
michael@0 | 4321 | mLayoutMode = LAYOUT_SET_SELECTION; |
michael@0 | 4322 | } |
michael@0 | 4323 | |
michael@0 | 4324 | // Restore selection |
michael@0 | 4325 | setNextSelectedPositionInt(newPos); |
michael@0 | 4326 | return; |
michael@0 | 4327 | } |
michael@0 | 4328 | } |
michael@0 | 4329 | } |
michael@0 | 4330 | break; |
michael@0 | 4331 | |
michael@0 | 4332 | case SYNC_FIRST_POSITION: |
michael@0 | 4333 | // Leave mSyncPosition as it is -- just pin to available range |
michael@0 | 4334 | mLayoutMode = LAYOUT_SYNC; |
michael@0 | 4335 | mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1); |
michael@0 | 4336 | |
michael@0 | 4337 | return; |
michael@0 | 4338 | } |
michael@0 | 4339 | } |
michael@0 | 4340 | |
michael@0 | 4341 | if (!isInTouchMode()) { |
michael@0 | 4342 | // We couldn't find matching data -- try to use the same position |
michael@0 | 4343 | newPos = getSelectedItemPosition(); |
michael@0 | 4344 | |
michael@0 | 4345 | // Pin position to the available range |
michael@0 | 4346 | if (newPos >= itemCount) { |
michael@0 | 4347 | newPos = itemCount - 1; |
michael@0 | 4348 | } |
michael@0 | 4349 | if (newPos < 0) { |
michael@0 | 4350 | newPos = 0; |
michael@0 | 4351 | } |
michael@0 | 4352 | |
michael@0 | 4353 | // Make sure we select something selectable -- first look down |
michael@0 | 4354 | selectablePos = lookForSelectablePosition(newPos, true); |
michael@0 | 4355 | |
michael@0 | 4356 | if (selectablePos >= 0) { |
michael@0 | 4357 | setNextSelectedPositionInt(selectablePos); |
michael@0 | 4358 | return; |
michael@0 | 4359 | } else { |
michael@0 | 4360 | // Looking down didn't work -- try looking up |
michael@0 | 4361 | selectablePos = lookForSelectablePosition(newPos, false); |
michael@0 | 4362 | if (selectablePos >= 0) { |
michael@0 | 4363 | setNextSelectedPositionInt(selectablePos); |
michael@0 | 4364 | return; |
michael@0 | 4365 | } |
michael@0 | 4366 | } |
michael@0 | 4367 | } else { |
michael@0 | 4368 | // We already know where we want to resurrect the selection |
michael@0 | 4369 | if (mResurrectToPosition >= 0) { |
michael@0 | 4370 | return; |
michael@0 | 4371 | } |
michael@0 | 4372 | } |
michael@0 | 4373 | } |
michael@0 | 4374 | |
michael@0 | 4375 | // Nothing is selected. Give up and reset everything. |
michael@0 | 4376 | mLayoutMode = LAYOUT_FORCE_TOP; |
michael@0 | 4377 | mSelectedPosition = INVALID_POSITION; |
michael@0 | 4378 | mSelectedRowId = INVALID_ROW_ID; |
michael@0 | 4379 | mNextSelectedPosition = INVALID_POSITION; |
michael@0 | 4380 | mNextSelectedRowId = INVALID_ROW_ID; |
michael@0 | 4381 | mNeedSync = false; |
michael@0 | 4382 | mPendingSync = null; |
michael@0 | 4383 | mSelectorPosition = INVALID_POSITION; |
michael@0 | 4384 | |
michael@0 | 4385 | checkSelectionChanged(); |
michael@0 | 4386 | } |
michael@0 | 4387 | |
michael@0 | 4388 | private int reconcileSelectedPosition() { |
michael@0 | 4389 | int position = mSelectedPosition; |
michael@0 | 4390 | if (position < 0) { |
michael@0 | 4391 | position = mResurrectToPosition; |
michael@0 | 4392 | } |
michael@0 | 4393 | |
michael@0 | 4394 | position = Math.max(0, position); |
michael@0 | 4395 | position = Math.min(position, mItemCount - 1); |
michael@0 | 4396 | |
michael@0 | 4397 | return position; |
michael@0 | 4398 | } |
michael@0 | 4399 | |
michael@0 | 4400 | boolean resurrectSelection() { |
michael@0 | 4401 | final int childCount = getChildCount(); |
michael@0 | 4402 | if (childCount <= 0) { |
michael@0 | 4403 | return false; |
michael@0 | 4404 | } |
michael@0 | 4405 | |
michael@0 | 4406 | int selectedStart = 0; |
michael@0 | 4407 | int selectedPosition; |
michael@0 | 4408 | |
michael@0 | 4409 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 4410 | final int end = |
michael@0 | 4411 | (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); |
michael@0 | 4412 | |
michael@0 | 4413 | final int firstPosition = mFirstPosition; |
michael@0 | 4414 | final int toPosition = mResurrectToPosition; |
michael@0 | 4415 | boolean down = true; |
michael@0 | 4416 | |
michael@0 | 4417 | if (toPosition >= firstPosition && toPosition < firstPosition + childCount) { |
michael@0 | 4418 | selectedPosition = toPosition; |
michael@0 | 4419 | |
michael@0 | 4420 | final View selected = getChildAt(selectedPosition - mFirstPosition); |
michael@0 | 4421 | selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 4422 | } else if (toPosition < firstPosition) { |
michael@0 | 4423 | // Default to selecting whatever is first |
michael@0 | 4424 | selectedPosition = firstPosition; |
michael@0 | 4425 | |
michael@0 | 4426 | for (int i = 0; i < childCount; i++) { |
michael@0 | 4427 | final View child = getChildAt(i); |
michael@0 | 4428 | final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); |
michael@0 | 4429 | |
michael@0 | 4430 | if (i == 0) { |
michael@0 | 4431 | // Remember the position of the first item |
michael@0 | 4432 | selectedStart = childStart; |
michael@0 | 4433 | } |
michael@0 | 4434 | |
michael@0 | 4435 | if (childStart >= start) { |
michael@0 | 4436 | // Found a view whose top is fully visible |
michael@0 | 4437 | selectedPosition = firstPosition + i; |
michael@0 | 4438 | selectedStart = childStart; |
michael@0 | 4439 | break; |
michael@0 | 4440 | } |
michael@0 | 4441 | } |
michael@0 | 4442 | } else { |
michael@0 | 4443 | selectedPosition = firstPosition + childCount - 1; |
michael@0 | 4444 | down = false; |
michael@0 | 4445 | |
michael@0 | 4446 | for (int i = childCount - 1; i >= 0; i--) { |
michael@0 | 4447 | final View child = getChildAt(i); |
michael@0 | 4448 | final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); |
michael@0 | 4449 | final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); |
michael@0 | 4450 | |
michael@0 | 4451 | if (i == childCount - 1) { |
michael@0 | 4452 | selectedStart = childStart; |
michael@0 | 4453 | } |
michael@0 | 4454 | |
michael@0 | 4455 | if (childEnd <= end) { |
michael@0 | 4456 | selectedPosition = firstPosition + i; |
michael@0 | 4457 | selectedStart = childStart; |
michael@0 | 4458 | break; |
michael@0 | 4459 | } |
michael@0 | 4460 | } |
michael@0 | 4461 | } |
michael@0 | 4462 | |
michael@0 | 4463 | mResurrectToPosition = INVALID_POSITION; |
michael@0 | 4464 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 4465 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
michael@0 | 4466 | |
michael@0 | 4467 | mSpecificStart = selectedStart; |
michael@0 | 4468 | |
michael@0 | 4469 | selectedPosition = lookForSelectablePosition(selectedPosition, down); |
michael@0 | 4470 | if (selectedPosition >= firstPosition && selectedPosition <= getLastVisiblePosition()) { |
michael@0 | 4471 | mLayoutMode = LAYOUT_SPECIFIC; |
michael@0 | 4472 | updateSelectorState(); |
michael@0 | 4473 | setSelectionInt(selectedPosition); |
michael@0 | 4474 | invokeOnItemScrollListener(); |
michael@0 | 4475 | } else { |
michael@0 | 4476 | selectedPosition = INVALID_POSITION; |
michael@0 | 4477 | } |
michael@0 | 4478 | |
michael@0 | 4479 | return selectedPosition >= 0; |
michael@0 | 4480 | } |
michael@0 | 4481 | |
michael@0 | 4482 | /** |
michael@0 | 4483 | * If there is a selection returns false. |
michael@0 | 4484 | * Otherwise resurrects the selection and returns true if resurrected. |
michael@0 | 4485 | */ |
michael@0 | 4486 | boolean resurrectSelectionIfNeeded() { |
michael@0 | 4487 | if (mSelectedPosition < 0 && resurrectSelection()) { |
michael@0 | 4488 | updateSelectorState(); |
michael@0 | 4489 | return true; |
michael@0 | 4490 | } |
michael@0 | 4491 | |
michael@0 | 4492 | return false; |
michael@0 | 4493 | } |
michael@0 | 4494 | |
michael@0 | 4495 | private int getChildWidthMeasureSpec(LayoutParams lp) { |
michael@0 | 4496 | if (!mIsVertical && lp.width == LayoutParams.WRAP_CONTENT) { |
michael@0 | 4497 | return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
michael@0 | 4498 | } else if (mIsVertical) { |
michael@0 | 4499 | final int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight(); |
michael@0 | 4500 | return MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY); |
michael@0 | 4501 | } else { |
michael@0 | 4502 | return MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); |
michael@0 | 4503 | } |
michael@0 | 4504 | } |
michael@0 | 4505 | |
michael@0 | 4506 | private int getChildHeightMeasureSpec(LayoutParams lp) { |
michael@0 | 4507 | if (mIsVertical && lp.height == LayoutParams.WRAP_CONTENT) { |
michael@0 | 4508 | return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); |
michael@0 | 4509 | } else if (!mIsVertical) { |
michael@0 | 4510 | final int maxHeight = getHeight() - getPaddingTop() - getPaddingBottom(); |
michael@0 | 4511 | return MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY); |
michael@0 | 4512 | } else { |
michael@0 | 4513 | return MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); |
michael@0 | 4514 | } |
michael@0 | 4515 | } |
michael@0 | 4516 | |
michael@0 | 4517 | private void measureChild(View child) { |
michael@0 | 4518 | measureChild(child, (LayoutParams) child.getLayoutParams()); |
michael@0 | 4519 | } |
michael@0 | 4520 | |
michael@0 | 4521 | private void measureChild(View child, LayoutParams lp) { |
michael@0 | 4522 | final int widthSpec = getChildWidthMeasureSpec(lp); |
michael@0 | 4523 | final int heightSpec = getChildHeightMeasureSpec(lp); |
michael@0 | 4524 | child.measure(widthSpec, heightSpec); |
michael@0 | 4525 | } |
michael@0 | 4526 | |
michael@0 | 4527 | private void relayoutMeasuredChild(View child) { |
michael@0 | 4528 | final int w = child.getMeasuredWidth(); |
michael@0 | 4529 | final int h = child.getMeasuredHeight(); |
michael@0 | 4530 | |
michael@0 | 4531 | final int childLeft = getPaddingLeft(); |
michael@0 | 4532 | final int childRight = childLeft + w; |
michael@0 | 4533 | final int childTop = child.getTop(); |
michael@0 | 4534 | final int childBottom = childTop + h; |
michael@0 | 4535 | |
michael@0 | 4536 | child.layout(childLeft, childTop, childRight, childBottom); |
michael@0 | 4537 | } |
michael@0 | 4538 | |
michael@0 | 4539 | private void measureScrapChild(View scrapChild, int position, int secondaryMeasureSpec) { |
michael@0 | 4540 | LayoutParams lp = (LayoutParams) scrapChild.getLayoutParams(); |
michael@0 | 4541 | if (lp == null) { |
michael@0 | 4542 | lp = generateDefaultLayoutParams(); |
michael@0 | 4543 | scrapChild.setLayoutParams(lp); |
michael@0 | 4544 | } |
michael@0 | 4545 | |
michael@0 | 4546 | lp.viewType = mAdapter.getItemViewType(position); |
michael@0 | 4547 | lp.forceAdd = true; |
michael@0 | 4548 | |
michael@0 | 4549 | final int widthMeasureSpec; |
michael@0 | 4550 | final int heightMeasureSpec; |
michael@0 | 4551 | if (mIsVertical) { |
michael@0 | 4552 | widthMeasureSpec = secondaryMeasureSpec; |
michael@0 | 4553 | heightMeasureSpec = getChildHeightMeasureSpec(lp); |
michael@0 | 4554 | } else { |
michael@0 | 4555 | widthMeasureSpec = getChildWidthMeasureSpec(lp); |
michael@0 | 4556 | heightMeasureSpec = secondaryMeasureSpec; |
michael@0 | 4557 | } |
michael@0 | 4558 | |
michael@0 | 4559 | scrapChild.measure(widthMeasureSpec, heightMeasureSpec); |
michael@0 | 4560 | } |
michael@0 | 4561 | |
michael@0 | 4562 | /** |
michael@0 | 4563 | * Measures the height of the given range of children (inclusive) and |
michael@0 | 4564 | * returns the height with this TwoWayView's padding and item margin heights |
michael@0 | 4565 | * included. If maxHeight is provided, the measuring will stop when the |
michael@0 | 4566 | * current height reaches maxHeight. |
michael@0 | 4567 | * |
michael@0 | 4568 | * @param widthMeasureSpec The width measure spec to be given to a child's |
michael@0 | 4569 | * {@link View#measure(int, int)}. |
michael@0 | 4570 | * @param startPosition The position of the first child to be shown. |
michael@0 | 4571 | * @param endPosition The (inclusive) position of the last child to be |
michael@0 | 4572 | * shown. Specify {@link #NO_POSITION} if the last child should be |
michael@0 | 4573 | * the last available child from the adapter. |
michael@0 | 4574 | * @param maxHeight The maximum height that will be returned (if all the |
michael@0 | 4575 | * children don't fit in this value, this value will be |
michael@0 | 4576 | * returned). |
michael@0 | 4577 | * @param disallowPartialChildPosition In general, whether the returned |
michael@0 | 4578 | * height should only contain entire children. This is more |
michael@0 | 4579 | * powerful--it is the first inclusive position at which partial |
michael@0 | 4580 | * children will not be allowed. Example: it looks nice to have |
michael@0 | 4581 | * at least 3 completely visible children, and in portrait this |
michael@0 | 4582 | * will most likely fit; but in landscape there could be times |
michael@0 | 4583 | * when even 2 children can not be completely shown, so a value |
michael@0 | 4584 | * of 2 (remember, inclusive) would be good (assuming |
michael@0 | 4585 | * startPosition is 0). |
michael@0 | 4586 | * @return The height of this TwoWayView with the given children. |
michael@0 | 4587 | */ |
michael@0 | 4588 | private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, |
michael@0 | 4589 | final int maxHeight, int disallowPartialChildPosition) { |
michael@0 | 4590 | |
michael@0 | 4591 | final int paddingTop = getPaddingTop(); |
michael@0 | 4592 | final int paddingBottom = getPaddingBottom(); |
michael@0 | 4593 | |
michael@0 | 4594 | final ListAdapter adapter = mAdapter; |
michael@0 | 4595 | if (adapter == null) { |
michael@0 | 4596 | return paddingTop + paddingBottom; |
michael@0 | 4597 | } |
michael@0 | 4598 | |
michael@0 | 4599 | // Include the padding of the list |
michael@0 | 4600 | int returnedHeight = paddingTop + paddingBottom; |
michael@0 | 4601 | final int itemMargin = mItemMargin; |
michael@0 | 4602 | |
michael@0 | 4603 | // The previous height value that was less than maxHeight and contained |
michael@0 | 4604 | // no partial children |
michael@0 | 4605 | int prevHeightWithoutPartialChild = 0; |
michael@0 | 4606 | int i; |
michael@0 | 4607 | View child; |
michael@0 | 4608 | |
michael@0 | 4609 | // mItemCount - 1 since endPosition parameter is inclusive |
michael@0 | 4610 | endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; |
michael@0 | 4611 | final RecycleBin recycleBin = mRecycler; |
michael@0 | 4612 | final boolean shouldRecycle = recycleOnMeasure(); |
michael@0 | 4613 | final boolean[] isScrap = mIsScrap; |
michael@0 | 4614 | |
michael@0 | 4615 | for (i = startPosition; i <= endPosition; ++i) { |
michael@0 | 4616 | child = obtainView(i, isScrap); |
michael@0 | 4617 | |
michael@0 | 4618 | measureScrapChild(child, i, widthMeasureSpec); |
michael@0 | 4619 | |
michael@0 | 4620 | if (i > 0) { |
michael@0 | 4621 | // Count the item margin for all but one child |
michael@0 | 4622 | returnedHeight += itemMargin; |
michael@0 | 4623 | } |
michael@0 | 4624 | |
michael@0 | 4625 | // Recycle the view before we possibly return from the method |
michael@0 | 4626 | if (shouldRecycle) { |
michael@0 | 4627 | recycleBin.addScrapView(child, -1); |
michael@0 | 4628 | } |
michael@0 | 4629 | |
michael@0 | 4630 | returnedHeight += child.getMeasuredHeight(); |
michael@0 | 4631 | |
michael@0 | 4632 | if (returnedHeight >= maxHeight) { |
michael@0 | 4633 | // We went over, figure out which height to return. If returnedHeight > maxHeight, |
michael@0 | 4634 | // then the i'th position did not fit completely. |
michael@0 | 4635 | return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) |
michael@0 | 4636 | && (i > disallowPartialChildPosition) // We've past the min pos |
michael@0 | 4637 | && (prevHeightWithoutPartialChild > 0) // We have a prev height |
michael@0 | 4638 | && (returnedHeight != maxHeight) // i'th child did not fit completely |
michael@0 | 4639 | ? prevHeightWithoutPartialChild |
michael@0 | 4640 | : maxHeight; |
michael@0 | 4641 | } |
michael@0 | 4642 | |
michael@0 | 4643 | if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { |
michael@0 | 4644 | prevHeightWithoutPartialChild = returnedHeight; |
michael@0 | 4645 | } |
michael@0 | 4646 | } |
michael@0 | 4647 | |
michael@0 | 4648 | // At this point, we went through the range of children, and they each |
michael@0 | 4649 | // completely fit, so return the returnedHeight |
michael@0 | 4650 | return returnedHeight; |
michael@0 | 4651 | } |
michael@0 | 4652 | |
michael@0 | 4653 | /** |
michael@0 | 4654 | * Measures the width of the given range of children (inclusive) and |
michael@0 | 4655 | * returns the width with this TwoWayView's padding and item margin widths |
michael@0 | 4656 | * included. If maxWidth is provided, the measuring will stop when the |
michael@0 | 4657 | * current width reaches maxWidth. |
michael@0 | 4658 | * |
michael@0 | 4659 | * @param heightMeasureSpec The height measure spec to be given to a child's |
michael@0 | 4660 | * {@link View#measure(int, int)}. |
michael@0 | 4661 | * @param startPosition The position of the first child to be shown. |
michael@0 | 4662 | * @param endPosition The (inclusive) position of the last child to be |
michael@0 | 4663 | * shown. Specify {@link #NO_POSITION} if the last child should be |
michael@0 | 4664 | * the last available child from the adapter. |
michael@0 | 4665 | * @param maxWidth The maximum width that will be returned (if all the |
michael@0 | 4666 | * children don't fit in this value, this value will be |
michael@0 | 4667 | * returned). |
michael@0 | 4668 | * @param disallowPartialChildPosition In general, whether the returned |
michael@0 | 4669 | * width should only contain entire children. This is more |
michael@0 | 4670 | * powerful--it is the first inclusive position at which partial |
michael@0 | 4671 | * children will not be allowed. Example: it looks nice to have |
michael@0 | 4672 | * at least 3 completely visible children, and in portrait this |
michael@0 | 4673 | * will most likely fit; but in landscape there could be times |
michael@0 | 4674 | * when even 2 children can not be completely shown, so a value |
michael@0 | 4675 | * of 2 (remember, inclusive) would be good (assuming |
michael@0 | 4676 | * startPosition is 0). |
michael@0 | 4677 | * @return The width of this TwoWayView with the given children. |
michael@0 | 4678 | */ |
michael@0 | 4679 | private int measureWidthOfChildren(int heightMeasureSpec, int startPosition, int endPosition, |
michael@0 | 4680 | final int maxWidth, int disallowPartialChildPosition) { |
michael@0 | 4681 | |
michael@0 | 4682 | final int paddingLeft = getPaddingLeft(); |
michael@0 | 4683 | final int paddingRight = getPaddingRight(); |
michael@0 | 4684 | |
michael@0 | 4685 | final ListAdapter adapter = mAdapter; |
michael@0 | 4686 | if (adapter == null) { |
michael@0 | 4687 | return paddingLeft + paddingRight; |
michael@0 | 4688 | } |
michael@0 | 4689 | |
michael@0 | 4690 | // Include the padding of the list |
michael@0 | 4691 | int returnedWidth = paddingLeft + paddingRight; |
michael@0 | 4692 | final int itemMargin = mItemMargin; |
michael@0 | 4693 | |
michael@0 | 4694 | // The previous height value that was less than maxHeight and contained |
michael@0 | 4695 | // no partial children |
michael@0 | 4696 | int prevWidthWithoutPartialChild = 0; |
michael@0 | 4697 | int i; |
michael@0 | 4698 | View child; |
michael@0 | 4699 | |
michael@0 | 4700 | // mItemCount - 1 since endPosition parameter is inclusive |
michael@0 | 4701 | endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; |
michael@0 | 4702 | final RecycleBin recycleBin = mRecycler; |
michael@0 | 4703 | final boolean shouldRecycle = recycleOnMeasure(); |
michael@0 | 4704 | final boolean[] isScrap = mIsScrap; |
michael@0 | 4705 | |
michael@0 | 4706 | for (i = startPosition; i <= endPosition; ++i) { |
michael@0 | 4707 | child = obtainView(i, isScrap); |
michael@0 | 4708 | |
michael@0 | 4709 | measureScrapChild(child, i, heightMeasureSpec); |
michael@0 | 4710 | |
michael@0 | 4711 | if (i > 0) { |
michael@0 | 4712 | // Count the item margin for all but one child |
michael@0 | 4713 | returnedWidth += itemMargin; |
michael@0 | 4714 | } |
michael@0 | 4715 | |
michael@0 | 4716 | // Recycle the view before we possibly return from the method |
michael@0 | 4717 | if (shouldRecycle) { |
michael@0 | 4718 | recycleBin.addScrapView(child, -1); |
michael@0 | 4719 | } |
michael@0 | 4720 | |
michael@0 | 4721 | returnedWidth += child.getMeasuredHeight(); |
michael@0 | 4722 | |
michael@0 | 4723 | if (returnedWidth >= maxWidth) { |
michael@0 | 4724 | // We went over, figure out which width to return. If returnedWidth > maxWidth, |
michael@0 | 4725 | // then the i'th position did not fit completely. |
michael@0 | 4726 | return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) |
michael@0 | 4727 | && (i > disallowPartialChildPosition) // We've past the min pos |
michael@0 | 4728 | && (prevWidthWithoutPartialChild > 0) // We have a prev width |
michael@0 | 4729 | && (returnedWidth != maxWidth) // i'th child did not fit completely |
michael@0 | 4730 | ? prevWidthWithoutPartialChild |
michael@0 | 4731 | : maxWidth; |
michael@0 | 4732 | } |
michael@0 | 4733 | |
michael@0 | 4734 | if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { |
michael@0 | 4735 | prevWidthWithoutPartialChild = returnedWidth; |
michael@0 | 4736 | } |
michael@0 | 4737 | } |
michael@0 | 4738 | |
michael@0 | 4739 | // At this point, we went through the range of children, and they each |
michael@0 | 4740 | // completely fit, so return the returnedWidth |
michael@0 | 4741 | return returnedWidth; |
michael@0 | 4742 | } |
michael@0 | 4743 | |
michael@0 | 4744 | private View makeAndAddView(int position, int offset, boolean flow, boolean selected) { |
michael@0 | 4745 | final int top; |
michael@0 | 4746 | final int left; |
michael@0 | 4747 | |
michael@0 | 4748 | if (mIsVertical) { |
michael@0 | 4749 | top = offset; |
michael@0 | 4750 | left = getPaddingLeft(); |
michael@0 | 4751 | } else { |
michael@0 | 4752 | top = getPaddingTop(); |
michael@0 | 4753 | left = offset; |
michael@0 | 4754 | } |
michael@0 | 4755 | |
michael@0 | 4756 | if (!mDataChanged) { |
michael@0 | 4757 | // Try to use an existing view for this position |
michael@0 | 4758 | final View activeChild = mRecycler.getActiveView(position); |
michael@0 | 4759 | if (activeChild != null) { |
michael@0 | 4760 | // Found it -- we're using an existing child |
michael@0 | 4761 | // This just needs to be positioned |
michael@0 | 4762 | setupChild(activeChild, position, top, left, flow, selected, true); |
michael@0 | 4763 | |
michael@0 | 4764 | return activeChild; |
michael@0 | 4765 | } |
michael@0 | 4766 | } |
michael@0 | 4767 | |
michael@0 | 4768 | // Make a new view for this position, or convert an unused view if possible |
michael@0 | 4769 | final View child = obtainView(position, mIsScrap); |
michael@0 | 4770 | |
michael@0 | 4771 | // This needs to be positioned and measured |
michael@0 | 4772 | setupChild(child, position, top, left, flow, selected, mIsScrap[0]); |
michael@0 | 4773 | |
michael@0 | 4774 | return child; |
michael@0 | 4775 | } |
michael@0 | 4776 | |
michael@0 | 4777 | @TargetApi(11) |
michael@0 | 4778 | private void setupChild(View child, int position, int top, int left, |
michael@0 | 4779 | boolean flow, boolean selected, boolean recycled) { |
michael@0 | 4780 | final boolean isSelected = selected && shouldShowSelector(); |
michael@0 | 4781 | final boolean updateChildSelected = isSelected != child.isSelected(); |
michael@0 | 4782 | final int touchMode = mTouchMode; |
michael@0 | 4783 | |
michael@0 | 4784 | final boolean isPressed = touchMode > TOUCH_MODE_DOWN && touchMode < TOUCH_MODE_DRAGGING && |
michael@0 | 4785 | mMotionPosition == position; |
michael@0 | 4786 | |
michael@0 | 4787 | final boolean updateChildPressed = isPressed != child.isPressed(); |
michael@0 | 4788 | final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); |
michael@0 | 4789 | |
michael@0 | 4790 | // Respect layout params that are already in the view. Otherwise make some up... |
michael@0 | 4791 | LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
michael@0 | 4792 | if (lp == null) { |
michael@0 | 4793 | lp = generateDefaultLayoutParams(); |
michael@0 | 4794 | } |
michael@0 | 4795 | |
michael@0 | 4796 | lp.viewType = mAdapter.getItemViewType(position); |
michael@0 | 4797 | |
michael@0 | 4798 | if (recycled && !lp.forceAdd) { |
michael@0 | 4799 | attachViewToParent(child, (flow ? -1 : 0), lp); |
michael@0 | 4800 | } else { |
michael@0 | 4801 | lp.forceAdd = false; |
michael@0 | 4802 | addViewInLayout(child, (flow ? -1 : 0), lp, true); |
michael@0 | 4803 | } |
michael@0 | 4804 | |
michael@0 | 4805 | if (updateChildSelected) { |
michael@0 | 4806 | child.setSelected(isSelected); |
michael@0 | 4807 | } |
michael@0 | 4808 | |
michael@0 | 4809 | if (updateChildPressed) { |
michael@0 | 4810 | child.setPressed(isPressed); |
michael@0 | 4811 | } |
michael@0 | 4812 | |
michael@0 | 4813 | if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mCheckStates != null) { |
michael@0 | 4814 | if (child instanceof Checkable) { |
michael@0 | 4815 | ((Checkable) child).setChecked(mCheckStates.get(position)); |
michael@0 | 4816 | } else if (getContext().getApplicationInfo().targetSdkVersion |
michael@0 | 4817 | >= Build.VERSION_CODES.HONEYCOMB) { |
michael@0 | 4818 | child.setActivated(mCheckStates.get(position)); |
michael@0 | 4819 | } |
michael@0 | 4820 | } |
michael@0 | 4821 | |
michael@0 | 4822 | if (needToMeasure) { |
michael@0 | 4823 | measureChild(child, lp); |
michael@0 | 4824 | } else { |
michael@0 | 4825 | cleanupLayoutState(child); |
michael@0 | 4826 | } |
michael@0 | 4827 | |
michael@0 | 4828 | final int w = child.getMeasuredWidth(); |
michael@0 | 4829 | final int h = child.getMeasuredHeight(); |
michael@0 | 4830 | |
michael@0 | 4831 | final int childTop = (mIsVertical && !flow ? top - h : top); |
michael@0 | 4832 | final int childLeft = (!mIsVertical && !flow ? left - w : left); |
michael@0 | 4833 | |
michael@0 | 4834 | if (needToMeasure) { |
michael@0 | 4835 | final int childRight = childLeft + w; |
michael@0 | 4836 | final int childBottom = childTop + h; |
michael@0 | 4837 | |
michael@0 | 4838 | child.layout(childLeft, childTop, childRight, childBottom); |
michael@0 | 4839 | } else { |
michael@0 | 4840 | child.offsetLeftAndRight(childLeft - child.getLeft()); |
michael@0 | 4841 | child.offsetTopAndBottom(childTop - child.getTop()); |
michael@0 | 4842 | } |
michael@0 | 4843 | } |
michael@0 | 4844 | |
michael@0 | 4845 | void fillGap(boolean down) { |
michael@0 | 4846 | final int childCount = getChildCount(); |
michael@0 | 4847 | |
michael@0 | 4848 | if (down) { |
michael@0 | 4849 | final int paddingStart = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 4850 | |
michael@0 | 4851 | final int lastEnd; |
michael@0 | 4852 | if (mIsVertical) { |
michael@0 | 4853 | lastEnd = getChildAt(childCount - 1).getBottom(); |
michael@0 | 4854 | } else { |
michael@0 | 4855 | lastEnd = getChildAt(childCount - 1).getRight(); |
michael@0 | 4856 | } |
michael@0 | 4857 | |
michael@0 | 4858 | final int offset = (childCount > 0 ? lastEnd + mItemMargin : paddingStart); |
michael@0 | 4859 | fillAfter(mFirstPosition + childCount, offset); |
michael@0 | 4860 | correctTooHigh(getChildCount()); |
michael@0 | 4861 | } else { |
michael@0 | 4862 | final int end; |
michael@0 | 4863 | final int firstStart; |
michael@0 | 4864 | |
michael@0 | 4865 | if (mIsVertical) { |
michael@0 | 4866 | end = getHeight() - getPaddingBottom(); |
michael@0 | 4867 | firstStart = getChildAt(0).getTop(); |
michael@0 | 4868 | } else { |
michael@0 | 4869 | end = getWidth() - getPaddingRight(); |
michael@0 | 4870 | firstStart = getChildAt(0).getLeft(); |
michael@0 | 4871 | } |
michael@0 | 4872 | |
michael@0 | 4873 | final int offset = (childCount > 0 ? firstStart - mItemMargin : end); |
michael@0 | 4874 | fillBefore(mFirstPosition - 1, offset); |
michael@0 | 4875 | correctTooLow(getChildCount()); |
michael@0 | 4876 | } |
michael@0 | 4877 | } |
michael@0 | 4878 | |
michael@0 | 4879 | private View fillBefore(int pos, int nextOffset) { |
michael@0 | 4880 | View selectedView = null; |
michael@0 | 4881 | |
michael@0 | 4882 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 4883 | |
michael@0 | 4884 | while (nextOffset > start && pos >= 0) { |
michael@0 | 4885 | boolean isSelected = (pos == mSelectedPosition); |
michael@0 | 4886 | View child = makeAndAddView(pos, nextOffset, false, isSelected); |
michael@0 | 4887 | |
michael@0 | 4888 | if (mIsVertical) { |
michael@0 | 4889 | nextOffset = child.getTop() - mItemMargin; |
michael@0 | 4890 | } else { |
michael@0 | 4891 | nextOffset = child.getLeft() - mItemMargin; |
michael@0 | 4892 | } |
michael@0 | 4893 | |
michael@0 | 4894 | if (isSelected) { |
michael@0 | 4895 | selectedView = child; |
michael@0 | 4896 | } |
michael@0 | 4897 | |
michael@0 | 4898 | pos--; |
michael@0 | 4899 | } |
michael@0 | 4900 | |
michael@0 | 4901 | mFirstPosition = pos + 1; |
michael@0 | 4902 | |
michael@0 | 4903 | return selectedView; |
michael@0 | 4904 | } |
michael@0 | 4905 | |
michael@0 | 4906 | private View fillAfter(int pos, int nextOffset) { |
michael@0 | 4907 | View selectedView = null; |
michael@0 | 4908 | |
michael@0 | 4909 | final int end = |
michael@0 | 4910 | (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); |
michael@0 | 4911 | |
michael@0 | 4912 | while (nextOffset < end && pos < mItemCount) { |
michael@0 | 4913 | boolean selected = (pos == mSelectedPosition); |
michael@0 | 4914 | |
michael@0 | 4915 | View child = makeAndAddView(pos, nextOffset, true, selected); |
michael@0 | 4916 | |
michael@0 | 4917 | if (mIsVertical) { |
michael@0 | 4918 | nextOffset = child.getBottom() + mItemMargin; |
michael@0 | 4919 | } else { |
michael@0 | 4920 | nextOffset = child.getRight() + mItemMargin; |
michael@0 | 4921 | } |
michael@0 | 4922 | |
michael@0 | 4923 | if (selected) { |
michael@0 | 4924 | selectedView = child; |
michael@0 | 4925 | } |
michael@0 | 4926 | |
michael@0 | 4927 | pos++; |
michael@0 | 4928 | } |
michael@0 | 4929 | |
michael@0 | 4930 | return selectedView; |
michael@0 | 4931 | } |
michael@0 | 4932 | |
michael@0 | 4933 | private View fillSpecific(int position, int offset) { |
michael@0 | 4934 | final boolean tempIsSelected = (position == mSelectedPosition); |
michael@0 | 4935 | View temp = makeAndAddView(position, offset, true, tempIsSelected); |
michael@0 | 4936 | |
michael@0 | 4937 | // Possibly changed again in fillBefore if we add rows above this one. |
michael@0 | 4938 | mFirstPosition = position; |
michael@0 | 4939 | |
michael@0 | 4940 | final int itemMargin = mItemMargin; |
michael@0 | 4941 | |
michael@0 | 4942 | final int offsetBefore; |
michael@0 | 4943 | if (mIsVertical) { |
michael@0 | 4944 | offsetBefore = temp.getTop() - itemMargin; |
michael@0 | 4945 | } else { |
michael@0 | 4946 | offsetBefore = temp.getLeft() - itemMargin; |
michael@0 | 4947 | } |
michael@0 | 4948 | final View before = fillBefore(position - 1, offsetBefore); |
michael@0 | 4949 | |
michael@0 | 4950 | // This will correct for the top of the first view not touching the top of the list |
michael@0 | 4951 | adjustViewsStartOrEnd(); |
michael@0 | 4952 | |
michael@0 | 4953 | final int offsetAfter; |
michael@0 | 4954 | if (mIsVertical) { |
michael@0 | 4955 | offsetAfter = temp.getBottom() + itemMargin; |
michael@0 | 4956 | } else { |
michael@0 | 4957 | offsetAfter = temp.getRight() + itemMargin; |
michael@0 | 4958 | } |
michael@0 | 4959 | final View after = fillAfter(position + 1, offsetAfter); |
michael@0 | 4960 | |
michael@0 | 4961 | final int childCount = getChildCount(); |
michael@0 | 4962 | if (childCount > 0) { |
michael@0 | 4963 | correctTooHigh(childCount); |
michael@0 | 4964 | } |
michael@0 | 4965 | |
michael@0 | 4966 | if (tempIsSelected) { |
michael@0 | 4967 | return temp; |
michael@0 | 4968 | } else if (before != null) { |
michael@0 | 4969 | return before; |
michael@0 | 4970 | } else { |
michael@0 | 4971 | return after; |
michael@0 | 4972 | } |
michael@0 | 4973 | } |
michael@0 | 4974 | |
michael@0 | 4975 | private View fillFromOffset(int nextOffset) { |
michael@0 | 4976 | mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); |
michael@0 | 4977 | mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); |
michael@0 | 4978 | |
michael@0 | 4979 | if (mFirstPosition < 0) { |
michael@0 | 4980 | mFirstPosition = 0; |
michael@0 | 4981 | } |
michael@0 | 4982 | |
michael@0 | 4983 | return fillAfter(mFirstPosition, nextOffset); |
michael@0 | 4984 | } |
michael@0 | 4985 | |
michael@0 | 4986 | private View fillFromMiddle(int start, int end) { |
michael@0 | 4987 | final int size = end - start; |
michael@0 | 4988 | int position = reconcileSelectedPosition(); |
michael@0 | 4989 | |
michael@0 | 4990 | View selected = makeAndAddView(position, start, true, true); |
michael@0 | 4991 | mFirstPosition = position; |
michael@0 | 4992 | |
michael@0 | 4993 | if (mIsVertical) { |
michael@0 | 4994 | int selectedHeight = selected.getMeasuredHeight(); |
michael@0 | 4995 | if (selectedHeight <= size) { |
michael@0 | 4996 | selected.offsetTopAndBottom((size - selectedHeight) / 2); |
michael@0 | 4997 | } |
michael@0 | 4998 | } else { |
michael@0 | 4999 | int selectedWidth = selected.getMeasuredWidth(); |
michael@0 | 5000 | if (selectedWidth <= size) { |
michael@0 | 5001 | selected.offsetLeftAndRight((size - selectedWidth) / 2); |
michael@0 | 5002 | } |
michael@0 | 5003 | } |
michael@0 | 5004 | |
michael@0 | 5005 | fillBeforeAndAfter(selected, position); |
michael@0 | 5006 | correctTooHigh(getChildCount()); |
michael@0 | 5007 | |
michael@0 | 5008 | return selected; |
michael@0 | 5009 | } |
michael@0 | 5010 | |
michael@0 | 5011 | private void fillBeforeAndAfter(View selected, int position) { |
michael@0 | 5012 | final int itemMargin = mItemMargin; |
michael@0 | 5013 | |
michael@0 | 5014 | final int offsetBefore; |
michael@0 | 5015 | if (mIsVertical) { |
michael@0 | 5016 | offsetBefore = selected.getTop() - itemMargin; |
michael@0 | 5017 | } else { |
michael@0 | 5018 | offsetBefore = selected.getLeft() - itemMargin; |
michael@0 | 5019 | } |
michael@0 | 5020 | |
michael@0 | 5021 | fillBefore(position - 1, offsetBefore); |
michael@0 | 5022 | |
michael@0 | 5023 | adjustViewsStartOrEnd(); |
michael@0 | 5024 | |
michael@0 | 5025 | final int offsetAfter; |
michael@0 | 5026 | if (mIsVertical) { |
michael@0 | 5027 | offsetAfter = selected.getBottom() + itemMargin; |
michael@0 | 5028 | } else { |
michael@0 | 5029 | offsetAfter = selected.getRight() + itemMargin; |
michael@0 | 5030 | } |
michael@0 | 5031 | |
michael@0 | 5032 | fillAfter(position + 1, offsetAfter); |
michael@0 | 5033 | } |
michael@0 | 5034 | |
michael@0 | 5035 | private View fillFromSelection(int selectedTop, int start, int end) { |
michael@0 | 5036 | final int selectedPosition = mSelectedPosition; |
michael@0 | 5037 | View selected; |
michael@0 | 5038 | |
michael@0 | 5039 | selected = makeAndAddView(selectedPosition, selectedTop, true, true); |
michael@0 | 5040 | |
michael@0 | 5041 | final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); |
michael@0 | 5042 | final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); |
michael@0 | 5043 | |
michael@0 | 5044 | // Some of the newly selected item extends below the bottom of the list |
michael@0 | 5045 | if (selectedEnd > end) { |
michael@0 | 5046 | // Find space available above the selection into which we can scroll |
michael@0 | 5047 | // upwards |
michael@0 | 5048 | final int spaceAbove = selectedStart - start; |
michael@0 | 5049 | |
michael@0 | 5050 | // Find space required to bring the bottom of the selected item |
michael@0 | 5051 | // fully into view |
michael@0 | 5052 | final int spaceBelow = selectedEnd - end; |
michael@0 | 5053 | |
michael@0 | 5054 | final int offset = Math.min(spaceAbove, spaceBelow); |
michael@0 | 5055 | |
michael@0 | 5056 | // Now offset the selected item to get it into view |
michael@0 | 5057 | selected.offsetTopAndBottom(-offset); |
michael@0 | 5058 | } else if (selectedStart < start) { |
michael@0 | 5059 | // Find space required to bring the top of the selected item fully |
michael@0 | 5060 | // into view |
michael@0 | 5061 | final int spaceAbove = start - selectedStart; |
michael@0 | 5062 | |
michael@0 | 5063 | // Find space available below the selection into which we can scroll |
michael@0 | 5064 | // downwards |
michael@0 | 5065 | final int spaceBelow = end - selectedEnd; |
michael@0 | 5066 | |
michael@0 | 5067 | final int offset = Math.min(spaceAbove, spaceBelow); |
michael@0 | 5068 | |
michael@0 | 5069 | // Offset the selected item to get it into view |
michael@0 | 5070 | selected.offsetTopAndBottom(offset); |
michael@0 | 5071 | } |
michael@0 | 5072 | |
michael@0 | 5073 | // Fill in views above and below |
michael@0 | 5074 | fillBeforeAndAfter(selected, selectedPosition); |
michael@0 | 5075 | correctTooHigh(getChildCount()); |
michael@0 | 5076 | |
michael@0 | 5077 | return selected; |
michael@0 | 5078 | } |
michael@0 | 5079 | |
michael@0 | 5080 | private void correctTooHigh(int childCount) { |
michael@0 | 5081 | // First see if the last item is visible. If it is not, it is OK for the |
michael@0 | 5082 | // top of the list to be pushed up. |
michael@0 | 5083 | final int lastPosition = mFirstPosition + childCount - 1; |
michael@0 | 5084 | if (lastPosition != mItemCount - 1 || childCount == 0) { |
michael@0 | 5085 | return; |
michael@0 | 5086 | } |
michael@0 | 5087 | |
michael@0 | 5088 | // Get the last child ... |
michael@0 | 5089 | final View lastChild = getChildAt(childCount - 1); |
michael@0 | 5090 | |
michael@0 | 5091 | // ... and its end edge |
michael@0 | 5092 | final int lastEnd; |
michael@0 | 5093 | if (mIsVertical) { |
michael@0 | 5094 | lastEnd = lastChild.getBottom(); |
michael@0 | 5095 | } else { |
michael@0 | 5096 | lastEnd = lastChild.getRight(); |
michael@0 | 5097 | } |
michael@0 | 5098 | |
michael@0 | 5099 | // This is bottom of our drawable area |
michael@0 | 5100 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 5101 | final int end = |
michael@0 | 5102 | (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); |
michael@0 | 5103 | |
michael@0 | 5104 | // This is how far the end edge of the last view is from the end of the |
michael@0 | 5105 | // drawable area |
michael@0 | 5106 | int endOffset = end - lastEnd; |
michael@0 | 5107 | |
michael@0 | 5108 | View firstChild = getChildAt(0); |
michael@0 | 5109 | int firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); |
michael@0 | 5110 | |
michael@0 | 5111 | // Make sure we are 1) Too high, and 2) Either there are more rows above the |
michael@0 | 5112 | // first row or the first row is scrolled off the top of the drawable area |
michael@0 | 5113 | if (endOffset > 0 && (mFirstPosition > 0 || firstStart < start)) { |
michael@0 | 5114 | if (mFirstPosition == 0) { |
michael@0 | 5115 | // Don't pull the top too far down |
michael@0 | 5116 | endOffset = Math.min(endOffset, start - firstStart); |
michael@0 | 5117 | } |
michael@0 | 5118 | |
michael@0 | 5119 | // Move everything down |
michael@0 | 5120 | offsetChildren(endOffset); |
michael@0 | 5121 | |
michael@0 | 5122 | if (mFirstPosition > 0) { |
michael@0 | 5123 | firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); |
michael@0 | 5124 | |
michael@0 | 5125 | // Fill the gap that was opened above mFirstPosition with more rows, if |
michael@0 | 5126 | // possible |
michael@0 | 5127 | fillBefore(mFirstPosition - 1, firstStart - mItemMargin); |
michael@0 | 5128 | |
michael@0 | 5129 | // Close up the remaining gap |
michael@0 | 5130 | adjustViewsStartOrEnd(); |
michael@0 | 5131 | } |
michael@0 | 5132 | } |
michael@0 | 5133 | } |
michael@0 | 5134 | |
michael@0 | 5135 | private void correctTooLow(int childCount) { |
michael@0 | 5136 | // First see if the first item is visible. If it is not, it is OK for the |
michael@0 | 5137 | // bottom of the list to be pushed down. |
michael@0 | 5138 | if (mFirstPosition != 0 || childCount == 0) { |
michael@0 | 5139 | return; |
michael@0 | 5140 | } |
michael@0 | 5141 | |
michael@0 | 5142 | final View first = getChildAt(0); |
michael@0 | 5143 | final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); |
michael@0 | 5144 | |
michael@0 | 5145 | final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); |
michael@0 | 5146 | |
michael@0 | 5147 | final int end; |
michael@0 | 5148 | if (mIsVertical) { |
michael@0 | 5149 | end = getHeight() - getPaddingBottom(); |
michael@0 | 5150 | } else { |
michael@0 | 5151 | end = getWidth() - getPaddingRight(); |
michael@0 | 5152 | } |
michael@0 | 5153 | |
michael@0 | 5154 | // This is how far the start edge of the first view is from the start of the |
michael@0 | 5155 | // drawable area |
michael@0 | 5156 | int startOffset = firstStart - start; |
michael@0 | 5157 | |
michael@0 | 5158 | View last = getChildAt(childCount - 1); |
michael@0 | 5159 | int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); |
michael@0 | 5160 | |
michael@0 | 5161 | int lastPosition = mFirstPosition + childCount - 1; |
michael@0 | 5162 | |
michael@0 | 5163 | // Make sure we are 1) Too low, and 2) Either there are more columns/rows below the |
michael@0 | 5164 | // last column/row or the last column/row is scrolled off the end of the |
michael@0 | 5165 | // drawable area |
michael@0 | 5166 | if (startOffset > 0) { |
michael@0 | 5167 | if (lastPosition < mItemCount - 1 || lastEnd > end) { |
michael@0 | 5168 | if (lastPosition == mItemCount - 1) { |
michael@0 | 5169 | // Don't pull the bottom too far up |
michael@0 | 5170 | startOffset = Math.min(startOffset, lastEnd - end); |
michael@0 | 5171 | } |
michael@0 | 5172 | |
michael@0 | 5173 | // Move everything up |
michael@0 | 5174 | offsetChildren(-startOffset); |
michael@0 | 5175 | |
michael@0 | 5176 | if (lastPosition < mItemCount - 1) { |
michael@0 | 5177 | lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); |
michael@0 | 5178 | |
michael@0 | 5179 | // Fill the gap that was opened below the last position with more rows, if |
michael@0 | 5180 | // possible |
michael@0 | 5181 | fillAfter(lastPosition + 1, lastEnd + mItemMargin); |
michael@0 | 5182 | |
michael@0 | 5183 | // Close up the remaining gap |
michael@0 | 5184 | adjustViewsStartOrEnd(); |
michael@0 | 5185 | } |
michael@0 | 5186 | } else if (lastPosition == mItemCount - 1) { |
michael@0 | 5187 | adjustViewsStartOrEnd(); |
michael@0 | 5188 | } |
michael@0 | 5189 | } |
michael@0 | 5190 | } |
michael@0 | 5191 | |
michael@0 | 5192 | private void adjustViewsStartOrEnd() { |
michael@0 | 5193 | if (getChildCount() == 0) { |
michael@0 | 5194 | return; |
michael@0 | 5195 | } |
michael@0 | 5196 | |
michael@0 | 5197 | final View firstChild = getChildAt(0); |
michael@0 | 5198 | |
michael@0 | 5199 | int delta; |
michael@0 | 5200 | if (mIsVertical) { |
michael@0 | 5201 | delta = firstChild.getTop() - getPaddingTop() - mItemMargin; |
michael@0 | 5202 | } else { |
michael@0 | 5203 | delta = firstChild.getLeft() - getPaddingLeft() - mItemMargin; |
michael@0 | 5204 | } |
michael@0 | 5205 | |
michael@0 | 5206 | if (delta < 0) { |
michael@0 | 5207 | // We only are looking to see if we are too low, not too high |
michael@0 | 5208 | delta = 0; |
michael@0 | 5209 | } |
michael@0 | 5210 | |
michael@0 | 5211 | if (delta != 0) { |
michael@0 | 5212 | offsetChildren(-delta); |
michael@0 | 5213 | } |
michael@0 | 5214 | } |
michael@0 | 5215 | |
michael@0 | 5216 | @TargetApi(14) |
michael@0 | 5217 | private SparseBooleanArray cloneCheckStates() { |
michael@0 | 5218 | if (mCheckStates == null) { |
michael@0 | 5219 | return null; |
michael@0 | 5220 | } |
michael@0 | 5221 | |
michael@0 | 5222 | SparseBooleanArray checkedStates; |
michael@0 | 5223 | |
michael@0 | 5224 | if (Build.VERSION.SDK_INT >= 14) { |
michael@0 | 5225 | checkedStates = mCheckStates.clone(); |
michael@0 | 5226 | } else { |
michael@0 | 5227 | checkedStates = new SparseBooleanArray(); |
michael@0 | 5228 | |
michael@0 | 5229 | for (int i = 0; i < mCheckStates.size(); i++) { |
michael@0 | 5230 | checkedStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i)); |
michael@0 | 5231 | } |
michael@0 | 5232 | } |
michael@0 | 5233 | |
michael@0 | 5234 | return checkedStates; |
michael@0 | 5235 | } |
michael@0 | 5236 | |
michael@0 | 5237 | private int findSyncPosition() { |
michael@0 | 5238 | int itemCount = mItemCount; |
michael@0 | 5239 | |
michael@0 | 5240 | if (itemCount == 0) { |
michael@0 | 5241 | return INVALID_POSITION; |
michael@0 | 5242 | } |
michael@0 | 5243 | |
michael@0 | 5244 | final long idToMatch = mSyncRowId; |
michael@0 | 5245 | |
michael@0 | 5246 | // If there isn't a selection don't hunt for it |
michael@0 | 5247 | if (idToMatch == INVALID_ROW_ID) { |
michael@0 | 5248 | return INVALID_POSITION; |
michael@0 | 5249 | } |
michael@0 | 5250 | |
michael@0 | 5251 | // Pin seed to reasonable values |
michael@0 | 5252 | int seed = mSyncPosition; |
michael@0 | 5253 | seed = Math.max(0, seed); |
michael@0 | 5254 | seed = Math.min(itemCount - 1, seed); |
michael@0 | 5255 | |
michael@0 | 5256 | long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; |
michael@0 | 5257 | |
michael@0 | 5258 | long rowId; |
michael@0 | 5259 | |
michael@0 | 5260 | // first position scanned so far |
michael@0 | 5261 | int first = seed; |
michael@0 | 5262 | |
michael@0 | 5263 | // last position scanned so far |
michael@0 | 5264 | int last = seed; |
michael@0 | 5265 | |
michael@0 | 5266 | // True if we should move down on the next iteration |
michael@0 | 5267 | boolean next = false; |
michael@0 | 5268 | |
michael@0 | 5269 | // True when we have looked at the first item in the data |
michael@0 | 5270 | boolean hitFirst; |
michael@0 | 5271 | |
michael@0 | 5272 | // True when we have looked at the last item in the data |
michael@0 | 5273 | boolean hitLast; |
michael@0 | 5274 | |
michael@0 | 5275 | // Get the item ID locally (instead of getItemIdAtPosition), so |
michael@0 | 5276 | // we need the adapter |
michael@0 | 5277 | final ListAdapter adapter = mAdapter; |
michael@0 | 5278 | if (adapter == null) { |
michael@0 | 5279 | return INVALID_POSITION; |
michael@0 | 5280 | } |
michael@0 | 5281 | |
michael@0 | 5282 | while (SystemClock.uptimeMillis() <= endTime) { |
michael@0 | 5283 | rowId = adapter.getItemId(seed); |
michael@0 | 5284 | if (rowId == idToMatch) { |
michael@0 | 5285 | // Found it! |
michael@0 | 5286 | return seed; |
michael@0 | 5287 | } |
michael@0 | 5288 | |
michael@0 | 5289 | hitLast = (last == itemCount - 1); |
michael@0 | 5290 | hitFirst = (first == 0); |
michael@0 | 5291 | |
michael@0 | 5292 | if (hitLast && hitFirst) { |
michael@0 | 5293 | // Looked at everything |
michael@0 | 5294 | break; |
michael@0 | 5295 | } |
michael@0 | 5296 | |
michael@0 | 5297 | if (hitFirst || (next && !hitLast)) { |
michael@0 | 5298 | // Either we hit the top, or we are trying to move down |
michael@0 | 5299 | last++; |
michael@0 | 5300 | seed = last; |
michael@0 | 5301 | |
michael@0 | 5302 | // Try going up next time |
michael@0 | 5303 | next = false; |
michael@0 | 5304 | } else if (hitLast || (!next && !hitFirst)) { |
michael@0 | 5305 | // Either we hit the bottom, or we are trying to move up |
michael@0 | 5306 | first--; |
michael@0 | 5307 | seed = first; |
michael@0 | 5308 | |
michael@0 | 5309 | // Try going down next time |
michael@0 | 5310 | next = true; |
michael@0 | 5311 | } |
michael@0 | 5312 | } |
michael@0 | 5313 | |
michael@0 | 5314 | return INVALID_POSITION; |
michael@0 | 5315 | } |
michael@0 | 5316 | |
michael@0 | 5317 | @TargetApi(16) |
michael@0 | 5318 | private View obtainView(int position, boolean[] isScrap) { |
michael@0 | 5319 | isScrap[0] = false; |
michael@0 | 5320 | |
michael@0 | 5321 | View scrapView = mRecycler.getTransientStateView(position); |
michael@0 | 5322 | if (scrapView != null) { |
michael@0 | 5323 | return scrapView; |
michael@0 | 5324 | } |
michael@0 | 5325 | |
michael@0 | 5326 | scrapView = mRecycler.getScrapView(position); |
michael@0 | 5327 | |
michael@0 | 5328 | final View child; |
michael@0 | 5329 | if (scrapView != null) { |
michael@0 | 5330 | child = mAdapter.getView(position, scrapView, this); |
michael@0 | 5331 | |
michael@0 | 5332 | if (child != scrapView) { |
michael@0 | 5333 | mRecycler.addScrapView(scrapView, position); |
michael@0 | 5334 | } else { |
michael@0 | 5335 | isScrap[0] = true; |
michael@0 | 5336 | } |
michael@0 | 5337 | } else { |
michael@0 | 5338 | child = mAdapter.getView(position, null, this); |
michael@0 | 5339 | } |
michael@0 | 5340 | |
michael@0 | 5341 | if (ViewCompat.getImportantForAccessibility(child) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { |
michael@0 | 5342 | ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); |
michael@0 | 5343 | } |
michael@0 | 5344 | |
michael@0 | 5345 | if (mHasStableIds) { |
michael@0 | 5346 | LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
michael@0 | 5347 | |
michael@0 | 5348 | if (lp == null) { |
michael@0 | 5349 | lp = generateDefaultLayoutParams(); |
michael@0 | 5350 | } else if (!checkLayoutParams(lp)) { |
michael@0 | 5351 | lp = generateLayoutParams(lp); |
michael@0 | 5352 | } |
michael@0 | 5353 | |
michael@0 | 5354 | lp.id = mAdapter.getItemId(position); |
michael@0 | 5355 | |
michael@0 | 5356 | child.setLayoutParams(lp); |
michael@0 | 5357 | } |
michael@0 | 5358 | |
michael@0 | 5359 | if (mAccessibilityDelegate == null) { |
michael@0 | 5360 | mAccessibilityDelegate = new ListItemAccessibilityDelegate(); |
michael@0 | 5361 | } |
michael@0 | 5362 | |
michael@0 | 5363 | ViewCompat.setAccessibilityDelegate(child, mAccessibilityDelegate); |
michael@0 | 5364 | |
michael@0 | 5365 | return child; |
michael@0 | 5366 | } |
michael@0 | 5367 | |
michael@0 | 5368 | void resetState() { |
michael@0 | 5369 | removeAllViewsInLayout(); |
michael@0 | 5370 | |
michael@0 | 5371 | mSelectedStart = 0; |
michael@0 | 5372 | mFirstPosition = 0; |
michael@0 | 5373 | mDataChanged = false; |
michael@0 | 5374 | mNeedSync = false; |
michael@0 | 5375 | mPendingSync = null; |
michael@0 | 5376 | mOldSelectedPosition = INVALID_POSITION; |
michael@0 | 5377 | mOldSelectedRowId = INVALID_ROW_ID; |
michael@0 | 5378 | |
michael@0 | 5379 | mOverScroll = 0; |
michael@0 | 5380 | |
michael@0 | 5381 | setSelectedPositionInt(INVALID_POSITION); |
michael@0 | 5382 | setNextSelectedPositionInt(INVALID_POSITION); |
michael@0 | 5383 | |
michael@0 | 5384 | mSelectorPosition = INVALID_POSITION; |
michael@0 | 5385 | mSelectorRect.setEmpty(); |
michael@0 | 5386 | |
michael@0 | 5387 | invalidate(); |
michael@0 | 5388 | } |
michael@0 | 5389 | |
michael@0 | 5390 | private void rememberSyncState() { |
michael@0 | 5391 | if (getChildCount() == 0) { |
michael@0 | 5392 | return; |
michael@0 | 5393 | } |
michael@0 | 5394 | |
michael@0 | 5395 | mNeedSync = true; |
michael@0 | 5396 | |
michael@0 | 5397 | if (mSelectedPosition >= 0) { |
michael@0 | 5398 | View child = getChildAt(mSelectedPosition - mFirstPosition); |
michael@0 | 5399 | |
michael@0 | 5400 | mSyncRowId = mNextSelectedRowId; |
michael@0 | 5401 | mSyncPosition = mNextSelectedPosition; |
michael@0 | 5402 | |
michael@0 | 5403 | if (child != null) { |
michael@0 | 5404 | mSpecificStart = (mIsVertical ? child.getTop() : child.getLeft()); |
michael@0 | 5405 | } |
michael@0 | 5406 | |
michael@0 | 5407 | mSyncMode = SYNC_SELECTED_POSITION; |
michael@0 | 5408 | } else { |
michael@0 | 5409 | // Sync the based on the offset of the first view |
michael@0 | 5410 | View child = getChildAt(0); |
michael@0 | 5411 | ListAdapter adapter = getAdapter(); |
michael@0 | 5412 | |
michael@0 | 5413 | if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { |
michael@0 | 5414 | mSyncRowId = adapter.getItemId(mFirstPosition); |
michael@0 | 5415 | } else { |
michael@0 | 5416 | mSyncRowId = NO_ID; |
michael@0 | 5417 | } |
michael@0 | 5418 | |
michael@0 | 5419 | mSyncPosition = mFirstPosition; |
michael@0 | 5420 | |
michael@0 | 5421 | if (child != null) { |
michael@0 | 5422 | mSpecificStart = child.getTop(); |
michael@0 | 5423 | } |
michael@0 | 5424 | |
michael@0 | 5425 | mSyncMode = SYNC_FIRST_POSITION; |
michael@0 | 5426 | } |
michael@0 | 5427 | } |
michael@0 | 5428 | |
michael@0 | 5429 | private ContextMenuInfo createContextMenuInfo(View view, int position, long id) { |
michael@0 | 5430 | return new AdapterContextMenuInfo(view, position, id); |
michael@0 | 5431 | } |
michael@0 | 5432 | |
michael@0 | 5433 | @TargetApi(11) |
michael@0 | 5434 | private void updateOnScreenCheckedViews() { |
michael@0 | 5435 | final int firstPos = mFirstPosition; |
michael@0 | 5436 | final int count = getChildCount(); |
michael@0 | 5437 | |
michael@0 | 5438 | final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion |
michael@0 | 5439 | >= Build.VERSION_CODES.HONEYCOMB; |
michael@0 | 5440 | |
michael@0 | 5441 | for (int i = 0; i < count; i++) { |
michael@0 | 5442 | final View child = getChildAt(i); |
michael@0 | 5443 | final int position = firstPos + i; |
michael@0 | 5444 | |
michael@0 | 5445 | if (child instanceof Checkable) { |
michael@0 | 5446 | ((Checkable) child).setChecked(mCheckStates.get(position)); |
michael@0 | 5447 | } else if (useActivated) { |
michael@0 | 5448 | child.setActivated(mCheckStates.get(position)); |
michael@0 | 5449 | } |
michael@0 | 5450 | } |
michael@0 | 5451 | } |
michael@0 | 5452 | |
michael@0 | 5453 | @Override |
michael@0 | 5454 | public boolean performItemClick(View view, int position, long id) { |
michael@0 | 5455 | boolean checkedStateChanged = false; |
michael@0 | 5456 | |
michael@0 | 5457 | if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { |
michael@0 | 5458 | boolean checked = !mCheckStates.get(position, false); |
michael@0 | 5459 | mCheckStates.put(position, checked); |
michael@0 | 5460 | |
michael@0 | 5461 | if (mCheckedIdStates != null && mAdapter.hasStableIds()) { |
michael@0 | 5462 | if (checked) { |
michael@0 | 5463 | mCheckedIdStates.put(mAdapter.getItemId(position), position); |
michael@0 | 5464 | } else { |
michael@0 | 5465 | mCheckedIdStates.delete(mAdapter.getItemId(position)); |
michael@0 | 5466 | } |
michael@0 | 5467 | } |
michael@0 | 5468 | |
michael@0 | 5469 | if (checked) { |
michael@0 | 5470 | mCheckedItemCount++; |
michael@0 | 5471 | } else { |
michael@0 | 5472 | mCheckedItemCount--; |
michael@0 | 5473 | } |
michael@0 | 5474 | |
michael@0 | 5475 | checkedStateChanged = true; |
michael@0 | 5476 | } else if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0) { |
michael@0 | 5477 | boolean checked = !mCheckStates.get(position, false); |
michael@0 | 5478 | if (checked) { |
michael@0 | 5479 | mCheckStates.clear(); |
michael@0 | 5480 | mCheckStates.put(position, true); |
michael@0 | 5481 | |
michael@0 | 5482 | if (mCheckedIdStates != null && mAdapter.hasStableIds()) { |
michael@0 | 5483 | mCheckedIdStates.clear(); |
michael@0 | 5484 | mCheckedIdStates.put(mAdapter.getItemId(position), position); |
michael@0 | 5485 | } |
michael@0 | 5486 | |
michael@0 | 5487 | mCheckedItemCount = 1; |
michael@0 | 5488 | } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { |
michael@0 | 5489 | mCheckedItemCount = 0; |
michael@0 | 5490 | } |
michael@0 | 5491 | |
michael@0 | 5492 | checkedStateChanged = true; |
michael@0 | 5493 | } |
michael@0 | 5494 | |
michael@0 | 5495 | if (checkedStateChanged) { |
michael@0 | 5496 | updateOnScreenCheckedViews(); |
michael@0 | 5497 | } |
michael@0 | 5498 | |
michael@0 | 5499 | return super.performItemClick(view, position, id); |
michael@0 | 5500 | } |
michael@0 | 5501 | |
michael@0 | 5502 | private boolean performLongPress(final View child, |
michael@0 | 5503 | final int longPressPosition, final long longPressId) { |
michael@0 | 5504 | // CHOICE_MODE_MULTIPLE_MODAL takes over long press. |
michael@0 | 5505 | boolean handled = false; |
michael@0 | 5506 | |
michael@0 | 5507 | OnItemLongClickListener listener = getOnItemLongClickListener(); |
michael@0 | 5508 | if (listener != null) { |
michael@0 | 5509 | handled = listener.onItemLongClick(TwoWayView.this, child, |
michael@0 | 5510 | longPressPosition, longPressId); |
michael@0 | 5511 | } |
michael@0 | 5512 | |
michael@0 | 5513 | if (!handled) { |
michael@0 | 5514 | mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); |
michael@0 | 5515 | handled = super.showContextMenuForChild(TwoWayView.this); |
michael@0 | 5516 | } |
michael@0 | 5517 | |
michael@0 | 5518 | if (handled) { |
michael@0 | 5519 | performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); |
michael@0 | 5520 | } |
michael@0 | 5521 | |
michael@0 | 5522 | return handled; |
michael@0 | 5523 | } |
michael@0 | 5524 | |
michael@0 | 5525 | @Override |
michael@0 | 5526 | protected LayoutParams generateDefaultLayoutParams() { |
michael@0 | 5527 | if (mIsVertical) { |
michael@0 | 5528 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); |
michael@0 | 5529 | } else { |
michael@0 | 5530 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); |
michael@0 | 5531 | } |
michael@0 | 5532 | } |
michael@0 | 5533 | |
michael@0 | 5534 | @Override |
michael@0 | 5535 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { |
michael@0 | 5536 | return new LayoutParams(lp); |
michael@0 | 5537 | } |
michael@0 | 5538 | |
michael@0 | 5539 | @Override |
michael@0 | 5540 | protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) { |
michael@0 | 5541 | return lp instanceof LayoutParams; |
michael@0 | 5542 | } |
michael@0 | 5543 | |
michael@0 | 5544 | @Override |
michael@0 | 5545 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { |
michael@0 | 5546 | return new LayoutParams(getContext(), attrs); |
michael@0 | 5547 | } |
michael@0 | 5548 | |
michael@0 | 5549 | @Override |
michael@0 | 5550 | protected ContextMenuInfo getContextMenuInfo() { |
michael@0 | 5551 | return mContextMenuInfo; |
michael@0 | 5552 | } |
michael@0 | 5553 | |
michael@0 | 5554 | @Override |
michael@0 | 5555 | public Parcelable onSaveInstanceState() { |
michael@0 | 5556 | Parcelable superState = super.onSaveInstanceState(); |
michael@0 | 5557 | SavedState ss = new SavedState(superState); |
michael@0 | 5558 | |
michael@0 | 5559 | if (mPendingSync != null) { |
michael@0 | 5560 | ss.selectedId = mPendingSync.selectedId; |
michael@0 | 5561 | ss.firstId = mPendingSync.firstId; |
michael@0 | 5562 | ss.viewStart = mPendingSync.viewStart; |
michael@0 | 5563 | ss.position = mPendingSync.position; |
michael@0 | 5564 | ss.height = mPendingSync.height; |
michael@0 | 5565 | |
michael@0 | 5566 | return ss; |
michael@0 | 5567 | } |
michael@0 | 5568 | |
michael@0 | 5569 | boolean haveChildren = (getChildCount() > 0 && mItemCount > 0); |
michael@0 | 5570 | long selectedId = getSelectedItemId(); |
michael@0 | 5571 | ss.selectedId = selectedId; |
michael@0 | 5572 | ss.height = getHeight(); |
michael@0 | 5573 | |
michael@0 | 5574 | if (selectedId >= 0) { |
michael@0 | 5575 | ss.viewStart = mSelectedStart; |
michael@0 | 5576 | ss.position = getSelectedItemPosition(); |
michael@0 | 5577 | ss.firstId = INVALID_POSITION; |
michael@0 | 5578 | } else if (haveChildren && mFirstPosition > 0) { |
michael@0 | 5579 | // Remember the position of the first child. |
michael@0 | 5580 | // We only do this if we are not currently at the top of |
michael@0 | 5581 | // the list, for two reasons: |
michael@0 | 5582 | // |
michael@0 | 5583 | // (1) The list may be in the process of becoming empty, in |
michael@0 | 5584 | // which case mItemCount may not be 0, but if we try to |
michael@0 | 5585 | // ask for any information about position 0 we will crash. |
michael@0 | 5586 | // |
michael@0 | 5587 | // (2) Being "at the top" seems like a special case, anyway, |
michael@0 | 5588 | // and the user wouldn't expect to end up somewhere else when |
michael@0 | 5589 | // they revisit the list even if its content has changed. |
michael@0 | 5590 | |
michael@0 | 5591 | View child = getChildAt(0); |
michael@0 | 5592 | ss.viewStart = (mIsVertical ? child.getTop() : child.getLeft()); |
michael@0 | 5593 | |
michael@0 | 5594 | int firstPos = mFirstPosition; |
michael@0 | 5595 | if (firstPos >= mItemCount) { |
michael@0 | 5596 | firstPos = mItemCount - 1; |
michael@0 | 5597 | } |
michael@0 | 5598 | |
michael@0 | 5599 | ss.position = firstPos; |
michael@0 | 5600 | ss.firstId = mAdapter.getItemId(firstPos); |
michael@0 | 5601 | } else { |
michael@0 | 5602 | ss.viewStart = 0; |
michael@0 | 5603 | ss.firstId = INVALID_POSITION; |
michael@0 | 5604 | ss.position = 0; |
michael@0 | 5605 | } |
michael@0 | 5606 | |
michael@0 | 5607 | if (mCheckStates != null) { |
michael@0 | 5608 | ss.checkState = cloneCheckStates(); |
michael@0 | 5609 | } |
michael@0 | 5610 | |
michael@0 | 5611 | if (mCheckedIdStates != null) { |
michael@0 | 5612 | final LongSparseArray<Integer> idState = new LongSparseArray<Integer>(); |
michael@0 | 5613 | |
michael@0 | 5614 | final int count = mCheckedIdStates.size(); |
michael@0 | 5615 | for (int i = 0; i < count; i++) { |
michael@0 | 5616 | idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i)); |
michael@0 | 5617 | } |
michael@0 | 5618 | |
michael@0 | 5619 | ss.checkIdState = idState; |
michael@0 | 5620 | } |
michael@0 | 5621 | |
michael@0 | 5622 | ss.checkedItemCount = mCheckedItemCount; |
michael@0 | 5623 | |
michael@0 | 5624 | return ss; |
michael@0 | 5625 | } |
michael@0 | 5626 | |
michael@0 | 5627 | @Override |
michael@0 | 5628 | public void onRestoreInstanceState(Parcelable state) { |
michael@0 | 5629 | SavedState ss = (SavedState) state; |
michael@0 | 5630 | super.onRestoreInstanceState(ss.getSuperState()); |
michael@0 | 5631 | |
michael@0 | 5632 | mDataChanged = true; |
michael@0 | 5633 | mSyncHeight = ss.height; |
michael@0 | 5634 | |
michael@0 | 5635 | if (ss.selectedId >= 0) { |
michael@0 | 5636 | mNeedSync = true; |
michael@0 | 5637 | mPendingSync = ss; |
michael@0 | 5638 | mSyncRowId = ss.selectedId; |
michael@0 | 5639 | mSyncPosition = ss.position; |
michael@0 | 5640 | mSpecificStart = ss.viewStart; |
michael@0 | 5641 | mSyncMode = SYNC_SELECTED_POSITION; |
michael@0 | 5642 | } else if (ss.firstId >= 0) { |
michael@0 | 5643 | setSelectedPositionInt(INVALID_POSITION); |
michael@0 | 5644 | |
michael@0 | 5645 | // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync |
michael@0 | 5646 | setNextSelectedPositionInt(INVALID_POSITION); |
michael@0 | 5647 | |
michael@0 | 5648 | mSelectorPosition = INVALID_POSITION; |
michael@0 | 5649 | mNeedSync = true; |
michael@0 | 5650 | mPendingSync = ss; |
michael@0 | 5651 | mSyncRowId = ss.firstId; |
michael@0 | 5652 | mSyncPosition = ss.position; |
michael@0 | 5653 | mSpecificStart = ss.viewStart; |
michael@0 | 5654 | mSyncMode = SYNC_FIRST_POSITION; |
michael@0 | 5655 | } |
michael@0 | 5656 | |
michael@0 | 5657 | if (ss.checkState != null) { |
michael@0 | 5658 | mCheckStates = ss.checkState; |
michael@0 | 5659 | } |
michael@0 | 5660 | |
michael@0 | 5661 | if (ss.checkIdState != null) { |
michael@0 | 5662 | mCheckedIdStates = ss.checkIdState; |
michael@0 | 5663 | } |
michael@0 | 5664 | |
michael@0 | 5665 | mCheckedItemCount = ss.checkedItemCount; |
michael@0 | 5666 | |
michael@0 | 5667 | requestLayout(); |
michael@0 | 5668 | } |
michael@0 | 5669 | |
michael@0 | 5670 | public static class LayoutParams extends ViewGroup.LayoutParams { |
michael@0 | 5671 | /** |
michael@0 | 5672 | * Type of this view as reported by the adapter |
michael@0 | 5673 | */ |
michael@0 | 5674 | int viewType; |
michael@0 | 5675 | |
michael@0 | 5676 | /** |
michael@0 | 5677 | * The stable ID of the item this view displays |
michael@0 | 5678 | */ |
michael@0 | 5679 | long id = -1; |
michael@0 | 5680 | |
michael@0 | 5681 | /** |
michael@0 | 5682 | * The position the view was removed from when pulled out of the |
michael@0 | 5683 | * scrap heap. |
michael@0 | 5684 | * @hide |
michael@0 | 5685 | */ |
michael@0 | 5686 | int scrappedFromPosition; |
michael@0 | 5687 | |
michael@0 | 5688 | /** |
michael@0 | 5689 | * When a TwoWayView is measured with an AT_MOST measure spec, it needs |
michael@0 | 5690 | * to obtain children views to measure itself. When doing so, the children |
michael@0 | 5691 | * are not attached to the window, but put in the recycler which assumes |
michael@0 | 5692 | * they've been attached before. Setting this flag will force the reused |
michael@0 | 5693 | * view to be attached to the window rather than just attached to the |
michael@0 | 5694 | * parent. |
michael@0 | 5695 | */ |
michael@0 | 5696 | boolean forceAdd; |
michael@0 | 5697 | |
michael@0 | 5698 | public LayoutParams(int width, int height) { |
michael@0 | 5699 | super(width, height); |
michael@0 | 5700 | |
michael@0 | 5701 | if (this.width == MATCH_PARENT) { |
michael@0 | 5702 | Log.w(LOGTAG, "Constructing LayoutParams with width FILL_PARENT " + |
michael@0 | 5703 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5704 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5705 | this.width = WRAP_CONTENT; |
michael@0 | 5706 | } |
michael@0 | 5707 | |
michael@0 | 5708 | if (this.height == MATCH_PARENT) { |
michael@0 | 5709 | Log.w(LOGTAG, "Constructing LayoutParams with height FILL_PARENT " + |
michael@0 | 5710 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5711 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5712 | this.height = WRAP_CONTENT; |
michael@0 | 5713 | } |
michael@0 | 5714 | } |
michael@0 | 5715 | |
michael@0 | 5716 | public LayoutParams(Context c, AttributeSet attrs) { |
michael@0 | 5717 | super(c, attrs); |
michael@0 | 5718 | |
michael@0 | 5719 | if (this.width == MATCH_PARENT) { |
michael@0 | 5720 | Log.w(LOGTAG, "Inflation setting LayoutParams width to MATCH_PARENT - " + |
michael@0 | 5721 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5722 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5723 | this.width = MATCH_PARENT; |
michael@0 | 5724 | } |
michael@0 | 5725 | |
michael@0 | 5726 | if (this.height == MATCH_PARENT) { |
michael@0 | 5727 | Log.w(LOGTAG, "Inflation setting LayoutParams height to MATCH_PARENT - " + |
michael@0 | 5728 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5729 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5730 | this.height = WRAP_CONTENT; |
michael@0 | 5731 | } |
michael@0 | 5732 | } |
michael@0 | 5733 | |
michael@0 | 5734 | public LayoutParams(ViewGroup.LayoutParams other) { |
michael@0 | 5735 | super(other); |
michael@0 | 5736 | |
michael@0 | 5737 | if (this.width == MATCH_PARENT) { |
michael@0 | 5738 | Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " + |
michael@0 | 5739 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5740 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5741 | this.width = WRAP_CONTENT; |
michael@0 | 5742 | } |
michael@0 | 5743 | |
michael@0 | 5744 | if (this.height == MATCH_PARENT) { |
michael@0 | 5745 | Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " + |
michael@0 | 5746 | "does not make much sense as the view might change orientation. " + |
michael@0 | 5747 | "Falling back to WRAP_CONTENT"); |
michael@0 | 5748 | this.height = WRAP_CONTENT; |
michael@0 | 5749 | } |
michael@0 | 5750 | } |
michael@0 | 5751 | } |
michael@0 | 5752 | |
michael@0 | 5753 | class RecycleBin { |
michael@0 | 5754 | private RecyclerListener mRecyclerListener; |
michael@0 | 5755 | private int mFirstActivePosition; |
michael@0 | 5756 | private View[] mActiveViews = new View[0]; |
michael@0 | 5757 | private ArrayList<View>[] mScrapViews; |
michael@0 | 5758 | private int mViewTypeCount; |
michael@0 | 5759 | private ArrayList<View> mCurrentScrap; |
michael@0 | 5760 | private SparseArrayCompat<View> mTransientStateViews; |
michael@0 | 5761 | |
michael@0 | 5762 | public void setViewTypeCount(int viewTypeCount) { |
michael@0 | 5763 | if (viewTypeCount < 1) { |
michael@0 | 5764 | throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); |
michael@0 | 5765 | } |
michael@0 | 5766 | |
michael@0 | 5767 | @SuppressWarnings({"unchecked", "rawtypes"}) |
michael@0 | 5768 | ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; |
michael@0 | 5769 | for (int i = 0; i < viewTypeCount; i++) { |
michael@0 | 5770 | scrapViews[i] = new ArrayList<View>(); |
michael@0 | 5771 | } |
michael@0 | 5772 | |
michael@0 | 5773 | mViewTypeCount = viewTypeCount; |
michael@0 | 5774 | mCurrentScrap = scrapViews[0]; |
michael@0 | 5775 | mScrapViews = scrapViews; |
michael@0 | 5776 | } |
michael@0 | 5777 | |
michael@0 | 5778 | public void markChildrenDirty() { |
michael@0 | 5779 | if (mViewTypeCount == 1) { |
michael@0 | 5780 | final ArrayList<View> scrap = mCurrentScrap; |
michael@0 | 5781 | final int scrapCount = scrap.size(); |
michael@0 | 5782 | |
michael@0 | 5783 | for (int i = 0; i < scrapCount; i++) { |
michael@0 | 5784 | scrap.get(i).forceLayout(); |
michael@0 | 5785 | } |
michael@0 | 5786 | } else { |
michael@0 | 5787 | final int typeCount = mViewTypeCount; |
michael@0 | 5788 | for (int i = 0; i < typeCount; i++) { |
michael@0 | 5789 | final ArrayList<View> scrap = mScrapViews[i]; |
michael@0 | 5790 | final int scrapCount = scrap.size(); |
michael@0 | 5791 | |
michael@0 | 5792 | for (int j = 0; j < scrapCount; j++) { |
michael@0 | 5793 | scrap.get(j).forceLayout(); |
michael@0 | 5794 | } |
michael@0 | 5795 | } |
michael@0 | 5796 | } |
michael@0 | 5797 | |
michael@0 | 5798 | if (mTransientStateViews != null) { |
michael@0 | 5799 | final int count = mTransientStateViews.size(); |
michael@0 | 5800 | for (int i = 0; i < count; i++) { |
michael@0 | 5801 | mTransientStateViews.valueAt(i).forceLayout(); |
michael@0 | 5802 | } |
michael@0 | 5803 | } |
michael@0 | 5804 | } |
michael@0 | 5805 | |
michael@0 | 5806 | public boolean shouldRecycleViewType(int viewType) { |
michael@0 | 5807 | return viewType >= 0; |
michael@0 | 5808 | } |
michael@0 | 5809 | |
michael@0 | 5810 | void clear() { |
michael@0 | 5811 | if (mViewTypeCount == 1) { |
michael@0 | 5812 | final ArrayList<View> scrap = mCurrentScrap; |
michael@0 | 5813 | final int scrapCount = scrap.size(); |
michael@0 | 5814 | |
michael@0 | 5815 | for (int i = 0; i < scrapCount; i++) { |
michael@0 | 5816 | removeDetachedView(scrap.remove(scrapCount - 1 - i), false); |
michael@0 | 5817 | } |
michael@0 | 5818 | } else { |
michael@0 | 5819 | final int typeCount = mViewTypeCount; |
michael@0 | 5820 | for (int i = 0; i < typeCount; i++) { |
michael@0 | 5821 | final ArrayList<View> scrap = mScrapViews[i]; |
michael@0 | 5822 | final int scrapCount = scrap.size(); |
michael@0 | 5823 | |
michael@0 | 5824 | for (int j = 0; j < scrapCount; j++) { |
michael@0 | 5825 | removeDetachedView(scrap.remove(scrapCount - 1 - j), false); |
michael@0 | 5826 | } |
michael@0 | 5827 | } |
michael@0 | 5828 | } |
michael@0 | 5829 | |
michael@0 | 5830 | if (mTransientStateViews != null) { |
michael@0 | 5831 | mTransientStateViews.clear(); |
michael@0 | 5832 | } |
michael@0 | 5833 | } |
michael@0 | 5834 | |
michael@0 | 5835 | void fillActiveViews(int childCount, int firstActivePosition) { |
michael@0 | 5836 | if (mActiveViews.length < childCount) { |
michael@0 | 5837 | mActiveViews = new View[childCount]; |
michael@0 | 5838 | } |
michael@0 | 5839 | |
michael@0 | 5840 | mFirstActivePosition = firstActivePosition; |
michael@0 | 5841 | |
michael@0 | 5842 | final View[] activeViews = mActiveViews; |
michael@0 | 5843 | for (int i = 0; i < childCount; i++) { |
michael@0 | 5844 | View child = getChildAt(i); |
michael@0 | 5845 | |
michael@0 | 5846 | // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. |
michael@0 | 5847 | // However, we will NOT place them into scrap views. |
michael@0 | 5848 | activeViews[i] = child; |
michael@0 | 5849 | } |
michael@0 | 5850 | } |
michael@0 | 5851 | |
michael@0 | 5852 | View getActiveView(int position) { |
michael@0 | 5853 | final int index = position - mFirstActivePosition; |
michael@0 | 5854 | final View[] activeViews = mActiveViews; |
michael@0 | 5855 | |
michael@0 | 5856 | if (index >= 0 && index < activeViews.length) { |
michael@0 | 5857 | final View match = activeViews[index]; |
michael@0 | 5858 | activeViews[index] = null; |
michael@0 | 5859 | |
michael@0 | 5860 | return match; |
michael@0 | 5861 | } |
michael@0 | 5862 | |
michael@0 | 5863 | return null; |
michael@0 | 5864 | } |
michael@0 | 5865 | |
michael@0 | 5866 | View getTransientStateView(int position) { |
michael@0 | 5867 | if (mTransientStateViews == null) { |
michael@0 | 5868 | return null; |
michael@0 | 5869 | } |
michael@0 | 5870 | |
michael@0 | 5871 | final int index = mTransientStateViews.indexOfKey(position); |
michael@0 | 5872 | if (index < 0) { |
michael@0 | 5873 | return null; |
michael@0 | 5874 | } |
michael@0 | 5875 | |
michael@0 | 5876 | final View result = mTransientStateViews.valueAt(index); |
michael@0 | 5877 | mTransientStateViews.removeAt(index); |
michael@0 | 5878 | |
michael@0 | 5879 | return result; |
michael@0 | 5880 | } |
michael@0 | 5881 | |
michael@0 | 5882 | void clearTransientStateViews() { |
michael@0 | 5883 | if (mTransientStateViews != null) { |
michael@0 | 5884 | mTransientStateViews.clear(); |
michael@0 | 5885 | } |
michael@0 | 5886 | } |
michael@0 | 5887 | |
michael@0 | 5888 | View getScrapView(int position) { |
michael@0 | 5889 | if (mViewTypeCount == 1) { |
michael@0 | 5890 | return retrieveFromScrap(mCurrentScrap, position); |
michael@0 | 5891 | } else { |
michael@0 | 5892 | int whichScrap = mAdapter.getItemViewType(position); |
michael@0 | 5893 | if (whichScrap >= 0 && whichScrap < mScrapViews.length) { |
michael@0 | 5894 | return retrieveFromScrap(mScrapViews[whichScrap], position); |
michael@0 | 5895 | } |
michael@0 | 5896 | } |
michael@0 | 5897 | |
michael@0 | 5898 | return null; |
michael@0 | 5899 | } |
michael@0 | 5900 | |
michael@0 | 5901 | @TargetApi(14) |
michael@0 | 5902 | void addScrapView(View scrap, int position) { |
michael@0 | 5903 | LayoutParams lp = (LayoutParams) scrap.getLayoutParams(); |
michael@0 | 5904 | if (lp == null) { |
michael@0 | 5905 | return; |
michael@0 | 5906 | } |
michael@0 | 5907 | |
michael@0 | 5908 | lp.scrappedFromPosition = position; |
michael@0 | 5909 | |
michael@0 | 5910 | final int viewType = lp.viewType; |
michael@0 | 5911 | final boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap); |
michael@0 | 5912 | |
michael@0 | 5913 | // Don't put views that should be ignored into the scrap heap |
michael@0 | 5914 | if (!shouldRecycleViewType(viewType) || scrapHasTransientState) { |
michael@0 | 5915 | if (scrapHasTransientState) { |
michael@0 | 5916 | if (mTransientStateViews == null) { |
michael@0 | 5917 | mTransientStateViews = new SparseArrayCompat<View>(); |
michael@0 | 5918 | } |
michael@0 | 5919 | |
michael@0 | 5920 | mTransientStateViews.put(position, scrap); |
michael@0 | 5921 | } |
michael@0 | 5922 | |
michael@0 | 5923 | return; |
michael@0 | 5924 | } |
michael@0 | 5925 | |
michael@0 | 5926 | if (mViewTypeCount == 1) { |
michael@0 | 5927 | mCurrentScrap.add(scrap); |
michael@0 | 5928 | } else { |
michael@0 | 5929 | mScrapViews[viewType].add(scrap); |
michael@0 | 5930 | } |
michael@0 | 5931 | |
michael@0 | 5932 | // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept |
michael@0 | 5933 | // null delegates. |
michael@0 | 5934 | if (Build.VERSION.SDK_INT >= 14) { |
michael@0 | 5935 | scrap.setAccessibilityDelegate(null); |
michael@0 | 5936 | } |
michael@0 | 5937 | |
michael@0 | 5938 | if (mRecyclerListener != null) { |
michael@0 | 5939 | mRecyclerListener.onMovedToScrapHeap(scrap); |
michael@0 | 5940 | } |
michael@0 | 5941 | } |
michael@0 | 5942 | |
michael@0 | 5943 | @TargetApi(14) |
michael@0 | 5944 | void scrapActiveViews() { |
michael@0 | 5945 | final View[] activeViews = mActiveViews; |
michael@0 | 5946 | final boolean multipleScraps = (mViewTypeCount > 1); |
michael@0 | 5947 | |
michael@0 | 5948 | ArrayList<View> scrapViews = mCurrentScrap; |
michael@0 | 5949 | final int count = activeViews.length; |
michael@0 | 5950 | |
michael@0 | 5951 | for (int i = count - 1; i >= 0; i--) { |
michael@0 | 5952 | final View victim = activeViews[i]; |
michael@0 | 5953 | if (victim != null) { |
michael@0 | 5954 | final LayoutParams lp = (LayoutParams) victim.getLayoutParams(); |
michael@0 | 5955 | int whichScrap = lp.viewType; |
michael@0 | 5956 | |
michael@0 | 5957 | activeViews[i] = null; |
michael@0 | 5958 | |
michael@0 | 5959 | final boolean scrapHasTransientState = ViewCompat.hasTransientState(victim); |
michael@0 | 5960 | if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) { |
michael@0 | 5961 | if (scrapHasTransientState) { |
michael@0 | 5962 | removeDetachedView(victim, false); |
michael@0 | 5963 | |
michael@0 | 5964 | if (mTransientStateViews == null) { |
michael@0 | 5965 | mTransientStateViews = new SparseArrayCompat<View>(); |
michael@0 | 5966 | } |
michael@0 | 5967 | |
michael@0 | 5968 | mTransientStateViews.put(mFirstActivePosition + i, victim); |
michael@0 | 5969 | } |
michael@0 | 5970 | |
michael@0 | 5971 | continue; |
michael@0 | 5972 | } |
michael@0 | 5973 | |
michael@0 | 5974 | if (multipleScraps) { |
michael@0 | 5975 | scrapViews = mScrapViews[whichScrap]; |
michael@0 | 5976 | } |
michael@0 | 5977 | |
michael@0 | 5978 | lp.scrappedFromPosition = mFirstActivePosition + i; |
michael@0 | 5979 | scrapViews.add(victim); |
michael@0 | 5980 | |
michael@0 | 5981 | // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept |
michael@0 | 5982 | // null delegates. |
michael@0 | 5983 | if (Build.VERSION.SDK_INT >= 14) { |
michael@0 | 5984 | victim.setAccessibilityDelegate(null); |
michael@0 | 5985 | } |
michael@0 | 5986 | |
michael@0 | 5987 | if (mRecyclerListener != null) { |
michael@0 | 5988 | mRecyclerListener.onMovedToScrapHeap(victim); |
michael@0 | 5989 | } |
michael@0 | 5990 | } |
michael@0 | 5991 | } |
michael@0 | 5992 | |
michael@0 | 5993 | pruneScrapViews(); |
michael@0 | 5994 | } |
michael@0 | 5995 | |
michael@0 | 5996 | private void pruneScrapViews() { |
michael@0 | 5997 | final int maxViews = mActiveViews.length; |
michael@0 | 5998 | final int viewTypeCount = mViewTypeCount; |
michael@0 | 5999 | final ArrayList<View>[] scrapViews = mScrapViews; |
michael@0 | 6000 | |
michael@0 | 6001 | for (int i = 0; i < viewTypeCount; ++i) { |
michael@0 | 6002 | final ArrayList<View> scrapPile = scrapViews[i]; |
michael@0 | 6003 | int size = scrapPile.size(); |
michael@0 | 6004 | final int extras = size - maxViews; |
michael@0 | 6005 | |
michael@0 | 6006 | size--; |
michael@0 | 6007 | |
michael@0 | 6008 | for (int j = 0; j < extras; j++) { |
michael@0 | 6009 | removeDetachedView(scrapPile.remove(size--), false); |
michael@0 | 6010 | } |
michael@0 | 6011 | } |
michael@0 | 6012 | |
michael@0 | 6013 | if (mTransientStateViews != null) { |
michael@0 | 6014 | for (int i = 0; i < mTransientStateViews.size(); i++) { |
michael@0 | 6015 | final View v = mTransientStateViews.valueAt(i); |
michael@0 | 6016 | if (!ViewCompat.hasTransientState(v)) { |
michael@0 | 6017 | mTransientStateViews.removeAt(i); |
michael@0 | 6018 | i--; |
michael@0 | 6019 | } |
michael@0 | 6020 | } |
michael@0 | 6021 | } |
michael@0 | 6022 | } |
michael@0 | 6023 | |
michael@0 | 6024 | void reclaimScrapViews(List<View> views) { |
michael@0 | 6025 | if (mViewTypeCount == 1) { |
michael@0 | 6026 | views.addAll(mCurrentScrap); |
michael@0 | 6027 | } else { |
michael@0 | 6028 | final int viewTypeCount = mViewTypeCount; |
michael@0 | 6029 | final ArrayList<View>[] scrapViews = mScrapViews; |
michael@0 | 6030 | |
michael@0 | 6031 | for (int i = 0; i < viewTypeCount; ++i) { |
michael@0 | 6032 | final ArrayList<View> scrapPile = scrapViews[i]; |
michael@0 | 6033 | views.addAll(scrapPile); |
michael@0 | 6034 | } |
michael@0 | 6035 | } |
michael@0 | 6036 | } |
michael@0 | 6037 | |
michael@0 | 6038 | View retrieveFromScrap(ArrayList<View> scrapViews, int position) { |
michael@0 | 6039 | int size = scrapViews.size(); |
michael@0 | 6040 | if (size <= 0) { |
michael@0 | 6041 | return null; |
michael@0 | 6042 | } |
michael@0 | 6043 | |
michael@0 | 6044 | for (int i = 0; i < size; i++) { |
michael@0 | 6045 | final View scrapView = scrapViews.get(i); |
michael@0 | 6046 | final LayoutParams lp = (LayoutParams) scrapView.getLayoutParams(); |
michael@0 | 6047 | |
michael@0 | 6048 | if (lp.scrappedFromPosition == position) { |
michael@0 | 6049 | scrapViews.remove(i); |
michael@0 | 6050 | return scrapView; |
michael@0 | 6051 | } |
michael@0 | 6052 | } |
michael@0 | 6053 | |
michael@0 | 6054 | return scrapViews.remove(size - 1); |
michael@0 | 6055 | } |
michael@0 | 6056 | } |
michael@0 | 6057 | |
michael@0 | 6058 | @Override |
michael@0 | 6059 | public void setEmptyView(View emptyView) { |
michael@0 | 6060 | super.setEmptyView(emptyView); |
michael@0 | 6061 | mEmptyView = emptyView; |
michael@0 | 6062 | updateEmptyStatus(); |
michael@0 | 6063 | } |
michael@0 | 6064 | |
michael@0 | 6065 | @Override |
michael@0 | 6066 | public void setFocusable(boolean focusable) { |
michael@0 | 6067 | final ListAdapter adapter = getAdapter(); |
michael@0 | 6068 | final boolean empty = (adapter == null || adapter.getCount() == 0); |
michael@0 | 6069 | |
michael@0 | 6070 | mDesiredFocusableState = focusable; |
michael@0 | 6071 | if (!focusable) { |
michael@0 | 6072 | mDesiredFocusableInTouchModeState = false; |
michael@0 | 6073 | } |
michael@0 | 6074 | |
michael@0 | 6075 | super.setFocusable(focusable && !empty); |
michael@0 | 6076 | } |
michael@0 | 6077 | |
michael@0 | 6078 | @Override |
michael@0 | 6079 | public void setFocusableInTouchMode(boolean focusable) { |
michael@0 | 6080 | final ListAdapter adapter = getAdapter(); |
michael@0 | 6081 | final boolean empty = (adapter == null || adapter.getCount() == 0); |
michael@0 | 6082 | |
michael@0 | 6083 | mDesiredFocusableInTouchModeState = focusable; |
michael@0 | 6084 | if (focusable) { |
michael@0 | 6085 | mDesiredFocusableState = true; |
michael@0 | 6086 | } |
michael@0 | 6087 | |
michael@0 | 6088 | super.setFocusableInTouchMode(focusable && !empty); |
michael@0 | 6089 | } |
michael@0 | 6090 | |
michael@0 | 6091 | private void checkFocus() { |
michael@0 | 6092 | final ListAdapter adapter = getAdapter(); |
michael@0 | 6093 | final boolean focusable = (adapter != null && adapter.getCount() > 0); |
michael@0 | 6094 | |
michael@0 | 6095 | // The order in which we set focusable in touch mode/focusable may matter |
michael@0 | 6096 | // for the client, see View.setFocusableInTouchMode() comments for more |
michael@0 | 6097 | // details |
michael@0 | 6098 | super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); |
michael@0 | 6099 | super.setFocusable(focusable && mDesiredFocusableState); |
michael@0 | 6100 | |
michael@0 | 6101 | if (mEmptyView != null) { |
michael@0 | 6102 | updateEmptyStatus(); |
michael@0 | 6103 | } |
michael@0 | 6104 | } |
michael@0 | 6105 | |
michael@0 | 6106 | private void updateEmptyStatus() { |
michael@0 | 6107 | final boolean isEmpty = (mAdapter == null || mAdapter.isEmpty()); |
michael@0 | 6108 | |
michael@0 | 6109 | if (isEmpty) { |
michael@0 | 6110 | if (mEmptyView != null) { |
michael@0 | 6111 | mEmptyView.setVisibility(View.VISIBLE); |
michael@0 | 6112 | setVisibility(View.GONE); |
michael@0 | 6113 | } else { |
michael@0 | 6114 | // If the caller just removed our empty view, make sure the list |
michael@0 | 6115 | // view is visible |
michael@0 | 6116 | setVisibility(View.VISIBLE); |
michael@0 | 6117 | } |
michael@0 | 6118 | |
michael@0 | 6119 | // We are now GONE, so pending layouts will not be dispatched. |
michael@0 | 6120 | // Force one here to make sure that the state of the list matches |
michael@0 | 6121 | // the state of the adapter. |
michael@0 | 6122 | if (mDataChanged) { |
michael@0 | 6123 | onLayout(false, getLeft(), getTop(), getRight(), getBottom()); |
michael@0 | 6124 | } |
michael@0 | 6125 | } else { |
michael@0 | 6126 | if (mEmptyView != null) { |
michael@0 | 6127 | mEmptyView.setVisibility(View.GONE); |
michael@0 | 6128 | } |
michael@0 | 6129 | |
michael@0 | 6130 | setVisibility(View.VISIBLE); |
michael@0 | 6131 | } |
michael@0 | 6132 | } |
michael@0 | 6133 | |
michael@0 | 6134 | private class AdapterDataSetObserver extends DataSetObserver { |
michael@0 | 6135 | private Parcelable mInstanceState = null; |
michael@0 | 6136 | |
michael@0 | 6137 | @Override |
michael@0 | 6138 | public void onChanged() { |
michael@0 | 6139 | mDataChanged = true; |
michael@0 | 6140 | mOldItemCount = mItemCount; |
michael@0 | 6141 | mItemCount = getAdapter().getCount(); |
michael@0 | 6142 | |
michael@0 | 6143 | // Detect the case where a cursor that was previously invalidated has |
michael@0 | 6144 | // been re-populated with new data. |
michael@0 | 6145 | if (TwoWayView.this.mHasStableIds && mInstanceState != null |
michael@0 | 6146 | && mOldItemCount == 0 && mItemCount > 0) { |
michael@0 | 6147 | TwoWayView.this.onRestoreInstanceState(mInstanceState); |
michael@0 | 6148 | mInstanceState = null; |
michael@0 | 6149 | } else { |
michael@0 | 6150 | rememberSyncState(); |
michael@0 | 6151 | } |
michael@0 | 6152 | |
michael@0 | 6153 | checkFocus(); |
michael@0 | 6154 | requestLayout(); |
michael@0 | 6155 | } |
michael@0 | 6156 | |
michael@0 | 6157 | @Override |
michael@0 | 6158 | public void onInvalidated() { |
michael@0 | 6159 | mDataChanged = true; |
michael@0 | 6160 | |
michael@0 | 6161 | if (TwoWayView.this.mHasStableIds) { |
michael@0 | 6162 | // Remember the current state for the case where our hosting activity is being |
michael@0 | 6163 | // stopped and later restarted |
michael@0 | 6164 | mInstanceState = TwoWayView.this.onSaveInstanceState(); |
michael@0 | 6165 | } |
michael@0 | 6166 | |
michael@0 | 6167 | // Data is invalid so we should reset our state |
michael@0 | 6168 | mOldItemCount = mItemCount; |
michael@0 | 6169 | mItemCount = 0; |
michael@0 | 6170 | |
michael@0 | 6171 | mSelectedPosition = INVALID_POSITION; |
michael@0 | 6172 | mSelectedRowId = INVALID_ROW_ID; |
michael@0 | 6173 | |
michael@0 | 6174 | mNextSelectedPosition = INVALID_POSITION; |
michael@0 | 6175 | mNextSelectedRowId = INVALID_ROW_ID; |
michael@0 | 6176 | |
michael@0 | 6177 | mNeedSync = false; |
michael@0 | 6178 | |
michael@0 | 6179 | checkFocus(); |
michael@0 | 6180 | requestLayout(); |
michael@0 | 6181 | } |
michael@0 | 6182 | } |
michael@0 | 6183 | |
michael@0 | 6184 | static class SavedState extends BaseSavedState { |
michael@0 | 6185 | long selectedId; |
michael@0 | 6186 | long firstId; |
michael@0 | 6187 | int viewStart; |
michael@0 | 6188 | int position; |
michael@0 | 6189 | int height; |
michael@0 | 6190 | int checkedItemCount; |
michael@0 | 6191 | SparseBooleanArray checkState; |
michael@0 | 6192 | LongSparseArray<Integer> checkIdState; |
michael@0 | 6193 | |
michael@0 | 6194 | /** |
michael@0 | 6195 | * Constructor called from {@link TwoWayView#onSaveInstanceState()} |
michael@0 | 6196 | */ |
michael@0 | 6197 | SavedState(Parcelable superState) { |
michael@0 | 6198 | super(superState); |
michael@0 | 6199 | } |
michael@0 | 6200 | |
michael@0 | 6201 | /** |
michael@0 | 6202 | * Constructor called from {@link #CREATOR} |
michael@0 | 6203 | */ |
michael@0 | 6204 | private SavedState(Parcel in) { |
michael@0 | 6205 | super(in); |
michael@0 | 6206 | |
michael@0 | 6207 | selectedId = in.readLong(); |
michael@0 | 6208 | firstId = in.readLong(); |
michael@0 | 6209 | viewStart = in.readInt(); |
michael@0 | 6210 | position = in.readInt(); |
michael@0 | 6211 | height = in.readInt(); |
michael@0 | 6212 | |
michael@0 | 6213 | checkedItemCount = in.readInt(); |
michael@0 | 6214 | checkState = in.readSparseBooleanArray(); |
michael@0 | 6215 | |
michael@0 | 6216 | final int N = in.readInt(); |
michael@0 | 6217 | if (N > 0) { |
michael@0 | 6218 | checkIdState = new LongSparseArray<Integer>(); |
michael@0 | 6219 | for (int i = 0; i < N; i++) { |
michael@0 | 6220 | final long key = in.readLong(); |
michael@0 | 6221 | final int value = in.readInt(); |
michael@0 | 6222 | checkIdState.put(key, value); |
michael@0 | 6223 | } |
michael@0 | 6224 | } |
michael@0 | 6225 | } |
michael@0 | 6226 | |
michael@0 | 6227 | @Override |
michael@0 | 6228 | public void writeToParcel(Parcel out, int flags) { |
michael@0 | 6229 | super.writeToParcel(out, flags); |
michael@0 | 6230 | |
michael@0 | 6231 | out.writeLong(selectedId); |
michael@0 | 6232 | out.writeLong(firstId); |
michael@0 | 6233 | out.writeInt(viewStart); |
michael@0 | 6234 | out.writeInt(position); |
michael@0 | 6235 | out.writeInt(height); |
michael@0 | 6236 | |
michael@0 | 6237 | out.writeInt(checkedItemCount); |
michael@0 | 6238 | out.writeSparseBooleanArray(checkState); |
michael@0 | 6239 | |
michael@0 | 6240 | final int N = checkIdState != null ? checkIdState.size() : 0; |
michael@0 | 6241 | out.writeInt(N); |
michael@0 | 6242 | |
michael@0 | 6243 | for (int i = 0; i < N; i++) { |
michael@0 | 6244 | out.writeLong(checkIdState.keyAt(i)); |
michael@0 | 6245 | out.writeInt(checkIdState.valueAt(i)); |
michael@0 | 6246 | } |
michael@0 | 6247 | } |
michael@0 | 6248 | |
michael@0 | 6249 | @Override |
michael@0 | 6250 | public String toString() { |
michael@0 | 6251 | return "TwoWayView.SavedState{" |
michael@0 | 6252 | + Integer.toHexString(System.identityHashCode(this)) |
michael@0 | 6253 | + " selectedId=" + selectedId |
michael@0 | 6254 | + " firstId=" + firstId |
michael@0 | 6255 | + " viewStart=" + viewStart |
michael@0 | 6256 | + " height=" + height |
michael@0 | 6257 | + " position=" + position |
michael@0 | 6258 | + " checkState=" + checkState + "}"; |
michael@0 | 6259 | } |
michael@0 | 6260 | |
michael@0 | 6261 | public static final Parcelable.Creator<SavedState> CREATOR |
michael@0 | 6262 | = new Parcelable.Creator<SavedState>() { |
michael@0 | 6263 | @Override |
michael@0 | 6264 | public SavedState createFromParcel(Parcel in) { |
michael@0 | 6265 | return new SavedState(in); |
michael@0 | 6266 | } |
michael@0 | 6267 | |
michael@0 | 6268 | @Override |
michael@0 | 6269 | public SavedState[] newArray(int size) { |
michael@0 | 6270 | return new SavedState[size]; |
michael@0 | 6271 | } |
michael@0 | 6272 | }; |
michael@0 | 6273 | } |
michael@0 | 6274 | |
michael@0 | 6275 | private class SelectionNotifier implements Runnable { |
michael@0 | 6276 | @Override |
michael@0 | 6277 | public void run() { |
michael@0 | 6278 | if (mDataChanged) { |
michael@0 | 6279 | // Data has changed between when this SelectionNotifier |
michael@0 | 6280 | // was posted and now. We need to wait until the AdapterView |
michael@0 | 6281 | // has been synched to the new data. |
michael@0 | 6282 | if (mAdapter != null) { |
michael@0 | 6283 | post(this); |
michael@0 | 6284 | } |
michael@0 | 6285 | } else { |
michael@0 | 6286 | fireOnSelected(); |
michael@0 | 6287 | performAccessibilityActionsOnSelected(); |
michael@0 | 6288 | } |
michael@0 | 6289 | } |
michael@0 | 6290 | } |
michael@0 | 6291 | |
michael@0 | 6292 | private class WindowRunnnable { |
michael@0 | 6293 | private int mOriginalAttachCount; |
michael@0 | 6294 | |
michael@0 | 6295 | public void rememberWindowAttachCount() { |
michael@0 | 6296 | mOriginalAttachCount = getWindowAttachCount(); |
michael@0 | 6297 | } |
michael@0 | 6298 | |
michael@0 | 6299 | public boolean sameWindow() { |
michael@0 | 6300 | return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount; |
michael@0 | 6301 | } |
michael@0 | 6302 | } |
michael@0 | 6303 | |
michael@0 | 6304 | private class PerformClick extends WindowRunnnable implements Runnable { |
michael@0 | 6305 | int mClickMotionPosition; |
michael@0 | 6306 | |
michael@0 | 6307 | @Override |
michael@0 | 6308 | public void run() { |
michael@0 | 6309 | if (mDataChanged) { |
michael@0 | 6310 | return; |
michael@0 | 6311 | } |
michael@0 | 6312 | |
michael@0 | 6313 | final ListAdapter adapter = mAdapter; |
michael@0 | 6314 | final int motionPosition = mClickMotionPosition; |
michael@0 | 6315 | |
michael@0 | 6316 | if (adapter != null && mItemCount > 0 && |
michael@0 | 6317 | motionPosition != INVALID_POSITION && |
michael@0 | 6318 | motionPosition < adapter.getCount() && sameWindow()) { |
michael@0 | 6319 | |
michael@0 | 6320 | final View child = getChildAt(motionPosition - mFirstPosition); |
michael@0 | 6321 | if (child != null) { |
michael@0 | 6322 | performItemClick(child, motionPosition, adapter.getItemId(motionPosition)); |
michael@0 | 6323 | } |
michael@0 | 6324 | } |
michael@0 | 6325 | } |
michael@0 | 6326 | } |
michael@0 | 6327 | |
michael@0 | 6328 | private final class CheckForTap implements Runnable { |
michael@0 | 6329 | @Override |
michael@0 | 6330 | public void run() { |
michael@0 | 6331 | if (mTouchMode != TOUCH_MODE_DOWN) { |
michael@0 | 6332 | return; |
michael@0 | 6333 | } |
michael@0 | 6334 | |
michael@0 | 6335 | mTouchMode = TOUCH_MODE_TAP; |
michael@0 | 6336 | |
michael@0 | 6337 | final View child = getChildAt(mMotionPosition - mFirstPosition); |
michael@0 | 6338 | if (child != null && !child.hasFocusable()) { |
michael@0 | 6339 | mLayoutMode = LAYOUT_NORMAL; |
michael@0 | 6340 | |
michael@0 | 6341 | if (!mDataChanged) { |
michael@0 | 6342 | setPressed(true); |
michael@0 | 6343 | child.setPressed(true); |
michael@0 | 6344 | |
michael@0 | 6345 | layoutChildren(); |
michael@0 | 6346 | positionSelector(mMotionPosition, child); |
michael@0 | 6347 | refreshDrawableState(); |
michael@0 | 6348 | |
michael@0 | 6349 | positionSelector(mMotionPosition, child); |
michael@0 | 6350 | refreshDrawableState(); |
michael@0 | 6351 | |
michael@0 | 6352 | final boolean longClickable = isLongClickable(); |
michael@0 | 6353 | |
michael@0 | 6354 | if (mSelector != null) { |
michael@0 | 6355 | Drawable d = mSelector.getCurrent(); |
michael@0 | 6356 | |
michael@0 | 6357 | if (d != null && d instanceof TransitionDrawable) { |
michael@0 | 6358 | if (longClickable) { |
michael@0 | 6359 | final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); |
michael@0 | 6360 | ((TransitionDrawable) d).startTransition(longPressTimeout); |
michael@0 | 6361 | } else { |
michael@0 | 6362 | ((TransitionDrawable) d).resetTransition(); |
michael@0 | 6363 | } |
michael@0 | 6364 | } |
michael@0 | 6365 | } |
michael@0 | 6366 | |
michael@0 | 6367 | if (longClickable) { |
michael@0 | 6368 | triggerCheckForLongPress(); |
michael@0 | 6369 | } else { |
michael@0 | 6370 | mTouchMode = TOUCH_MODE_DONE_WAITING; |
michael@0 | 6371 | } |
michael@0 | 6372 | } else { |
michael@0 | 6373 | mTouchMode = TOUCH_MODE_DONE_WAITING; |
michael@0 | 6374 | } |
michael@0 | 6375 | } |
michael@0 | 6376 | } |
michael@0 | 6377 | } |
michael@0 | 6378 | |
michael@0 | 6379 | private class CheckForLongPress extends WindowRunnnable implements Runnable { |
michael@0 | 6380 | @Override |
michael@0 | 6381 | public void run() { |
michael@0 | 6382 | final int motionPosition = mMotionPosition; |
michael@0 | 6383 | final View child = getChildAt(motionPosition - mFirstPosition); |
michael@0 | 6384 | |
michael@0 | 6385 | if (child != null) { |
michael@0 | 6386 | final long longPressId = mAdapter.getItemId(mMotionPosition); |
michael@0 | 6387 | |
michael@0 | 6388 | boolean handled = false; |
michael@0 | 6389 | if (sameWindow() && !mDataChanged) { |
michael@0 | 6390 | handled = performLongPress(child, motionPosition, longPressId); |
michael@0 | 6391 | } |
michael@0 | 6392 | |
michael@0 | 6393 | if (handled) { |
michael@0 | 6394 | mTouchMode = TOUCH_MODE_REST; |
michael@0 | 6395 | setPressed(false); |
michael@0 | 6396 | child.setPressed(false); |
michael@0 | 6397 | } else { |
michael@0 | 6398 | mTouchMode = TOUCH_MODE_DONE_WAITING; |
michael@0 | 6399 | } |
michael@0 | 6400 | } |
michael@0 | 6401 | } |
michael@0 | 6402 | } |
michael@0 | 6403 | |
michael@0 | 6404 | private class CheckForKeyLongPress extends WindowRunnnable implements Runnable { |
michael@0 | 6405 | public void run() { |
michael@0 | 6406 | if (!isPressed() || mSelectedPosition < 0) { |
michael@0 | 6407 | return; |
michael@0 | 6408 | } |
michael@0 | 6409 | |
michael@0 | 6410 | final int index = mSelectedPosition - mFirstPosition; |
michael@0 | 6411 | final View v = getChildAt(index); |
michael@0 | 6412 | |
michael@0 | 6413 | if (!mDataChanged) { |
michael@0 | 6414 | boolean handled = false; |
michael@0 | 6415 | |
michael@0 | 6416 | if (sameWindow()) { |
michael@0 | 6417 | handled = performLongPress(v, mSelectedPosition, mSelectedRowId); |
michael@0 | 6418 | } |
michael@0 | 6419 | |
michael@0 | 6420 | if (handled) { |
michael@0 | 6421 | setPressed(false); |
michael@0 | 6422 | v.setPressed(false); |
michael@0 | 6423 | } |
michael@0 | 6424 | } else { |
michael@0 | 6425 | setPressed(false); |
michael@0 | 6426 | |
michael@0 | 6427 | if (v != null) { |
michael@0 | 6428 | v.setPressed(false); |
michael@0 | 6429 | } |
michael@0 | 6430 | } |
michael@0 | 6431 | } |
michael@0 | 6432 | } |
michael@0 | 6433 | |
michael@0 | 6434 | private static class ArrowScrollFocusResult { |
michael@0 | 6435 | private int mSelectedPosition; |
michael@0 | 6436 | private int mAmountToScroll; |
michael@0 | 6437 | |
michael@0 | 6438 | /** |
michael@0 | 6439 | * How {@link TwoWayView#arrowScrollFocused} returns its values. |
michael@0 | 6440 | */ |
michael@0 | 6441 | void populate(int selectedPosition, int amountToScroll) { |
michael@0 | 6442 | mSelectedPosition = selectedPosition; |
michael@0 | 6443 | mAmountToScroll = amountToScroll; |
michael@0 | 6444 | } |
michael@0 | 6445 | |
michael@0 | 6446 | public int getSelectedPosition() { |
michael@0 | 6447 | return mSelectedPosition; |
michael@0 | 6448 | } |
michael@0 | 6449 | |
michael@0 | 6450 | public int getAmountToScroll() { |
michael@0 | 6451 | return mAmountToScroll; |
michael@0 | 6452 | } |
michael@0 | 6453 | } |
michael@0 | 6454 | |
michael@0 | 6455 | private class ListItemAccessibilityDelegate extends AccessibilityDelegateCompat { |
michael@0 | 6456 | @Override |
michael@0 | 6457 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { |
michael@0 | 6458 | super.onInitializeAccessibilityNodeInfo(host, info); |
michael@0 | 6459 | |
michael@0 | 6460 | final int position = getPositionForView(host); |
michael@0 | 6461 | final ListAdapter adapter = getAdapter(); |
michael@0 | 6462 | |
michael@0 | 6463 | // Cannot perform actions on invalid items |
michael@0 | 6464 | if (position == INVALID_POSITION || adapter == null) { |
michael@0 | 6465 | return; |
michael@0 | 6466 | } |
michael@0 | 6467 | |
michael@0 | 6468 | // Cannot perform actions on disabled items |
michael@0 | 6469 | if (!isEnabled() || !adapter.isEnabled(position)) { |
michael@0 | 6470 | return; |
michael@0 | 6471 | } |
michael@0 | 6472 | |
michael@0 | 6473 | if (position == getSelectedItemPosition()) { |
michael@0 | 6474 | info.setSelected(true); |
michael@0 | 6475 | info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION); |
michael@0 | 6476 | } else { |
michael@0 | 6477 | info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT); |
michael@0 | 6478 | } |
michael@0 | 6479 | |
michael@0 | 6480 | if (isClickable()) { |
michael@0 | 6481 | info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); |
michael@0 | 6482 | info.setClickable(true); |
michael@0 | 6483 | } |
michael@0 | 6484 | |
michael@0 | 6485 | if (isLongClickable()) { |
michael@0 | 6486 | info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK); |
michael@0 | 6487 | info.setLongClickable(true); |
michael@0 | 6488 | } |
michael@0 | 6489 | } |
michael@0 | 6490 | |
michael@0 | 6491 | @Override |
michael@0 | 6492 | public boolean performAccessibilityAction(View host, int action, Bundle arguments) { |
michael@0 | 6493 | if (super.performAccessibilityAction(host, action, arguments)) { |
michael@0 | 6494 | return true; |
michael@0 | 6495 | } |
michael@0 | 6496 | |
michael@0 | 6497 | final int position = getPositionForView(host); |
michael@0 | 6498 | final ListAdapter adapter = getAdapter(); |
michael@0 | 6499 | |
michael@0 | 6500 | // Cannot perform actions on invalid items |
michael@0 | 6501 | if (position == INVALID_POSITION || adapter == null) { |
michael@0 | 6502 | return false; |
michael@0 | 6503 | } |
michael@0 | 6504 | |
michael@0 | 6505 | // Cannot perform actions on disabled items |
michael@0 | 6506 | if (!isEnabled() || !adapter.isEnabled(position)) { |
michael@0 | 6507 | return false; |
michael@0 | 6508 | } |
michael@0 | 6509 | |
michael@0 | 6510 | final long id = getItemIdAtPosition(position); |
michael@0 | 6511 | |
michael@0 | 6512 | switch (action) { |
michael@0 | 6513 | case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION: |
michael@0 | 6514 | if (getSelectedItemPosition() == position) { |
michael@0 | 6515 | setSelection(INVALID_POSITION); |
michael@0 | 6516 | return true; |
michael@0 | 6517 | } |
michael@0 | 6518 | return false; |
michael@0 | 6519 | |
michael@0 | 6520 | case AccessibilityNodeInfoCompat.ACTION_SELECT: |
michael@0 | 6521 | if (getSelectedItemPosition() != position) { |
michael@0 | 6522 | setSelection(position); |
michael@0 | 6523 | return true; |
michael@0 | 6524 | } |
michael@0 | 6525 | return false; |
michael@0 | 6526 | |
michael@0 | 6527 | case AccessibilityNodeInfoCompat.ACTION_CLICK: |
michael@0 | 6528 | if (isClickable()) { |
michael@0 | 6529 | return performItemClick(host, position, id); |
michael@0 | 6530 | } |
michael@0 | 6531 | return false; |
michael@0 | 6532 | |
michael@0 | 6533 | case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK: |
michael@0 | 6534 | if (isLongClickable()) { |
michael@0 | 6535 | return performLongPress(host, position, id); |
michael@0 | 6536 | } |
michael@0 | 6537 | return false; |
michael@0 | 6538 | } |
michael@0 | 6539 | |
michael@0 | 6540 | return false; |
michael@0 | 6541 | } |
michael@0 | 6542 | } |
michael@0 | 6543 | } |