From e425c8ddce059090fd923fad3b4e739e7a7c5e6d Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 18 Jan 2024 13:03:59 +0100 Subject: [PATCH] Revert "Build RecyclerView inline" This reverts commit 445dc9f25f7e8e48ee928945533489e239bbda2b. --- app/build.gradle | 4 +- .../recyclerview/widget/AdapterHelper.java | 776 - .../widget/AdapterListUpdateCallback.java | 65 - .../widget/AsyncDifferConfig.java | 148 - .../recyclerview/widget/AsyncListDiffer.java | 405 - .../recyclerview/widget/AsyncListUtil.java | 596 - .../widget/BatchingListUpdateCallback.java | 128 - .../recyclerview/widget/ChildHelper.java | 601 - .../recyclerview/widget/ConcatAdapter.java | 481 - .../widget/ConcatAdapterController.java | 528 - .../widget/DefaultItemAnimator.java | 674 - .../recyclerview/widget/DiffUtil.java | 1058 -- .../widget/DividerItemDecoration.java | 194 - .../recyclerview/widget/FastScroller.java | 588 - .../recyclerview/widget/GapWorker.java | 407 - .../widget/GridLayoutManager.java | 1450 -- .../recyclerview/widget/ItemTouchHelper.java | 2494 --- .../recyclerview/widget/ItemTouchUIUtil.java | 68 - .../widget/ItemTouchUIUtilImpl.java | 105 - .../recyclerview/widget/LayoutState.java | 114 - .../widget/LinearLayoutManager.java | 2624 --- .../widget/LinearSmoothScroller.java | 360 - .../recyclerview/widget/LinearSnapHelper.java | 276 - .../recyclerview/widget/ListAdapter.java | 190 - .../widget/ListUpdateCallback.java | 57 - .../widget/MessageThreadUtil.java | 294 - .../widget/NestedAdapterWrapper.java | 201 - .../recyclerview/widget/OpReorderer.java | 233 - .../widget/OrientationHelper.java | 446 - .../recyclerview/widget/PagerSnapHelper.java | 273 - .../recyclerview/widget/RecyclerView.java | 14454 ---------------- .../RecyclerViewAccessibilityDelegate.java | 272 - .../recyclerview/widget/ScrollbarHelper.java | 101 - .../widget/SimpleItemAnimator.java | 479 - .../recyclerview/widget/SnapHelper.java | 308 - .../recyclerview/widget/SortedList.java | 1015 -- .../widget/SortedListAdapterCallback.java | 67 - .../recyclerview/widget/StableIdStorage.java | 106 - .../widget/StaggeredGridLayoutManager.java | 3282 ---- .../recyclerview/widget/ThreadUtil.java | 49 - .../recyclerview/widget/TileList.java | 115 - .../recyclerview/widget/ViewBoundsCheck.java | 269 - .../recyclerview/widget/ViewInfoStore.java | 329 - .../recyclerview/widget/ViewTypeStorage.java | 197 - 44 files changed, 1 insertion(+), 36880 deletions(-) delete mode 100644 app/src/main/java/androidx/recyclerview/widget/AdapterHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/AdapterListUpdateCallback.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/AsyncDifferConfig.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/AsyncListUtil.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/BatchingListUpdateCallback.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ChildHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ConcatAdapterController.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/DefaultItemAnimator.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/DiffUtil.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/DividerItemDecoration.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/FastScroller.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/GapWorker.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtil.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/LayoutState.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/LinearSmoothScroller.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/LinearSnapHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ListAdapter.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ListUpdateCallback.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/MessageThreadUtil.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/OpReorderer.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/OrientationHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/RecyclerView.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ScrollbarHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/SimpleItemAnimator.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/SnapHelper.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/SortedList.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/SortedListAdapterCallback.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/StableIdStorage.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/StaggeredGridLayoutManager.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ThreadUtil.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/TileList.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ViewBoundsCheck.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ViewInfoStore.java delete mode 100644 app/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java diff --git a/app/build.gradle b/app/build.gradle index 756a43373d..88b0e572c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -608,9 +608,7 @@ dependencies { // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview-selection - //implementation "androidx.recyclerview:recyclerview:$recyclerview_version" - implementation "androidx.customview:customview:1.1.0" - implementation "androidx.customview:customview-poolingcontainer:1.0.0" + implementation "androidx.recyclerview:recyclerview:$recyclerview_version" //implementation "androidx.recyclerview:recyclerview-selection:1.1.0" // 1.2.0-alpha01 // https://mvnrepository.com/artifact/androidx.coordinatorlayout/coordinatorlayout diff --git a/app/src/main/java/androidx/recyclerview/widget/AdapterHelper.java b/app/src/main/java/androidx/recyclerview/widget/AdapterHelper.java deleted file mode 100644 index 004f7445ce..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/AdapterHelper.java +++ /dev/null @@ -1,776 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.util.Log; - -import androidx.core.util.Pools; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Helper class that can enqueue and process adapter update operations. - *

- * To support animations, RecyclerView presents an older version the Adapter to best represent - * previous state of the layout. Sometimes, this is not trivial when items are removed that were - * not laid out, in which case, RecyclerView has no way of providing that item's view for - * animations. - *

- * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During - * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass - * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them - * according to previously deferred operation and dispatch them before the first layout pass. It - * also takes care of updating deferred UpdateOps since order of operations is changed by this - * process. - *

- * Although operations may be forwarded to LayoutManager in different orders, resulting data set - * is guaranteed to be the consistent. - */ -final class AdapterHelper implements OpReorderer.Callback { - - static final int POSITION_TYPE_INVISIBLE = 0; - - static final int POSITION_TYPE_NEW_OR_LAID_OUT = 1; - - private static final boolean DEBUG = false; - - private static final String TAG = "AHT"; - - private Pools.Pool mUpdateOpPool = new Pools.SimplePool(UpdateOp.POOL_SIZE); - - final ArrayList mPendingUpdates = new ArrayList(); - - final ArrayList mPostponedList = new ArrayList(); - - final Callback mCallback; - - Runnable mOnItemProcessedCallback; - - final boolean mDisableRecycler; - - final OpReorderer mOpReorderer; - - private int mExistingUpdateTypes = 0; - - AdapterHelper(Callback callback) { - this(callback, false); - } - - AdapterHelper(Callback callback, boolean disableRecycler) { - mCallback = callback; - mDisableRecycler = disableRecycler; - mOpReorderer = new OpReorderer(this); - } - - AdapterHelper addUpdateOp(UpdateOp... ops) { - Collections.addAll(mPendingUpdates, ops); - return this; - } - - void reset() { - recycleUpdateOpsAndClearList(mPendingUpdates); - recycleUpdateOpsAndClearList(mPostponedList); - mExistingUpdateTypes = 0; - } - - void preProcess() { - mOpReorderer.reorderOps(mPendingUpdates); - final int count = mPendingUpdates.size(); - for (int i = 0; i < count; i++) { - UpdateOp op = mPendingUpdates.get(i); - switch (op.cmd) { - case UpdateOp.ADD: - applyAdd(op); - break; - case UpdateOp.REMOVE: - applyRemove(op); - break; - case UpdateOp.UPDATE: - applyUpdate(op); - break; - case UpdateOp.MOVE: - applyMove(op); - break; - } - if (mOnItemProcessedCallback != null) { - mOnItemProcessedCallback.run(); - } - } - mPendingUpdates.clear(); - } - - void consumePostponedUpdates() { - final int count = mPostponedList.size(); - for (int i = 0; i < count; i++) { - mCallback.onDispatchSecondPass(mPostponedList.get(i)); - } - recycleUpdateOpsAndClearList(mPostponedList); - mExistingUpdateTypes = 0; - } - - private void applyMove(UpdateOp op) { - // MOVE ops are pre-processed so at this point, we know that item is still in the adapter. - // otherwise, it would be converted into a REMOVE operation - postponeAndUpdateViewHolders(op); - } - - private void applyRemove(UpdateOp op) { - int tmpStart = op.positionStart; - int tmpCount = 0; - int tmpEnd = op.positionStart + op.itemCount; - int type = -1; - for (int position = op.positionStart; position < tmpEnd; position++) { - boolean typeChanged = false; - RecyclerView.ViewHolder vh = mCallback.findViewHolder(position); - if (vh != null || canFindInPreLayout(position)) { - // If a ViewHolder exists or this is a newly added item, we can defer this update - // to post layout stage. - // * For existing ViewHolders, we'll fake its existence in the pre-layout phase. - // * For items that are added and removed in the same process cycle, they won't - // have any effect in pre-layout since their add ops are already deferred to - // post-layout pass. - if (type == POSITION_TYPE_INVISIBLE) { - // Looks like we have other updates that we cannot merge with this one. - // Create an UpdateOp and dispatch it to LayoutManager. - UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); - dispatchAndUpdateViewHolders(newOp); - typeChanged = true; - } - type = POSITION_TYPE_NEW_OR_LAID_OUT; - } else { - // This update cannot be recovered because we don't have a ViewHolder representing - // this position. Instead, post it to LayoutManager immediately - if (type == POSITION_TYPE_NEW_OR_LAID_OUT) { - // Looks like we have other updates that we cannot merge with this one. - // Create UpdateOp op and dispatch it to LayoutManager. - UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); - postponeAndUpdateViewHolders(newOp); - typeChanged = true; - } - type = POSITION_TYPE_INVISIBLE; - } - if (typeChanged) { - position -= tmpCount; // also equal to tmpStart - tmpEnd -= tmpCount; - tmpCount = 1; - } else { - tmpCount++; - } - } - if (tmpCount != op.itemCount) { // all 1 effect - recycleUpdateOp(op); - op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null); - } - if (type == POSITION_TYPE_INVISIBLE) { - dispatchAndUpdateViewHolders(op); - } else { - postponeAndUpdateViewHolders(op); - } - } - - private void applyUpdate(UpdateOp op) { - int tmpStart = op.positionStart; - int tmpCount = 0; - int tmpEnd = op.positionStart + op.itemCount; - int type = -1; - for (int position = op.positionStart; position < tmpEnd; position++) { - RecyclerView.ViewHolder vh = mCallback.findViewHolder(position); - if (vh != null || canFindInPreLayout(position)) { // deferred - if (type == POSITION_TYPE_INVISIBLE) { - UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, - op.payload); - dispatchAndUpdateViewHolders(newOp); - tmpCount = 0; - tmpStart = position; - } - type = POSITION_TYPE_NEW_OR_LAID_OUT; - } else { // applied - if (type == POSITION_TYPE_NEW_OR_LAID_OUT) { - UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, - op.payload); - postponeAndUpdateViewHolders(newOp); - tmpCount = 0; - tmpStart = position; - } - type = POSITION_TYPE_INVISIBLE; - } - tmpCount++; - } - if (tmpCount != op.itemCount) { // all 1 effect - Object payload = op.payload; - recycleUpdateOp(op); - op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, payload); - } - if (type == POSITION_TYPE_INVISIBLE) { - dispatchAndUpdateViewHolders(op); - } else { - postponeAndUpdateViewHolders(op); - } - } - - private void dispatchAndUpdateViewHolders(UpdateOp op) { - // tricky part. - // traverse all postpones and revert their changes on this op if necessary, apply updated - // dispatch to them since now they are after this op. - if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) { - throw new IllegalArgumentException("should not dispatch add or move for pre layout"); - } - if (DEBUG) { - Log.d(TAG, "dispatch (pre)" + op); - Log.d(TAG, "postponed state before:"); - for (UpdateOp updateOp : mPostponedList) { - Log.d(TAG, updateOp.toString()); - } - Log.d(TAG, "----"); - } - - // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial - // TODO Since move ops are pushed to end, we should not need this anymore - int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd); - if (DEBUG) { - Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart); - } - int tmpCnt = 1; - int offsetPositionForPartial = op.positionStart; - final int positionMultiplier; - switch (op.cmd) { - case UpdateOp.UPDATE: - positionMultiplier = 1; - break; - case UpdateOp.REMOVE: - positionMultiplier = 0; - break; - default: - throw new IllegalArgumentException("op should be remove or update." + op); - } - for (int p = 1; p < op.itemCount; p++) { - final int pos = op.positionStart + (positionMultiplier * p); - int updatedPos = updatePositionWithPostponed(pos, op.cmd); - if (DEBUG) { - Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos); - } - boolean continuous = false; - switch (op.cmd) { - case UpdateOp.UPDATE: - continuous = updatedPos == tmpStart + 1; - break; - case UpdateOp.REMOVE: - continuous = updatedPos == tmpStart; - break; - } - if (continuous) { - tmpCnt++; - } else { - // need to dispatch this separately - UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, op.payload); - if (DEBUG) { - Log.d(TAG, "need to dispatch separately " + tmp); - } - dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); - recycleUpdateOp(tmp); - if (op.cmd == UpdateOp.UPDATE) { - offsetPositionForPartial += tmpCnt; - } - tmpStart = updatedPos; // need to remove previously dispatched - tmpCnt = 1; - } - } - Object payload = op.payload; - recycleUpdateOp(op); - if (tmpCnt > 0) { - UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, payload); - if (DEBUG) { - Log.d(TAG, "dispatching:" + tmp); - } - dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial); - recycleUpdateOp(tmp); - } - if (DEBUG) { - Log.d(TAG, "post dispatch"); - Log.d(TAG, "postponed state after:"); - for (UpdateOp updateOp : mPostponedList) { - Log.d(TAG, updateOp.toString()); - } - Log.d(TAG, "----"); - } - } - - void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) { - mCallback.onDispatchFirstPass(op); - switch (op.cmd) { - case UpdateOp.REMOVE: - mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount); - break; - case UpdateOp.UPDATE: - mCallback.markViewHoldersUpdated(offsetStart, op.itemCount, op.payload); - break; - default: - throw new IllegalArgumentException("only remove and update ops can be dispatched" - + " in first pass"); - } - } - - private int updatePositionWithPostponed(int pos, int cmd) { - final int count = mPostponedList.size(); - for (int i = count - 1; i >= 0; i--) { - UpdateOp postponed = mPostponedList.get(i); - if (postponed.cmd == UpdateOp.MOVE) { - int start, end; - if (postponed.positionStart < postponed.itemCount) { - start = postponed.positionStart; - end = postponed.itemCount; - } else { - start = postponed.itemCount; - end = postponed.positionStart; - } - if (pos >= start && pos <= end) { - //i'm affected - if (start == postponed.positionStart) { - if (cmd == UpdateOp.ADD) { - postponed.itemCount++; - } else if (cmd == UpdateOp.REMOVE) { - postponed.itemCount--; - } - // op moved to left, move it right to revert - pos++; - } else { - if (cmd == UpdateOp.ADD) { - postponed.positionStart++; - } else if (cmd == UpdateOp.REMOVE) { - postponed.positionStart--; - } - // op was moved right, move left to revert - pos--; - } - } else if (pos < postponed.positionStart) { - // postponed MV is outside the dispatched OP. if it is before, offset - if (cmd == UpdateOp.ADD) { - postponed.positionStart++; - postponed.itemCount++; - } else if (cmd == UpdateOp.REMOVE) { - postponed.positionStart--; - postponed.itemCount--; - } - } - } else { - if (postponed.positionStart <= pos) { - if (postponed.cmd == UpdateOp.ADD) { - pos -= postponed.itemCount; - } else if (postponed.cmd == UpdateOp.REMOVE) { - pos += postponed.itemCount; - } - } else { - if (cmd == UpdateOp.ADD) { - postponed.positionStart++; - } else if (cmd == UpdateOp.REMOVE) { - postponed.positionStart--; - } - } - } - if (DEBUG) { - Log.d(TAG, "dispath (step" + i + ")"); - Log.d(TAG, "postponed state:" + i + ", pos:" + pos); - for (UpdateOp updateOp : mPostponedList) { - Log.d(TAG, updateOp.toString()); - } - Log.d(TAG, "----"); - } - } - for (int i = mPostponedList.size() - 1; i >= 0; i--) { - UpdateOp op = mPostponedList.get(i); - if (op.cmd == UpdateOp.MOVE) { - if (op.itemCount == op.positionStart || op.itemCount < 0) { - mPostponedList.remove(i); - recycleUpdateOp(op); - } - } else if (op.itemCount <= 0) { - mPostponedList.remove(i); - recycleUpdateOp(op); - } - } - return pos; - } - - private boolean canFindInPreLayout(int position) { - final int count = mPostponedList.size(); - for (int i = 0; i < count; i++) { - UpdateOp op = mPostponedList.get(i); - if (op.cmd == UpdateOp.MOVE) { - if (findPositionOffset(op.itemCount, i + 1) == position) { - return true; - } - } else if (op.cmd == UpdateOp.ADD) { - // TODO optimize. - final int end = op.positionStart + op.itemCount; - for (int pos = op.positionStart; pos < end; pos++) { - if (findPositionOffset(pos, i + 1) == position) { - return true; - } - } - } - } - return false; - } - - private void applyAdd(UpdateOp op) { - postponeAndUpdateViewHolders(op); - } - - private void postponeAndUpdateViewHolders(UpdateOp op) { - if (DEBUG) { - Log.d(TAG, "postponing " + op); - } - mPostponedList.add(op); - switch (op.cmd) { - case UpdateOp.ADD: - mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); - break; - case UpdateOp.MOVE: - mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); - break; - case UpdateOp.REMOVE: - mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, - op.itemCount); - break; - case UpdateOp.UPDATE: - mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); - break; - default: - throw new IllegalArgumentException("Unknown update op type for " + op); - } - } - - boolean hasPendingUpdates() { - return mPendingUpdates.size() > 0; - } - - boolean hasAnyUpdateTypes(int updateTypes) { - return (mExistingUpdateTypes & updateTypes) != 0; - } - - int findPositionOffset(int position) { - return findPositionOffset(position, 0); - } - - int findPositionOffset(int position, int firstPostponedItem) { - int count = mPostponedList.size(); - for (int i = firstPostponedItem; i < count; ++i) { - UpdateOp op = mPostponedList.get(i); - if (op.cmd == UpdateOp.MOVE) { - if (op.positionStart == position) { - position = op.itemCount; - } else { - if (op.positionStart < position) { - position--; // like a remove - } - if (op.itemCount <= position) { - position++; // like an add - } - } - } else if (op.positionStart <= position) { - if (op.cmd == UpdateOp.REMOVE) { - if (position < op.positionStart + op.itemCount) { - return -1; - } - position -= op.itemCount; - } else if (op.cmd == UpdateOp.ADD) { - position += op.itemCount; - } - } - } - return position; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { - if (itemCount < 1) { - return false; - } - mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); - mExistingUpdateTypes |= UpdateOp.UPDATE; - return mPendingUpdates.size() == 1; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeInserted(int positionStart, int itemCount) { - if (itemCount < 1) { - return false; - } - mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null)); - mExistingUpdateTypes |= UpdateOp.ADD; - return mPendingUpdates.size() == 1; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeRemoved(int positionStart, int itemCount) { - if (itemCount < 1) { - return false; - } - mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null)); - mExistingUpdateTypes |= UpdateOp.REMOVE; - return mPendingUpdates.size() == 1; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeMoved(int from, int to, int itemCount) { - if (from == to) { - return false; // no-op - } - if (itemCount != 1) { - throw new IllegalArgumentException("Moving more than 1 item is not supported yet"); - } - mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null)); - mExistingUpdateTypes |= UpdateOp.MOVE; - return mPendingUpdates.size() == 1; - } - - /** - * Skips pre-processing and applies all updates in one pass. - */ - void consumeUpdatesInOnePass() { - // we still consume postponed updates (if there is) in case there was a pre-process call - // w/o a matching consumePostponedUpdates. - consumePostponedUpdates(); - final int count = mPendingUpdates.size(); - for (int i = 0; i < count; i++) { - UpdateOp op = mPendingUpdates.get(i); - switch (op.cmd) { - case UpdateOp.ADD: - mCallback.onDispatchSecondPass(op); - mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount); - break; - case UpdateOp.REMOVE: - mCallback.onDispatchSecondPass(op); - mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount); - break; - case UpdateOp.UPDATE: - mCallback.onDispatchSecondPass(op); - mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload); - break; - case UpdateOp.MOVE: - mCallback.onDispatchSecondPass(op); - mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); - break; - } - if (mOnItemProcessedCallback != null) { - mOnItemProcessedCallback.run(); - } - } - recycleUpdateOpsAndClearList(mPendingUpdates); - mExistingUpdateTypes = 0; - } - - public int applyPendingUpdatesToPosition(int position) { - final int size = mPendingUpdates.size(); - for (int i = 0; i < size; i++) { - UpdateOp op = mPendingUpdates.get(i); - switch (op.cmd) { - case UpdateOp.ADD: - if (op.positionStart <= position) { - position += op.itemCount; - } - break; - case UpdateOp.REMOVE: - if (op.positionStart <= position) { - final int end = op.positionStart + op.itemCount; - if (end > position) { - return RecyclerView.NO_POSITION; - } - position -= op.itemCount; - } - break; - case UpdateOp.MOVE: - if (op.positionStart == position) { - position = op.itemCount; //position end - } else { - if (op.positionStart < position) { - position -= 1; - } - if (op.itemCount <= position) { - position += 1; - } - } - break; - } - } - return position; - } - - boolean hasUpdates() { - return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty(); - } - - /** - * Queued operation to happen when child views are updated. - */ - static final class UpdateOp { - - static final int ADD = 1; - - static final int REMOVE = 1 << 1; - - static final int UPDATE = 1 << 2; - - static final int MOVE = 1 << 3; - - static final int POOL_SIZE = 30; - - int cmd; - - int positionStart; - - Object payload; - - // holds the target position if this is a MOVE - int itemCount; - - UpdateOp(int cmd, int positionStart, int itemCount, Object payload) { - this.cmd = cmd; - this.positionStart = positionStart; - this.itemCount = itemCount; - this.payload = payload; - } - - String cmdToString() { - switch (cmd) { - case ADD: - return "add"; - case REMOVE: - return "rm"; - case UPDATE: - return "up"; - case MOVE: - return "mv"; - } - return "??"; - } - - @Override - public String toString() { - return Integer.toHexString(System.identityHashCode(this)) - + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount - + ",p:" + payload + "]"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof UpdateOp)) { - return false; - } - - UpdateOp op = (UpdateOp) o; - - if (cmd != op.cmd) { - return false; - } - if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) { - // reverse of this is also true - if (itemCount == op.positionStart && positionStart == op.itemCount) { - return true; - } - } - if (itemCount != op.itemCount) { - return false; - } - if (positionStart != op.positionStart) { - return false; - } - if (payload != null) { - if (!payload.equals(op.payload)) { - return false; - } - } else if (op.payload != null) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = cmd; - result = 31 * result + positionStart; - result = 31 * result + itemCount; - return result; - } - } - - @Override - public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) { - UpdateOp op = mUpdateOpPool.acquire(); - if (op == null) { - op = new UpdateOp(cmd, positionStart, itemCount, payload); - } else { - op.cmd = cmd; - op.positionStart = positionStart; - op.itemCount = itemCount; - op.payload = payload; - } - return op; - } - - @Override - public void recycleUpdateOp(UpdateOp op) { - if (!mDisableRecycler) { - op.payload = null; - mUpdateOpPool.release(op); - } - } - - void recycleUpdateOpsAndClearList(List ops) { - final int count = ops.size(); - for (int i = 0; i < count; i++) { - recycleUpdateOp(ops.get(i)); - } - ops.clear(); - } - - /** - * Contract between AdapterHelper and RecyclerView. - */ - interface Callback { - - RecyclerView.ViewHolder findViewHolder(int position); - - void offsetPositionsForRemovingInvisible(int positionStart, int itemCount); - - void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount); - - void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads); - - void onDispatchFirstPass(UpdateOp updateOp); - - void onDispatchSecondPass(UpdateOp updateOp); - - void offsetPositionsForAdd(int positionStart, int itemCount); - - void offsetPositionsForMove(int from, int to); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/AdapterListUpdateCallback.java b/app/src/main/java/androidx/recyclerview/widget/AdapterListUpdateCallback.java deleted file mode 100644 index ec94f9c445..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/AdapterListUpdateCallback.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; - -import androidx.annotation.NonNull; - -/** - * ListUpdateCallback that dispatches update events to the given adapter. - * - * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) - */ -public final class AdapterListUpdateCallback implements ListUpdateCallback { - @NonNull - private final RecyclerView.Adapter mAdapter; - - /** - * Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter. - * - * @param adapter The Adapter to send updates to. - */ - public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) { - mAdapter = adapter; - } - - /** {@inheritDoc} */ - @Override - public void onInserted(int position, int count) { - mAdapter.notifyItemRangeInserted(position, count); - } - - /** {@inheritDoc} */ - @Override - public void onRemoved(int position, int count) { - mAdapter.notifyItemRangeRemoved(position, count); - } - - /** {@inheritDoc} */ - @Override - public void onMoved(int fromPosition, int toPosition) { - mAdapter.notifyItemMoved(fromPosition, toPosition); - } - - /** {@inheritDoc} */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChanged(int position, int count, Object payload) { - mAdapter.notifyItemRangeChanged(position, count, payload); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/AsyncDifferConfig.java b/app/src/main/java/androidx/recyclerview/widget/AsyncDifferConfig.java deleted file mode 100644 index ccd9cfae63..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/AsyncDifferConfig.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * Configuration object for {@link ListAdapter}, {@link AsyncListDiffer}, and similar - * background-thread list diffing adapter logic. - *

- * At minimum, defines item diffing behavior with a {@link DiffUtil.ItemCallback}, used to compute - * item differences to pass to a RecyclerView adapter. - * - * @param Type of items in the lists, and being compared. - */ -public final class AsyncDifferConfig { - @Nullable - private final Executor mMainThreadExecutor; - @NonNull - private final Executor mBackgroundThreadExecutor; - @NonNull - private final DiffUtil.ItemCallback mDiffCallback; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - AsyncDifferConfig( - @Nullable Executor mainThreadExecutor, - @NonNull Executor backgroundThreadExecutor, - @NonNull DiffUtil.ItemCallback diffCallback) { - mMainThreadExecutor = mainThreadExecutor; - mBackgroundThreadExecutor = backgroundThreadExecutor; - mDiffCallback = diffCallback; - } - - /** @hide */ - @SuppressWarnings("WeakerAccess") - @RestrictTo(RestrictTo.Scope.LIBRARY) - @Nullable - public Executor getMainThreadExecutor() { - return mMainThreadExecutor; - } - - @SuppressWarnings("WeakerAccess") - @NonNull - public Executor getBackgroundThreadExecutor() { - return mBackgroundThreadExecutor; - } - - @SuppressWarnings("WeakerAccess") - @NonNull - public DiffUtil.ItemCallback getDiffCallback() { - return mDiffCallback; - } - - /** - * Builder class for {@link AsyncDifferConfig}. - * - * @param - */ - public static final class Builder { - @Nullable - private Executor mMainThreadExecutor; - private Executor mBackgroundThreadExecutor; - private final DiffUtil.ItemCallback mDiffCallback; - - public Builder(@NonNull DiffUtil.ItemCallback diffCallback) { - mDiffCallback = diffCallback; - } - - /** - * If provided, defines the main thread executor used to dispatch adapter update - * notifications on the main thread. - *

- * If not provided or null, it will default to the main thread. - * - * @param executor The executor which can run tasks in the UI thread. - * @return this - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY) - @NonNull - public Builder setMainThreadExecutor(@Nullable Executor executor) { - mMainThreadExecutor = executor; - return this; - } - - /** - * If provided, defines the background executor used to calculate the diff between an old - * and a new list. - *

- * If not provided or null, defaults to two thread pool executor, shared by all - * ListAdapterConfigs. - * - * @param executor The background executor to run list diffing. - * @return this - */ - @SuppressWarnings({"unused", "WeakerAccess"}) - @NonNull - public Builder setBackgroundThreadExecutor(@Nullable Executor executor) { - mBackgroundThreadExecutor = executor; - return this; - } - - /** - * Creates a {@link AsyncListDiffer} with the given parameters. - * - * @return A new AsyncDifferConfig. - */ - @NonNull - public AsyncDifferConfig build() { - if (mBackgroundThreadExecutor == null) { - synchronized (sExecutorLock) { - if (sDiffExecutor == null) { - sDiffExecutor = Executors.newFixedThreadPool(2); - } - } - mBackgroundThreadExecutor = sDiffExecutor; - } - return new AsyncDifferConfig<>( - mMainThreadExecutor, - mBackgroundThreadExecutor, - mDiffCallback); - } - - // TODO: remove the below once supportlib has its own appropriate executors - private static final Object sExecutorLock = new Object(); - private static Executor sDiffExecutor = null; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java b/app/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java deleted file mode 100644 index c1fdb87858..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/AsyncListDiffer.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executor; - -/** - * Helper for computing the difference between two lists via {@link DiffUtil} on a background - * thread. - *

- * It can be connected to a - * {@link RecyclerView.Adapter RecyclerView.Adapter}, and will signal the - * adapter of changes between sumbitted lists. - *

- * For simplicity, the {@link ListAdapter} wrapper class can often be used instead of the - * AsyncListDiffer directly. This AsyncListDiffer can be used for complex cases, where overriding an - * adapter base class to support asynchronous List diffing isn't convenient. - *

- * The AsyncListDiffer can consume the values from a LiveData of List and present the - * data simply for an adapter. It computes differences in list contents via {@link DiffUtil} on a - * background thread as new Lists are received. - *

- * Use {@link #getCurrentList()} to access the current List, and present its data objects. Diff - * results will be dispatched to the ListUpdateCallback immediately before the current list is - * updated. If you're dispatching list updates directly to an Adapter, this means the Adapter can - * safely access list items and total size via {@link #getCurrentList()}. - *

- * A complete usage pattern with Room would look like this: - *

- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract LiveData<List<User>> usersByLastName();
- * }
- *
- * class MyViewModel extends ViewModel {
- *     public final LiveData<List<User>> usersList;
- *     public MyViewModel(UserDao userDao) {
- *         usersList = userDao.usersByLastName();
- *     }
- * }
- *
- * class MyActivity extends AppCompatActivity {
- *     {@literal @}Override
- *     public void onCreate(Bundle savedState) {
- *         super.onCreate(savedState);
- *         MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
- *         RecyclerView recyclerView = findViewById(R.id.user_list);
- *         UserAdapter adapter = new UserAdapter();
- *         viewModel.usersList.observe(this, list -> adapter.submitList(list));
- *         recyclerView.setAdapter(adapter);
- *     }
- * }
- *
- * class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
- *     private final AsyncListDiffer<User> mDiffer = new AsyncListDiffer(this, DIFF_CALLBACK);
- *     {@literal @}Override
- *     public int getItemCount() {
- *         return mDiffer.getCurrentList().size();
- *     }
- *     public void submitList(List<User> list) {
- *         mDiffer.submitList(list);
- *     }
- *     {@literal @}Override
- *     public void onBindViewHolder(UserViewHolder holder, int position) {
- *         User user = mDiffer.getCurrentList().get(position);
- *         holder.bindTo(user);
- *     }
- *     public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK
- *             = new DiffUtil.ItemCallback<User>() {
- *         {@literal @}Override
- *         public boolean areItemsTheSame(
- *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *             // User properties may have changed if reloaded from the DB, but ID is fixed
- *             return oldUser.getId() == newUser.getId();
- *         }
- *         {@literal @}Override
- *         public boolean areContentsTheSame(
- *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *             // NOTE: if you use equals, your object must properly override Object#equals()
- *             // Incorrectly returning false here will result in too many animations.
- *             return oldUser.equals(newUser);
- *         }
- *     }
- * }
- * - * @param Type of the lists this AsyncListDiffer will receive. - * - * @see DiffUtil - * @see AdapterListUpdateCallback - */ -public class AsyncListDiffer { - private final ListUpdateCallback mUpdateCallback; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final AsyncDifferConfig mConfig; - Executor mMainThreadExecutor; - - private static class MainThreadExecutor implements Executor { - final Handler mHandler = new Handler(Looper.getMainLooper()); - MainThreadExecutor() {} - @Override - public void execute(@NonNull Runnable command) { - mHandler.post(command); - } - } - - // TODO: use MainThreadExecutor from supportlib once one exists - private static final Executor sMainThreadExecutor = new MainThreadExecutor(); - - /** - * Listener for when the current List is updated. - * - * @param Type of items in List - */ - public interface ListListener { - /** - * Called after the current List has been updated. - * - * @param previousList The previous list. - * @param currentList The new current list. - */ - void onCurrentListChanged(@NonNull List previousList, @NonNull List currentList); - } - - private final List> mListeners = new CopyOnWriteArrayList<>(); - - /** - * Convenience for - * {@code AsyncListDiffer(new AdapterListUpdateCallback(adapter), - * new AsyncDifferConfig.Builder().setDiffCallback(diffCallback).build());} - * - * @param adapter Adapter to dispatch position updates to. - * @param diffCallback ItemCallback that compares items to dispatch appropriate animations when - * - * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) - */ - public AsyncListDiffer(@NonNull RecyclerView.Adapter adapter, - @NonNull DiffUtil.ItemCallback diffCallback) { - this(new AdapterListUpdateCallback(adapter), - new AsyncDifferConfig.Builder<>(diffCallback).build()); - } - - /** - * Create a AsyncListDiffer with the provided config, and ListUpdateCallback to dispatch - * updates to. - * - * @param listUpdateCallback Callback to dispatch updates to. - * @param config Config to define background work Executor, and DiffUtil.ItemCallback for - * computing List diffs. - * - * @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter) - */ - @SuppressWarnings("WeakerAccess") - public AsyncListDiffer(@NonNull ListUpdateCallback listUpdateCallback, - @NonNull AsyncDifferConfig config) { - mUpdateCallback = listUpdateCallback; - mConfig = config; - if (config.getMainThreadExecutor() != null) { - mMainThreadExecutor = config.getMainThreadExecutor(); - } else { - mMainThreadExecutor = sMainThreadExecutor; - } - } - - @Nullable - private List mList; - - /** - * Non-null, unmodifiable version of mList. - *

- * Collections.emptyList when mList is null, wrapped by Collections.unmodifiableList otherwise - */ - @NonNull - private List mReadOnlyList = Collections.emptyList(); - - // Max generation of currently scheduled runnable - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mMaxScheduledGeneration; - - /** - * Get the current List - any diffing to present this list has already been computed and - * dispatched via the ListUpdateCallback. - *

- * If a null List, or no List has been submitted, an empty list will be returned. - *

- * The returned list may not be mutated - mutations to content must be done through - * {@link #submitList(List)}. - * - * @return current List. - */ - @NonNull - public List getCurrentList() { - return mReadOnlyList; - } - - /** - * Pass a new List to the AdapterHelper. Adapter updates will be computed on a background - * thread. - *

- * If a List is already present, a diff will be computed asynchronously on a background thread. - * When the diff is computed, it will be applied (dispatched to the {@link ListUpdateCallback}), - * and the new List will be swapped in. - * - * @param newList The new List. - */ - @SuppressWarnings("WeakerAccess") - public void submitList(@Nullable final List newList) { - submitList(newList, null); - } - - /** - * Pass a new List to the AdapterHelper. Adapter updates will be computed on a background - * thread. - *

- * If a List is already present, a diff will be computed asynchronously on a background thread. - * When the diff is computed, it will be applied (dispatched to the {@link ListUpdateCallback}), - * and the new List will be swapped in. - *

- * The commit callback can be used to know when the List is committed, but note that it - * may not be executed. If List B is submitted immediately after List A, and is - * committed directly, the callback associated with List A will not be run. - * - * @param newList The new List. - * @param commitCallback Optional runnable that is executed when the List is committed, if - * it is committed. - */ - @SuppressWarnings("WeakerAccess") - public void submitList(@Nullable final List newList, - @Nullable final Runnable commitCallback) { - // incrementing generation means any currently-running diffs are discarded when they finish - final int runGeneration = ++mMaxScheduledGeneration; - - if (newList == mList) { - // nothing to do (Note - still had to inc generation, since may have ongoing work) - if (commitCallback != null) { - commitCallback.run(); - } - return; - } - - final List previousList = mReadOnlyList; - - // fast simple remove all - if (newList == null) { - //noinspection ConstantConditions - int countRemoved = mList.size(); - mList = null; - mReadOnlyList = Collections.emptyList(); - // notify last, after list is updated - mUpdateCallback.onRemoved(0, countRemoved); - onCurrentListChanged(previousList, commitCallback); - return; - } - - // fast simple first insert - if (mList == null) { - mList = newList; - mReadOnlyList = Collections.unmodifiableList(newList); - // notify last, after list is updated - mUpdateCallback.onInserted(0, newList.size()); - onCurrentListChanged(previousList, commitCallback); - return; - } - - final List oldList = mList; - mConfig.getBackgroundThreadExecutor().execute(new Runnable() { - @Override - public void run() { - final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { - @Override - public int getOldListSize() { - return oldList.size(); - } - - @Override - public int getNewListSize() { - return newList.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition); - T newItem = newList.get(newItemPosition); - if (oldItem != null && newItem != null) { - return mConfig.getDiffCallback().areItemsTheSame(oldItem, newItem); - } - // If both items are null we consider them the same. - return oldItem == null && newItem == null; - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition); - T newItem = newList.get(newItemPosition); - if (oldItem != null && newItem != null) { - return mConfig.getDiffCallback().areContentsTheSame(oldItem, newItem); - } - if (oldItem == null && newItem == null) { - return true; - } - // There is an implementation bug if we reach this point. Per the docs, this - // method should only be invoked when areItemsTheSame returns true. That - // only occurs when both items are non-null or both are null and both of - // those cases are handled above. - throw new AssertionError(); - } - - @Nullable - @Override - public Object getChangePayload(int oldItemPosition, int newItemPosition) { - T oldItem = oldList.get(oldItemPosition); - T newItem = newList.get(newItemPosition); - if (oldItem != null && newItem != null) { - return mConfig.getDiffCallback().getChangePayload(oldItem, newItem); - } - // There is an implementation bug if we reach this point. Per the docs, this - // method should only be invoked when areItemsTheSame returns true AND - // areContentsTheSame returns false. That only occurs when both items are - // non-null which is the only case handled above. - throw new AssertionError(); - } - }); - - mMainThreadExecutor.execute(new Runnable() { - @Override - public void run() { - if (mMaxScheduledGeneration == runGeneration) { - latchList(newList, result, commitCallback); - } - } - }); - } - }); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void latchList( - @NonNull List newList, - @NonNull DiffUtil.DiffResult diffResult, - @Nullable Runnable commitCallback) { - final List previousList = mReadOnlyList; - mList = newList; - // notify last, after list is updated - mReadOnlyList = Collections.unmodifiableList(newList); - diffResult.dispatchUpdatesTo(mUpdateCallback); - onCurrentListChanged(previousList, commitCallback); - } - - private void onCurrentListChanged(@NonNull List previousList, - @Nullable Runnable commitCallback) { - // current list is always mReadOnlyList - for (ListListener listener : mListeners) { - listener.onCurrentListChanged(previousList, mReadOnlyList); - } - if (commitCallback != null) { - commitCallback.run(); - } - } - - /** - * Add a ListListener to receive updates when the current List changes. - * - * @param listener Listener to receive updates. - * - * @see #getCurrentList() - * @see #removeListListener(ListListener) - */ - public void addListListener(@NonNull ListListener listener) { - mListeners.add(listener); - } - - /** - * Remove a previously registered ListListener. - * - * @param listener Previously registered listener. - * @see #getCurrentList() - * @see #addListListener(ListListener) - */ - public void removeListListener(@NonNull ListListener listener) { - mListeners.remove(listener); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/AsyncListUtil.java b/app/src/main/java/androidx/recyclerview/widget/AsyncListUtil.java deleted file mode 100644 index b50f6a8137..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/AsyncListUtil.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.util.Log; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; - -/** - * A utility class that supports asynchronous content loading. - *

- * It can be used to load Cursor data in chunks without querying the Cursor on the UI Thread while - * keeping UI and cache synchronous for better user experience. - *

- * It loads the data on a background thread and keeps only a limited number of fixed sized - * chunks in memory at all times. - *

- * {@link AsyncListUtil} queries the currently visible range through {@link ViewCallback}, - * loads the required data items in the background through {@link DataCallback}, and notifies a - * {@link ViewCallback} when the data is loaded. It may load some extra items for smoother - * scrolling. - *

- * Note that this class uses a single thread to load the data, so it suitable to load data from - * secondary storage such as disk, but not from network. - *

- * This class is designed to work with {@link RecyclerView}, but it does - * not depend on it and can be used with other list views. - * - */ -public class AsyncListUtil { - static final String TAG = "AsyncListUtil"; - - static final boolean DEBUG = false; - - final Class mTClass; - final int mTileSize; - final DataCallback mDataCallback; - final ViewCallback mViewCallback; - - final TileList mTileList; - - final ThreadUtil.MainThreadCallback mMainThreadProxy; - final ThreadUtil.BackgroundCallback mBackgroundProxy; - - final int[] mTmpRange = new int[2]; - final int[] mPrevRange = new int[2]; - final int[] mTmpRangeExtended = new int[2]; - - boolean mAllowScrollHints; - private int mScrollHint = ViewCallback.HINT_SCROLL_NONE; - - int mItemCount = 0; - - int mDisplayedGeneration = 0; - int mRequestedGeneration = mDisplayedGeneration; - - final SparseIntArray mMissingPositions = new SparseIntArray(); - - void log(String s, Object... args) { - Log.d(TAG, "[MAIN] " + String.format(s, args)); - } - - /** - * Creates an AsyncListUtil. - * - * @param klass Class of the data item. - * @param tileSize Number of item per chunk loaded at once. - * @param dataCallback Data access callback. - * @param viewCallback Callback for querying visible item range and update notifications. - */ - public AsyncListUtil(@NonNull Class klass, int tileSize, - @NonNull DataCallback dataCallback, @NonNull ViewCallback viewCallback) { - mTClass = klass; - mTileSize = tileSize; - mDataCallback = dataCallback; - mViewCallback = viewCallback; - - mTileList = new TileList(mTileSize); - - ThreadUtil threadUtil = new MessageThreadUtil(); - mMainThreadProxy = threadUtil.getMainThreadProxy(mMainThreadCallback); - mBackgroundProxy = threadUtil.getBackgroundProxy(mBackgroundCallback); - - refresh(); - } - - private boolean isRefreshPending() { - return mRequestedGeneration != mDisplayedGeneration; - } - - /** - * Updates the currently visible item range. - * - *

- * Identifies the data items that have not been loaded yet and initiates loading them in the - * background. Should be called from the view's scroll listener (such as - * {@link RecyclerView.OnScrollListener#onScrolled}). - */ - public void onRangeChanged() { - if (isRefreshPending()) { - return; // Will update range will the refresh result arrives. - } - updateRange(); - mAllowScrollHints = true; - } - - /** - * Forces reloading the data. - *

- * Discards all the cached data and reloads all required data items for the currently visible - * range. To be called when the data item count and/or contents has changed. - */ - public void refresh() { - mMissingPositions.clear(); - mBackgroundProxy.refresh(++mRequestedGeneration); - } - - /** - * Returns the data item at the given position or null if it has not been loaded - * yet. - * - *

- * If this method has been called for a specific position and returned null, then - * {@link ViewCallback#onItemLoaded(int)} will be called when it finally loads. Note that if - * this position stays outside of the cached item range (as defined by - * {@link ViewCallback#extendRangeInto} method), then the callback will never be called for - * this position. - * - * @param position Item position. - * - * @return The data item at the given position or null if it has not been loaded - * yet. - */ - @Nullable - public T getItem(int position) { - if (position < 0 || position >= mItemCount) { - throw new IndexOutOfBoundsException(position + " is not within 0 and " + mItemCount); - } - T item = mTileList.getItemAt(position); - if (item == null && !isRefreshPending()) { - mMissingPositions.put(position, 0); - } - return item; - } - - /** - * Returns the number of items in the data set. - * - *

- * This is the number returned by a recent call to - * {@link DataCallback#refreshData()}. - * - * @return Number of items. - */ - public int getItemCount() { - return mItemCount; - } - - void updateRange() { - mViewCallback.getItemRangeInto(mTmpRange); - if (mTmpRange[0] > mTmpRange[1] || mTmpRange[0] < 0) { - return; - } - if (mTmpRange[1] >= mItemCount) { - // Invalid range may arrive soon after the refresh. - return; - } - - if (!mAllowScrollHints) { - mScrollHint = ViewCallback.HINT_SCROLL_NONE; - } else if (mTmpRange[0] > mPrevRange[1] || mPrevRange[0] > mTmpRange[1]) { - // Ranges do not intersect, long leap not a scroll. - mScrollHint = ViewCallback.HINT_SCROLL_NONE; - } else if (mTmpRange[0] < mPrevRange[0]) { - mScrollHint = ViewCallback.HINT_SCROLL_DESC; - } else if (mTmpRange[0] > mPrevRange[0]) { - mScrollHint = ViewCallback.HINT_SCROLL_ASC; - } - - mPrevRange[0] = mTmpRange[0]; - mPrevRange[1] = mTmpRange[1]; - - mViewCallback.extendRangeInto(mTmpRange, mTmpRangeExtended, mScrollHint); - mTmpRangeExtended[0] = Math.min(mTmpRange[0], Math.max(mTmpRangeExtended[0], 0)); - mTmpRangeExtended[1] = - Math.max(mTmpRange[1], Math.min(mTmpRangeExtended[1], mItemCount - 1)); - - mBackgroundProxy.updateRange(mTmpRange[0], mTmpRange[1], - mTmpRangeExtended[0], mTmpRangeExtended[1], mScrollHint); - } - - private final ThreadUtil.MainThreadCallback - mMainThreadCallback = new ThreadUtil.MainThreadCallback() { - @Override - public void updateItemCount(int generation, int itemCount) { - if (DEBUG) { - log("updateItemCount: size=%d, gen #%d", itemCount, generation); - } - if (!isRequestedGeneration(generation)) { - return; - } - mItemCount = itemCount; - mViewCallback.onDataRefresh(); - mDisplayedGeneration = mRequestedGeneration; - recycleAllTiles(); - - mAllowScrollHints = false; // Will be set to true after a first real scroll. - // There will be no scroll event if the size change does not affect the current range. - updateRange(); - } - - @Override - public void addTile(int generation, TileList.Tile tile) { - if (!isRequestedGeneration(generation)) { - if (DEBUG) { - log("recycling an older generation tile @%d", tile.mStartPosition); - } - mBackgroundProxy.recycleTile(tile); - return; - } - TileList.Tile duplicate = mTileList.addOrReplace(tile); - if (duplicate != null) { - Log.e(TAG, "duplicate tile @" + duplicate.mStartPosition); - mBackgroundProxy.recycleTile(duplicate); - } - if (DEBUG) { - log("gen #%d, added tile @%d, total tiles: %d", - generation, tile.mStartPosition, mTileList.size()); - } - int endPosition = tile.mStartPosition + tile.mItemCount; - int index = 0; - while (index < mMissingPositions.size()) { - final int position = mMissingPositions.keyAt(index); - if (tile.mStartPosition <= position && position < endPosition) { - mMissingPositions.removeAt(index); - mViewCallback.onItemLoaded(position); - } else { - index++; - } - } - } - - @Override - public void removeTile(int generation, int position) { - if (!isRequestedGeneration(generation)) { - return; - } - TileList.Tile tile = mTileList.removeAtPos(position); - if (tile == null) { - Log.e(TAG, "tile not found @" + position); - return; - } - if (DEBUG) { - log("recycling tile @%d, total tiles: %d", tile.mStartPosition, mTileList.size()); - } - mBackgroundProxy.recycleTile(tile); - } - - private void recycleAllTiles() { - if (DEBUG) { - log("recycling all %d tiles", mTileList.size()); - } - for (int i = 0; i < mTileList.size(); i++) { - mBackgroundProxy.recycleTile(mTileList.getAtIndex(i)); - } - mTileList.clear(); - } - - private boolean isRequestedGeneration(int generation) { - return generation == mRequestedGeneration; - } - }; - - private final ThreadUtil.BackgroundCallback - mBackgroundCallback = new ThreadUtil.BackgroundCallback() { - - private TileList.Tile mRecycledRoot; - - final SparseBooleanArray mLoadedTiles = new SparseBooleanArray(); - - private int mGeneration; - private int mItemCount; - - private int mFirstRequiredTileStart; - private int mLastRequiredTileStart; - - @Override - public void refresh(int generation) { - mGeneration = generation; - mLoadedTiles.clear(); - mItemCount = mDataCallback.refreshData(); - mMainThreadProxy.updateItemCount(mGeneration, mItemCount); - } - - @Override - public void updateRange(int rangeStart, int rangeEnd, int extRangeStart, int extRangeEnd, - int scrollHint) { - if (DEBUG) { - log("updateRange: %d..%d extended to %d..%d, scroll hint: %d", - rangeStart, rangeEnd, extRangeStart, extRangeEnd, scrollHint); - } - - if (rangeStart > rangeEnd) { - return; - } - - final int firstVisibleTileStart = getTileStart(rangeStart); - final int lastVisibleTileStart = getTileStart(rangeEnd); - - mFirstRequiredTileStart = getTileStart(extRangeStart); - mLastRequiredTileStart = getTileStart(extRangeEnd); - if (DEBUG) { - log("requesting tile range: %d..%d", - mFirstRequiredTileStart, mLastRequiredTileStart); - } - - // All pending tile requests are removed by ThreadUtil at this point. - // Re-request all required tiles in the most optimal order. - if (scrollHint == ViewCallback.HINT_SCROLL_DESC) { - requestTiles(mFirstRequiredTileStart, lastVisibleTileStart, scrollHint, true); - requestTiles(lastVisibleTileStart + mTileSize, mLastRequiredTileStart, scrollHint, - false); - } else { - requestTiles(firstVisibleTileStart, mLastRequiredTileStart, scrollHint, false); - requestTiles(mFirstRequiredTileStart, firstVisibleTileStart - mTileSize, scrollHint, - true); - } - } - - private int getTileStart(int position) { - return position - position % mTileSize; - } - - private void requestTiles(int firstTileStart, int lastTileStart, int scrollHint, - boolean backwards) { - for (int i = firstTileStart; i <= lastTileStart; i += mTileSize) { - int tileStart = backwards ? (lastTileStart + firstTileStart - i) : i; - if (DEBUG) { - log("requesting tile @%d", tileStart); - } - mBackgroundProxy.loadTile(tileStart, scrollHint); - } - } - - @Override - public void loadTile(int position, int scrollHint) { - if (isTileLoaded(position)) { - if (DEBUG) { - log("already loaded tile @%d", position); - } - return; - } - TileList.Tile tile = acquireTile(); - tile.mStartPosition = position; - tile.mItemCount = Math.min(mTileSize, mItemCount - tile.mStartPosition); - mDataCallback.fillData(tile.mItems, tile.mStartPosition, tile.mItemCount); - flushTileCache(scrollHint); - addTile(tile); - } - - @Override - public void recycleTile(TileList.Tile tile) { - if (DEBUG) { - log("recycling tile @%d", tile.mStartPosition); - } - mDataCallback.recycleData(tile.mItems, tile.mItemCount); - - tile.mNext = mRecycledRoot; - mRecycledRoot = tile; - } - - private TileList.Tile acquireTile() { - if (mRecycledRoot != null) { - TileList.Tile result = mRecycledRoot; - mRecycledRoot = mRecycledRoot.mNext; - return result; - } - return new TileList.Tile(mTClass, mTileSize); - } - - private boolean isTileLoaded(int position) { - return mLoadedTiles.get(position); - } - - private void addTile(TileList.Tile tile) { - mLoadedTiles.put(tile.mStartPosition, true); - mMainThreadProxy.addTile(mGeneration, tile); - if (DEBUG) { - log("loaded tile @%d, total tiles: %d", tile.mStartPosition, mLoadedTiles.size()); - } - } - - private void removeTile(int position) { - mLoadedTiles.delete(position); - mMainThreadProxy.removeTile(mGeneration, position); - if (DEBUG) { - log("flushed tile @%d, total tiles: %s", position, mLoadedTiles.size()); - } - } - - private void flushTileCache(int scrollHint) { - final int cacheSizeLimit = mDataCallback.getMaxCachedTiles(); - while (mLoadedTiles.size() >= cacheSizeLimit) { - int firstLoadedTileStart = mLoadedTiles.keyAt(0); - int lastLoadedTileStart = mLoadedTiles.keyAt(mLoadedTiles.size() - 1); - int startMargin = mFirstRequiredTileStart - firstLoadedTileStart; - int endMargin = lastLoadedTileStart - mLastRequiredTileStart; - if (startMargin > 0 && (startMargin >= endMargin || - (scrollHint == ViewCallback.HINT_SCROLL_ASC))) { - removeTile(firstLoadedTileStart); - } else if (endMargin > 0 && (startMargin < endMargin || - (scrollHint == ViewCallback.HINT_SCROLL_DESC))){ - removeTile(lastLoadedTileStart); - } else { - // Could not flush on either side, bail out. - return; - } - } - } - - private void log(String s, Object... args) { - Log.d(TAG, "[BKGR] " + String.format(s, args)); - } - }; - - /** - * The callback that provides data access for {@link AsyncListUtil}. - * - *

- * All methods are called on the background thread. - */ - public static abstract class DataCallback { - - /** - * Refresh the data set and return the new data item count. - * - *

- * If the data is being accessed through {@link android.database.Cursor} this is where - * the new cursor should be created. - * - * @return Data item count. - */ - @WorkerThread - public abstract int refreshData(); - - /** - * Fill the given tile. - * - *

- * The provided tile might be a recycled tile, in which case it will already have objects. - * It is suggested to re-use these objects if possible in your use case. - * - * @param startPosition The start position in the list. - * @param itemCount The data item count. - * @param data The data item array to fill into. Should not be accessed beyond - * itemCount. - */ - @WorkerThread - public abstract void fillData(@NonNull T[] data, int startPosition, int itemCount); - - /** - * Recycle the objects created in {@link #fillData} if necessary. - * - * - * @param data Array of data items. Should not be accessed beyond itemCount. - * @param itemCount The data item count. - */ - @WorkerThread - public void recycleData(@NonNull T[] data, int itemCount) { - } - - /** - * Returns tile cache size limit (in tiles). - * - *

- * The actual number of cached tiles will be the maximum of this value and the number of - * tiles that is required to cover the range returned by - * {@link ViewCallback#extendRangeInto(int[], int[], int)}. - *

- * For example, if this method returns 10, and the most - * recent call to {@link ViewCallback#extendRangeInto(int[], int[], int)} returned - * {100, 179}, and the tile size is 5, then the maximum number of cached tiles will be 16. - *

- * However, if the tile size is 20, then the maximum number of cached tiles will be 10. - *

- * The default implementation returns 10. - * - * @return Maximum cache size. - */ - @WorkerThread - public int getMaxCachedTiles() { - return 10; - } - } - - /** - * The callback that links {@link AsyncListUtil} with the list view. - * - *

- * All methods are called on the main thread. - */ - public static abstract class ViewCallback { - - /** - * No scroll direction hint available. - */ - public static final int HINT_SCROLL_NONE = 0; - - /** - * Scrolling in descending order (from higher to lower positions in the order of the backing - * storage). - */ - public static final int HINT_SCROLL_DESC = 1; - - /** - * Scrolling in ascending order (from lower to higher positions in the order of the backing - * storage). - */ - public static final int HINT_SCROLL_ASC = 2; - - /** - * Compute the range of visible item positions. - *

- * outRange[0] is the position of the first visible item (in the order of the backing - * storage). - *

- * outRange[1] is the position of the last visible item (in the order of the backing - * storage). - *

- * Negative positions and positions greater or equal to {@link #getItemCount} are invalid. - * If the returned range contains invalid positions it is ignored (no item will be loaded). - * - * @param outRange The visible item range. - */ - @UiThread - public abstract void getItemRangeInto(@NonNull int[] outRange); - - /** - * Compute a wider range of items that will be loaded for smoother scrolling. - * - *

- * If there is no scroll hint, the default implementation extends the visible range by half - * its length in both directions. If there is a scroll hint, the range is extended by - * its full length in the scroll direction, and by half in the other direction. - *

- * For example, if range is {100, 200} and scrollHint - * is {@link #HINT_SCROLL_ASC}, then outRange will be {50, 300}. - *

- * However, if scrollHint is {@link #HINT_SCROLL_NONE}, then - * outRange will be {50, 250} - * - * @param range Visible item range. - * @param outRange Extended range. - * @param scrollHint The scroll direction hint. - */ - @UiThread - public void extendRangeInto(@NonNull int[] range, @NonNull int[] outRange, int scrollHint) { - final int fullRange = range[1] - range[0] + 1; - final int halfRange = fullRange / 2; - outRange[0] = range[0] - (scrollHint == HINT_SCROLL_DESC ? fullRange : halfRange); - outRange[1] = range[1] + (scrollHint == HINT_SCROLL_ASC ? fullRange : halfRange); - } - - /** - * Called when the entire data set has changed. - */ - @UiThread - public abstract void onDataRefresh(); - - /** - * Called when an item at the given position is loaded. - * @param position Item position. - */ - @UiThread - public abstract void onItemLoaded(int position); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/BatchingListUpdateCallback.java b/app/src/main/java/androidx/recyclerview/widget/BatchingListUpdateCallback.java deleted file mode 100644 index bad8cc942e..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/BatchingListUpdateCallback.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; - -import androidx.annotation.NonNull; - -/** - * Wraps a {@link ListUpdateCallback} callback and batches operations that can be merged. - *

- * For instance, when 2 add operations comes that adds 2 consecutive elements, - * BatchingListUpdateCallback merges them and calls the wrapped callback only once. - *

- * This is a general purpose class and is also used by - * {@link DiffUtil.DiffResult DiffResult} and - * {@link SortedList} to minimize the number of updates that are dispatched. - *

- * If you use this class to batch updates, you must call {@link #dispatchLastEvent()} when the - * stream of update events drain. - */ -public class BatchingListUpdateCallback implements ListUpdateCallback { - private static final int TYPE_NONE = 0; - private static final int TYPE_ADD = 1; - private static final int TYPE_REMOVE = 2; - private static final int TYPE_CHANGE = 3; - - final ListUpdateCallback mWrapped; - - int mLastEventType = TYPE_NONE; - int mLastEventPosition = -1; - int mLastEventCount = -1; - Object mLastEventPayload = null; - - public BatchingListUpdateCallback(@NonNull ListUpdateCallback callback) { - mWrapped = callback; - } - - /** - * BatchingListUpdateCallback holds onto the last event to see if it can be merged with the - * next one. When stream of events finish, you should call this method to dispatch the last - * event. - */ - public void dispatchLastEvent() { - if (mLastEventType == TYPE_NONE) { - return; - } - switch (mLastEventType) { - case TYPE_ADD: - mWrapped.onInserted(mLastEventPosition, mLastEventCount); - break; - case TYPE_REMOVE: - mWrapped.onRemoved(mLastEventPosition, mLastEventCount); - break; - case TYPE_CHANGE: - mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload); - break; - } - mLastEventPayload = null; - mLastEventType = TYPE_NONE; - } - - @Override - public void onInserted(int position, int count) { - if (mLastEventType == TYPE_ADD && position >= mLastEventPosition - && position <= mLastEventPosition + mLastEventCount) { - mLastEventCount += count; - mLastEventPosition = Math.min(position, mLastEventPosition); - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_ADD; - } - - @Override - public void onRemoved(int position, int count) { - if (mLastEventType == TYPE_REMOVE && mLastEventPosition >= position && - mLastEventPosition <= position + count) { - mLastEventCount += count; - mLastEventPosition = position; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_REMOVE; - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - dispatchLastEvent(); // moves are not merged - mWrapped.onMoved(fromPosition, toPosition); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChanged(int position, int count, Object payload) { - if (mLastEventType == TYPE_CHANGE && - !(position > mLastEventPosition + mLastEventCount - || position + count < mLastEventPosition || mLastEventPayload != payload)) { - // take potential overlap into account - int previousEnd = mLastEventPosition + mLastEventCount; - mLastEventPosition = Math.min(position, mLastEventPosition); - mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventPayload = payload; - mLastEventType = TYPE_CHANGE; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ChildHelper.java b/app/src/main/java/androidx/recyclerview/widget/ChildHelper.java deleted file mode 100644 index 1541ba8fd3..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ChildHelper.java +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -/** - * Helper class to manage children. - *

- * It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods - * provided by this class. Regular methods are the ones that replicate ViewGroup methods - * like getChildAt, getChildCount etc. These methods ignore hidden children. - *

- * When RecyclerView needs direct access to the view group children, it can call unfiltered - * methods like get getUnfilteredChildCount or getUnfilteredChildAt. - */ -class ChildHelper { - - private static final boolean DEBUG = false; - - private static final String TAG = "ChildrenHelper"; - - /** Not in call to removeView/removeViewAt/removeViewIfHidden. */ - private static final int REMOVE_STATUS_NONE = 0; - - /** Within a call to removeView/removeViewAt. */ - private static final int REMOVE_STATUS_IN_REMOVE = 1; - - /** Within a call to removeViewIfHidden. */ - private static final int REMOVE_STATUS_IN_REMOVE_IF_HIDDEN = 2; - - final Callback mCallback; - - final Bucket mBucket; - - final List mHiddenViews; - - /** - * One of REMOVE_STATUS_NONE, REMOVE_STATUS_IN_REMOVE, REMOVE_STATUS_IN_REMOVE_IF_HIDDEN. - * removeView and removeViewIfHidden may call each other: - * 1. removeView triggers removeViewIfHidden: this happens when removeView stops the item - * animation. removeViewIfHidden should do nothing. - * 2. removeView triggers removeView: this should not happen. - * 3. removeViewIfHidden triggers removeViewIfHidden: this should not happen, since the - * animation was stopped before the first removeViewIfHidden, it won't trigger another - * removeViewIfHidden. - * 4. removeViewIfHidden triggers removeView: this should not happen. - */ - private int mRemoveStatus = REMOVE_STATUS_NONE; - /** The view to remove in REMOVE_STATUS_IN_REMOVE. */ - private View mViewInRemoveView; - - ChildHelper(Callback callback) { - mCallback = callback; - mBucket = new Bucket(); - mHiddenViews = new ArrayList(); - } - - /** - * Marks a child view as hidden - * - * @param child View to hide. - */ - private void hideViewInternal(View child) { - mHiddenViews.add(child); - mCallback.onEnteredHiddenState(child); - } - - /** - * Unmarks a child view as hidden. - * - * @param child View to hide. - */ - private boolean unhideViewInternal(View child) { - if (mHiddenViews.remove(child)) { - mCallback.onLeftHiddenState(child); - return true; - } else { - return false; - } - } - - /** - * Adds a view to the ViewGroup - * - * @param child View to add. - * @param hidden If set to true, this item will be invisible from regular methods. - */ - void addView(View child, boolean hidden) { - addView(child, -1, hidden); - } - - /** - * Add a view to the ViewGroup at an index - * - * @param child View to add. - * @param index Index of the child from the regular perspective (excluding hidden views). - * ChildHelper offsets this index to actual ViewGroup index. - * @param hidden If set to true, this item will be invisible from regular methods. - */ - void addView(View child, int index, boolean hidden) { - final int offset; - if (index < 0) { - offset = mCallback.getChildCount(); - } else { - offset = getOffset(index); - } - mBucket.insert(offset, hidden); - if (hidden) { - hideViewInternal(child); - } - mCallback.addView(child, offset); - if (DEBUG) { - Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this); - } - } - - private int getOffset(int index) { - if (index < 0) { - return -1; //anything below 0 won't work as diff will be undefined. - } - final int limit = mCallback.getChildCount(); - int offset = index; - while (offset < limit) { - final int removedBefore = mBucket.countOnesBefore(offset); - final int diff = index - (offset - removedBefore); - if (diff == 0) { - while (mBucket.get(offset)) { // ensure this offset is not hidden - offset++; - } - return offset; - } else { - offset += diff; - } - } - return -1; - } - - /** - * Removes the provided View from underlying RecyclerView. - * - * @param view The view to remove. - */ - void removeView(View view) { - if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE) { - throw new IllegalStateException("Cannot call removeView(At) within removeView(At)"); - } else if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE_IF_HIDDEN) { - throw new IllegalStateException("Cannot call removeView(At) within removeViewIfHidden"); - } - try { - mRemoveStatus = REMOVE_STATUS_IN_REMOVE; - mViewInRemoveView = view; - int index = mCallback.indexOfChild(view); - if (index < 0) { - return; - } - if (mBucket.remove(index)) { - unhideViewInternal(view); - } - mCallback.removeViewAt(index); - if (DEBUG) { - Log.d(TAG, "remove View off:" + index + "," + this); - } - } finally { - mRemoveStatus = REMOVE_STATUS_NONE; - mViewInRemoveView = null; - } - } - - /** - * Removes the view at the provided index from RecyclerView. - * - * @param index Index of the child from the regular perspective (excluding hidden views). - * ChildHelper offsets this index to actual ViewGroup index. - */ - void removeViewAt(int index) { - if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE) { - throw new IllegalStateException("Cannot call removeView(At) within removeView(At)"); - } else if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE_IF_HIDDEN) { - throw new IllegalStateException("Cannot call removeView(At) within removeViewIfHidden"); - } - try { - final int offset = getOffset(index); - final View view = mCallback.getChildAt(offset); - if (view == null) { - return; - } - mRemoveStatus = REMOVE_STATUS_IN_REMOVE; - mViewInRemoveView = view; - if (mBucket.remove(offset)) { - unhideViewInternal(view); - } - mCallback.removeViewAt(offset); - if (DEBUG) { - Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this); - } - } finally { - mRemoveStatus = REMOVE_STATUS_NONE; - mViewInRemoveView = null; - } - } - - /** - * Returns the child at provided index. - * - * @param index Index of the child to return in regular perspective. - */ - View getChildAt(int index) { - final int offset = getOffset(index); - return mCallback.getChildAt(offset); - } - - /** - * Removes all views from the ViewGroup including the hidden ones. - */ - void removeAllViewsUnfiltered() { - mBucket.reset(); - for (int i = mHiddenViews.size() - 1; i >= 0; i--) { - mCallback.onLeftHiddenState(mHiddenViews.get(i)); - mHiddenViews.remove(i); - } - mCallback.removeAllViews(); - if (DEBUG) { - Log.d(TAG, "removeAllViewsUnfiltered"); - } - } - - /** - * This can be used to find a disappearing view by position. - * - * @param position The adapter position of the item. - * @return A hidden view with a valid ViewHolder that matches the position. - */ - View findHiddenNonRemovedView(int position) { - final int count = mHiddenViews.size(); - for (int i = 0; i < count; i++) { - final View view = mHiddenViews.get(i); - RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view); - if (holder.getLayoutPosition() == position - && !holder.isInvalid() - && !holder.isRemoved()) { - return view; - } - } - return null; - } - - /** - * Attaches the provided view to the underlying ViewGroup. - * - * @param child Child to attach. - * @param index Index of the child to attach in regular perspective. - * @param layoutParams LayoutParams for the child. - * @param hidden If set to true, this item will be invisible to the regular methods. - */ - void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams, - boolean hidden) { - final int offset; - if (index < 0) { - offset = mCallback.getChildCount(); - } else { - offset = getOffset(index); - } - mBucket.insert(offset, hidden); - if (hidden) { - hideViewInternal(child); - } - mCallback.attachViewToParent(child, offset, layoutParams); - if (DEBUG) { - Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," - + "h:" + hidden + ", " + this); - } - } - - /** - * Returns the number of children that are not hidden. - * - * @return Number of children that are not hidden. - * @see #getChildAt(int) - */ - int getChildCount() { - return mCallback.getChildCount() - mHiddenViews.size(); - } - - /** - * Returns the total number of children. - * - * @return The total number of children including the hidden views. - * @see #getUnfilteredChildAt(int) - */ - int getUnfilteredChildCount() { - return mCallback.getChildCount(); - } - - /** - * Returns a child by ViewGroup offset. ChildHelper won't offset this index. - * - * @param index ViewGroup index of the child to return. - * @return The view in the provided index. - */ - View getUnfilteredChildAt(int index) { - return mCallback.getChildAt(index); - } - - /** - * Detaches the view at the provided index. - * - * @param index Index of the child to return in regular perspective. - */ - void detachViewFromParent(int index) { - final int offset = getOffset(index); - mBucket.remove(offset); - mCallback.detachViewFromParent(offset); - if (DEBUG) { - Log.d(TAG, "detach view from parent " + index + ", off:" + offset); - } - } - - /** - * Returns the index of the child in regular perspective. - * - * @param child The child whose index will be returned. - * @return The regular perspective index of the child or -1 if it does not exists. - */ - int indexOfChild(View child) { - final int index = mCallback.indexOfChild(child); - if (index == -1) { - return -1; - } - if (mBucket.get(index)) { - if (DEBUG) { - throw new IllegalArgumentException("cannot get index of a hidden child"); - } else { - return -1; - } - } - // reverse the index - return index - mBucket.countOnesBefore(index); - } - - /** - * Returns whether a View is visible to LayoutManager or not. - * - * @param view The child view to check. Should be a child of the Callback. - * @return True if the View is not visible to LayoutManager - */ - boolean isHidden(View view) { - return mHiddenViews.contains(view); - } - - /** - * Marks a child view as hidden. - * - * @param view The view to hide. - */ - void hide(View view) { - final int offset = mCallback.indexOfChild(view); - if (offset < 0) { - throw new IllegalArgumentException("view is not a child, cannot hide " + view); - } - if (DEBUG && mBucket.get(offset)) { - throw new RuntimeException("trying to hide same view twice, how come ? " + view); - } - mBucket.set(offset); - hideViewInternal(view); - if (DEBUG) { - Log.d(TAG, "hiding child " + view + " at offset " + offset + ", " + this); - } - } - - /** - * Moves a child view from hidden list to regular list. - * Calling this method should probably be followed by a detach, otherwise, it will suddenly - * show up in LayoutManager's children list. - * - * @param view The hidden View to unhide - */ - void unhide(View view) { - final int offset = mCallback.indexOfChild(view); - if (offset < 0) { - throw new IllegalArgumentException("view is not a child, cannot hide " + view); - } - if (!mBucket.get(offset)) { - throw new RuntimeException("trying to unhide a view that was not hidden" + view); - } - mBucket.clear(offset); - unhideViewInternal(view); - } - - @Override - public String toString() { - return mBucket.toString() + ", hidden list:" + mHiddenViews.size(); - } - - /** - * Removes a view from the ViewGroup if it is hidden. - * - * @param view The view to remove. - * @return True if the View is found and it is hidden. False otherwise. - */ - boolean removeViewIfHidden(View view) { - if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE) { - if (mViewInRemoveView != view) { - throw new IllegalStateException("Cannot call removeViewIfHidden within removeView" - + "(At) for a different view"); - } - // removeView ends the ItemAnimation and triggers removeViewIfHidden - return false; - } else if (mRemoveStatus == REMOVE_STATUS_IN_REMOVE_IF_HIDDEN) { - throw new IllegalStateException("Cannot call removeViewIfHidden within" - + " removeViewIfHidden"); - } - try { - mRemoveStatus = REMOVE_STATUS_IN_REMOVE_IF_HIDDEN; - final int index = mCallback.indexOfChild(view); - if (index == -1) { - if (unhideViewInternal(view) && DEBUG) { - throw new IllegalStateException("view is in hidden list but not in view group"); - } - return true; - } - if (mBucket.get(index)) { - mBucket.remove(index); - if (!unhideViewInternal(view) && DEBUG) { - throw new IllegalStateException( - "removed a hidden view but it is not in hidden views list"); - } - mCallback.removeViewAt(index); - return true; - } - return false; - } finally { - mRemoveStatus = REMOVE_STATUS_NONE; - } - } - - /** - * Bitset implementation that provides methods to offset indices. - */ - static class Bucket { - - static final int BITS_PER_WORD = Long.SIZE; - - static final long LAST_BIT = 1L << (Long.SIZE - 1); - - long mData = 0; - - Bucket mNext; - - void set(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - mNext.set(index - BITS_PER_WORD); - } else { - mData |= 1L << index; - } - } - - private void ensureNext() { - if (mNext == null) { - mNext = new Bucket(); - } - } - - void clear(int index) { - if (index >= BITS_PER_WORD) { - if (mNext != null) { - mNext.clear(index - BITS_PER_WORD); - } - } else { - mData &= ~(1L << index); - } - - } - - boolean get(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - return mNext.get(index - BITS_PER_WORD); - } else { - return (mData & (1L << index)) != 0; - } - } - - void reset() { - mData = 0; - if (mNext != null) { - mNext.reset(); - } - } - - void insert(int index, boolean value) { - if (index >= BITS_PER_WORD) { - ensureNext(); - mNext.insert(index - BITS_PER_WORD, value); - } else { - final boolean lastBit = (mData & LAST_BIT) != 0; - long mask = (1L << index) - 1; - final long before = mData & mask; - final long after = (mData & ~mask) << 1; - mData = before | after; - if (value) { - set(index); - } else { - clear(index); - } - if (lastBit || mNext != null) { - ensureNext(); - mNext.insert(0, lastBit); - } - } - } - - boolean remove(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - return mNext.remove(index - BITS_PER_WORD); - } else { - long mask = (1L << index); - final boolean value = (mData & mask) != 0; - mData &= ~mask; - mask = mask - 1; - final long before = mData & mask; - // cannot use >> because it adds one. - final long after = Long.rotateRight(mData & ~mask, 1); - mData = before | after; - if (mNext != null) { - if (mNext.get(0)) { - set(BITS_PER_WORD - 1); - } - mNext.remove(0); - } - return value; - } - } - - int countOnesBefore(int index) { - if (mNext == null) { - if (index >= BITS_PER_WORD) { - return Long.bitCount(mData); - } - return Long.bitCount(mData & ((1L << index) - 1)); - } - if (index < BITS_PER_WORD) { - return Long.bitCount(mData & ((1L << index) - 1)); - } else { - return mNext.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData); - } - } - - @Override - public String toString() { - return mNext == null ? Long.toBinaryString(mData) - : mNext.toString() + "xx" + Long.toBinaryString(mData); - } - } - - interface Callback { - - int getChildCount(); - - void addView(View child, int index); - - int indexOfChild(View view); - - void removeViewAt(int index); - - View getChildAt(int offset); - - void removeAllViews(); - - RecyclerView.ViewHolder getChildViewHolder(View view); - - void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams); - - void detachViewFromParent(int offset); - - void onEnteredHiddenState(View child); - - void onLeftHiddenState(View child); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java b/app/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java deleted file mode 100644 index 45b719fe71..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode.NO_STABLE_IDS; - -import android.util.Pair; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * An {@link Adapter} implementation that presents the contents of multiple adapters in sequence. - * - *

- * MyAdapter adapter1 = ...;
- * AnotherAdapter adapter2 = ...;
- * ConcatAdapter concatenated = new ConcatAdapter(adapter1, adapter2);
- * recyclerView.setAdapter(concatenated);
- * 
- *

- * By default, {@link ConcatAdapter} isolates view types of nested adapters from each other such - * that - * it will change the view type before reporting it back to the {@link RecyclerView} to avoid any - * conflicts between the view types of added adapters. This also means each added adapter will have - * its own isolated pool of {@link ViewHolder}s, with no re-use in between added adapters. - *

- * If your {@link Adapter}s share the same view types, and can support sharing {@link ViewHolder} - * s between added adapters, provide an instance of {@link Config} where you set - * {@link Config#isolateViewTypes} to {@code false}. A common usage pattern for this is to return - * the {@code R.layout.} from the {@link Adapter#getItemViewType(int)} method. - *

- * When an added adapter calls one of the {@code notify} methods, {@link ConcatAdapter} properly - * offsets values before reporting it back to the {@link RecyclerView}. - * If an adapter calls {@link Adapter#notifyDataSetChanged()}, {@link ConcatAdapter} also calls - * {@link Adapter#notifyDataSetChanged()} as calling - * {@link Adapter#notifyItemRangeChanged(int, int)} will confuse the {@link RecyclerView}. - * You are highly encouraged to to use {@link SortedList} or {@link ListAdapter} to avoid - * calling {@link Adapter#notifyDataSetChanged()}. - *

- * Whether {@link ConcatAdapter} should support stable ids is defined in the {@link Config} - * object. Calling {@link Adapter#setHasStableIds(boolean)} has no effect. See documentation - * for {@link Config.StableIdMode} for details on how to configure {@link ConcatAdapter} to use - * stable ids. By default, it will not use stable ids and sub adapter stable ids will be ignored. - * Similar to the case above, you are highly encouraged to use {@link ListAdapter}, which will - * automatically calculate the changes in the data set for you so you won't need stable ids. - *

- * It is common to find the adapter position of a {@link ViewHolder} to handle user action on the - * {@link ViewHolder}. For those cases, instead of calling {@link ViewHolder#getAdapterPosition()}, - * use {@link ViewHolder#getBindingAdapterPosition()}. If your adapters share {@link ViewHolder}s, - * you can use the {@link ViewHolder#getBindingAdapter()} method to find the adapter which last - * bound that {@link ViewHolder}. - */ -@SuppressWarnings("unchecked") -public final class ConcatAdapter extends Adapter { - static final String TAG = "ConcatAdapter"; - /** - * Bulk of the logic is in the controller to keep this class isolated to the public API. - */ - private final ConcatAdapterController mController; - - /** - * Creates a ConcatAdapter with {@link Config#DEFAULT} and the given adapters in the given - * order. - * - * @param adapters The list of adapters to add - */ - @SafeVarargs - public ConcatAdapter(@NonNull Adapter... adapters) { - this(Config.DEFAULT, adapters); - } - - /** - * Creates a ConcatAdapter with the given config and the given adapters in the given order. - * - * @param config The configuration for this ConcatAdapter - * @param adapters The list of adapters to add - * @see Config.Builder - */ - @SafeVarargs - public ConcatAdapter( - @NonNull Config config, - @NonNull Adapter... adapters) { - this(config, Arrays.asList(adapters)); - } - - /** - * Creates a ConcatAdapter with {@link Config#DEFAULT} and the given adapters in the given - * order. - * - * @param adapters The list of adapters to add - */ - public ConcatAdapter(@NonNull List> adapters) { - this(Config.DEFAULT, adapters); - } - - /** - * Creates a ConcatAdapter with the given config and the given adapters in the given order. - * - * @param config The configuration for this ConcatAdapter - * @param adapters The list of adapters to add - * @see Config.Builder - */ - public ConcatAdapter( - @NonNull Config config, - @NonNull List> adapters) { - mController = new ConcatAdapterController(this, config); - for (Adapter adapter : adapters) { - addAdapter(adapter); - } - // go through super as we override it to be no-op - super.setHasStableIds(mController.hasStableIds()); - } - - /** - * Appends the given adapter to the existing list of adapters and notifies the observers of - * this {@link ConcatAdapter}. - * - * @param adapter The new adapter to add - * @return {@code true} if the adapter is successfully added because it did not already exist, - * {@code false} otherwise. - * @see #addAdapter(int, Adapter) - * @see #removeAdapter(Adapter) - */ - public boolean addAdapter(@NonNull Adapter adapter) { - return mController.addAdapter((Adapter) adapter); - } - - /** - * Adds the given adapter to the given index among other adapters that are already added. - * - * @param index The index into which to insert the adapter. ConcatAdapter will throw an - * {@link IndexOutOfBoundsException} if the index is not between 0 and current - * adapter count (inclusive). - * @param adapter The new adapter to add to the adapters list. - * @return {@code true} if the adapter is successfully added because it did not already exist, - * {@code false} otherwise. - * @see #addAdapter(Adapter) - * @see #removeAdapter(Adapter) - */ - public boolean addAdapter(int index, @NonNull Adapter adapter) { - return mController.addAdapter(index, (Adapter) adapter); - } - - /** - * Removes the given adapter from the adapters list if it exists - * - * @param adapter The adapter to remove - * @return {@code true} if the adapter was previously added to this {@code ConcatAdapter} and - * now removed or {@code false} if it couldn't be found. - */ - public boolean removeAdapter(@NonNull Adapter adapter) { - return mController.removeAdapter((Adapter) adapter); - } - - @Override - public int getItemViewType(int position) { - return mController.getItemViewType(position); - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return mController.onCreateViewHolder(parent, viewType); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - mController.onBindViewHolder(holder, position); - } - - /** - * Calling this method is an error and will result in an {@link UnsupportedOperationException}. - * You should use the {@link Config} object passed into the ConcatAdapter to configure this - * behavior. - * - * @param hasStableIds Whether items in data set have unique identifiers or not. - */ - @Override - public void setHasStableIds(boolean hasStableIds) { - throw new UnsupportedOperationException( - "Calling setHasStableIds is not allowed on the ConcatAdapter. " - + "Use the Config object passed in the constructor to control this " - + "behavior"); - } - - /** - * Calling this method is an error and will result in an {@link UnsupportedOperationException}. - * - * ConcatAdapter infers this value from added {@link Adapter}s. - * - * @param strategy The saved state restoration strategy for this Adapter such that - * {@link ConcatAdapter} will allow state restoration only if all added - * adapters allow it or - * there are no adapters. - */ - @Override - public void setStateRestorationPolicy(@NonNull StateRestorationPolicy strategy) { - // do nothing - throw new UnsupportedOperationException( - "Calling setStateRestorationPolicy is not allowed on the ConcatAdapter." - + " This value is inferred from added adapters"); - } - - @Override - public long getItemId(int position) { - return mController.getItemId(position); - } - - /** - * Internal method called by the ConcatAdapterController. - */ - void internalSetStateRestorationPolicy(@NonNull StateRestorationPolicy strategy) { - super.setStateRestorationPolicy(strategy); - } - - @Override - public int getItemCount() { - return mController.getTotalCount(); - } - - @Override - public boolean onFailedToRecycleView(@NonNull ViewHolder holder) { - return mController.onFailedToRecycleView(holder); - } - - @Override - public void onViewAttachedToWindow(@NonNull ViewHolder holder) { - mController.onViewAttachedToWindow(holder); - } - - @Override - public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { - mController.onViewDetachedFromWindow(holder); - } - - @Override - public void onViewRecycled(@NonNull ViewHolder holder) { - mController.onViewRecycled(holder); - } - - @Override - public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { - mController.onAttachedToRecyclerView(recyclerView); - } - - @Override - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - mController.onDetachedFromRecyclerView(recyclerView); - } - - /** - * Returns an unmodifiable copy of the list of adapters in this {@link ConcatAdapter}. - * Note that this is a copy hence future changes in the ConcatAdapter are not reflected in - * this list. - * - * @return A copy of the list of adapters in this ConcatAdapter. - */ - @NonNull - public List> getAdapters() { - return Collections.unmodifiableList(mController.getCopyOfAdapters()); - } - - /** - * Returns the position of the given {@link ViewHolder} in the given {@link Adapter}. - * - * If the given {@link Adapter} is not part of this {@link ConcatAdapter}, - * {@link RecyclerView#NO_POSITION} is returned. - * - * @param adapter The adapter which is a sub adapter of this ConcatAdapter or itself. - * @param viewHolder The view holder whose local position in the given adapter will be - * returned. - * @param localPosition The position of the given {@link ViewHolder} in this {@link Adapter}. - * @return The local position of the given {@link ViewHolder} in the given {@link Adapter} or - * {@link RecyclerView#NO_POSITION} if the {@link ViewHolder} is not bound to an item or the - * given {@link Adapter} is not part of this ConcatAdapter. - */ - @Override - public int findRelativeAdapterPositionIn( - @NonNull Adapter adapter, - @NonNull ViewHolder viewHolder, - int localPosition) { - return mController.getLocalAdapterPosition(adapter, viewHolder, localPosition); - } - - - /** - * Retrieve the adapter and local position for a given position in this {@code ConcatAdapter}. - * - * This allows for retrieving wrapped adapter information in situations where you don't have a - * {@link ViewHolder}, such as within a - * {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup} in which you want to - * look up information from the source adapter. - * - * @param globalPosition The position in this {@code ConcatAdapter}. - * @return a Pair with the first element set to the wrapped {@code Adapter} containing that - * position and the second element set to the local position in the wrapped adapter - * @throws IllegalArgumentException if the specified {@code globalPosition} does not - * correspond to a valid element of this adapter. That is, if {@code globalPosition} is less - * than 0 or greater than the total number of items in the {@code ConcatAdapter} - */ - @NonNull - public Pair, Integer> getWrappedAdapterAndPosition(int - globalPosition) { - return mController.getWrappedAdapterAndPosition(globalPosition); - } - - /** - * The configuration object for a {@link ConcatAdapter}. - */ - public static final class Config { - /** - * If {@code false}, {@link ConcatAdapter} assumes all assigned adapters share a global - * view type pool such that they use the same view types to refer to the same - * {@link ViewHolder}s. - *

- * Setting this to {@code false} will allow nested adapters to share {@link ViewHolder}s but - * it also means these adapters should not have conflicting view types - * ({@link Adapter#getItemViewType(int)}) such that two different adapters return the same - * view type for different {@link ViewHolder}s. - * - * By default, it is set to {@code true} which means {@link ConcatAdapter} will isolate - * view types across adapters, preventing them from using the same {@link ViewHolder}s. - */ - public final boolean isolateViewTypes; - - /** - * Defines whether the {@link ConcatAdapter} should support stable ids or not - * ({@link Adapter#hasStableIds()}. - *

- * There are 3 possible options: - * - * {@link StableIdMode#NO_STABLE_IDS}: In this mode, {@link ConcatAdapter} ignores the - * stable - * ids reported by sub adapters. This is the default mode. - * - * {@link StableIdMode#ISOLATED_STABLE_IDS}: In this mode, {@link ConcatAdapter} will return - * {@code true} from {@link ConcatAdapter#hasStableIds()} and will require all added - * {@link Adapter}s to have stable ids. As two different adapters may return same stable ids - * because they are unaware of each-other, {@link ConcatAdapter} will isolate each - * {@link Adapter}'s id pool from each other such that it will overwrite the reported stable - * id before reporting back to the {@link RecyclerView}. In this mode, the value returned - * from {@link ViewHolder#getItemId()} might differ from the value returned from - * {@link Adapter#getItemId(int)}. - * - * {@link StableIdMode#SHARED_STABLE_IDS}: In this mode, {@link ConcatAdapter} will return - * {@code true} from {@link ConcatAdapter#hasStableIds()} and will require all added - * {@link Adapter}s to have stable ids. Unlike {@link StableIdMode#ISOLATED_STABLE_IDS}, - * {@link ConcatAdapter} will not override the returned item ids. In this mode, - * child {@link Adapter}s must be aware of each-other and never return the same id unless - * an item is moved between {@link Adapter}s. - * - * Default value is {@link StableIdMode#NO_STABLE_IDS}. - */ - @NonNull - public final StableIdMode stableIdMode; - - - /** - * Default configuration for {@link ConcatAdapter} where {@link Config#isolateViewTypes} - * is set to {@code true} and {@link Config#stableIdMode} is set to - * {@link StableIdMode#NO_STABLE_IDS}. - */ - @NonNull - public static final Config DEFAULT = new Config(true, NO_STABLE_IDS); - - Config(boolean isolateViewTypes, @NonNull StableIdMode stableIdMode) { - this.isolateViewTypes = isolateViewTypes; - this.stableIdMode = stableIdMode; - } - - /** - * Defines how {@link ConcatAdapter} handle stable ids ({@link Adapter#hasStableIds()}). - */ - public enum StableIdMode { - /** - * In this mode, {@link ConcatAdapter} ignores the stable - * ids reported by sub adapters. This is the default mode. - * Adding an {@link Adapter} with stable ids will result in a warning as it will be - * ignored. - */ - NO_STABLE_IDS, - /** - * In this mode, {@link ConcatAdapter} will return {@code true} from - * {@link ConcatAdapter#hasStableIds()} and will require all added - * {@link Adapter}s to have stable ids. As two different adapters may return - * same stable ids because they are unaware of each-other, {@link ConcatAdapter} will - * isolate each {@link Adapter}'s id pool from each other such that it will overwrite - * the reported stable id before reporting back to the {@link RecyclerView}. In this - * mode, the value returned from {@link ViewHolder#getItemId()} might differ from the - * value returned from {@link Adapter#getItemId(int)}. - * - * Adding an adapter without stable ids will result in an - * {@link IllegalArgumentException}. - */ - ISOLATED_STABLE_IDS, - /** - * In this mode, {@link ConcatAdapter} will return {@code true} from - * {@link ConcatAdapter#hasStableIds()} and will require all added - * {@link Adapter}s to have stable ids. Unlike {@link StableIdMode#ISOLATED_STABLE_IDS}, - * {@link ConcatAdapter} will not override the returned item ids. In this mode, - * child {@link Adapter}s must be aware of each-other and never return the same id - * unless and item is moved between {@link Adapter}s. - * Adding an adapter without stable ids will result in an - * {@link IllegalArgumentException}. - */ - SHARED_STABLE_IDS - } - - /** - * The builder for {@link Config} class. - */ - public static final class Builder { - private boolean mIsolateViewTypes = DEFAULT.isolateViewTypes; - private StableIdMode mStableIdMode = DEFAULT.stableIdMode; - - /** - * Sets whether {@link ConcatAdapter} should isolate view types of nested adapters from - * each other. - * - * @param isolateViewTypes {@code true} if {@link ConcatAdapter} should override view - * types of nested adapters to avoid view type - * conflicts, {@code false} otherwise. - * Defaults to {@link Config#DEFAULT}'s - * {@link Config#isolateViewTypes} value ({@code true}). - * @return this - * @see Config#isolateViewTypes - */ - @NonNull - public Builder setIsolateViewTypes(boolean isolateViewTypes) { - mIsolateViewTypes = isolateViewTypes; - return this; - } - - /** - * Sets how the {@link ConcatAdapter} should handle stable ids - * ({@link Adapter#hasStableIds()}). See documentation in {@link Config#stableIdMode} - * for details. - * - * @param stableIdMode The stable id mode for the {@link ConcatAdapter}. Defaults to - * {@link Config#DEFAULT}'s {@link Config#stableIdMode} value - * ({@link StableIdMode#NO_STABLE_IDS}). - * @return this - * @see Config#stableIdMode - */ - @NonNull - public Builder setStableIdMode(@NonNull StableIdMode stableIdMode) { - mStableIdMode = stableIdMode; - return this; - } - - /** - * @return A new instance of {@link Config} with the given parameters. - */ - @NonNull - public Config build() { - return new Config(mIsolateViewTypes, mStableIdMode); - } - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ConcatAdapterController.java b/app/src/main/java/androidx/recyclerview/widget/ConcatAdapterController.java deleted file mode 100644 index d31540c649..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ConcatAdapterController.java +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode.ISOLATED_STABLE_IDS; -import static androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode.NO_STABLE_IDS; -import static androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS; -import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.ALLOW; -import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT; -import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY; -import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; - -import android.util.Log; -import android.util.Pair; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.util.Preconditions; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; - -/** - * All logic for the {@link ConcatAdapter} is here so that we can clearly see a separation - * between an adapter implementation and merging logic. - */ -class ConcatAdapterController implements NestedAdapterWrapper.Callback { - private final ConcatAdapter mConcatAdapter; - - /** - * Holds the mapping from the view type to the adapter which reported that type. - */ - private final ViewTypeStorage mViewTypeStorage; - - /** - * We hold onto the list of attached recyclerviews so that we can dispatch attach/detach to - * any adapter that was added later on. - * Probably does not need to be a weak reference but playing safe here. - */ - private List> mAttachedRecyclerViews = new ArrayList<>(); - - /** - * Keeps the information about which ViewHolder is bound by which adapter. - * It is set in onBind, reset at onRecycle. - */ - private final IdentityHashMap - mBinderLookup = new IdentityHashMap<>(); - - private List mWrappers = new ArrayList<>(); - - // keep one of these around so that we can return wrapper & position w/o allocation ¯\_(ツ)_/¯ - private WrapperAndLocalPosition mReusableHolder = new WrapperAndLocalPosition(); - - @NonNull - private final ConcatAdapter.Config.StableIdMode mStableIdMode; - - /** - * This is where we keep stable ids, if supported - */ - private final StableIdStorage mStableIdStorage; - - ConcatAdapterController( - ConcatAdapter concatAdapter, - ConcatAdapter.Config config) { - mConcatAdapter = concatAdapter; - - // setup view type handling - if (config.isolateViewTypes) { - mViewTypeStorage = new ViewTypeStorage.IsolatedViewTypeStorage(); - } else { - mViewTypeStorage = new ViewTypeStorage.SharedIdRangeViewTypeStorage(); - } - - // setup stable id handling - mStableIdMode = config.stableIdMode; - if (config.stableIdMode == NO_STABLE_IDS) { - mStableIdStorage = new StableIdStorage.NoStableIdStorage(); - } else if (config.stableIdMode == ISOLATED_STABLE_IDS) { - mStableIdStorage = new StableIdStorage.IsolatedStableIdStorage(); - } else if (config.stableIdMode == SHARED_STABLE_IDS) { - mStableIdStorage = new StableIdStorage.SharedPoolStableIdStorage(); - } else { - throw new IllegalArgumentException("unknown stable id mode"); - } - } - - @Nullable - private NestedAdapterWrapper findWrapperFor(Adapter adapter) { - final int index = indexOfWrapper(adapter); - if (index == -1) { - return null; - } - return mWrappers.get(index); - } - - private int indexOfWrapper(Adapter adapter) { - final int limit = mWrappers.size(); - for (int i = 0; i < limit; i++) { - if (mWrappers.get(i).adapter == adapter) { - return i; - } - } - return -1; - } - - /** - * return true if added, false otherwise. - * - * @see ConcatAdapter#addAdapter(Adapter) - */ - boolean addAdapter(Adapter adapter) { - return addAdapter(mWrappers.size(), adapter); - } - - /** - * return true if added, false otherwise. - * throws exception if index is out of bounds - * - * @see ConcatAdapter#addAdapter(int, Adapter) - */ - boolean addAdapter(int index, Adapter adapter) { - if (index < 0 || index > mWrappers.size()) { - throw new IndexOutOfBoundsException("Index must be between 0 and " - + mWrappers.size() + ". Given:" + index); - } - if (hasStableIds()) { - Preconditions.checkArgument(adapter.hasStableIds(), - "All sub adapters must have stable ids when stable id mode " - + "is ISOLATED_STABLE_IDS or SHARED_STABLE_IDS"); - } else { - if (adapter.hasStableIds()) { - Log.w(ConcatAdapter.TAG, "Stable ids in the adapter will be ignored as the" - + " ConcatAdapter is configured not to have stable ids"); - } - } - NestedAdapterWrapper existing = findWrapperFor(adapter); - if (existing != null) { - return false; - } - NestedAdapterWrapper wrapper = new NestedAdapterWrapper(adapter, this, - mViewTypeStorage, mStableIdStorage.createStableIdLookup()); - mWrappers.add(index, wrapper); - // notify attach for all recyclerview - for (WeakReference reference : mAttachedRecyclerViews) { - RecyclerView recyclerView = reference.get(); - if (recyclerView != null) { - adapter.onAttachedToRecyclerView(recyclerView); - } - } - // new items, notify add for them - if (wrapper.getCachedItemCount() > 0) { - mConcatAdapter.notifyItemRangeInserted( - countItemsBefore(wrapper), - wrapper.getCachedItemCount() - ); - } - // reset state restoration strategy - calculateAndUpdateStateRestorationPolicy(); - return true; - } - - boolean removeAdapter(Adapter adapter) { - final int index = indexOfWrapper(adapter); - if (index == -1) { - return false; - } - NestedAdapterWrapper wrapper = mWrappers.get(index); - int offset = countItemsBefore(wrapper); - mWrappers.remove(index); - mConcatAdapter.notifyItemRangeRemoved(offset, wrapper.getCachedItemCount()); - // notify detach for all recyclerviews - for (WeakReference reference : mAttachedRecyclerViews) { - RecyclerView recyclerView = reference.get(); - if (recyclerView != null) { - adapter.onDetachedFromRecyclerView(recyclerView); - } - } - wrapper.dispose(); - calculateAndUpdateStateRestorationPolicy(); - return true; - } - - private int countItemsBefore(NestedAdapterWrapper wrapper) { - int count = 0; - for (NestedAdapterWrapper item : mWrappers) { - if (item != wrapper) { - count += item.getCachedItemCount(); - } else { - break; - } - } - return count; - } - - public long getItemId(int globalPosition) { - WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); - long globalItemId = wrapperAndPos.mWrapper.getItemId(wrapperAndPos.mLocalPosition); - releaseWrapperAndLocalPosition(wrapperAndPos); - return globalItemId; - } - - @Override - public void onChanged(@NonNull NestedAdapterWrapper wrapper) { - // TODO should we notify more cleverly, maybe in v2 - mConcatAdapter.notifyDataSetChanged(); - calculateAndUpdateStateRestorationPolicy(); - } - - @Override - public void onItemRangeChanged(@NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, int itemCount) { - final int offset = countItemsBefore(nestedAdapterWrapper); - mConcatAdapter.notifyItemRangeChanged( - positionStart + offset, - itemCount - ); - } - - @Override - public void onItemRangeChanged(@NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, int itemCount, @Nullable Object payload) { - final int offset = countItemsBefore(nestedAdapterWrapper); - mConcatAdapter.notifyItemRangeChanged( - positionStart + offset, - itemCount, - payload - ); - } - - @Override - public void onItemRangeInserted(@NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, int itemCount) { - final int offset = countItemsBefore(nestedAdapterWrapper); - mConcatAdapter.notifyItemRangeInserted( - positionStart + offset, - itemCount - ); - } - - @Override - public void onItemRangeRemoved(@NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, int itemCount) { - int offset = countItemsBefore(nestedAdapterWrapper); - mConcatAdapter.notifyItemRangeRemoved( - positionStart + offset, - itemCount - ); - } - - @Override - public void onItemRangeMoved(@NonNull NestedAdapterWrapper nestedAdapterWrapper, - int fromPosition, int toPosition) { - int offset = countItemsBefore(nestedAdapterWrapper); - mConcatAdapter.notifyItemMoved( - fromPosition + offset, - toPosition + offset - ); - } - - @Override - public void onStateRestorationPolicyChanged(NestedAdapterWrapper nestedAdapterWrapper) { - calculateAndUpdateStateRestorationPolicy(); - } - - private void calculateAndUpdateStateRestorationPolicy() { - StateRestorationPolicy newPolicy = computeStateRestorationPolicy(); - if (newPolicy != mConcatAdapter.getStateRestorationPolicy()) { - mConcatAdapter.internalSetStateRestorationPolicy(newPolicy); - } - } - - private StateRestorationPolicy computeStateRestorationPolicy() { - for (NestedAdapterWrapper wrapper : mWrappers) { - StateRestorationPolicy strategy = - wrapper.adapter.getStateRestorationPolicy(); - if (strategy == PREVENT) { - // one adapter can block all - return PREVENT; - } else if (strategy == PREVENT_WHEN_EMPTY && wrapper.getCachedItemCount() == 0) { - // an adapter wants to allow w/ size but we need to make sure there is no prevent - return PREVENT; - } - } - return ALLOW; - } - - public int getTotalCount() { - // should we cache this as well ? - int total = 0; - for (NestedAdapterWrapper wrapper : mWrappers) { - total += wrapper.getCachedItemCount(); - } - return total; - } - - public int getItemViewType(int globalPosition) { - WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); - int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition); - releaseWrapperAndLocalPosition(wrapperAndPos); - return itemViewType; - } - - public ViewHolder onCreateViewHolder(ViewGroup parent, int globalViewType) { - NestedAdapterWrapper wrapper = mViewTypeStorage.getWrapperForGlobalType(globalViewType); - return wrapper.onCreateViewHolder(parent, globalViewType); - } - - public Pair, Integer> getWrappedAdapterAndPosition( - int globalPosition) { - WrapperAndLocalPosition wrapper = findWrapperAndLocalPosition(globalPosition); - Pair, Integer> pair = new Pair<>(wrapper.mWrapper.adapter, - wrapper.mLocalPosition); - releaseWrapperAndLocalPosition(wrapper); - return pair; - } - - /** - * Always call {@link #releaseWrapperAndLocalPosition(WrapperAndLocalPosition)} when you are - * done with it - */ - @NonNull - private WrapperAndLocalPosition findWrapperAndLocalPosition( - int globalPosition - ) { - WrapperAndLocalPosition result; - if (mReusableHolder.mInUse) { - result = new WrapperAndLocalPosition(); - } else { - mReusableHolder.mInUse = true; - result = mReusableHolder; - } - int localPosition = globalPosition; - for (NestedAdapterWrapper wrapper : mWrappers) { - if (wrapper.getCachedItemCount() > localPosition) { - result.mWrapper = wrapper; - result.mLocalPosition = localPosition; - break; - } - localPosition -= wrapper.getCachedItemCount(); - } - if (result.mWrapper == null) { - throw new IllegalArgumentException("Cannot find wrapper for " + globalPosition); - } - return result; - } - - private void releaseWrapperAndLocalPosition(WrapperAndLocalPosition wrapperAndLocalPosition) { - wrapperAndLocalPosition.mInUse = false; - wrapperAndLocalPosition.mWrapper = null; - wrapperAndLocalPosition.mLocalPosition = -1; - mReusableHolder = wrapperAndLocalPosition; - } - - public void onBindViewHolder(ViewHolder holder, int globalPosition) { - WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition); - mBinderLookup.put(holder, wrapperAndPos.mWrapper); - wrapperAndPos.mWrapper.onBindViewHolder(holder, wrapperAndPos.mLocalPosition); - releaseWrapperAndLocalPosition(wrapperAndPos); - } - - public boolean canRestoreState() { - for (NestedAdapterWrapper wrapper : mWrappers) { - if (!wrapper.adapter.canRestoreState()) { - return false; - } - } - return true; - } - - public void onViewAttachedToWindow(ViewHolder holder) { - NestedAdapterWrapper wrapper = getWrapper(holder); - wrapper.adapter.onViewAttachedToWindow(holder); - } - - public void onViewDetachedFromWindow(ViewHolder holder) { - NestedAdapterWrapper wrapper = getWrapper(holder); - wrapper.adapter.onViewDetachedFromWindow(holder); - } - - public void onViewRecycled(ViewHolder holder) { - NestedAdapterWrapper wrapper = mBinderLookup.get(holder); - if (wrapper == null) { - throw new IllegalStateException("Cannot find wrapper for " + holder - + ", seems like it is not bound by this adapter: " + this); - } - wrapper.adapter.onViewRecycled(holder); - mBinderLookup.remove(holder); - } - - public boolean onFailedToRecycleView(ViewHolder holder) { - NestedAdapterWrapper wrapper = mBinderLookup.get(holder); - if (wrapper == null) { - throw new IllegalStateException("Cannot find wrapper for " + holder - + ", seems like it is not bound by this adapter: " + this); - } - final boolean result = wrapper.adapter.onFailedToRecycleView(holder); - mBinderLookup.remove(holder); - return result; - } - - @NonNull - private NestedAdapterWrapper getWrapper(ViewHolder holder) { - NestedAdapterWrapper wrapper = mBinderLookup.get(holder); - if (wrapper == null) { - throw new IllegalStateException("Cannot find wrapper for " + holder - + ", seems like it is not bound by this adapter: " + this); - } - return wrapper; - } - - private boolean isAttachedTo(RecyclerView recyclerView) { - for (WeakReference reference : mAttachedRecyclerViews) { - if (reference.get() == recyclerView) { - return true; - } - } - return false; - } - - public void onAttachedToRecyclerView(RecyclerView recyclerView) { - if (isAttachedTo(recyclerView)) { - return; - } - mAttachedRecyclerViews.add(new WeakReference<>(recyclerView)); - for (NestedAdapterWrapper wrapper : mWrappers) { - wrapper.adapter.onAttachedToRecyclerView(recyclerView); - } - } - - public void onDetachedFromRecyclerView(RecyclerView recyclerView) { - for (int i = mAttachedRecyclerViews.size() - 1; i >= 0; i--) { - WeakReference reference = mAttachedRecyclerViews.get(i); - if (reference.get() == null) { - mAttachedRecyclerViews.remove(i); - } else if (reference.get() == recyclerView) { - mAttachedRecyclerViews.remove(i); - break; // here we can break as we don't keep duplicates - } - } - for (NestedAdapterWrapper wrapper : mWrappers) { - wrapper.adapter.onDetachedFromRecyclerView(recyclerView); - } - } - - public int getLocalAdapterPosition( - Adapter adapter, - ViewHolder viewHolder, - int globalPosition - ) { - NestedAdapterWrapper wrapper = mBinderLookup.get(viewHolder); - if (wrapper == null) { - return NO_POSITION; - } - int itemsBefore = countItemsBefore(wrapper); - // local position is globalPosition - itemsBefore - int localPosition = globalPosition - itemsBefore; - // Early error detection: - int itemCount = wrapper.adapter.getItemCount(); - if (localPosition < 0 || localPosition >= itemCount) { - throw new IllegalStateException("Detected inconsistent adapter updates. The" - + " local position of the view holder maps to " + localPosition + " which" - + " is out of bounds for the adapter with size " - + itemCount + "." - + "Make sure to immediately call notify methods in your adapter when you " - + "change the backing data" - + "viewHolder:" + viewHolder - + "adapter:" + adapter); - } - return wrapper.adapter.findRelativeAdapterPositionIn(adapter, viewHolder, localPosition); - } - - - @Nullable - public Adapter getBoundAdapter(ViewHolder viewHolder) { - NestedAdapterWrapper wrapper = mBinderLookup.get(viewHolder); - if (wrapper == null) { - return null; - } - return wrapper.adapter; - } - - @SuppressWarnings("MixedMutabilityReturnType") - public List> getCopyOfAdapters() { - if (mWrappers.isEmpty()) { - return Collections.emptyList(); - } - List> adapters = new ArrayList<>(mWrappers.size()); - for (NestedAdapterWrapper wrapper : mWrappers) { - adapters.add(wrapper.adapter); - } - return adapters; - } - - public boolean hasStableIds() { - return mStableIdMode != NO_STABLE_IDS; - } - - /** - * Helper class to hold onto wrapper and local position without allocating objects as this is - * a very common call. - */ - static class WrapperAndLocalPosition { - NestedAdapterWrapper mWrapper; - int mLocalPosition; - boolean mInUse; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/DefaultItemAnimator.java b/app/src/main/java/androidx/recyclerview/widget/DefaultItemAnimator.java deleted file mode 100644 index a520aa98d9..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/DefaultItemAnimator.java +++ /dev/null @@ -1,674 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.view.View; -import android.view.ViewPropertyAnimator; - -import androidx.annotation.NonNull; -import androidx.core.view.ViewCompat; - -import java.util.ArrayList; -import java.util.List; - -/** - * This implementation of {@link RecyclerView.ItemAnimator} provides basic - * animations on remove, add, and move events that happen to the items in - * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. - * - * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) - */ -public class DefaultItemAnimator extends SimpleItemAnimator { - private static final boolean DEBUG = false; - - private static TimeInterpolator sDefaultInterpolator; - - private ArrayList mPendingRemovals = new ArrayList<>(); - private ArrayList mPendingAdditions = new ArrayList<>(); - private ArrayList mPendingMoves = new ArrayList<>(); - private ArrayList mPendingChanges = new ArrayList<>(); - - ArrayList> mAdditionsList = new ArrayList<>(); - ArrayList> mMovesList = new ArrayList<>(); - ArrayList> mChangesList = new ArrayList<>(); - - ArrayList mAddAnimations = new ArrayList<>(); - ArrayList mMoveAnimations = new ArrayList<>(); - ArrayList mRemoveAnimations = new ArrayList<>(); - ArrayList mChangeAnimations = new ArrayList<>(); - - private static class MoveInfo { - public RecyclerView.ViewHolder holder; - public int fromX, fromY, toX, toY; - - MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { - this.holder = holder; - this.fromX = fromX; - this.fromY = fromY; - this.toX = toX; - this.toY = toY; - } - } - - private static class ChangeInfo { - public RecyclerView.ViewHolder oldHolder, newHolder; - public int fromX, fromY, toX, toY; - private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) { - this.oldHolder = oldHolder; - this.newHolder = newHolder; - } - - ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, - int fromX, int fromY, int toX, int toY) { - this(oldHolder, newHolder); - this.fromX = fromX; - this.fromY = fromY; - this.toX = toX; - this.toY = toY; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public String toString() { - return "ChangeInfo{" - + "oldHolder=" + oldHolder - + ", newHolder=" + newHolder - + ", fromX=" + fromX - + ", fromY=" + fromY - + ", toX=" + toX - + ", toY=" + toY - + '}'; - } - } - - @Override - public void runPendingAnimations() { - boolean removalsPending = !mPendingRemovals.isEmpty(); - boolean movesPending = !mPendingMoves.isEmpty(); - boolean changesPending = !mPendingChanges.isEmpty(); - boolean additionsPending = !mPendingAdditions.isEmpty(); - if (!removalsPending && !movesPending && !additionsPending && !changesPending) { - // nothing to animate - return; - } - // First, remove stuff - for (RecyclerView.ViewHolder holder : mPendingRemovals) { - animateRemoveImpl(holder); - } - mPendingRemovals.clear(); - // Next, move stuff - if (movesPending) { - final ArrayList moves = new ArrayList<>(); - moves.addAll(mPendingMoves); - mMovesList.add(moves); - mPendingMoves.clear(); - Runnable mover = new Runnable() { - @Override - public void run() { - for (MoveInfo moveInfo : moves) { - animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, - moveInfo.toX, moveInfo.toY); - } - moves.clear(); - mMovesList.remove(moves); - } - }; - if (removalsPending) { - View view = moves.get(0).holder.itemView; - ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); - } else { - mover.run(); - } - } - // Next, change stuff, to run in parallel with move animations - if (changesPending) { - final ArrayList changes = new ArrayList<>(); - changes.addAll(mPendingChanges); - mChangesList.add(changes); - mPendingChanges.clear(); - Runnable changer = new Runnable() { - @Override - public void run() { - for (ChangeInfo change : changes) { - animateChangeImpl(change); - } - changes.clear(); - mChangesList.remove(changes); - } - }; - if (removalsPending) { - RecyclerView.ViewHolder holder = changes.get(0).oldHolder; - ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); - } else { - changer.run(); - } - } - // Next, add stuff - if (additionsPending) { - final ArrayList additions = new ArrayList<>(); - additions.addAll(mPendingAdditions); - mAdditionsList.add(additions); - mPendingAdditions.clear(); - Runnable adder = new Runnable() { - @Override - public void run() { - for (RecyclerView.ViewHolder holder : additions) { - animateAddImpl(holder); - } - additions.clear(); - mAdditionsList.remove(additions); - } - }; - if (removalsPending || movesPending || changesPending) { - long removeDuration = removalsPending ? getRemoveDuration() : 0; - long moveDuration = movesPending ? getMoveDuration() : 0; - long changeDuration = changesPending ? getChangeDuration() : 0; - long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); - View view = additions.get(0).itemView; - ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); - } else { - adder.run(); - } - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public boolean animateRemove(final RecyclerView.ViewHolder holder) { - resetAnimation(holder); - mPendingRemovals.add(holder); - return true; - } - - private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { - final View view = holder.itemView; - final ViewPropertyAnimator animation = view.animate(); - mRemoveAnimations.add(holder); - animation.setDuration(getRemoveDuration()).alpha(0).setListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchRemoveStarting(holder); - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - view.setAlpha(1); - dispatchRemoveFinished(holder); - mRemoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public boolean animateAdd(final RecyclerView.ViewHolder holder) { - resetAnimation(holder); - holder.itemView.setAlpha(0); - mPendingAdditions.add(holder); - return true; - } - - void animateAddImpl(final RecyclerView.ViewHolder holder) { - final View view = holder.itemView; - final ViewPropertyAnimator animation = view.animate(); - mAddAnimations.add(holder); - animation.alpha(1).setDuration(getAddDuration()) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchAddStarting(holder); - } - - @Override - public void onAnimationCancel(Animator animator) { - view.setAlpha(1); - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - dispatchAddFinished(holder); - mAddAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, - int toX, int toY) { - final View view = holder.itemView; - fromX += (int) holder.itemView.getTranslationX(); - fromY += (int) holder.itemView.getTranslationY(); - resetAnimation(holder); - int deltaX = toX - fromX; - int deltaY = toY - fromY; - if (deltaX == 0 && deltaY == 0) { - dispatchMoveFinished(holder); - return false; - } - if (deltaX != 0) { - view.setTranslationX(-deltaX); - } - if (deltaY != 0) { - view.setTranslationY(-deltaY); - } - mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); - return true; - } - - void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { - final View view = holder.itemView; - final int deltaX = toX - fromX; - final int deltaY = toY - fromY; - if (deltaX != 0) { - view.animate().translationX(0); - } - if (deltaY != 0) { - view.animate().translationY(0); - } - // TODO: make EndActions end listeners instead, since end actions aren't called when - // vpas are canceled (and can't end them. why?) - // need listener functionality in VPACompat for this. Ick. - final ViewPropertyAnimator animation = view.animate(); - mMoveAnimations.add(holder); - animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchMoveStarting(holder); - } - - @Override - public void onAnimationCancel(Animator animator) { - if (deltaX != 0) { - view.setTranslationX(0); - } - if (deltaY != 0) { - view.setTranslationY(0); - } - } - - @Override - public void onAnimationEnd(Animator animator) { - animation.setListener(null); - dispatchMoveFinished(holder); - mMoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public boolean animateChange(RecyclerView.ViewHolder oldHolder, - RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { - if (oldHolder == newHolder) { - // Don't know how to run change animations when the same view holder is re-used. - // run a move animation to handle position changes. - return animateMove(oldHolder, fromLeft, fromTop, toLeft, toTop); - } - final float prevTranslationX = oldHolder.itemView.getTranslationX(); - final float prevTranslationY = oldHolder.itemView.getTranslationY(); - final float prevAlpha = oldHolder.itemView.getAlpha(); - resetAnimation(oldHolder); - int deltaX = (int) (toLeft - fromLeft - prevTranslationX); - int deltaY = (int) (toTop - fromTop - prevTranslationY); - // recover prev translation state after ending animation - oldHolder.itemView.setTranslationX(prevTranslationX); - oldHolder.itemView.setTranslationY(prevTranslationY); - oldHolder.itemView.setAlpha(prevAlpha); - if (newHolder != null) { - // carry over translation values - resetAnimation(newHolder); - newHolder.itemView.setTranslationX(-deltaX); - newHolder.itemView.setTranslationY(-deltaY); - newHolder.itemView.setAlpha(0); - } - mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop)); - return true; - } - - void animateChangeImpl(final ChangeInfo changeInfo) { - final RecyclerView.ViewHolder holder = changeInfo.oldHolder; - final View view = holder == null ? null : holder.itemView; - final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; - final View newView = newHolder != null ? newHolder.itemView : null; - if (view != null) { - final ViewPropertyAnimator oldViewAnim = view.animate().setDuration( - getChangeDuration()); - mChangeAnimations.add(changeInfo.oldHolder); - oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); - oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); - oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchChangeStarting(changeInfo.oldHolder, true); - } - - @Override - public void onAnimationEnd(Animator animator) { - oldViewAnim.setListener(null); - view.setAlpha(1); - view.setTranslationX(0); - view.setTranslationY(0); - dispatchChangeFinished(changeInfo.oldHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); - } - }).start(); - } - if (newView != null) { - final ViewPropertyAnimator newViewAnimation = newView.animate(); - mChangeAnimations.add(changeInfo.newHolder); - newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()) - .alpha(1).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - dispatchChangeStarting(changeInfo.newHolder, false); - } - @Override - public void onAnimationEnd(Animator animator) { - newViewAnimation.setListener(null); - newView.setAlpha(1); - newView.setTranslationX(0); - newView.setTranslationY(0); - dispatchChangeFinished(changeInfo.newHolder, false); - mChangeAnimations.remove(changeInfo.newHolder); - dispatchFinishedWhenDone(); - } - }).start(); - } - } - - private void endChangeAnimation(List infoList, RecyclerView.ViewHolder item) { - for (int i = infoList.size() - 1; i >= 0; i--) { - ChangeInfo changeInfo = infoList.get(i); - if (endChangeAnimationIfNecessary(changeInfo, item)) { - if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { - infoList.remove(changeInfo); - } - } - } - } - - private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { - if (changeInfo.oldHolder != null) { - endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); - } - if (changeInfo.newHolder != null) { - endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); - } - } - private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) { - boolean oldItem = false; - if (changeInfo.newHolder == item) { - changeInfo.newHolder = null; - } else if (changeInfo.oldHolder == item) { - changeInfo.oldHolder = null; - oldItem = true; - } else { - return false; - } - item.itemView.setAlpha(1); - item.itemView.setTranslationX(0); - item.itemView.setTranslationY(0); - dispatchChangeFinished(item, oldItem); - return true; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void endAnimation(RecyclerView.ViewHolder item) { - final View view = item.itemView; - // this will trigger end callback which should set properties to their target values. - view.animate().cancel(); - // TODO if some other animations are chained to end, how do we cancel them as well? - for (int i = mPendingMoves.size() - 1; i >= 0; i--) { - MoveInfo moveInfo = mPendingMoves.get(i); - if (moveInfo.holder == item) { - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item); - mPendingMoves.remove(i); - } - } - endChangeAnimation(mPendingChanges, item); - if (mPendingRemovals.remove(item)) { - view.setAlpha(1); - dispatchRemoveFinished(item); - } - if (mPendingAdditions.remove(item)) { - view.setAlpha(1); - dispatchAddFinished(item); - } - - for (int i = mChangesList.size() - 1; i >= 0; i--) { - ArrayList changes = mChangesList.get(i); - endChangeAnimation(changes, item); - if (changes.isEmpty()) { - mChangesList.remove(i); - } - } - for (int i = mMovesList.size() - 1; i >= 0; i--) { - ArrayList moves = mMovesList.get(i); - for (int j = moves.size() - 1; j >= 0; j--) { - MoveInfo moveInfo = moves.get(j); - if (moveInfo.holder == item) { - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item); - moves.remove(j); - if (moves.isEmpty()) { - mMovesList.remove(i); - } - break; - } - } - } - for (int i = mAdditionsList.size() - 1; i >= 0; i--) { - ArrayList additions = mAdditionsList.get(i); - if (additions.remove(item)) { - view.setAlpha(1); - dispatchAddFinished(item); - if (additions.isEmpty()) { - mAdditionsList.remove(i); - } - } - } - - // animations should be ended by the cancel above. - //noinspection PointlessBooleanExpression,ConstantConditions - if (mRemoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mRemoveAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mAddAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mAddAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mChangeAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mChangeAnimations list"); - } - - //noinspection PointlessBooleanExpression,ConstantConditions - if (mMoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mMoveAnimations list"); - } - dispatchFinishedWhenDone(); - } - - private void resetAnimation(RecyclerView.ViewHolder holder) { - if (sDefaultInterpolator == null) { - sDefaultInterpolator = new ValueAnimator().getInterpolator(); - } - holder.itemView.animate().setInterpolator(sDefaultInterpolator); - endAnimation(holder); - } - - @Override - public boolean isRunning() { - return (!mPendingAdditions.isEmpty() - || !mPendingChanges.isEmpty() - || !mPendingMoves.isEmpty() - || !mPendingRemovals.isEmpty() - || !mMoveAnimations.isEmpty() - || !mRemoveAnimations.isEmpty() - || !mAddAnimations.isEmpty() - || !mChangeAnimations.isEmpty() - || !mMovesList.isEmpty() - || !mAdditionsList.isEmpty() - || !mChangesList.isEmpty()); - } - - /** - * Check the state of currently pending and running animations. If there are none - * pending/running, call {@link #dispatchAnimationsFinished()} to notify any - * listeners. - */ - void dispatchFinishedWhenDone() { - if (!isRunning()) { - dispatchAnimationsFinished(); - } - } - - @Override - public void endAnimations() { - int count = mPendingMoves.size(); - for (int i = count - 1; i >= 0; i--) { - MoveInfo item = mPendingMoves.get(i); - View view = item.holder.itemView; - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(item.holder); - mPendingMoves.remove(i); - } - count = mPendingRemovals.size(); - for (int i = count - 1; i >= 0; i--) { - RecyclerView.ViewHolder item = mPendingRemovals.get(i); - dispatchRemoveFinished(item); - mPendingRemovals.remove(i); - } - count = mPendingAdditions.size(); - for (int i = count - 1; i >= 0; i--) { - RecyclerView.ViewHolder item = mPendingAdditions.get(i); - item.itemView.setAlpha(1); - dispatchAddFinished(item); - mPendingAdditions.remove(i); - } - count = mPendingChanges.size(); - for (int i = count - 1; i >= 0; i--) { - endChangeAnimationIfNecessary(mPendingChanges.get(i)); - } - mPendingChanges.clear(); - if (!isRunning()) { - return; - } - - int listCount = mMovesList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList moves = mMovesList.get(i); - count = moves.size(); - for (int j = count - 1; j >= 0; j--) { - MoveInfo moveInfo = moves.get(j); - RecyclerView.ViewHolder item = moveInfo.holder; - View view = item.itemView; - view.setTranslationY(0); - view.setTranslationX(0); - dispatchMoveFinished(moveInfo.holder); - moves.remove(j); - if (moves.isEmpty()) { - mMovesList.remove(moves); - } - } - } - listCount = mAdditionsList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList additions = mAdditionsList.get(i); - count = additions.size(); - for (int j = count - 1; j >= 0; j--) { - RecyclerView.ViewHolder item = additions.get(j); - View view = item.itemView; - view.setAlpha(1); - dispatchAddFinished(item); - additions.remove(j); - if (additions.isEmpty()) { - mAdditionsList.remove(additions); - } - } - } - listCount = mChangesList.size(); - for (int i = listCount - 1; i >= 0; i--) { - ArrayList changes = mChangesList.get(i); - count = changes.size(); - for (int j = count - 1; j >= 0; j--) { - endChangeAnimationIfNecessary(changes.get(j)); - if (changes.isEmpty()) { - mChangesList.remove(changes); - } - } - } - - cancelAll(mRemoveAnimations); - cancelAll(mMoveAnimations); - cancelAll(mAddAnimations); - cancelAll(mChangeAnimations); - - dispatchAnimationsFinished(); - } - - void cancelAll(List viewHolders) { - for (int i = viewHolders.size() - 1; i >= 0; i--) { - viewHolders.get(i).itemView.animate().cancel(); - } - } - - /** - * {@inheritDoc} - *

- * If the payload list is not empty, DefaultItemAnimator returns true. - * When this is the case: - *

    - *
  • If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both - * ViewHolder arguments will be the same instance. - *
  • - *
  • - * If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, - * then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and - * run a move animation instead. - *
  • - *
- */ - @Override - public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, - @NonNull List payloads) { - return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/DiffUtil.java b/app/src/main/java/androidx/recyclerview/widget/DiffUtil.java deleted file mode 100644 index 940901e5a4..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/DiffUtil.java +++ /dev/null @@ -1,1058 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -/** - * DiffUtil is a utility class that calculates the difference between two lists and outputs a - * list of update operations that converts the first list into the second one. - *

- * It can be used to calculate updates for a RecyclerView Adapter. See {@link ListAdapter} and - * {@link AsyncListDiffer} which can simplify the use of DiffUtil on a background thread. - *

- * DiffUtil uses Eugene W. Myers's difference algorithm to calculate the minimal number of updates - * to convert one list into another. Myers's algorithm does not handle items that are moved so - * DiffUtil runs a second pass on the result to detect items that were moved. - *

- * Note that DiffUtil, {@link ListAdapter}, and {@link AsyncListDiffer} require the list to not - * mutate while in use. - * This generally means that both the lists themselves and their elements (or at least, the - * properties of elements used in diffing) should not be modified directly. Instead, new lists - * should be provided any time content changes. It's common for lists passed to DiffUtil to share - * elements that have not mutated, so it is not strictly required to reload all data to use - * DiffUtil. - *

- * If the lists are large, this operation may take significant time so you are advised to run this - * on a background thread, get the {@link DiffResult} then apply it on the RecyclerView on the main - * thread. - *

- * This algorithm is optimized for space and uses O(N) space to find the minimal - * number of addition and removal operations between the two lists. It has O(N + D^2) expected time - * performance where D is the length of the edit script. - *

- * If move detection is enabled, it takes an additional O(MN) time where M is the total number of - * added items and N is the total number of removed items. If your lists are already sorted by - * the same constraint (e.g. a created timestamp for a list of posts), you can disable move - * detection to improve performance. - *

- * The actual runtime of the algorithm significantly depends on the number of changes in the list - * and the cost of your comparison methods. Below are some average run times for reference: - * (The test list is composed of random UUID Strings and the tests are run on Nexus 5X with M) - *

    - *
  • 100 items and 10 modifications: avg: 0.39 ms, median: 0.35 ms - *
  • 100 items and 100 modifications: 3.82 ms, median: 3.75 ms - *
  • 100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms - *
  • 1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms - *
  • 1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms - *
  • 1000 items and 200 modifications: 27.07 ms, median: 26.92 ms - *
  • 1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms - *
- *

- * Due to implementation constraints, the max size of the list can be 2^26. - * - * @see ListAdapter - * @see AsyncListDiffer - */ -public class DiffUtil { - private DiffUtil() { - // utility class, no instance. - } - - private static final Comparator DIAGONAL_COMPARATOR = new Comparator() { - @Override - public int compare(Diagonal o1, Diagonal o2) { - return o1.x - o2.x; - } - }; - - // Myers' algorithm uses two lists as axis labels. In DiffUtil's implementation, `x` axis is - // used for old list and `y` axis is used for new list. - - /** - * Calculates the list of update operations that can covert one list into the other one. - * - * @param cb The callback that acts as a gateway to the backing list data - * @return A DiffResult that contains the information about the edit sequence to convert the - * old list into the new list. - */ - @NonNull - public static DiffResult calculateDiff(@NonNull Callback cb) { - return calculateDiff(cb, true); - } - - /** - * Calculates the list of update operations that can covert one list into the other one. - *

- * If your old and new lists are sorted by the same constraint and items never move (swap - * positions), you can disable move detection which takes O(N^2) time where - * N is the number of added, moved, removed items. - * - * @param cb The callback that acts as a gateway to the backing list data - * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise. - * - * @return A DiffResult that contains the information about the edit sequence to convert the - * old list into the new list. - */ - @NonNull - public static DiffResult calculateDiff(@NonNull Callback cb, boolean detectMoves) { - final int oldSize = cb.getOldListSize(); - final int newSize = cb.getNewListSize(); - - final List diagonals = new ArrayList<>(); - - // instead of a recursive implementation, we keep our own stack to avoid potential stack - // overflow exceptions - final List stack = new ArrayList<>(); - - stack.add(new Range(0, oldSize, 0, newSize)); - - final int max = (oldSize + newSize + 1) / 2; - // allocate forward and backward k-lines. K lines are diagonal lines in the matrix. (see the - // paper for details) - // These arrays lines keep the max reachable position for each k-line. - final CenteredArray forward = new CenteredArray(max * 2 + 1); - final CenteredArray backward = new CenteredArray(max * 2 + 1); - - // We pool the ranges to avoid allocations for each recursive call. - final List rangePool = new ArrayList<>(); - while (!stack.isEmpty()) { - final Range range = stack.remove(stack.size() - 1); - final Snake snake = midPoint(range, cb, forward, backward); - if (snake != null) { - // if it has a diagonal, save it - if (snake.diagonalSize() > 0) { - diagonals.add(snake.toDiagonal()); - } - // add new ranges for left and right - final Range left = rangePool.isEmpty() ? new Range() : rangePool.remove( - rangePool.size() - 1); - left.oldListStart = range.oldListStart; - left.newListStart = range.newListStart; - left.oldListEnd = snake.startX; - left.newListEnd = snake.startY; - stack.add(left); - - // re-use range for right - //noinspection UnnecessaryLocalVariable - final Range right = range; - right.oldListEnd = range.oldListEnd; - right.newListEnd = range.newListEnd; - right.oldListStart = snake.endX; - right.newListStart = snake.endY; - stack.add(right); - } else { - rangePool.add(range); - } - - } - // sort snakes - Collections.sort(diagonals, DIAGONAL_COMPARATOR); - - return new DiffResult(cb, diagonals, - forward.backingData(), backward.backingData(), - detectMoves); - } - - /** - * Finds a middle snake in the given range. - */ - @Nullable - private static Snake midPoint( - Range range, - Callback cb, - CenteredArray forward, - CenteredArray backward) { - if (range.oldSize() < 1 || range.newSize() < 1) { - return null; - } - int max = (range.oldSize() + range.newSize() + 1) / 2; - forward.set(1, range.oldListStart); - backward.set(1, range.oldListEnd); - for (int d = 0; d < max; d++) { - Snake snake = forward(range, cb, forward, backward, d); - if (snake != null) { - return snake; - } - snake = backward(range, cb, forward, backward, d); - if (snake != null) { - return snake; - } - } - return null; - } - - @Nullable - private static Snake forward( - Range range, - Callback cb, - CenteredArray forward, - CenteredArray backward, - int d) { - boolean checkForSnake = Math.abs(range.oldSize() - range.newSize()) % 2 == 1; - int delta = range.oldSize() - range.newSize(); - for (int k = -d; k <= d; k += 2) { - // we either come from d-1, k-1 OR d-1. k+1 - // as we move in steps of 2, array always holds both current and previous d values - // k = x - y and each array value holds the max X, y = x - k - final int startX; - final int startY; - int x, y; - if (k == -d || (k != d && forward.get(k + 1) > forward.get(k - 1))) { - // picking k + 1, incrementing Y (by simply not incrementing X) - x = startX = forward.get(k + 1); - } else { - // picking k - 1, incrementing X - startX = forward.get(k - 1); - x = startX + 1; - } - y = range.newListStart + (x - range.oldListStart) - k; - startY = (d == 0 || x != startX) ? y : y - 1; - // now find snake size - while (x < range.oldListEnd - && y < range.newListEnd - && cb.areItemsTheSame(x, y)) { - x++; - y++; - } - // now we have furthest reaching x, record it - forward.set(k, x); - if (checkForSnake) { - // see if we did pass over a backwards array - // mapping function: delta - k - int backwardsK = delta - k; - // if backwards K is calculated and it passed me, found match - if (backwardsK >= -d + 1 - && backwardsK <= d - 1 - && backward.get(backwardsK) <= x) { - // match - Snake snake = new Snake(); - snake.startX = startX; - snake.startY = startY; - snake.endX = x; - snake.endY = y; - snake.reverse = false; - return snake; - } - } - } - return null; - } - - @Nullable - private static Snake backward( - Range range, - Callback cb, - CenteredArray forward, - CenteredArray backward, - int d) { - boolean checkForSnake = (range.oldSize() - range.newSize()) % 2 == 0; - int delta = range.oldSize() - range.newSize(); - // same as forward but we go backwards from end of the lists to be beginning - // this also means we'll try to optimize for minimizing x instead of maximizing it - for (int k = -d; k <= d; k += 2) { - // we either come from d-1, k-1 OR d-1, k+1 - // as we move in steps of 2, array always holds both current and previous d values - // k = x - y and each array value holds the MIN X, y = x - k - // when x's are equal, we prioritize deletion over insertion - final int startX; - final int startY; - int x, y; - - if (k == -d || (k != d && backward.get(k + 1) < backward.get(k - 1))) { - // picking k + 1, decrementing Y (by simply not decrementing X) - x = startX = backward.get(k + 1); - } else { - // picking k - 1, decrementing X - startX = backward.get(k - 1); - x = startX - 1; - } - y = range.newListEnd - ((range.oldListEnd - x) - k); - startY = (d == 0 || x != startX) ? y : y + 1; - // now find snake size - while (x > range.oldListStart - && y > range.newListStart - && cb.areItemsTheSame(x - 1, y - 1)) { - x--; - y--; - } - // now we have furthest point, record it (min X) - backward.set(k, x); - if (checkForSnake) { - // see if we did pass over a backwards array - // mapping function: delta - k - int forwardsK = delta - k; - // if forwards K is calculated and it passed me, found match - if (forwardsK >= -d - && forwardsK <= d - && forward.get(forwardsK) >= x) { - // match - Snake snake = new Snake(); - // assignment are reverse since we are a reverse snake - snake.startX = x; - snake.startY = y; - snake.endX = startX; - snake.endY = startY; - snake.reverse = true; - return snake; - } - } - } - return null; - } - - /** - * A Callback class used by DiffUtil while calculating the diff between two lists. - */ - public abstract static class Callback { - /** - * Returns the size of the old list. - * - * @return The size of the old list. - */ - public abstract int getOldListSize(); - - /** - * Returns the size of the new list. - * - * @return The size of the new list. - */ - public abstract int getNewListSize(); - - /** - * Called by the DiffUtil to decide whether two object represent the same Item. - *

- * For example, if your items have unique ids, this method should check their id equality. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list - * @return True if the two items represent the same object or false if they are different. - */ - public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); - - /** - * Called by the DiffUtil when it wants to check whether two items have the same data. - * DiffUtil uses this information to detect if the contents of an item has changed. - *

- * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} - * so that you can change its behavior depending on your UI. - * For example, if you are using DiffUtil with a - * {@link RecyclerView.Adapter RecyclerView.Adapter}, you should - * return whether the items' visual representations are the same. - *

- * This method is called only if {@link #areItemsTheSame(int, int)} returns - * {@code true} for these items. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list which replaces the - * oldItem - * @return True if the contents of the items are the same or false if they are different. - */ - public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); - - /** - * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and - * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil - * calls this method to get a payload about the change. - *

- * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the - * particular field that changed in the item and your - * {@link RecyclerView.ItemAnimator ItemAnimator} can use that - * information to run the correct animation. - *

- * Default implementation returns {@code null}. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list - * @return A payload object that represents the change between the two items. - */ - @Nullable - public Object getChangePayload(int oldItemPosition, int newItemPosition) { - return null; - } - } - - /** - * Callback for calculating the diff between two non-null items in a list. - *

- * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles - * just the second of these, which allows separation of code that indexes into an array or List - * from the presentation-layer and content specific diffing code. - * - * @param Type of items to compare. - */ - public abstract static class ItemCallback { - /** - * Called to check whether two objects represent the same item. - *

- * For example, if your items have unique ids, this method should check their id equality. - *

- * Note: {@code null} items in the list are assumed to be the same as another {@code null} - * item and are assumed to not be the same as a non-{@code null} item. This callback will - * not be invoked for either of those cases. - * - * @param oldItem The item in the old list. - * @param newItem The item in the new list. - * @return True if the two items represent the same object or false if they are different. - * @see Callback#areItemsTheSame(int, int) - */ - public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem); - - /** - * Called to check whether two items have the same data. - *

- * This information is used to detect if the contents of an item have changed. - *

- * This method to check equality instead of {@link Object#equals(Object)} so that you can - * change its behavior depending on your UI. - *

- * For example, if you are using DiffUtil with a - * {@link RecyclerView.Adapter RecyclerView.Adapter}, you should - * return whether the items' visual representations are the same. - *

- * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for - * these items. - *

- * Note: Two {@code null} items are assumed to represent the same contents. This callback - * will not be invoked for this case. - * - * @param oldItem The item in the old list. - * @param newItem The item in the new list. - * @return True if the contents of the items are the same or false if they are different. - * @see Callback#areContentsTheSame(int, int) - */ - public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem); - - /** - * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and - * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to - * get a payload about the change. - *

- * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the - * particular field that changed in the item and your - * {@link RecyclerView.ItemAnimator ItemAnimator} can use that - * information to run the correct animation. - *

- * Default implementation returns {@code null}. - * - * @see Callback#getChangePayload(int, int) - */ - @SuppressWarnings({"unused"}) - @Nullable - public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) { - return null; - } - } - - /** - * A diagonal is a match in the graph. - * Rather than snakes, we only record the diagonals in the path. - */ - static class Diagonal { - public final int x; - public final int y; - public final int size; - - Diagonal(int x, int y, int size) { - this.x = x; - this.y = y; - this.size = size; - } - - int endX() { - return x + size; - } - - int endY() { - return y + size; - } - } - - /** - * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an - * add or remove operation. See the Myers' paper for details. - */ - @SuppressWarnings("WeakerAccess") - static class Snake { - /** - * Position in the old list - */ - public int startX; - - /** - * Position in the new list - */ - public int startY; - - /** - * End position in the old list, exclusive - */ - public int endX; - - /** - * End position in the new list, exclusive - */ - public int endY; - - /** - * True if this snake was created in the reverse search, false otherwise. - */ - public boolean reverse; - - boolean hasAdditionOrRemoval() { - return endY - startY != endX - startX; - } - - boolean isAddition() { - return endY - startY > endX - startX; - } - - int diagonalSize() { - return Math.min(endX - startX, endY - startY); - } - - /** - * Extract the diagonal of the snake to make reasoning easier for the rest of the - * algorithm where we try to produce a path and also find moves. - */ - @NonNull - Diagonal toDiagonal() { - if (hasAdditionOrRemoval()) { - if (reverse) { - // snake edge it at the end - return new Diagonal(startX, startY, diagonalSize()); - } else { - // snake edge it at the beginning - if (isAddition()) { - return new Diagonal(startX, startY + 1, diagonalSize()); - } else { - return new Diagonal(startX + 1, startY, diagonalSize()); - } - } - } else { - // we are a pure diagonal - return new Diagonal(startX, startY, endX - startX); - } - } - } - - /** - * Represents a range in two lists that needs to be solved. - *

- * This internal class is used when running Myers' algorithm without recursion. - *

- * Ends are exclusive - */ - static class Range { - - int oldListStart, oldListEnd; - - int newListStart, newListEnd; - - public Range() { - } - - public Range(int oldListStart, int oldListEnd, int newListStart, int newListEnd) { - this.oldListStart = oldListStart; - this.oldListEnd = oldListEnd; - this.newListStart = newListStart; - this.newListEnd = newListEnd; - } - - int oldSize() { - return oldListEnd - oldListStart; - } - - int newSize() { - return newListEnd - newListStart; - } - } - - /** - * This class holds the information about the result of a - * {@link DiffUtil#calculateDiff(Callback, boolean)} call. - *

- * You can consume the updates in a DiffResult via - * {@link #dispatchUpdatesTo(ListUpdateCallback)} or directly stream the results into a - * {@link RecyclerView.Adapter} via {@link #dispatchUpdatesTo(RecyclerView.Adapter)}. - */ - public static class DiffResult { - /** - * Signifies an item not present in the list. - */ - public static final int NO_POSITION = -1; - - - /** - * While reading the flags below, keep in mind that when multiple items move in a list, - * Myers's may pick any of them as the anchor item and consider that one NOT_CHANGED while - * picking others as additions and removals. This is completely fine as we later detect - * all moves. - *

- * Below, when an item is mentioned to stay in the same "location", it means we won't - * dispatch a move/add/remove for it, it DOES NOT mean the item is still in the same - * position. - */ - // item stayed the same. - private static final int FLAG_NOT_CHANGED = 1; - // item stayed in the same location but changed. - private static final int FLAG_CHANGED = FLAG_NOT_CHANGED << 1; - // Item has moved and also changed. - private static final int FLAG_MOVED_CHANGED = FLAG_CHANGED << 1; - // Item has moved but did not change. - private static final int FLAG_MOVED_NOT_CHANGED = FLAG_MOVED_CHANGED << 1; - // Item moved - private static final int FLAG_MOVED = FLAG_MOVED_CHANGED | FLAG_MOVED_NOT_CHANGED; - - // since we are re-using the int arrays that were created in the Myers' step, we mask - // change flags - private static final int FLAG_OFFSET = 4; - - private static final int FLAG_MASK = (1 << FLAG_OFFSET) - 1; - - // The diagonals extracted from The Myers' snakes. - private final List mDiagonals; - - // The list to keep oldItemStatuses. As we traverse old items, we assign flags to them - // which also includes whether they were a real removal or a move (and its new index). - private final int[] mOldItemStatuses; - // The list to keep newItemStatuses. As we traverse new items, we assign flags to them - // which also includes whether they were a real addition or a move(and its old index). - private final int[] mNewItemStatuses; - // The callback that was given to calculate diff method. - private final Callback mCallback; - - private final int mOldListSize; - - private final int mNewListSize; - - private final boolean mDetectMoves; - - /** - * @param callback The callback that was used to calculate the diff - * @param diagonals Matches between the two lists - * @param oldItemStatuses An int[] that can be re-purposed to keep metadata - * @param newItemStatuses An int[] that can be re-purposed to keep metadata - * @param detectMoves True if this DiffResult will try to detect moved items - */ - DiffResult(Callback callback, List diagonals, int[] oldItemStatuses, - int[] newItemStatuses, boolean detectMoves) { - mDiagonals = diagonals; - mOldItemStatuses = oldItemStatuses; - mNewItemStatuses = newItemStatuses; - Arrays.fill(mOldItemStatuses, 0); - Arrays.fill(mNewItemStatuses, 0); - mCallback = callback; - mOldListSize = callback.getOldListSize(); - mNewListSize = callback.getNewListSize(); - mDetectMoves = detectMoves; - addEdgeDiagonals(); - findMatchingItems(); - } - - /** - * Add edge diagonals so that we can iterate as long as there are diagonals w/o lots of - * null checks around - */ - private void addEdgeDiagonals() { - Diagonal first = mDiagonals.isEmpty() ? null : mDiagonals.get(0); - // see if we should add 1 to the 0,0 - if (first == null || first.x != 0 || first.y != 0) { - mDiagonals.add(0, new Diagonal(0, 0, 0)); - } - // always add one last - mDiagonals.add(new Diagonal(mOldListSize, mNewListSize, 0)); - } - - /** - * Find position mapping from old list to new list. - * If moves are requested, we'll also try to do an n^2 search between additions and - * removals to find moves. - */ - private void findMatchingItems() { - for (Diagonal diagonal : mDiagonals) { - for (int offset = 0; offset < diagonal.size; offset++) { - int posX = diagonal.x + offset; - int posY = diagonal.y + offset; - final boolean theSame = mCallback.areContentsTheSame(posX, posY); - final int changeFlag = theSame ? FLAG_NOT_CHANGED : FLAG_CHANGED; - mOldItemStatuses[posX] = (posY << FLAG_OFFSET) | changeFlag; - mNewItemStatuses[posY] = (posX << FLAG_OFFSET) | changeFlag; - } - } - // now all matches are marked, lets look for moves - if (mDetectMoves) { - // traverse each addition / removal from the end of the list, find matching - // addition removal from before - findMoveMatches(); - } - } - - private void findMoveMatches() { - // for each removal, find matching addition - int posX = 0; - for (Diagonal diagonal : mDiagonals) { - while (posX < diagonal.x) { - if (mOldItemStatuses[posX] == 0) { - // there is a removal, find matching addition from the rest - findMatchingAddition(posX); - } - posX++; - } - // snap back for the next diagonal - posX = diagonal.endX(); - } - } - - /** - * Search the whole list to find the addition for the given removal of position posX - * - * @param posX position in the old list - */ - private void findMatchingAddition(int posX) { - int posY = 0; - final int diagonalsSize = mDiagonals.size(); - for (int i = 0; i < diagonalsSize; i++) { - final Diagonal diagonal = mDiagonals.get(i); - while (posY < diagonal.y) { - // found some additions, evaluate - if (mNewItemStatuses[posY] == 0) { // not evaluated yet - boolean matching = mCallback.areItemsTheSame(posX, posY); - if (matching) { - // yay found it, set values - boolean contentsMatching = mCallback.areContentsTheSame(posX, posY); - final int changeFlag = contentsMatching ? FLAG_MOVED_NOT_CHANGED - : FLAG_MOVED_CHANGED; - // once we process one of these, it will mark the other one as ignored. - mOldItemStatuses[posX] = (posY << FLAG_OFFSET) | changeFlag; - mNewItemStatuses[posY] = (posX << FLAG_OFFSET) | changeFlag; - return; - } - } - posY++; - } - posY = diagonal.endY(); - } - } - - /** - * Given a position in the old list, returns the position in the new list, or - * {@code NO_POSITION} if it was removed. - * - * @param oldListPosition Position of item in old list - * @return Position of item in new list, or {@code NO_POSITION} if not present. - * @see #NO_POSITION - * @see #convertNewPositionToOld(int) - */ - public int convertOldPositionToNew(@IntRange(from = 0) int oldListPosition) { - if (oldListPosition < 0 || oldListPosition >= mOldListSize) { - throw new IndexOutOfBoundsException("Index out of bounds - passed position = " - + oldListPosition + ", old list size = " + mOldListSize); - } - final int status = mOldItemStatuses[oldListPosition]; - if ((status & FLAG_MASK) == 0) { - return NO_POSITION; - } else { - return status >> FLAG_OFFSET; - } - } - - /** - * Given a position in the new list, returns the position in the old list, or - * {@code NO_POSITION} if it was removed. - * - * @param newListPosition Position of item in new list - * @return Position of item in old list, or {@code NO_POSITION} if not present. - * @see #NO_POSITION - * @see #convertOldPositionToNew(int) - */ - public int convertNewPositionToOld(@IntRange(from = 0) int newListPosition) { - if (newListPosition < 0 || newListPosition >= mNewListSize) { - throw new IndexOutOfBoundsException("Index out of bounds - passed position = " - + newListPosition + ", new list size = " + mNewListSize); - } - final int status = mNewItemStatuses[newListPosition]; - if ((status & FLAG_MASK) == 0) { - return NO_POSITION; - } else { - return status >> FLAG_OFFSET; - } - } - - /** - * Dispatches the update events to the given adapter. - *

- * For example, if you have an {@link RecyclerView.Adapter Adapter} - * that is backed by a {@link List}, you can swap the list with the new one then call this - * method to dispatch all updates to the RecyclerView. - *

-         *     List oldList = mAdapter.getData();
-         *     DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldList, newList));
-         *     mAdapter.setData(newList);
-         *     result.dispatchUpdatesTo(mAdapter);
-         * 
- *

- * Note that the RecyclerView requires you to dispatch adapter updates immediately when you - * change the data (you cannot defer {@code notify*} calls). The usage above adheres to this - * rule because updates are sent to the adapter right after the backing data is changed, - * before RecyclerView tries to read it. - *

- * On the other hand, if you have another - * {@link RecyclerView.AdapterDataObserver AdapterDataObserver} - * that tries to process events synchronously, this may confuse that observer because the - * list is instantly moved to its final state while the adapter updates are dispatched later - * on, one by one. If you have such an - * {@link RecyclerView.AdapterDataObserver AdapterDataObserver}, - * you can use - * {@link #dispatchUpdatesTo(ListUpdateCallback)} to handle each modification - * manually. - * - * @param adapter A RecyclerView adapter which was displaying the old list and will start - * displaying the new list. - * @see AdapterListUpdateCallback - */ - public void dispatchUpdatesTo(@NonNull final RecyclerView.Adapter adapter) { - dispatchUpdatesTo(new AdapterListUpdateCallback(adapter)); - } - - /** - * Dispatches update operations to the given Callback. - *

- * These updates are atomic such that the first update call affects every update call that - * comes after it (the same as RecyclerView). - * - * @param updateCallback The callback to receive the update operations. - * @see #dispatchUpdatesTo(RecyclerView.Adapter) - */ - public void dispatchUpdatesTo(@NonNull ListUpdateCallback updateCallback) { - final BatchingListUpdateCallback batchingCallback; - - if (updateCallback instanceof BatchingListUpdateCallback) { - batchingCallback = (BatchingListUpdateCallback) updateCallback; - } else { - batchingCallback = new BatchingListUpdateCallback(updateCallback); - // replace updateCallback with a batching callback and override references to - // updateCallback so that we don't call it directly by mistake - //noinspection UnusedAssignment - updateCallback = batchingCallback; - } - // track up to date current list size for moves - // when a move is found, we record its position from the end of the list (which is - // less likely to change since we iterate in reverse). - // Later when we find the match of that move, we dispatch the update - int currentListSize = mOldListSize; - // list of postponed moves - final Collection postponedUpdates = new ArrayDeque<>(); - // posX and posY are exclusive - int posX = mOldListSize; - int posY = mNewListSize; - // iterate from end of the list to the beginning. - // this just makes offsets easier since changes in the earlier indices has an effect - // on the later indices. - for (int diagonalIndex = mDiagonals.size() - 1; diagonalIndex >= 0; diagonalIndex--) { - final Diagonal diagonal = mDiagonals.get(diagonalIndex); - int endX = diagonal.endX(); - int endY = diagonal.endY(); - // dispatch removals and additions until we reach to that diagonal - // first remove then add so that it can go into its place and we don't need - // to offset values - while (posX > endX) { - posX--; - // REMOVAL - int status = mOldItemStatuses[posX]; - if ((status & FLAG_MOVED) != 0) { - int newPos = status >> FLAG_OFFSET; - // get postponed addition - PostponedUpdate postponedUpdate = getPostponedUpdate(postponedUpdates, - newPos, false); - if (postponedUpdate != null) { - // this is an addition that was postponed. Now dispatch it. - int updatedNewPos = currentListSize - postponedUpdate.currentPos; - batchingCallback.onMoved(posX, updatedNewPos - 1); - if ((status & FLAG_MOVED_CHANGED) != 0) { - Object changePayload = mCallback.getChangePayload(posX, newPos); - batchingCallback.onChanged(updatedNewPos - 1, 1, changePayload); - } - } else { - // first time we are seeing this, we'll see a matching addition - postponedUpdates.add(new PostponedUpdate( - posX, - currentListSize - posX - 1, - true - )); - } - } else { - // simple removal - batchingCallback.onRemoved(posX, 1); - currentListSize--; - } - } - while (posY > endY) { - posY--; - // ADDITION - int status = mNewItemStatuses[posY]; - if ((status & FLAG_MOVED) != 0) { - // this is a move not an addition. - // see if this is postponed - int oldPos = status >> FLAG_OFFSET; - // get postponed removal - PostponedUpdate postponedUpdate = getPostponedUpdate(postponedUpdates, - oldPos, true); - // empty size returns 0 for indexOf - if (postponedUpdate == null) { - // postpone it until we see the removal - postponedUpdates.add(new PostponedUpdate( - posY, - currentListSize - posX, - false - )); - } else { - // oldPosFromEnd = foundListSize - posX - // we can find posX if we swap the list sizes - // posX = listSize - oldPosFromEnd - int updatedOldPos = currentListSize - postponedUpdate.currentPos - 1; - batchingCallback.onMoved(updatedOldPos, posX); - if ((status & FLAG_MOVED_CHANGED) != 0) { - Object changePayload = mCallback.getChangePayload(oldPos, posY); - batchingCallback.onChanged(posX, 1, changePayload); - } - } - } else { - // simple addition - batchingCallback.onInserted(posX, 1); - currentListSize++; - } - } - // now dispatch updates for the diagonal - posX = diagonal.x; - posY = diagonal.y; - for (int i = 0; i < diagonal.size; i++) { - // dispatch changes - if ((mOldItemStatuses[posX] & FLAG_MASK) == FLAG_CHANGED) { - Object changePayload = mCallback.getChangePayload(posX, posY); - batchingCallback.onChanged(posX, 1, changePayload); - } - posX++; - posY++; - } - // snap back for the next diagonal - posX = diagonal.x; - posY = diagonal.y; - } - batchingCallback.dispatchLastEvent(); - } - - @Nullable - private static PostponedUpdate getPostponedUpdate( - Collection postponedUpdates, - int posInList, - boolean removal) { - PostponedUpdate postponedUpdate = null; - Iterator itr = postponedUpdates.iterator(); - while (itr.hasNext()) { - PostponedUpdate update = itr.next(); - if (update.posInOwnerList == posInList && update.removal == removal) { - postponedUpdate = update; - itr.remove(); - break; - } - } - while (itr.hasNext()) { - // re-offset all others - PostponedUpdate update = itr.next(); - if (removal) { - update.currentPos--; - } else { - update.currentPos++; - } - } - return postponedUpdate; - } - } - - /** - * Represents an update that we skipped because it was a move. - *

- * When an update is skipped, it is tracked as other updates are dispatched until the matching - * add/remove operation is found at which point the tracked position is used to dispatch the - * update. - */ - private static class PostponedUpdate { - /** - * position in the list that owns this item - */ - int posInOwnerList; - - /** - * position wrt to the end of the list - */ - int currentPos; - - /** - * true if this is a removal, false otherwise - */ - boolean removal; - - PostponedUpdate(int posInOwnerList, int currentPos, boolean removal) { - this.posInOwnerList = posInOwnerList; - this.currentPos = currentPos; - this.removal = removal; - } - } - - /** - * Array wrapper w/ negative index support. - * We use this array instead of a regular array so that algorithm is easier to read without - * too many offsets when accessing the "k" array in the algorithm. - */ - static class CenteredArray { - private final int[] mData; - private final int mMid; - - CenteredArray(int size) { - mData = new int[size]; - mMid = mData.length / 2; - } - - int get(int index) { - return mData[index + mMid]; - } - - int[] backingData() { - return mData; - } - - void set(int index, int value) { - mData[index + mMid] = value; - } - - public void fill(int value) { - Arrays.fill(mData, value); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/DividerItemDecoration.java b/app/src/main/java/androidx/recyclerview/widget/DividerItemDecoration.java deleted file mode 100644 index b4598edfed..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/DividerItemDecoration.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.Log; -import android.view.View; -import android.widget.LinearLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider - * between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and - * {@link #VERTICAL} orientations. - * - *

- *     mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
- *             mLayoutManager.getOrientation());
- *     recyclerView.addItemDecoration(mDividerItemDecoration);
- * 
- */ -public class DividerItemDecoration extends RecyclerView.ItemDecoration { - public static final int HORIZONTAL = LinearLayout.HORIZONTAL; - public static final int VERTICAL = LinearLayout.VERTICAL; - - private static final String TAG = "DividerItem"; - private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; - - private Drawable mDivider; - - /** - * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. - */ - private int mOrientation; - - private final Rect mBounds = new Rect(); - - /** - * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a - * {@link LinearLayoutManager}. - * - * @param context Current context, it will be used to access resources. - * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public DividerItemDecoration(Context context, int orientation) { - final TypedArray a = context.obtainStyledAttributes(ATTRS); - mDivider = a.getDrawable(0); - if (mDivider == null) { - Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this " - + "DividerItemDecoration. Please set that attribute all call setDrawable()"); - } - a.recycle(); - setOrientation(orientation); - } - - /** - * Sets the orientation for this divider. This should be called if - * {@link RecyclerView.LayoutManager} changes orientation. - * - * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} - */ - public void setOrientation(int orientation) { - if (orientation != HORIZONTAL && orientation != VERTICAL) { - throw new IllegalArgumentException( - "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); - } - mOrientation = orientation; - } - - /** - * Sets the {@link Drawable} for this divider. - * - * @param drawable Drawable that should be used as a divider. - */ - public void setDrawable(@NonNull Drawable drawable) { - if (drawable == null) { - throw new IllegalArgumentException("Drawable cannot be null."); - } - mDivider = drawable; - } - - /** - * @return the {@link Drawable} for this divider. - */ - @Nullable - public Drawable getDrawable() { - return mDivider; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - if (parent.getLayoutManager() == null || mDivider == null) { - return; - } - if (mOrientation == VERTICAL) { - drawVertical(c, parent); - } else { - drawHorizontal(c, parent); - } - } - - private void drawVertical(Canvas canvas, RecyclerView parent) { - canvas.save(); - final int left; - final int right; - //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. - if (parent.getClipToPadding()) { - left = parent.getPaddingLeft(); - right = parent.getWidth() - parent.getPaddingRight(); - canvas.clipRect(left, parent.getPaddingTop(), right, - parent.getHeight() - parent.getPaddingBottom()); - } else { - left = 0; - right = parent.getWidth(); - } - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - parent.getDecoratedBoundsWithMargins(child, mBounds); - final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); - final int top = bottom - mDivider.getIntrinsicHeight(); - mDivider.setBounds(left, top, right, bottom); - mDivider.draw(canvas); - } - canvas.restore(); - } - - private void drawHorizontal(Canvas canvas, RecyclerView parent) { - canvas.save(); - final int top; - final int bottom; - //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. - if (parent.getClipToPadding()) { - top = parent.getPaddingTop(); - bottom = parent.getHeight() - parent.getPaddingBottom(); - canvas.clipRect(parent.getPaddingLeft(), top, - parent.getWidth() - parent.getPaddingRight(), bottom); - } else { - top = 0; - bottom = parent.getHeight(); - } - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); - final int right = mBounds.right + Math.round(child.getTranslationX()); - final int left = right - mDivider.getIntrinsicWidth(); - mDivider.setBounds(left, top, right, bottom); - mDivider.draw(canvas); - } - canvas.restore(); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - if (mDivider == null) { - outRect.set(0, 0, 0, 0); - return; - } - if (mOrientation == VERTICAL) { - outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); - } else { - outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/FastScroller.java b/app/src/main/java/androidx/recyclerview/widget/FastScroller.java deleted file mode 100644 index 180adcc54a..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/FastScroller.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; -import android.view.MotionEvent; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.ViewCompat; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Class responsible to animate and provide a fast scroller. - */ -@VisibleForTesting -class FastScroller extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener { - @IntDef({STATE_HIDDEN, STATE_VISIBLE, STATE_DRAGGING}) - @Retention(RetentionPolicy.SOURCE) - private @interface State { } - // Scroll thumb not showing - private static final int STATE_HIDDEN = 0; - // Scroll thumb visible and moving along with the scrollbar - private static final int STATE_VISIBLE = 1; - // Scroll thumb being dragged by user - private static final int STATE_DRAGGING = 2; - - @IntDef({DRAG_X, DRAG_Y, DRAG_NONE}) - @Retention(RetentionPolicy.SOURCE) - private @interface DragState{ } - private static final int DRAG_NONE = 0; - private static final int DRAG_X = 1; - private static final int DRAG_Y = 2; - - @IntDef({ANIMATION_STATE_OUT, ANIMATION_STATE_FADING_IN, ANIMATION_STATE_IN, - ANIMATION_STATE_FADING_OUT}) - @Retention(RetentionPolicy.SOURCE) - private @interface AnimationState { } - private static final int ANIMATION_STATE_OUT = 0; - private static final int ANIMATION_STATE_FADING_IN = 1; - private static final int ANIMATION_STATE_IN = 2; - private static final int ANIMATION_STATE_FADING_OUT = 3; - - private static final int SHOW_DURATION_MS = 500; - private static final int HIDE_DELAY_AFTER_VISIBLE_MS = 1500; - private static final int HIDE_DELAY_AFTER_DRAGGING_MS = 1200; - private static final int HIDE_DURATION_MS = 500; - private static final int SCROLLBAR_FULL_OPAQUE = 255; - - private static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed}; - private static final int[] EMPTY_STATE_SET = new int[]{}; - - private final int mScrollbarMinimumRange; - private final int mMargin; - - // Final values for the vertical scroll bar - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final StateListDrawable mVerticalThumbDrawable; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Drawable mVerticalTrackDrawable; - private final int mVerticalThumbWidth; - private final int mVerticalTrackWidth; - - // Final values for the horizontal scroll bar - private final StateListDrawable mHorizontalThumbDrawable; - private final Drawable mHorizontalTrackDrawable; - private final int mHorizontalThumbHeight; - private final int mHorizontalTrackHeight; - - // Dynamic values for the vertical scroll bar - @VisibleForTesting int mVerticalThumbHeight; - @VisibleForTesting int mVerticalThumbCenterY; - @VisibleForTesting float mVerticalDragY; - - // Dynamic values for the horizontal scroll bar - @VisibleForTesting int mHorizontalThumbWidth; - @VisibleForTesting int mHorizontalThumbCenterX; - @VisibleForTesting float mHorizontalDragX; - - private int mRecyclerViewWidth = 0; - private int mRecyclerViewHeight = 0; - - private RecyclerView mRecyclerView; - /** - * Whether the document is long/wide enough to require scrolling. If not, we don't show the - * relevant scroller. - */ - private boolean mNeedVerticalScrollbar = false; - private boolean mNeedHorizontalScrollbar = false; - @State private int mState = STATE_HIDDEN; - @DragState private int mDragState = DRAG_NONE; - - private final int[] mVerticalRange = new int[2]; - private final int[] mHorizontalRange = new int[2]; - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final ValueAnimator mShowHideAnimator = ValueAnimator.ofFloat(0, 1); - @SuppressWarnings("WeakerAccess") /* synthetic access */ - @AnimationState int mAnimationState = ANIMATION_STATE_OUT; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hide(HIDE_DURATION_MS); - } - }; - private final RecyclerView.OnScrollListener - mOnScrollListener = new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - updateScrollPosition(recyclerView.computeHorizontalScrollOffset(), - recyclerView.computeVerticalScrollOffset()); - } - }; - - FastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable, - Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, - Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange, - int margin) { - mVerticalThumbDrawable = verticalThumbDrawable; - mVerticalTrackDrawable = verticalTrackDrawable; - mHorizontalThumbDrawable = horizontalThumbDrawable; - mHorizontalTrackDrawable = horizontalTrackDrawable; - mVerticalThumbWidth = Math.max(defaultWidth, verticalThumbDrawable.getIntrinsicWidth()); - mVerticalTrackWidth = Math.max(defaultWidth, verticalTrackDrawable.getIntrinsicWidth()); - mHorizontalThumbHeight = Math - .max(defaultWidth, horizontalThumbDrawable.getIntrinsicWidth()); - mHorizontalTrackHeight = Math - .max(defaultWidth, horizontalTrackDrawable.getIntrinsicWidth()); - mScrollbarMinimumRange = scrollbarMinimumRange; - mMargin = margin; - mVerticalThumbDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); - mVerticalTrackDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); - - mShowHideAnimator.addListener(new AnimatorListener()); - mShowHideAnimator.addUpdateListener(new AnimatorUpdater()); - - attachToRecyclerView(recyclerView); - } - - public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { - if (mRecyclerView == recyclerView) { - return; // nothing to do - } - if (mRecyclerView != null) { - destroyCallbacks(); - } - mRecyclerView = recyclerView; - if (mRecyclerView != null) { - setupCallbacks(); - } - } - - private void setupCallbacks() { - mRecyclerView.addItemDecoration(this); - mRecyclerView.addOnItemTouchListener(this); - mRecyclerView.addOnScrollListener(mOnScrollListener); - } - - private void destroyCallbacks() { - mRecyclerView.removeItemDecoration(this); - mRecyclerView.removeOnItemTouchListener(this); - mRecyclerView.removeOnScrollListener(mOnScrollListener); - cancelHide(); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void requestRedraw() { - mRecyclerView.invalidate(); - } - - void setState(@State int state) { - if (state == STATE_DRAGGING && mState != STATE_DRAGGING) { - mVerticalThumbDrawable.setState(PRESSED_STATE_SET); - cancelHide(); - } - - if (state == STATE_HIDDEN) { - requestRedraw(); - } else { - show(); - } - - if (mState == STATE_DRAGGING && state != STATE_DRAGGING) { - mVerticalThumbDrawable.setState(EMPTY_STATE_SET); - resetHideDelay(HIDE_DELAY_AFTER_DRAGGING_MS); - } else if (state == STATE_VISIBLE) { - resetHideDelay(HIDE_DELAY_AFTER_VISIBLE_MS); - } - mState = state; - } - - private boolean isLayoutRTL() { - return ViewCompat.getLayoutDirection(mRecyclerView) == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - public boolean isDragging() { - return mState == STATE_DRAGGING; - } - - @VisibleForTesting boolean isVisible() { - return mState == STATE_VISIBLE; - } - - public void show() { - switch (mAnimationState) { - case ANIMATION_STATE_FADING_OUT: - mShowHideAnimator.cancel(); - // fall through - case ANIMATION_STATE_OUT: - mAnimationState = ANIMATION_STATE_FADING_IN; - mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1); - mShowHideAnimator.setDuration(SHOW_DURATION_MS); - mShowHideAnimator.setStartDelay(0); - mShowHideAnimator.start(); - break; - } - } - - @VisibleForTesting - void hide(int duration) { - switch (mAnimationState) { - case ANIMATION_STATE_FADING_IN: - mShowHideAnimator.cancel(); - // fall through - case ANIMATION_STATE_IN: - mAnimationState = ANIMATION_STATE_FADING_OUT; - mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0); - mShowHideAnimator.setDuration(duration); - mShowHideAnimator.start(); - break; - } - } - - private void cancelHide() { - mRecyclerView.removeCallbacks(mHideRunnable); - } - - private void resetHideDelay(int delay) { - cancelHide(); - mRecyclerView.postDelayed(mHideRunnable, delay); - } - - @Override - public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { - if (mRecyclerViewWidth != mRecyclerView.getWidth() - || mRecyclerViewHeight != mRecyclerView.getHeight()) { - mRecyclerViewWidth = mRecyclerView.getWidth(); - mRecyclerViewHeight = mRecyclerView.getHeight(); - // This is due to the different events ordering when keyboard is opened or - // retracted vs rotate. Hence to avoid corner cases we just disable the - // scroller when size changed, and wait until the scroll position is recomputed - // before showing it back. - setState(STATE_HIDDEN); - return; - } - - if (mAnimationState != ANIMATION_STATE_OUT) { - if (mNeedVerticalScrollbar) { - drawVerticalScrollbar(canvas); - } - if (mNeedHorizontalScrollbar) { - drawHorizontalScrollbar(canvas); - } - } - } - - private void drawVerticalScrollbar(Canvas canvas) { - int viewWidth = mRecyclerViewWidth; - - int left = viewWidth - mVerticalThumbWidth; - int top = mVerticalThumbCenterY - mVerticalThumbHeight / 2; - mVerticalThumbDrawable.setBounds(0, 0, mVerticalThumbWidth, mVerticalThumbHeight); - mVerticalTrackDrawable - .setBounds(0, 0, mVerticalTrackWidth, mRecyclerViewHeight); - - if (isLayoutRTL()) { - mVerticalTrackDrawable.draw(canvas); - canvas.translate(mVerticalThumbWidth, top); - canvas.scale(-1, 1); - mVerticalThumbDrawable.draw(canvas); - canvas.scale(-1, 1); - canvas.translate(-mVerticalThumbWidth, -top); - } else { - canvas.translate(left, 0); - mVerticalTrackDrawable.draw(canvas); - canvas.translate(0, top); - mVerticalThumbDrawable.draw(canvas); - canvas.translate(-left, -top); - } - } - - private void drawHorizontalScrollbar(Canvas canvas) { - int viewHeight = mRecyclerViewHeight; - - int top = viewHeight - mHorizontalThumbHeight; - int left = mHorizontalThumbCenterX - mHorizontalThumbWidth / 2; - mHorizontalThumbDrawable.setBounds(0, 0, mHorizontalThumbWidth, mHorizontalThumbHeight); - mHorizontalTrackDrawable - .setBounds(0, 0, mRecyclerViewWidth, mHorizontalTrackHeight); - - canvas.translate(0, top); - mHorizontalTrackDrawable.draw(canvas); - canvas.translate(left, 0); - mHorizontalThumbDrawable.draw(canvas); - canvas.translate(-left, -top); - } - - /** - * Notify the scroller of external change of the scroll, e.g. through dragging or flinging on - * the view itself. - * - * @param offsetX The new scroll X offset. - * @param offsetY The new scroll Y offset. - */ - void updateScrollPosition(int offsetX, int offsetY) { - int verticalContentLength = mRecyclerView.computeVerticalScrollRange(); - int verticalVisibleLength = mRecyclerViewHeight; - mNeedVerticalScrollbar = verticalContentLength - verticalVisibleLength > 0 - && mRecyclerViewHeight >= mScrollbarMinimumRange; - - int horizontalContentLength = mRecyclerView.computeHorizontalScrollRange(); - int horizontalVisibleLength = mRecyclerViewWidth; - mNeedHorizontalScrollbar = horizontalContentLength - horizontalVisibleLength > 0 - && mRecyclerViewWidth >= mScrollbarMinimumRange; - - if (!mNeedVerticalScrollbar && !mNeedHorizontalScrollbar) { - if (mState != STATE_HIDDEN) { - setState(STATE_HIDDEN); - } - return; - } - - if (mNeedVerticalScrollbar) { - float middleScreenPos = offsetY + verticalVisibleLength / 2.0f; - mVerticalThumbCenterY = - (int) ((verticalVisibleLength * middleScreenPos) / verticalContentLength); - mVerticalThumbHeight = Math.min(verticalVisibleLength, - (verticalVisibleLength * verticalVisibleLength) / verticalContentLength); - } - - if (mNeedHorizontalScrollbar) { - float middleScreenPos = offsetX + horizontalVisibleLength / 2.0f; - mHorizontalThumbCenterX = - (int) ((horizontalVisibleLength * middleScreenPos) / horizontalContentLength); - mHorizontalThumbWidth = Math.min(horizontalVisibleLength, - (horizontalVisibleLength * horizontalVisibleLength) / horizontalContentLength); - } - - if (mState == STATE_HIDDEN || mState == STATE_VISIBLE) { - setState(STATE_VISIBLE); - } - } - - @Override - public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, - @NonNull MotionEvent ev) { - final boolean handled; - if (mState == STATE_VISIBLE) { - boolean insideVerticalThumb = isPointInsideVerticalThumb(ev.getX(), ev.getY()); - boolean insideHorizontalThumb = isPointInsideHorizontalThumb(ev.getX(), ev.getY()); - if (ev.getAction() == MotionEvent.ACTION_DOWN - && (insideVerticalThumb || insideHorizontalThumb)) { - if (insideHorizontalThumb) { - mDragState = DRAG_X; - mHorizontalDragX = (int) ev.getX(); - } else if (insideVerticalThumb) { - mDragState = DRAG_Y; - mVerticalDragY = (int) ev.getY(); - } - - setState(STATE_DRAGGING); - handled = true; - } else { - handled = false; - } - } else if (mState == STATE_DRAGGING) { - handled = true; - } else { - handled = false; - } - return handled; - } - - @Override - public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent me) { - if (mState == STATE_HIDDEN) { - return; - } - - if (me.getAction() == MotionEvent.ACTION_DOWN) { - boolean insideVerticalThumb = isPointInsideVerticalThumb(me.getX(), me.getY()); - boolean insideHorizontalThumb = isPointInsideHorizontalThumb(me.getX(), me.getY()); - if (insideVerticalThumb || insideHorizontalThumb) { - if (insideHorizontalThumb) { - mDragState = DRAG_X; - mHorizontalDragX = (int) me.getX(); - } else if (insideVerticalThumb) { - mDragState = DRAG_Y; - mVerticalDragY = (int) me.getY(); - } - setState(STATE_DRAGGING); - } - } else if (me.getAction() == MotionEvent.ACTION_UP && mState == STATE_DRAGGING) { - mVerticalDragY = 0; - mHorizontalDragX = 0; - setState(STATE_VISIBLE); - mDragState = DRAG_NONE; - } else if (me.getAction() == MotionEvent.ACTION_MOVE && mState == STATE_DRAGGING) { - show(); - if (mDragState == DRAG_X) { - horizontalScrollTo(me.getX()); - } - if (mDragState == DRAG_Y) { - verticalScrollTo(me.getY()); - } - } - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } - - private void verticalScrollTo(float y) { - final int[] scrollbarRange = getVerticalRange(); - y = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], y)); - if (Math.abs(mVerticalThumbCenterY - y) < 2) { - return; - } - int scrollingBy = scrollTo(mVerticalDragY, y, scrollbarRange, - mRecyclerView.computeVerticalScrollRange(), - mRecyclerView.computeVerticalScrollOffset(), mRecyclerViewHeight); - if (scrollingBy != 0) { - mRecyclerView.scrollBy(0, scrollingBy); - } - mVerticalDragY = y; - } - - private void horizontalScrollTo(float x) { - final int[] scrollbarRange = getHorizontalRange(); - x = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], x)); - if (Math.abs(mHorizontalThumbCenterX - x) < 2) { - return; - } - - int scrollingBy = scrollTo(mHorizontalDragX, x, scrollbarRange, - mRecyclerView.computeHorizontalScrollRange(), - mRecyclerView.computeHorizontalScrollOffset(), mRecyclerViewWidth); - if (scrollingBy != 0) { - mRecyclerView.scrollBy(scrollingBy, 0); - } - - mHorizontalDragX = x; - } - - private int scrollTo(float oldDragPos, float newDragPos, int[] scrollbarRange, int scrollRange, - int scrollOffset, int viewLength) { - int scrollbarLength = scrollbarRange[1] - scrollbarRange[0]; - if (scrollbarLength == 0) { - return 0; - } - float percentage = ((newDragPos - oldDragPos) / (float) scrollbarLength); - int totalPossibleOffset = scrollRange - viewLength; - int scrollingBy = (int) (percentage * totalPossibleOffset); - int absoluteOffset = scrollOffset + scrollingBy; - if (absoluteOffset < totalPossibleOffset && absoluteOffset >= 0) { - return scrollingBy; - } else { - return 0; - } - } - - @VisibleForTesting - boolean isPointInsideVerticalThumb(float x, float y) { - return (isLayoutRTL() ? x <= mVerticalThumbWidth - : x >= mRecyclerViewWidth - mVerticalThumbWidth) - && y >= mVerticalThumbCenterY - mVerticalThumbHeight / 2 - && y <= mVerticalThumbCenterY + mVerticalThumbHeight / 2; - } - - @VisibleForTesting - boolean isPointInsideHorizontalThumb(float x, float y) { - return (y >= mRecyclerViewHeight - mHorizontalThumbHeight) - && x >= mHorizontalThumbCenterX - mHorizontalThumbWidth / 2 - && x <= mHorizontalThumbCenterX + mHorizontalThumbWidth / 2; - } - - @VisibleForTesting - Drawable getHorizontalTrackDrawable() { - return mHorizontalTrackDrawable; - } - - @VisibleForTesting - Drawable getHorizontalThumbDrawable() { - return mHorizontalThumbDrawable; - } - - @VisibleForTesting - Drawable getVerticalTrackDrawable() { - return mVerticalTrackDrawable; - } - - @VisibleForTesting - Drawable getVerticalThumbDrawable() { - return mVerticalThumbDrawable; - } - - /** - * Gets the (min, max) vertical positions of the vertical scroll bar. - */ - private int[] getVerticalRange() { - mVerticalRange[0] = mMargin; - mVerticalRange[1] = mRecyclerViewHeight - mMargin; - return mVerticalRange; - } - - /** - * Gets the (min, max) horizontal positions of the horizontal scroll bar. - */ - private int[] getHorizontalRange() { - mHorizontalRange[0] = mMargin; - mHorizontalRange[1] = mRecyclerViewWidth - mMargin; - return mHorizontalRange; - } - - private class AnimatorListener extends AnimatorListenerAdapter { - - private boolean mCanceled = false; - - AnimatorListener() { - } - - @Override - public void onAnimationEnd(Animator animation) { - // Cancel is always followed by a new directive, so don't update state. - if (mCanceled) { - mCanceled = false; - return; - } - if ((float) mShowHideAnimator.getAnimatedValue() == 0) { - mAnimationState = ANIMATION_STATE_OUT; - setState(STATE_HIDDEN); - } else { - mAnimationState = ANIMATION_STATE_IN; - requestRedraw(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - mCanceled = true; - } - } - - private class AnimatorUpdater implements AnimatorUpdateListener { - AnimatorUpdater() { - } - - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - int alpha = (int) (SCROLLBAR_FULL_OPAQUE * ((float) valueAnimator.getAnimatedValue())); - mVerticalThumbDrawable.setAlpha(alpha); - mVerticalTrackDrawable.setAlpha(alpha); - requestRedraw(); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/GapWorker.java b/app/src/main/java/androidx/recyclerview/widget/GapWorker.java deleted file mode 100644 index 5bbdcf1b48..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/GapWorker.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.view.View; - -import androidx.annotation.Nullable; -import androidx.core.os.TraceCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.concurrent.TimeUnit; - -final class GapWorker implements Runnable { - - static final ThreadLocal sGapWorker = new ThreadLocal<>(); - - ArrayList mRecyclerViews = new ArrayList<>(); - long mPostTimeNs; - long mFrameIntervalNs; - - static class Task { - public boolean immediate; - public int viewVelocity; - public int distanceToItem; - public RecyclerView view; - public int position; - - public void clear() { - immediate = false; - viewVelocity = 0; - distanceToItem = 0; - view = null; - position = 0; - } - } - - /** - * Temporary storage for prefetch Tasks that execute in {@link #prefetch(long)}. Task objects - * are pooled in the ArrayList, and never removed to avoid allocations, but always cleared - * in between calls. - */ - private ArrayList mTasks = new ArrayList<>(); - - /** - * Prefetch information associated with a specific RecyclerView. - */ - @SuppressLint("VisibleForTests") - static class LayoutPrefetchRegistryImpl - implements RecyclerView.LayoutManager.LayoutPrefetchRegistry { - int mPrefetchDx; - int mPrefetchDy; - int[] mPrefetchArray; - - int mCount; - - void setPrefetchVector(int dx, int dy) { - mPrefetchDx = dx; - mPrefetchDy = dy; - } - - void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) { - mCount = 0; - if (mPrefetchArray != null) { - Arrays.fill(mPrefetchArray, -1); - } - - final RecyclerView.LayoutManager layout = view.mLayout; - if (view.mAdapter != null - && layout != null - && layout.isItemPrefetchEnabled()) { - if (nested) { - // nested prefetch, only if no adapter updates pending. Note: we don't query - // view.hasPendingAdapterUpdates(), as first layout may not have occurred - if (!view.mAdapterHelper.hasPendingUpdates()) { - layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this); - } - } else { - // momentum based prefetch, only if we trust current child/adapter state - if (!view.hasPendingAdapterUpdates()) { - layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy, - view.mState, this); - } - } - - if (mCount > layout.mPrefetchMaxCountObserved) { - layout.mPrefetchMaxCountObserved = mCount; - layout.mPrefetchMaxObservedInInitialPrefetch = nested; - view.mRecycler.updateViewCacheSize(); - } - } - } - - @Override - public void addPosition(int layoutPosition, int pixelDistance) { - if (layoutPosition < 0) { - throw new IllegalArgumentException("Layout positions must be non-negative"); - } - - if (pixelDistance < 0) { - throw new IllegalArgumentException("Pixel distance must be non-negative"); - } - - // allocate or expand array as needed, doubling when needed - final int storagePosition = mCount * 2; - if (mPrefetchArray == null) { - mPrefetchArray = new int[4]; - Arrays.fill(mPrefetchArray, -1); - } else if (storagePosition >= mPrefetchArray.length) { - final int[] oldArray = mPrefetchArray; - mPrefetchArray = new int[storagePosition * 2]; - System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length); - } - - // add position - mPrefetchArray[storagePosition] = layoutPosition; - mPrefetchArray[storagePosition + 1] = pixelDistance; - - mCount++; - } - - boolean lastPrefetchIncludedPosition(int position) { - if (mPrefetchArray != null) { - final int count = mCount * 2; - for (int i = 0; i < count; i += 2) { - if (mPrefetchArray[i] == position) return true; - } - } - return false; - } - - /** - * Called when prefetch indices are no longer valid for cache prioritization. - */ - void clearPrefetchPositions() { - if (mPrefetchArray != null) { - Arrays.fill(mPrefetchArray, -1); - } - mCount = 0; - } - } - - public void add(RecyclerView recyclerView) { - if (RecyclerView.sDebugAssertionsEnabled && mRecyclerViews.contains(recyclerView)) { - throw new IllegalStateException("RecyclerView already present in worker list!"); - } - mRecyclerViews.add(recyclerView); - } - - public void remove(RecyclerView recyclerView) { - boolean removeSuccess = mRecyclerViews.remove(recyclerView); - if (RecyclerView.sDebugAssertionsEnabled && !removeSuccess) { - throw new IllegalStateException("RecyclerView removal failed!"); - } - } - - /** - * Schedule a prefetch immediately after the current traversal. - */ - void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) { - if (recyclerView.isAttachedToWindow()) { - if (RecyclerView.sDebugAssertionsEnabled && !mRecyclerViews.contains(recyclerView)) { - throw new IllegalStateException("attempting to post unregistered view!"); - } - if (mPostTimeNs == 0) { - mPostTimeNs = recyclerView.getNanoTime(); - recyclerView.post(this); - } - } - - recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy); - } - - static Comparator sTaskComparator = new Comparator() { - @Override - public int compare(Task lhs, Task rhs) { - // first, prioritize non-cleared tasks - if ((lhs.view == null) != (rhs.view == null)) { - return lhs.view == null ? 1 : -1; - } - - // then prioritize immediate - if (lhs.immediate != rhs.immediate) { - return lhs.immediate ? -1 : 1; - } - - // then prioritize _highest_ view velocity - int deltaViewVelocity = rhs.viewVelocity - lhs.viewVelocity; - if (deltaViewVelocity != 0) return deltaViewVelocity; - - // then prioritize _lowest_ distance to item - int deltaDistanceToItem = lhs.distanceToItem - rhs.distanceToItem; - if (deltaDistanceToItem != 0) return deltaDistanceToItem; - - return 0; - } - }; - - private void buildTaskList() { - // Update PrefetchRegistry in each view - final int viewCount = mRecyclerViews.size(); - int totalTaskCount = 0; - for (int i = 0; i < viewCount; i++) { - RecyclerView view = mRecyclerViews.get(i); - if (view.getWindowVisibility() == View.VISIBLE) { - view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false); - totalTaskCount += view.mPrefetchRegistry.mCount; - } - } - - // Populate task list from prefetch data... - mTasks.ensureCapacity(totalTaskCount); - int totalTaskIndex = 0; - for (int i = 0; i < viewCount; i++) { - RecyclerView view = mRecyclerViews.get(i); - if (view.getWindowVisibility() != View.VISIBLE) { - // Invisible view, don't bother prefetching - continue; - } - - LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry; - final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx) - + Math.abs(prefetchRegistry.mPrefetchDy); - for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) { - final Task task; - if (totalTaskIndex >= mTasks.size()) { - task = new Task(); - mTasks.add(task); - } else { - task = mTasks.get(totalTaskIndex); - } - final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1]; - - task.immediate = distanceToItem <= viewVelocity; - task.viewVelocity = viewVelocity; - task.distanceToItem = distanceToItem; - task.view = view; - task.position = prefetchRegistry.mPrefetchArray[j]; - - totalTaskIndex++; - } - } - - // ... and priority sort - Collections.sort(mTasks, sTaskComparator); - } - - static boolean isPrefetchPositionAttached(RecyclerView view, int position) { - final int childCount = view.mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - View attachedView = view.mChildHelper.getUnfilteredChildAt(i); - RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(attachedView); - // Note: can use mPosition here because adapter doesn't have pending updates - if (holder.mPosition == position && !holder.isInvalid()) { - return true; - } - } - return false; - } - - private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view, - int position, long deadlineNs) { - if (isPrefetchPositionAttached(view, position)) { - // don't attempt to prefetch attached views - return null; - } - - RecyclerView.Recycler recycler = view.mRecycler; - RecyclerView.ViewHolder holder; - try { - view.onEnterLayoutOrScroll(); - holder = recycler.tryGetViewHolderForPositionByDeadline( - position, false, deadlineNs); - - if (holder != null) { - if (holder.isBound() && !holder.isInvalid()) { - // Only give the view a chance to go into the cache if binding succeeded - // Note that we must use public method, since item may need cleanup - recycler.recycleView(holder.itemView); - } else { - // Didn't bind, so we can't cache the view, but it will stay in the pool until - // next prefetch/traversal. If a View fails to bind, it means we didn't have - // enough time prior to the deadline (and won't for other instances of this - // type, during this GapWorker prefetch pass). - recycler.addViewHolderToRecycledViewPool(holder, false); - } - } - } finally { - view.onExitLayoutOrScroll(false); - } - return holder; - } - - private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView, - long deadlineNs) { - if (innerView == null) { - return; - } - - if (innerView.mDataSetHasChangedAfterLayout - && innerView.mChildHelper.getUnfilteredChildCount() != 0) { - // RecyclerView has new data, but old attached views. Clear everything, so that - // we can prefetch without partially stale data. - innerView.removeAndRecycleViews(); - } - - // do nested prefetch! - final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry; - innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true); - - if (innerPrefetchRegistry.mCount != 0) { - try { - TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG); - innerView.mState.prepareForNestedPrefetch(innerView.mAdapter); - for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) { - // Note that we ignore immediate flag for inner items because - // we have lower confidence they're needed next frame. - final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i]; - prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs); - } - } finally { - TraceCompat.endSection(); - } - } - } - - private void flushTaskWithDeadline(Task task, long deadlineNs) { - long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs; - RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view, - task.position, taskDeadlineNs); - if (holder != null - && holder.mNestedRecyclerView != null - && holder.isBound() - && !holder.isInvalid()) { - prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs); - } - } - - private void flushTasksWithDeadline(long deadlineNs) { - for (int i = 0; i < mTasks.size(); i++) { - final Task task = mTasks.get(i); - if (task.view == null) { - break; // done with populated tasks - } - flushTaskWithDeadline(task, deadlineNs); - task.clear(); - } - } - - void prefetch(long deadlineNs) { - buildTaskList(); - flushTasksWithDeadline(deadlineNs); - } - - @Override - public void run() { - try { - TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG); - - if (mRecyclerViews.isEmpty()) { - // abort - no work to do - return; - } - - // Query most recent vsync so we can predict next one. Note that drawing time not yet - // valid in animation/input callbacks, so query it here to be safe. - final int size = mRecyclerViews.size(); - long latestFrameVsyncMs = 0; - for (int i = 0; i < size; i++) { - RecyclerView view = mRecyclerViews.get(i); - if (view.getWindowVisibility() == View.VISIBLE) { - latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs); - } - } - - if (latestFrameVsyncMs == 0) { - // abort - either no views visible, or couldn't get last vsync for estimating next - return; - } - - long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs; - - prefetch(nextFrameNs); - - // TODO: consider rescheduling self, if there's more work to do - } finally { - mPostTimeNs = 0; - TraceCompat.endSection(); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java b/app/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java deleted file mode 100644 index 01935b50c0..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/GridLayoutManager.java +++ /dev/null @@ -1,1450 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.GridView; - -import androidx.annotation.NonNull; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; - -import java.util.Arrays; - -/** - * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid. - *

- * By default, each item occupies 1 span. You can change it by providing a custom - * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}. - */ -public class GridLayoutManager extends LinearLayoutManager { - - private static final boolean DEBUG = false; - private static final String TAG = "GridLayoutManager"; - public static final int DEFAULT_SPAN_COUNT = -1; - /** - * Span size have been changed but we've not done a new layout calculation. - */ - boolean mPendingSpanCountChange = false; - int mSpanCount = DEFAULT_SPAN_COUNT; - /** - * Right borders for each span. - *

For i-th item start is {@link #mCachedBorders}[i-1] + 1 - * and end is {@link #mCachedBorders}[i]. - */ - int [] mCachedBorders; - /** - * Temporary array to keep views in layoutChunk method - */ - View[] mSet; - final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray(); - final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray(); - SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup(); - // re-used variable to acquire decor insets from RecyclerView - final Rect mDecorInsets = new Rect(); - - private boolean mUsingSpansToEstimateScrollBarDimensions; - - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". If spanCount is not specified in the XML, it defaults to a - * single column. - * - * {@link androidx.recyclerview.R.attr#spanCount} - */ - public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setSpanCount(properties.spanCount); - } - - /** - * Creates a vertical GridLayoutManager - * - * @param context Current context, will be used to access resources. - * @param spanCount The number of columns in the grid - */ - public GridLayoutManager(Context context, int spanCount) { - super(context); - setSpanCount(spanCount); - } - - /** - * @param context Current context, will be used to access resources. - * @param spanCount The number of columns or rows in the grid - * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link - * #VERTICAL}. - * @param reverseLayout When set to true, layouts from end to start. - */ - public GridLayoutManager(Context context, int spanCount, - @RecyclerView.Orientation int orientation, boolean reverseLayout) { - super(context, orientation, reverseLayout); - setSpanCount(spanCount); - } - - /** - * stackFromEnd is not supported by GridLayoutManager. Consider using - * {@link #setReverseLayout(boolean)}. - */ - @Override - public void setStackFromEnd(boolean stackFromEnd) { - if (stackFromEnd) { - throw new UnsupportedOperationException( - "GridLayoutManager does not support stack from end." - + " Consider using reverse layout"); - } - super.setStackFromEnd(false); - } - - @Override - public int getRowCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == HORIZONTAL) { - return mSpanCount; - } - if (state.getItemCount() < 1) { - return 0; - } - - // Row count is one more than the last item's row index. - return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1; - } - - @Override - public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return mSpanCount; - } - if (state.getItemCount() < 1) { - return 0; - } - - // Column count is one more than the last item's column index. - return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1; - } - - @Override - public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, - RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { - ViewGroup.LayoutParams lp = host.getLayoutParams(); - if (!(lp instanceof LayoutParams)) { - super.onInitializeAccessibilityNodeInfoForItem(host, info); - return; - } - LayoutParams glp = (LayoutParams) lp; - int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition()); - if (mOrientation == HORIZONTAL) { - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - glp.getSpanIndex(), glp.getSpanSize(), - spanGroupIndex, 1, false, false)); - } else { // VERTICAL - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - spanGroupIndex , 1, - glp.getSpanIndex(), glp.getSpanSize(), false, false)); - } - } - - @Override - public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler, - @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(recycler, state, info); - // Set the class name so this is treated as a grid. A11y services should identify grids - // and list via CollectionInfos, but an almost empty grid may be incorrectly identified - // as a list. - info.setClassName(GridView.class.getName()); - } - - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - if (state.isPreLayout()) { - cachePreLayoutSpanMapping(); - } - super.onLayoutChildren(recycler, state); - if (DEBUG) { - validateChildOrder(); - } - clearPreLayoutSpanMappingCache(); - } - - @Override - public void onLayoutCompleted(RecyclerView.State state) { - super.onLayoutCompleted(state); - mPendingSpanCountChange = false; - } - - private void clearPreLayoutSpanMappingCache() { - mPreLayoutSpanSizeCache.clear(); - mPreLayoutSpanIndexCache.clear(); - } - - private void cachePreLayoutSpanMapping() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); - final int viewPosition = lp.getViewLayoutPosition(); - mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize()); - mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex()); - } - } - - @Override - public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - mSpanSizeLookup.invalidateSpanGroupIndexCache(); - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - mSpanSizeLookup.invalidateSpanIndexCache(); - mSpanSizeLookup.invalidateSpanGroupIndexCache(); - } - - @Override - public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - mSpanSizeLookup.invalidateSpanGroupIndexCache(); - } - - @Override - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, - Object payload) { - mSpanSizeLookup.invalidateSpanIndexCache(); - mSpanSizeLookup.invalidateSpanGroupIndexCache(); - } - - @Override - public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - mSpanSizeLookup.invalidateSpanGroupIndexCache(); - } - - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - if (mOrientation == HORIZONTAL) { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT); - } else { - return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { - return new LayoutParams(c, attrs); - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { - if (lp instanceof ViewGroup.MarginLayoutParams) { - return new LayoutParams((ViewGroup.MarginLayoutParams) lp); - } else { - return new LayoutParams(lp); - } - } - - @Override - public boolean checkLayoutParams(RecyclerView.LayoutParams lp) { - return lp instanceof LayoutParams; - } - - /** - * Sets the source to get the number of spans occupied by each item in the adapter. - * - * @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans - * occupied by each item - */ - public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) { - mSpanSizeLookup = spanSizeLookup; - } - - /** - * Returns the current {@link SpanSizeLookup} used by the GridLayoutManager. - * - * @return The current {@link SpanSizeLookup} used by the GridLayoutManager. - */ - public SpanSizeLookup getSpanSizeLookup() { - return mSpanSizeLookup; - } - - private void updateMeasurements() { - int totalSpace; - if (getOrientation() == VERTICAL) { - totalSpace = getWidth() - getPaddingRight() - getPaddingLeft(); - } else { - totalSpace = getHeight() - getPaddingBottom() - getPaddingTop(); - } - calculateItemBorders(totalSpace); - } - - @Override - public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { - if (mCachedBorders == null) { - super.setMeasuredDimension(childrenBounds, wSpec, hSpec); - } - final int width, height; - final int horizontalPadding = getPaddingLeft() + getPaddingRight(); - final int verticalPadding = getPaddingTop() + getPaddingBottom(); - if (mOrientation == VERTICAL) { - final int usedHeight = childrenBounds.height() + verticalPadding; - height = chooseSize(hSpec, usedHeight, getMinimumHeight()); - width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding, - getMinimumWidth()); - } else { - final int usedWidth = childrenBounds.width() + horizontalPadding; - width = chooseSize(wSpec, usedWidth, getMinimumWidth()); - height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding, - getMinimumHeight()); - } - setMeasuredDimension(width, height); - } - - /** - * @param totalSpace Total available space after padding is removed - */ - private void calculateItemBorders(int totalSpace) { - mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace); - } - - /** - * @param cachedBorders The out array - * @param spanCount number of spans - * @param totalSpace total available space after padding is removed - * @return The updated array. Might be the same instance as the provided array if its size - * has not changed. - */ - static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) { - if (cachedBorders == null || cachedBorders.length != spanCount + 1 - || cachedBorders[cachedBorders.length - 1] != totalSpace) { - cachedBorders = new int[spanCount + 1]; - } - cachedBorders[0] = 0; - int sizePerSpan = totalSpace / spanCount; - int sizePerSpanRemainder = totalSpace % spanCount; - int consumedPixels = 0; - int additionalSize = 0; - for (int i = 1; i <= spanCount; i++) { - int itemSize = sizePerSpan; - additionalSize += sizePerSpanRemainder; - if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) { - itemSize += 1; - additionalSize -= spanCount; - } - consumedPixels += itemSize; - cachedBorders[i] = consumedPixels; - } - return cachedBorders; - } - - int getSpaceForSpanRange(int startSpan, int spanSize) { - if (mOrientation == VERTICAL && isLayoutRTL()) { - return mCachedBorders[mSpanCount - startSpan] - - mCachedBorders[mSpanCount - startSpan - spanSize]; - } else { - return mCachedBorders[startSpan + spanSize] - mCachedBorders[startSpan]; - } - } - - @Override - void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo, int itemDirection) { - super.onAnchorReady(recycler, state, anchorInfo, itemDirection); - updateMeasurements(); - if (state.getItemCount() > 0 && !state.isPreLayout()) { - ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection); - } - ensureViewSet(); - } - - private void ensureViewSet() { - if (mSet == null || mSet.length != mSpanCount) { - mSet = new View[mSpanCount]; - } - } - - @Override - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - updateMeasurements(); - ensureViewSet(); - return super.scrollHorizontallyBy(dx, recycler, state); - } - - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - updateMeasurements(); - ensureViewSet(); - return super.scrollVerticallyBy(dy, recycler, state); - } - - private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler, - RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) { - final boolean layingOutInPrimaryDirection = - itemDirection == LayoutState.ITEM_DIRECTION_TAIL; - int span = getSpanIndex(recycler, state, anchorInfo.mPosition); - if (layingOutInPrimaryDirection) { - // choose span 0 - while (span > 0 && anchorInfo.mPosition > 0) { - anchorInfo.mPosition--; - span = getSpanIndex(recycler, state, anchorInfo.mPosition); - } - } else { - // choose the max span we can get. hopefully last one - final int indexLimit = state.getItemCount() - 1; - int pos = anchorInfo.mPosition; - int bestSpan = span; - while (pos < indexLimit) { - int next = getSpanIndex(recycler, state, pos + 1); - if (next > bestSpan) { - pos += 1; - bestSpan = next; - } else { - break; - } - } - anchorInfo.mPosition = pos; - } - } - - @Override - View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) { - - int start = 0; - int end = getChildCount(); - int diff = 1; - if (traverseChildrenInReverseOrder) { - start = getChildCount() - 1; - end = -1; - diff = -1; - } - - int itemCount = state.getItemCount(); - - ensureLayoutState(); - View invalidMatch = null; - View outOfBoundsMatch = null; - - final int boundsStart = mOrientationHelper.getStartAfterPadding(); - final int boundsEnd = mOrientationHelper.getEndAfterPadding(); - - for (int i = start; i != end; i += diff) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - final int span = getSpanIndex(recycler, state, position); - if (span != 0) { - continue; - } - if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) { - if (invalidMatch == null) { - invalidMatch = view; // removed item, least preferred - } - } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd - || mOrientationHelper.getDecoratedEnd(view) < boundsStart) { - if (outOfBoundsMatch == null) { - outOfBoundsMatch = view; // item is not visible, less preferred - } - } else { - return view; - } - } - } - return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch; - } - - private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state, - int viewPosition) { - if (!state.isPreLayout()) { - return mSpanSizeLookup.getCachedSpanGroupIndex(viewPosition, mSpanCount); - } - final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition); - if (adapterPosition == -1) { - if (DEBUG) { - throw new RuntimeException("Cannot find span group index for position " - + viewPosition); - } - Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition); - return 0; - } - return mSpanSizeLookup.getCachedSpanGroupIndex(adapterPosition, mSpanCount); - } - - private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { - if (!state.isPreLayout()) { - return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount); - } - final int cached = mPreLayoutSpanIndexCache.get(pos, -1); - if (cached != -1) { - return cached; - } - final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos); - if (adapterPosition == -1) { - if (DEBUG) { - throw new RuntimeException("Cannot find span index for pre layout position. It is" - + " not cached, not in the adapter. Pos:" + pos); - } - Log.w(TAG, "Cannot find span size for pre layout position. It is" - + " not cached, not in the adapter. Pos:" + pos); - return 0; - } - return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount); - } - - private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) { - if (!state.isPreLayout()) { - return mSpanSizeLookup.getSpanSize(pos); - } - final int cached = mPreLayoutSpanSizeCache.get(pos, -1); - if (cached != -1) { - return cached; - } - final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos); - if (adapterPosition == -1) { - if (DEBUG) { - throw new RuntimeException("Cannot find span size for pre layout position. It is" - + " not cached, not in the adapter. Pos:" + pos); - } - Log.w(TAG, "Cannot find span size for pre layout position. It is" - + " not cached, not in the adapter. Pos:" + pos); - return 1; - } - return mSpanSizeLookup.getSpanSize(adapterPosition); - } - - @Override - void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - int remainingSpan = mSpanCount; - int count = 0; - while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) { - final int pos = layoutState.mCurrentPosition; - layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset)); - final int spanSize = mSpanSizeLookup.getSpanSize(pos); - remainingSpan -= spanSize; - layoutState.mCurrentPosition += layoutState.mItemDirection; - count++; - } - } - - @Override - void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, - LayoutState layoutState, LayoutChunkResult result) { - final int otherDirSpecMode = mOrientationHelper.getModeInOther(); - final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY; - final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0; - // if grid layout's dimensions are not specified, let the new row change the measurements - // This is not perfect since we not covering all rows but still solves an important case - // where they may have a header row which should be laid out according to children. - if (flexibleInOtherDir) { - updateMeasurements(); // reset measurements - } - final boolean layingOutInPrimaryDirection = - layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL; - int count = 0; - int remainingSpan = mSpanCount; - if (!layingOutInPrimaryDirection) { - int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition); - int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition); - remainingSpan = itemSpanIndex + itemSpanSize; - } - while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) { - int pos = layoutState.mCurrentPosition; - final int spanSize = getSpanSize(recycler, state, pos); - if (spanSize > mSpanCount) { - throw new IllegalArgumentException("Item at position " + pos + " requires " - + spanSize + " spans but GridLayoutManager has only " + mSpanCount - + " spans."); - } - remainingSpan -= spanSize; - if (remainingSpan < 0) { - break; // item did not fit into this row or column - } - View view = layoutState.next(recycler); - if (view == null) { - break; - } - mSet[count] = view; - count++; - } - - if (count == 0) { - result.mFinished = true; - return; - } - - int maxSize = 0; - float maxSizeInOther = 0; // use a float to get size per span - - // we should assign spans before item decor offsets are calculated - assignSpans(recycler, state, count, layingOutInPrimaryDirection); - for (int i = 0; i < count; i++) { - View view = mSet[i]; - if (layoutState.mScrapList == null) { - if (layingOutInPrimaryDirection) { - addView(view); - } else { - addView(view, 0); - } - } else { - if (layingOutInPrimaryDirection) { - addDisappearingView(view); - } else { - addDisappearingView(view, 0); - } - } - calculateItemDecorationsForChild(view, mDecorInsets); - - measureChild(view, otherDirSpecMode, false); - final int size = mOrientationHelper.getDecoratedMeasurement(view); - if (size > maxSize) { - maxSize = size; - } - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) - / lp.mSpanSize; - if (otherSize > maxSizeInOther) { - maxSizeInOther = otherSize; - } - } - if (flexibleInOtherDir) { - // re-distribute columns - guessMeasurement(maxSizeInOther, currentOtherDirSize); - // now we should re-measure any item that was match parent. - maxSize = 0; - for (int i = 0; i < count; i++) { - View view = mSet[i]; - measureChild(view, View.MeasureSpec.EXACTLY, true); - final int size = mOrientationHelper.getDecoratedMeasurement(view); - if (size > maxSize) { - maxSize = size; - } - } - } - - // Views that did not measure the maxSize has to be re-measured - // We will stop doing this once we introduce Gravity in the GLM layout params - for (int i = 0; i < count; i++) { - final View view = mSet[i]; - if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) { - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final Rect decorInsets = lp.mDecorInsets; - final int verticalInsets = decorInsets.top + decorInsets.bottom - + lp.topMargin + lp.bottomMargin; - final int horizontalInsets = decorInsets.left + decorInsets.right - + lp.leftMargin + lp.rightMargin; - final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize); - final int wSpec; - final int hSpec; - if (mOrientation == VERTICAL) { - wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, - horizontalInsets, lp.width, false); - hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets, - View.MeasureSpec.EXACTLY); - } else { - wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets, - View.MeasureSpec.EXACTLY); - hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, - verticalInsets, lp.height, false); - } - measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true); - } - } - - result.mConsumed = maxSize; - - int left = 0, right = 0, top = 0, bottom = 0; - if (mOrientation == VERTICAL) { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - bottom = layoutState.mOffset; - top = bottom - maxSize; - } else { - top = layoutState.mOffset; - bottom = top + maxSize; - } - } else { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - right = layoutState.mOffset; - left = right - maxSize; - } else { - left = layoutState.mOffset; - right = left + maxSize; - } - } - for (int i = 0; i < count; i++) { - View view = mSet[i]; - LayoutParams params = (LayoutParams) view.getLayoutParams(); - if (mOrientation == VERTICAL) { - if (isLayoutRTL()) { - right = getPaddingLeft() + mCachedBorders[mSpanCount - params.mSpanIndex]; - left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); - } else { - left = getPaddingLeft() + mCachedBorders[params.mSpanIndex]; - right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); - } - } else { - top = getPaddingTop() + mCachedBorders[params.mSpanIndex]; - bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); - } - // We calculate everything with View's bounding box (which includes decor and margins) - // To calculate correct layout position, we subtract margins. - layoutDecoratedWithMargins(view, left, top, right, bottom); - if (DEBUG) { - Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" - + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" - + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin) - + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize); - } - // Consume the available space if the view is not removed OR changed - if (params.isItemRemoved() || params.isItemChanged()) { - result.mIgnoreConsumed = true; - } - result.mFocusable |= view.hasFocusable(); - } - Arrays.fill(mSet, null); - } - - /** - * Measures a child with currently known information. This is not necessarily the child's final - * measurement. (see fillChunk for details). - * - * @param view The child view to be measured - * @param otherDirParentSpecMode The RV measure spec that should be used in the secondary - * orientation - * @param alreadyMeasured True if we've already measured this view once - */ - private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final Rect decorInsets = lp.mDecorInsets; - final int verticalInsets = decorInsets.top + decorInsets.bottom - + lp.topMargin + lp.bottomMargin; - final int horizontalInsets = decorInsets.left + decorInsets.right - + lp.leftMargin + lp.rightMargin; - final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize); - final int wSpec; - final int hSpec; - if (mOrientation == VERTICAL) { - wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, - horizontalInsets, lp.width, false); - hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), - verticalInsets, lp.height, true); - } else { - hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, - verticalInsets, lp.height, false); - wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(), - horizontalInsets, lp.width, true); - } - measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured); - } - - /** - * This is called after laying out a row (if vertical) or a column (if horizontal) when the - * RecyclerView does not have exact measurement specs. - *

- * Here we try to assign a best guess width or height and re-do the layout to update other - * views that wanted to MATCH_PARENT in the non-scroll orientation. - * - * @param maxSizeInOther The maximum size per span ratio from the measurement of the children. - * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below. - */ - private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) { - final int contentSize = Math.round(maxSizeInOther * mSpanCount); - // always re-calculate because borders were stretched during the fill - calculateItemBorders(Math.max(contentSize, currentOtherDirSize)); - } - - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, - boolean alreadyMeasured) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - final boolean measure; - if (alreadyMeasured) { - measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp); - } else { - measure = shouldMeasureChild(child, widthSpec, heightSpec, lp); - } - if (measure) { - child.measure(widthSpec, heightSpec); - } - } - - private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, - boolean layingOutInPrimaryDirection) { - // spans are always assigned from 0 to N no matter if it is RTL or not. - // RTL is used only when positioning the view. - int span, start, end, diff; - // make sure we traverse from min position to max position - if (layingOutInPrimaryDirection) { - start = 0; - end = count; - diff = 1; - } else { - start = count - 1; - end = -1; - diff = -1; - } - span = 0; - for (int i = start; i != end; i += diff) { - View view = mSet[i]; - LayoutParams params = (LayoutParams) view.getLayoutParams(); - params.mSpanSize = getSpanSize(recycler, state, getPosition(view)); - params.mSpanIndex = span; - span += params.mSpanSize; - } - } - - /** - * Returns the number of spans laid out by this grid. - * - * @return The number of spans - * @see #setSpanCount(int) - */ - public int getSpanCount() { - return mSpanCount; - } - - /** - * Sets the number of spans to be laid out. - *

- * If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns. - * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows. - * - * @param spanCount The total number of spans in the grid - * @see #getSpanCount() - */ - public void setSpanCount(int spanCount) { - if (spanCount == mSpanCount) { - return; - } - mPendingSpanCountChange = true; - if (spanCount < 1) { - throw new IllegalArgumentException("Span count should be at least 1. Provided " - + spanCount); - } - mSpanCount = spanCount; - mSpanSizeLookup.invalidateSpanIndexCache(); - requestLayout(); - } - - /** - * A helper class to provide the number of spans each item occupies. - *

- * Default implementation sets each item to occupy exactly 1 span. - * - * @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup) - */ - public abstract static class SpanSizeLookup { - - final SparseIntArray mSpanIndexCache = new SparseIntArray(); - final SparseIntArray mSpanGroupIndexCache = new SparseIntArray(); - - private boolean mCacheSpanIndices = false; - private boolean mCacheSpanGroupIndices = false; - - /** - * Returns the number of span occupied by the item at position. - * - * @param position The adapter position of the item - * @return The number of spans occupied by the item at the provided position - */ - public abstract int getSpanSize(int position); - - /** - * Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or - * not. By default these values are not cached. If you are not overriding - * {@link #getSpanIndex(int, int)} with something highly performant, you should set this - * to true for better performance. - * - * @param cacheSpanIndices Whether results of getSpanIndex should be cached or not. - */ - public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) { - if (!cacheSpanIndices) { - mSpanGroupIndexCache.clear(); - } - mCacheSpanIndices = cacheSpanIndices; - } - - /** - * Sets whether the results of {@link #getSpanGroupIndex(int, int)} method should be cached - * or not. By default these values are not cached. If you are not overriding - * {@link #getSpanGroupIndex(int, int)} with something highly performant, and you are using - * spans to calculate scrollbar offset and range, you should set this to true for better - * performance. - * - * @param cacheSpanGroupIndices Whether results of getGroupSpanIndex should be cached or - * not. - */ - public void setSpanGroupIndexCacheEnabled(boolean cacheSpanGroupIndices) { - if (!cacheSpanGroupIndices) { - mSpanGroupIndexCache.clear(); - } - mCacheSpanGroupIndices = cacheSpanGroupIndices; - } - - /** - * Clears the span index cache. GridLayoutManager automatically calls this method when - * adapter changes occur. - */ - public void invalidateSpanIndexCache() { - mSpanIndexCache.clear(); - } - - /** - * Clears the span group index cache. GridLayoutManager automatically calls this method - * when adapter changes occur. - */ - public void invalidateSpanGroupIndexCache() { - mSpanGroupIndexCache.clear(); - } - - /** - * Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not. - * - * @return True if results of {@link #getSpanIndex(int, int)} are cached. - */ - public boolean isSpanIndexCacheEnabled() { - return mCacheSpanIndices; - } - - /** - * Returns whether results of {@link #getSpanGroupIndex(int, int)} method are cached or not. - * - * @return True if results of {@link #getSpanGroupIndex(int, int)} are cached. - */ - public boolean isSpanGroupIndexCacheEnabled() { - return mCacheSpanGroupIndices; - } - - int getCachedSpanIndex(int position, int spanCount) { - if (!mCacheSpanIndices) { - return getSpanIndex(position, spanCount); - } - final int existing = mSpanIndexCache.get(position, -1); - if (existing != -1) { - return existing; - } - final int value = getSpanIndex(position, spanCount); - mSpanIndexCache.put(position, value); - return value; - } - - int getCachedSpanGroupIndex(int position, int spanCount) { - if (!mCacheSpanGroupIndices) { - return getSpanGroupIndex(position, spanCount); - } - final int existing = mSpanGroupIndexCache.get(position, -1); - if (existing != -1) { - return existing; - } - final int value = getSpanGroupIndex(position, spanCount); - mSpanGroupIndexCache.put(position, value); - return value; - } - - /** - * Returns the final span index of the provided position. - *

- * If you have a faster way to calculate span index for your items, you should override - * this method. Otherwise, you should enable span index cache - * ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is - * disabled, default implementation traverses all items from 0 to - * position. When caching is enabled, it calculates from the closest cached - * value before the position. - *

- * If you override this method, you need to make sure it is consistent with - * {@link #getSpanSize(int)}. GridLayoutManager does not call this method for - * each item. It is called only for the reference item and rest of the items - * are assigned to spans based on the reference item. For example, you cannot assign a - * position to span 2 while span 1 is empty. - *

- * Note that span offsets always start with 0 and are not affected by RTL. - * - * @param position The position of the item - * @param spanCount The total number of spans in the grid - * @return The final span position of the item. Should be between 0 (inclusive) and - * spanCount(exclusive) - */ - public int getSpanIndex(int position, int spanCount) { - int positionSpanSize = getSpanSize(position); - if (positionSpanSize == spanCount) { - return 0; // quick return for full-span items - } - int span = 0; - int startPos = 0; - // If caching is enabled, try to jump - if (mCacheSpanIndices) { - int prevKey = findFirstKeyLessThan(mSpanIndexCache, position); - if (prevKey >= 0) { - span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey); - startPos = prevKey + 1; - } - } - for (int i = startPos; i < position; i++) { - int size = getSpanSize(i); - span += size; - if (span == spanCount) { - span = 0; - } else if (span > spanCount) { - // did not fit, moving to next row / column - span = size; - } - } - if (span + positionSpanSize <= spanCount) { - return span; - } - return 0; - } - - static int findFirstKeyLessThan(SparseIntArray cache, int position) { - int lo = 0; - int hi = cache.size() - 1; - - while (lo <= hi) { - // Using unsigned shift here to divide by two because it is guaranteed to not - // overflow. - final int mid = (lo + hi) >>> 1; - final int midVal = cache.keyAt(mid); - if (midVal < position) { - lo = mid + 1; - } else { - hi = mid - 1; - } - } - int index = lo - 1; - if (index >= 0 && index < cache.size()) { - return cache.keyAt(index); - } - return -1; - } - - /** - * Returns the index of the group this position belongs. - *

- * For example, if grid has 3 columns and each item occupies 1 span, span group index - * for item 1 will be 0, item 5 will be 1. - * - * @param adapterPosition The position in adapter - * @param spanCount The total number of spans in the grid - * @return The index of the span group including the item at the given adapter position - */ - public int getSpanGroupIndex(int adapterPosition, int spanCount) { - int span = 0; - int group = 0; - int start = 0; - if (mCacheSpanGroupIndices) { - // This finds the first non empty cached group cache key. - int prevKey = findFirstKeyLessThan(mSpanGroupIndexCache, adapterPosition); - if (prevKey != -1) { - group = mSpanGroupIndexCache.get(prevKey); - start = prevKey + 1; - span = getCachedSpanIndex(prevKey, spanCount) + getSpanSize(prevKey); - if (span == spanCount) { - span = 0; - group++; - } - } - } - int positionSpanSize = getSpanSize(adapterPosition); - for (int i = start; i < adapterPosition; i++) { - int size = getSpanSize(i); - span += size; - if (span == spanCount) { - span = 0; - group++; - } else if (span > spanCount) { - // did not fit, moving to next row / column - span = size; - group++; - } - } - if (span + positionSpanSize > spanCount) { - group++; - } - return group; - } - } - - @Override - public View onFocusSearchFailed(View focused, int direction, - RecyclerView.Recycler recycler, RecyclerView.State state) { - View prevFocusedChild = findContainingItemView(focused); - if (prevFocusedChild == null) { - return null; - } - LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams(); - final int prevSpanStart = lp.mSpanIndex; - final int prevSpanEnd = lp.mSpanIndex + lp.mSpanSize; - View view = super.onFocusSearchFailed(focused, direction, recycler, state); - if (view == null) { - return null; - } - // LinearLayoutManager finds the last child. What we want is the child which has the same - // spanIndex. - final int layoutDir = convertFocusDirectionToLayoutDirection(direction); - final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout; - final int start, inc, limit; - if (ascend) { - start = getChildCount() - 1; - inc = -1; - limit = -1; - } else { - start = 0; - inc = 1; - limit = getChildCount(); - } - final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL(); - - // The focusable candidate to be picked if no perfect focusable candidate is found. - // The best focusable candidate is the one with the highest amount of span overlap with - // the currently focused view. - View focusableWeakCandidate = null; // somewhat matches but not strong - int focusableWeakCandidateSpanIndex = -1; - int focusableWeakCandidateOverlap = 0; // how many spans overlap - - // The unfocusable candidate to become visible on the screen next, if no perfect or - // weak focusable candidates are found to receive focus next. - // We are only interested in partially visible unfocusable views. These are views that are - // not fully visible, that is either partially overlapping, or out-of-bounds and right below - // or above RV's padded bounded area. The best unfocusable candidate is the one with the - // highest amount of span overlap with the currently focused view. - View unfocusableWeakCandidate = null; // somewhat matches but not strong - int unfocusableWeakCandidateSpanIndex = -1; - int unfocusableWeakCandidateOverlap = 0; // how many spans overlap - - // The span group index of the start child. This indicates the span group index of the - // next focusable item to receive focus, if a focusable item within the same span group - // exists. Any focusable item beyond this group index are not relevant since they - // were already stored in the layout before onFocusSearchFailed call and were not picked - // by the focusSearch algorithm. - int focusableSpanGroupIndex = getSpanGroupIndex(recycler, state, start); - for (int i = start; i != limit; i += inc) { - int spanGroupIndex = getSpanGroupIndex(recycler, state, i); - View candidate = getChildAt(i); - if (candidate == prevFocusedChild) { - break; - } - - if (candidate.hasFocusable() && spanGroupIndex != focusableSpanGroupIndex) { - // We are past the allowable span group index for the next focusable item. - // The search only continues if no focusable weak candidates have been found up - // until this point, in order to find the best unfocusable candidate to become - // visible on the screen next. - if (focusableWeakCandidate != null) { - break; - } - continue; - } - - final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams(); - final int candidateStart = candidateLp.mSpanIndex; - final int candidateEnd = candidateLp.mSpanIndex + candidateLp.mSpanSize; - if (candidate.hasFocusable() && candidateStart == prevSpanStart - && candidateEnd == prevSpanEnd) { - return candidate; // perfect match - } - boolean assignAsWeek = false; - if ((candidate.hasFocusable() && focusableWeakCandidate == null) - || (!candidate.hasFocusable() && unfocusableWeakCandidate == null)) { - assignAsWeek = true; - } else { - int maxStart = Math.max(candidateStart, prevSpanStart); - int minEnd = Math.min(candidateEnd, prevSpanEnd); - int overlap = minEnd - maxStart; - if (candidate.hasFocusable()) { - if (overlap > focusableWeakCandidateOverlap) { - assignAsWeek = true; - } else if (overlap == focusableWeakCandidateOverlap - && preferLastSpan == (candidateStart - > focusableWeakCandidateSpanIndex)) { - assignAsWeek = true; - } - } else if (focusableWeakCandidate == null - && isViewPartiallyVisible(candidate, false, true)) { - if (overlap > unfocusableWeakCandidateOverlap) { - assignAsWeek = true; - } else if (overlap == unfocusableWeakCandidateOverlap - && preferLastSpan == (candidateStart - > unfocusableWeakCandidateSpanIndex)) { - assignAsWeek = true; - } - } - } - - if (assignAsWeek) { - if (candidate.hasFocusable()) { - focusableWeakCandidate = candidate; - focusableWeakCandidateSpanIndex = candidateLp.mSpanIndex; - focusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) - - Math.max(candidateStart, prevSpanStart); - } else { - unfocusableWeakCandidate = candidate; - unfocusableWeakCandidateSpanIndex = candidateLp.mSpanIndex; - unfocusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) - - Math.max(candidateStart, prevSpanStart); - } - } - } - return (focusableWeakCandidate != null) ? focusableWeakCandidate : unfocusableWeakCandidate; - } - - @Override - public boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null && !mPendingSpanCountChange; - } - - @Override - public int computeHorizontalScrollRange(RecyclerView.State state) { - if (mUsingSpansToEstimateScrollBarDimensions) { - return computeScrollRangeWithSpanInfo(state); - } else { - return super.computeHorizontalScrollRange(state); - } - } - - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - if (mUsingSpansToEstimateScrollBarDimensions) { - return computeScrollRangeWithSpanInfo(state); - } else { - return super.computeVerticalScrollRange(state); - } - } - - @Override - public int computeHorizontalScrollOffset(RecyclerView.State state) { - if (mUsingSpansToEstimateScrollBarDimensions) { - return computeScrollOffsetWithSpanInfo(state); - } else { - return super.computeHorizontalScrollOffset(state); - } - } - - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - if (mUsingSpansToEstimateScrollBarDimensions) { - return computeScrollOffsetWithSpanInfo(state); - } else { - return super.computeVerticalScrollOffset(state); - } - } - - /** - * When this flag is set, the scroll offset and scroll range calculations will take account - * of span information. - * - *

This is will increase the accuracy of the scroll bar's size and offset but will require - * more calls to {@link SpanSizeLookup#getSpanGroupIndex(int, int)}". - * - *

This additional accuracy may or may not be needed, depending on the characteristics of - * your layout. You will likely benefit from this accuracy when: - * - *

    - *
  • The variation in item span sizes is large. - *
  • The size of your data set is small (if your data set is large, the scrollbar will - * likely be very small anyway, and thus the increased accuracy has less impact). - *
  • Calls to {@link SpanSizeLookup#getSpanGroupIndex(int, int)} are fast. - *
- * - *

If you decide to enable this feature, you should be sure that calls to - * {@link SpanSizeLookup#getSpanGroupIndex(int, int)} are fast, that set span group index - * caching is set to true via a call to - * {@link SpanSizeLookup#setSpanGroupIndexCacheEnabled(boolean), - * and span index caching is also enabled via a call to - * {@link SpanSizeLookup#setSpanIndexCacheEnabled(boolean)}}. - */ - public void setUsingSpansToEstimateScrollbarDimensions( - boolean useSpansToEstimateScrollBarDimensions) { - mUsingSpansToEstimateScrollBarDimensions = useSpansToEstimateScrollBarDimensions; - } - - /** - * Returns true if the scroll offset and scroll range calculations take account of span - * information. See {@link #setUsingSpansToEstimateScrollbarDimensions(boolean)} for more - * information on this topic. Defaults to {@code false}. - * - * @return true if the scroll offset and scroll range calculations take account of span - * information. - */ - public boolean isUsingSpansToEstimateScrollbarDimensions() { - return mUsingSpansToEstimateScrollBarDimensions; - } - - private int computeScrollRangeWithSpanInfo(RecyclerView.State state) { - if (getChildCount() == 0 || state.getItemCount() == 0) { - return 0; - } - ensureLayoutState(); - - View startChild = findFirstVisibleChildClosestToStart(!isSmoothScrollbarEnabled(), true); - View endChild = findFirstVisibleChildClosestToEnd(!isSmoothScrollbarEnabled(), true); - - if (startChild == null || endChild == null) { - return 0; - } - if (!isSmoothScrollbarEnabled()) { - return mSpanSizeLookup.getCachedSpanGroupIndex( - state.getItemCount() - 1, mSpanCount) + 1; - } - - // smooth scrollbar enabled. try to estimate better. - final int laidOutArea = mOrientationHelper.getDecoratedEnd(endChild) - - mOrientationHelper.getDecoratedStart(startChild); - - final int firstVisibleSpan = - mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild), mSpanCount); - final int lastVisibleSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild), - mSpanCount); - final int totalSpans = mSpanSizeLookup.getCachedSpanGroupIndex(state.getItemCount() - 1, - mSpanCount) + 1; - final int laidOutSpans = lastVisibleSpan - firstVisibleSpan + 1; - - // estimate a size for full list. - return (int) (((float) laidOutArea / laidOutSpans) * totalSpans); - } - - private int computeScrollOffsetWithSpanInfo(RecyclerView.State state) { - if (getChildCount() == 0 || state.getItemCount() == 0) { - return 0; - } - ensureLayoutState(); - - boolean smoothScrollEnabled = isSmoothScrollbarEnabled(); - View startChild = findFirstVisibleChildClosestToStart(!smoothScrollEnabled, true); - View endChild = findFirstVisibleChildClosestToEnd(!smoothScrollEnabled, true); - if (startChild == null || endChild == null) { - return 0; - } - int startChildSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild), - mSpanCount); - int endChildSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild), - mSpanCount); - - final int minSpan = Math.min(startChildSpan, endChildSpan); - final int maxSpan = Math.max(startChildSpan, endChildSpan); - final int totalSpans = mSpanSizeLookup.getCachedSpanGroupIndex(state.getItemCount() - 1, - mSpanCount) + 1; - - final int spansBefore = mShouldReverseLayout - ? Math.max(0, totalSpans - maxSpan - 1) - : Math.max(0, minSpan); - if (!smoothScrollEnabled) { - return spansBefore; - } - final int laidOutArea = Math.abs(mOrientationHelper.getDecoratedEnd(endChild) - - mOrientationHelper.getDecoratedStart(startChild)); - - final int firstVisibleSpan = - mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(startChild), mSpanCount); - final int lastVisibleSpan = mSpanSizeLookup.getCachedSpanGroupIndex(getPosition(endChild), - mSpanCount); - final int laidOutSpans = lastVisibleSpan - firstVisibleSpan + 1; - final float avgSizePerSpan = (float) laidOutArea / laidOutSpans; - - return Math.round(spansBefore * avgSizePerSpan + (mOrientationHelper.getStartAfterPadding() - - mOrientationHelper.getDecoratedStart(startChild))); - } - - /** - * Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span. - */ - public static final class DefaultSpanSizeLookup extends SpanSizeLookup { - - @Override - public int getSpanSize(int position) { - return 1; - } - - @Override - public int getSpanIndex(int position, int spanCount) { - return position % spanCount; - } - } - - /** - * LayoutParams used by GridLayoutManager. - *

- * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the - * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is - * expected to fill all of the space given to it. - */ - public static class LayoutParams extends RecyclerView.LayoutParams { - - /** - * Span Id for Views that are not laid out yet. - */ - public static final int INVALID_SPAN_ID = -1; - - int mSpanIndex = INVALID_SPAN_ID; - - int mSpanSize = 0; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(RecyclerView.LayoutParams source) { - super(source); - } - - /** - * Returns the current span index of this View. If the View is not laid out yet, the return - * value is undefined. - *

- * Starting with RecyclerView 24.2.0, span indices are always indexed from position 0 - * even if the layout is RTL. In a vertical GridLayoutManager, leftmost span is span - * 0 if the layout is LTR and rightmost span is span 0 if the layout is - * RTL. Prior to 24.2.0, it was the opposite which was conflicting with - * {@link SpanSizeLookup#getSpanIndex(int, int)}. - *

- * If the View occupies multiple spans, span with the minimum index is returned. - * - * @return The span index of the View. - */ - public int getSpanIndex() { - return mSpanIndex; - } - - /** - * Returns the number of spans occupied by this View. If the View not laid out yet, the - * return value is undefined. - * - * @return The number of spans occupied by this View. - */ - public int getSpanSize() { - return mSpanSize; - } - } - -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java b/app/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java deleted file mode 100644 index 2865dadd18..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java +++ /dev/null @@ -1,2494 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Build; -import android.util.Log; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewParent; -import android.view.animation.Interpolator; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.GestureDetectorCompat; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.R; -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - -import java.util.ArrayList; -import java.util.List; - -/** - * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. - *

- * It works with a RecyclerView and a Callback class, which configures what type of interactions - * are enabled and also receives events when user performs these actions. - *

- * Depending on which functionality you support, you should override - * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or - * {@link Callback#onSwiped(ViewHolder, int)}. - *

- * This class is designed to work with any LayoutManager but for certain situations, it can be - * optimized for your custom LayoutManager by extending methods in the - * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler} - * interface in your LayoutManager. - *

- * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can - * customize these behaviors by overriding {@link Callback#onChildDraw(Canvas, RecyclerView, - * ViewHolder, float, float, int, boolean)} - * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, - * boolean)}. - *

- * Most of the time you only need to override onChildDraw. - */ -public class ItemTouchHelper extends RecyclerView.ItemDecoration - implements RecyclerView.OnChildAttachStateChangeListener { - - /** - * Up direction, used for swipe & drag control. - */ - public static final int UP = 1; - - /** - * Down direction, used for swipe & drag control. - */ - public static final int DOWN = 1 << 1; - - /** - * Left direction, used for swipe & drag control. - */ - public static final int LEFT = 1 << 2; - - /** - * Right direction, used for swipe & drag control. - */ - public static final int RIGHT = 1 << 3; - - // If you change these relative direction values, update Callback#convertToAbsoluteDirection, - // Callback#convertToRelativeDirection. - /** - * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout - * direction. Used for swipe & drag control. - */ - public static final int START = LEFT << 2; - - /** - * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout - * direction. Used for swipe & drag control. - */ - public static final int END = RIGHT << 2; - - /** - * ItemTouchHelper is in idle state. At this state, either there is no related motion event by - * the user or latest motion events have not yet triggered a swipe or drag. - */ - public static final int ACTION_STATE_IDLE = 0; - - /** - * A View is currently being swiped. - */ - @SuppressWarnings("WeakerAccess") - public static final int ACTION_STATE_SWIPE = 1; - - /** - * A View is currently being dragged. - */ - @SuppressWarnings("WeakerAccess") - public static final int ACTION_STATE_DRAG = 2; - - /** - * Animation type for views which are swiped successfully. - */ - @SuppressWarnings("WeakerAccess") - public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1; - - /** - * Animation type for views which are not completely swiped thus will animate back to their - * original position. - */ - @SuppressWarnings("WeakerAccess") - public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2; - - /** - * Animation type for views that were dragged and now will animate to their final position. - */ - @SuppressWarnings("WeakerAccess") - public static final int ANIMATION_TYPE_DRAG = 1 << 3; - - private static final String TAG = "ItemTouchHelper"; - - private static final boolean DEBUG = false; - - private static final int ACTIVE_POINTER_ID_NONE = -1; - - static final int DIRECTION_FLAG_COUNT = 8; - - private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1; - - static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT; - - static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT; - - /** - * The unit we are using to track velocity - */ - private static final int PIXELS_PER_SECOND = 1000; - - /** - * Views, whose state should be cleared after they are detached from RecyclerView. - * This is necessary after swipe dismissing an item. We wait until animator finishes its job - * to clean these views. - */ - final List mPendingCleanup = new ArrayList<>(); - - /** - * Re-use array to calculate dx dy for a ViewHolder - */ - private final float[] mTmpPosition = new float[2]; - - /** - * Currently selected view holder - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - ViewHolder mSelected = null; - - /** - * The reference coordinates for the action start. For drag & drop, this is the time long - * press is completed vs for swipe, this is the initial touch point. - */ - float mInitialTouchX; - - float mInitialTouchY; - - /** - * Set when ItemTouchHelper is assigned to a RecyclerView. - */ - private float mSwipeEscapeVelocity; - - /** - * Set when ItemTouchHelper is assigned to a RecyclerView. - */ - private float mMaxSwipeVelocity; - - /** - * The diff between the last event and initial touch. - */ - float mDx; - - float mDy; - - /** - * The coordinates of the selected view at the time it is selected. We record these values - * when action starts so that we can consistently position it even if LayoutManager moves the - * View. - */ - private float mSelectedStartX; - - private float mSelectedStartY; - - /** - * The pointer we are tracking. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mActivePointerId = ACTIVE_POINTER_ID_NONE; - - /** - * Developer callback which controls the behavior of ItemTouchHelper. - */ - @NonNull - Callback mCallback; - - /** - * Current mode. - */ - private int mActionState = ACTION_STATE_IDLE; - - /** - * The direction flags obtained from unmasking - * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current - * action state. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mSelectedFlags; - - /** - * When a View is dragged or swiped and needs to go back to where it was, we create a Recover - * Animation and animate it to its location using this custom Animator, instead of using - * framework Animators. - * Using framework animators has the side effect of clashing with ItemAnimator, creating - * jumpy UIs. - */ - @VisibleForTesting - List mRecoverAnimations = new ArrayList<>(); - - private int mSlop; - - RecyclerView mRecyclerView; - - /** - * When user drags a view to the edge, we start scrolling the LayoutManager as long as View - * is partially out of bounds. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final Runnable mScrollRunnable = new Runnable() { - @Override - public void run() { - if (mSelected != null && scrollIfNecessary()) { - if (mSelected != null) { //it might be lost during scrolling - moveIfNecessary(mSelected); - } - mRecyclerView.removeCallbacks(mScrollRunnable); - ViewCompat.postOnAnimation(mRecyclerView, this); - } - } - }; - - /** - * Used for detecting fling swipe - */ - VelocityTracker mVelocityTracker; - - //re-used list for selecting a swap target - private List mSwapTargets; - - //re used for for sorting swap targets - private List mDistances; - - /** - * If drag & drop is supported, we use child drawing order to bring them to front. - */ - private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null; - - /** - * This keeps a reference to the child dragged by the user. Even after user stops dragging, - * until view reaches its final position (end of recover animation), we keep a reference so - * that it can be drawn above other children. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - View mOverdrawChild = null; - - /** - * We cache the position of the overdraw child to avoid recalculating it each time child - * position callback is called. This value is invalidated whenever a child is attached or - * detached. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - int mOverdrawChildPosition = -1; - - /** - * Used to detect long press. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - GestureDetectorCompat mGestureDetector; - - /** - * Callback for when long press occurs. - */ - private ItemTouchHelperGestureListener mItemTouchHelperGestureListener; - - private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() { - @Override - public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, - @NonNull MotionEvent event) { - mGestureDetector.onTouchEvent(event); - if (DEBUG) { - Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event); - } - final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN) { - mActivePointerId = event.getPointerId(0); - mInitialTouchX = event.getX(); - mInitialTouchY = event.getY(); - obtainVelocityTracker(); - if (mSelected == null) { - final RecoverAnimation animation = findAnimation(event); - if (animation != null) { - mInitialTouchX -= animation.mX; - mInitialTouchY -= animation.mY; - endRecoverAnimation(animation.mViewHolder, true); - if (mPendingCleanup.remove(animation.mViewHolder.itemView)) { - mCallback.clearView(mRecyclerView, animation.mViewHolder); - } - select(animation.mViewHolder, animation.mActionState); - updateDxDy(event, mSelectedFlags, 0); - } - } - } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mActivePointerId = ACTIVE_POINTER_ID_NONE; - select(null, ACTION_STATE_IDLE); - } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { - // in a non scroll orientation, if distance change is above threshold, we - // can select the item - final int index = event.findPointerIndex(mActivePointerId); - if (DEBUG) { - Log.d(TAG, "pointer index " + index); - } - if (index >= 0) { - checkSelectForSwipe(action, event, index); - } - } - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(event); - } - return mSelected != null; - } - - @Override - public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) { - mGestureDetector.onTouchEvent(event); - if (DEBUG) { - Log.d(TAG, - "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event); - } - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(event); - } - if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { - return; - } - final int action = event.getActionMasked(); - final int activePointerIndex = event.findPointerIndex(mActivePointerId); - if (activePointerIndex >= 0) { - checkSelectForSwipe(action, event, activePointerIndex); - } - ViewHolder viewHolder = mSelected; - if (viewHolder == null) { - return; - } - switch (action) { - case MotionEvent.ACTION_MOVE: { - // Find the index of the active pointer and fetch its position - if (activePointerIndex >= 0) { - updateDxDy(event, mSelectedFlags, activePointerIndex); - moveIfNecessary(viewHolder); - mRecyclerView.removeCallbacks(mScrollRunnable); - mScrollRunnable.run(); - mRecyclerView.invalidate(); - } - break; - } - case MotionEvent.ACTION_CANCEL: - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - // fall through - case MotionEvent.ACTION_UP: - select(null, ACTION_STATE_IDLE); - mActivePointerId = ACTIVE_POINTER_ID_NONE; - break; - case MotionEvent.ACTION_POINTER_UP: { - final int pointerIndex = event.getActionIndex(); - final int pointerId = event.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mActivePointerId = event.getPointerId(newPointerIndex); - updateDxDy(event, mSelectedFlags, pointerIndex); - } - break; - } - } - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - if (!disallowIntercept) { - return; - } - select(null, ACTION_STATE_IDLE); - } - }; - - /** - * Temporary rect instance that is used when we need to lookup Item decorations. - */ - private Rect mTmpRect; - - /** - * When user started to drag scroll. Reset when we don't scroll - */ - private long mDragScrollStartTimeInMs; - - /** - * Creates an ItemTouchHelper that will work with the given Callback. - *

- * You can attach ItemTouchHelper to a RecyclerView via - * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration, - * an onItemTouchListener and a Child attach / detach listener to the RecyclerView. - * - * @param callback The Callback which controls the behavior of this touch helper. - */ - public ItemTouchHelper(@NonNull Callback callback) { - mCallback = callback; - } - - private static boolean hitTest(View child, float x, float y, float left, float top) { - return x >= left - && x <= left + child.getWidth() - && y >= top - && y <= top + child.getHeight(); - } - - /** - * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already - * attached to a RecyclerView, it will first detach from the previous one. You can call this - * method with {@code null} to detach it from the current RecyclerView. - * - * @param recyclerView The RecyclerView instance to which you want to add this helper or - * {@code null} if you want to remove ItemTouchHelper from the current - * RecyclerView. - */ - public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { - if (mRecyclerView == recyclerView) { - return; // nothing to do - } - if (mRecyclerView != null) { - destroyCallbacks(); - } - mRecyclerView = recyclerView; - if (recyclerView != null) { - final Resources resources = recyclerView.getResources(); - mSwipeEscapeVelocity = resources - .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity); - mMaxSwipeVelocity = resources - .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity); - setupCallbacks(); - } - } - - private void setupCallbacks() { - ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext()); - mSlop = vc.getScaledTouchSlop(); - mRecyclerView.addItemDecoration(this); - mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); - mRecyclerView.addOnChildAttachStateChangeListener(this); - startGestureDetection(); - } - - private void destroyCallbacks() { - mRecyclerView.removeItemDecoration(this); - mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener); - mRecyclerView.removeOnChildAttachStateChangeListener(this); - // clean all attached - final int recoverAnimSize = mRecoverAnimations.size(); - for (int i = recoverAnimSize - 1; i >= 0; i--) { - final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0); - recoverAnimation.cancel(); - mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder); - } - mRecoverAnimations.clear(); - mOverdrawChild = null; - mOverdrawChildPosition = -1; - releaseVelocityTracker(); - stopGestureDetection(); - } - - private void startGestureDetection() { - mItemTouchHelperGestureListener = new ItemTouchHelperGestureListener(); - mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(), - mItemTouchHelperGestureListener); - } - - private void stopGestureDetection() { - if (mItemTouchHelperGestureListener != null) { - mItemTouchHelperGestureListener.doNotReactToLongPress(); - mItemTouchHelperGestureListener = null; - } - if (mGestureDetector != null) { - mGestureDetector = null; - } - } - - private void getSelectedDxDy(float[] outPosition) { - if ((mSelectedFlags & (LEFT | RIGHT)) != 0) { - outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft(); - } else { - outPosition[0] = mSelected.itemView.getTranslationX(); - } - if ((mSelectedFlags & (UP | DOWN)) != 0) { - outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop(); - } else { - outPosition[1] = mSelected.itemView.getTranslationY(); - } - } - - @Override - public void onDrawOver( - @NonNull Canvas c, - @NonNull RecyclerView parent, - @NonNull RecyclerView.State state - ) { - float dx = 0, dy = 0; - if (mSelected != null) { - getSelectedDxDy(mTmpPosition); - dx = mTmpPosition[0]; - dy = mTmpPosition[1]; - } - mCallback.onDrawOver(c, parent, mSelected, - mRecoverAnimations, mActionState, dx, dy); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - // we don't know if RV changed something so we should invalidate this index. - mOverdrawChildPosition = -1; - float dx = 0, dy = 0; - if (mSelected != null) { - getSelectedDxDy(mTmpPosition); - dx = mTmpPosition[0]; - dy = mTmpPosition[1]; - } - mCallback.onDraw(c, parent, mSelected, - mRecoverAnimations, mActionState, dx, dy); - } - - /** - * Starts dragging or swiping the given View. Call with null if you want to clear it. - * - * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the - * current action, but may not be null if actionState is ACTION_STATE_DRAG. - * @param actionState The type of action - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void select(@Nullable ViewHolder selected, int actionState) { - if (selected == mSelected && actionState == mActionState) { - return; - } - mDragScrollStartTimeInMs = Long.MIN_VALUE; - final int prevActionState = mActionState; - // prevent duplicate animations - endRecoverAnimation(selected, true); - mActionState = actionState; - if (actionState == ACTION_STATE_DRAG) { - if (selected == null) { - throw new IllegalArgumentException("Must pass a ViewHolder when dragging"); - } - - // we remove after animation is complete. this means we only elevate the last drag - // child but that should perform good enough as it is very hard to start dragging a - // new child before the previous one settles. - mOverdrawChild = selected.itemView; - addChildDrawingOrderCallback(); - } - int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)) - - 1; - boolean preventLayout = false; - - if (mSelected != null) { - final ViewHolder prevSelected = mSelected; - if (prevSelected.itemView.getParent() != null) { - final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0 - : swipeIfNecessary(prevSelected); - releaseVelocityTracker(); - // find where we should animate to - final float targetTranslateX, targetTranslateY; - int animationType; - switch (swipeDir) { - case LEFT: - case RIGHT: - case START: - case END: - targetTranslateY = 0; - targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth(); - break; - case UP: - case DOWN: - targetTranslateX = 0; - targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight(); - break; - default: - targetTranslateX = 0; - targetTranslateY = 0; - } - if (prevActionState == ACTION_STATE_DRAG) { - animationType = ANIMATION_TYPE_DRAG; - } else if (swipeDir > 0) { - animationType = ANIMATION_TYPE_SWIPE_SUCCESS; - } else { - animationType = ANIMATION_TYPE_SWIPE_CANCEL; - } - getSelectedDxDy(mTmpPosition); - final float currentTranslateX = mTmpPosition[0]; - final float currentTranslateY = mTmpPosition[1]; - final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType, - prevActionState, currentTranslateX, currentTranslateY, - targetTranslateX, targetTranslateY) { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (this.mOverridden) { - return; - } - if (swipeDir <= 0) { - // this is a drag or failed swipe. recover immediately - mCallback.clearView(mRecyclerView, prevSelected); - // full cleanup will happen on onDrawOver - } else { - // wait until remove animation is complete. - mPendingCleanup.add(prevSelected.itemView); - mIsPendingCleanup = true; - if (swipeDir > 0) { - // Animation might be ended by other animators during a layout. - // We defer callback to avoid editing adapter during a layout. - postDispatchSwipe(this, swipeDir); - } - } - // removed from the list after it is drawn for the last time - if (mOverdrawChild == prevSelected.itemView) { - removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); - } - } - }; - final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType, - targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY); - rv.setDuration(duration); - mRecoverAnimations.add(rv); - rv.start(); - preventLayout = true; - } else { - removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView); - mCallback.clearView(mRecyclerView, prevSelected); - } - mSelected = null; - } - if (selected != null) { - mSelectedFlags = - (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask) - >> (mActionState * DIRECTION_FLAG_COUNT); - mSelectedStartX = selected.itemView.getLeft(); - mSelectedStartY = selected.itemView.getTop(); - mSelected = selected; - - if (actionState == ACTION_STATE_DRAG) { - mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - } - final ViewParent rvParent = mRecyclerView.getParent(); - if (rvParent != null) { - rvParent.requestDisallowInterceptTouchEvent(mSelected != null); - } - if (!preventLayout) { - mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); - } - mCallback.onSelectedChanged(mSelected, mActionState); - mRecyclerView.invalidate(); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) { - // wait until animations are complete. - mRecyclerView.post(new Runnable() { - @Override - public void run() { - if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() - && !anim.mOverridden - && anim.mViewHolder.getAbsoluteAdapterPosition() - != RecyclerView.NO_POSITION) { - final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator(); - // if animator is running or we have other active recover animations, we try - // not to call onSwiped because DefaultItemAnimator is not good at merging - // animations. Instead, we wait and batch. - if ((animator == null || !animator.isRunning(null)) - && !hasRunningRecoverAnim()) { - mCallback.onSwiped(anim.mViewHolder, swipeDir); - } else { - mRecyclerView.post(this); - } - } - } - }); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean hasRunningRecoverAnim() { - final int size = mRecoverAnimations.size(); - for (int i = 0; i < size; i++) { - if (!mRecoverAnimations.get(i).mEnded) { - return true; - } - } - return false; - } - - /** - * If user drags the view to the edge, trigger a scroll if necessary. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean scrollIfNecessary() { - if (mSelected == null) { - mDragScrollStartTimeInMs = Long.MIN_VALUE; - return false; - } - final long now = System.currentTimeMillis(); - final long scrollDuration = mDragScrollStartTimeInMs - == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs; - RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); - if (mTmpRect == null) { - mTmpRect = new Rect(); - } - int scrollX = 0; - int scrollY = 0; - lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect); - if (lm.canScrollHorizontally()) { - int curX = (int) (mSelectedStartX + mDx); - final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft(); - if (mDx < 0 && leftDiff < 0) { - scrollX = leftDiff; - } else if (mDx > 0) { - final int rightDiff = - curX + mSelected.itemView.getWidth() + mTmpRect.right - - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight()); - if (rightDiff > 0) { - scrollX = rightDiff; - } - } - } - if (lm.canScrollVertically()) { - int curY = (int) (mSelectedStartY + mDy); - final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop(); - if (mDy < 0 && topDiff < 0) { - scrollY = topDiff; - } else if (mDy > 0) { - final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom - - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom()); - if (bottomDiff > 0) { - scrollY = bottomDiff; - } - } - } - if (scrollX != 0) { - scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView, - mSelected.itemView.getWidth(), scrollX, - mRecyclerView.getWidth(), scrollDuration); - } - if (scrollY != 0) { - scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView, - mSelected.itemView.getHeight(), scrollY, - mRecyclerView.getHeight(), scrollDuration); - } - if (scrollX != 0 || scrollY != 0) { - if (mDragScrollStartTimeInMs == Long.MIN_VALUE) { - mDragScrollStartTimeInMs = now; - } - mRecyclerView.scrollBy(scrollX, scrollY); - return true; - } - mDragScrollStartTimeInMs = Long.MIN_VALUE; - return false; - } - - private List findSwapTargets(ViewHolder viewHolder) { - if (mSwapTargets == null) { - mSwapTargets = new ArrayList<>(); - mDistances = new ArrayList<>(); - } else { - mSwapTargets.clear(); - mDistances.clear(); - } - final int margin = mCallback.getBoundingBoxMargin(); - final int left = Math.round(mSelectedStartX + mDx) - margin; - final int top = Math.round(mSelectedStartY + mDy) - margin; - final int right = left + viewHolder.itemView.getWidth() + 2 * margin; - final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin; - final int centerX = (left + right) / 2; - final int centerY = (top + bottom) / 2; - final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); - final int childCount = lm.getChildCount(); - for (int i = 0; i < childCount; i++) { - View other = lm.getChildAt(i); - if (other == viewHolder.itemView) { - continue; //myself! - } - if (other.getBottom() < top || other.getTop() > bottom - || other.getRight() < left || other.getLeft() > right) { - continue; - } - final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other); - if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) { - // find the index to add - final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2); - final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2); - final int dist = dx * dx + dy * dy; - - int pos = 0; - final int cnt = mSwapTargets.size(); - for (int j = 0; j < cnt; j++) { - if (dist > mDistances.get(j)) { - pos++; - } else { - break; - } - } - mSwapTargets.add(pos, otherVh); - mDistances.add(pos, dist); - } - } - return mSwapTargets; - } - - /** - * Checks if we should swap w/ another view holder. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void moveIfNecessary(ViewHolder viewHolder) { - if (mRecyclerView.isLayoutRequested()) { - return; - } - if (mActionState != ACTION_STATE_DRAG) { - return; - } - - final float threshold = mCallback.getMoveThreshold(viewHolder); - final int x = (int) (mSelectedStartX + mDx); - final int y = (int) (mSelectedStartY + mDy); - if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold - && Math.abs(x - viewHolder.itemView.getLeft()) - < viewHolder.itemView.getWidth() * threshold) { - return; - } - List swapTargets = findSwapTargets(viewHolder); - if (swapTargets.size() == 0) { - return; - } - // may swap. - ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y); - if (target == null) { - mSwapTargets.clear(); - mDistances.clear(); - return; - } - final int toPosition = target.getAbsoluteAdapterPosition(); - final int fromPosition = viewHolder.getAbsoluteAdapterPosition(); - if (mCallback.onMove(mRecyclerView, viewHolder, target)) { - // keep target visible - mCallback.onMoved(mRecyclerView, viewHolder, fromPosition, - target, toPosition, x, y); - } - } - - @Override - public void onChildViewAttachedToWindow(@NonNull View view) { - } - - @Override - public void onChildViewDetachedFromWindow(@NonNull View view) { - removeChildDrawingOrderCallbackIfNecessary(view); - final ViewHolder holder = mRecyclerView.getChildViewHolder(view); - if (holder == null) { - return; - } - if (mSelected != null && holder == mSelected) { - select(null, ACTION_STATE_IDLE); - } else { - endRecoverAnimation(holder, false); // this may push it into pending cleanup list. - if (mPendingCleanup.remove(holder.itemView)) { - mCallback.clearView(mRecyclerView, holder); - } - } - } - - /** - * Returns the animation type or 0 if cannot be found. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void endRecoverAnimation(ViewHolder viewHolder, boolean override) { - final int recoverAnimSize = mRecoverAnimations.size(); - for (int i = recoverAnimSize - 1; i >= 0; i--) { - final RecoverAnimation anim = mRecoverAnimations.get(i); - if (anim.mViewHolder == viewHolder) { - anim.mOverridden |= override; - if (!anim.mEnded) { - anim.cancel(); - } - mRecoverAnimations.remove(i); - return; - } - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, - RecyclerView.State state) { - outRect.setEmpty(); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void obtainVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - } - mVelocityTracker = VelocityTracker.obtain(); - } - - private void releaseVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private ViewHolder findSwipedView(MotionEvent motionEvent) { - final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); - if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { - return null; - } - final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId); - final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX; - final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY; - final float absDx = Math.abs(dx); - final float absDy = Math.abs(dy); - - if (absDx < mSlop && absDy < mSlop) { - return null; - } - if (absDx > absDy && lm.canScrollHorizontally()) { - return null; - } else if (absDy > absDx && lm.canScrollVertically()) { - return null; - } - View child = findChildView(motionEvent); - if (child == null) { - return null; - } - return mRecyclerView.getChildViewHolder(child); - } - - /** - * Checks whether we should select a View for swiping. - */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { - if (mSelected != null || action != MotionEvent.ACTION_MOVE - || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) { - return; - } - if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) { - return; - } - final ViewHolder vh = findSwipedView(motionEvent); - if (vh == null) { - return; - } - final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh); - - final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK) - >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE); - - if (swipeFlags == 0) { - return; - } - - // mDx and mDy are only set in allowed directions. We use custom x/y here instead of - // updateDxDy to avoid swiping if user moves more in the other direction - final float x = motionEvent.getX(pointerIndex); - final float y = motionEvent.getY(pointerIndex); - - // Calculate the distance moved - final float dx = x - mInitialTouchX; - final float dy = y - mInitialTouchY; - // swipe target is chose w/o applying flags so it does not really check if swiping in that - // direction is allowed. This why here, we use mDx mDy to check slope value again. - final float absDx = Math.abs(dx); - final float absDy = Math.abs(dy); - - if (absDx < mSlop && absDy < mSlop) { - return; - } - if (absDx > absDy) { - if (dx < 0 && (swipeFlags & LEFT) == 0) { - return; - } - if (dx > 0 && (swipeFlags & RIGHT) == 0) { - return; - } - } else { - if (dy < 0 && (swipeFlags & UP) == 0) { - return; - } - if (dy > 0 && (swipeFlags & DOWN) == 0) { - return; - } - } - mDx = mDy = 0f; - mActivePointerId = motionEvent.getPointerId(0); - select(vh, ACTION_STATE_SWIPE); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - View findChildView(MotionEvent event) { - // first check elevated views, if none, then call RV - final float x = event.getX(); - final float y = event.getY(); - if (mSelected != null) { - final View selectedView = mSelected.itemView; - if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) { - return selectedView; - } - } - for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { - final RecoverAnimation anim = mRecoverAnimations.get(i); - final View view = anim.mViewHolder.itemView; - if (hitTest(view, x, y, anim.mX, anim.mY)) { - return view; - } - } - return mRecyclerView.findChildViewUnder(x, y); - } - - /** - * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a - * View is long pressed. You can disable that behavior by overriding - * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}. - *

- * For this method to work: - *

    - *
  • The provided ViewHolder must be a child of the RecyclerView to which this - * ItemTouchHelper - * is attached.
  • - *
  • {@link ItemTouchHelper.Callback} must have dragging enabled.
  • - *
  • There must be a previous touch event that was reported to the ItemTouchHelper - * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener - * grabs previous events, this should work as expected.
  • - *
- * - * For example, if you would like to let your user to be able to drag an Item by touching one - * of its descendants, you may implement it as follows: - *
-     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
-     *         public boolean onTouch(View v, MotionEvent event) {
-     *             if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
-     *                 mItemTouchHelper.startDrag(viewHolder);
-     *             }
-     *             return false;
-     *         }
-     *     });
-     * 
- *

- * - * @param viewHolder The ViewHolder to start dragging. It must be a direct child of - * RecyclerView. - * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled() - */ - public void startDrag(@NonNull ViewHolder viewHolder) { - if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) { - Log.e(TAG, "Start drag has been called but dragging is not enabled"); - return; - } - if (viewHolder.itemView.getParent() != mRecyclerView) { - Log.e(TAG, "Start drag has been called with a view holder which is not a child of " - + "the RecyclerView which is controlled by this ItemTouchHelper."); - return; - } - obtainVelocityTracker(); - mDx = mDy = 0f; - select(viewHolder, ACTION_STATE_DRAG); - } - - /** - * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View - * when user swipes their finger (or mouse pointer) over the View. You can disable this - * behavior - * by overriding {@link ItemTouchHelper.Callback} - *

- * For this method to work: - *

    - *
  • The provided ViewHolder must be a child of the RecyclerView to which this - * ItemTouchHelper is attached.
  • - *
  • {@link ItemTouchHelper.Callback} must have swiping enabled.
  • - *
  • There must be a previous touch event that was reported to the ItemTouchHelper - * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener - * grabs previous events, this should work as expected.
  • - *
- * - * For example, if you would like to let your user to be able to swipe an Item by touching one - * of its descendants, you may implement it as follows: - *
-     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
-     *         public boolean onTouch(View v, MotionEvent event) {
-     *             if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
-     *                 mItemTouchHelper.startSwipe(viewHolder);
-     *             }
-     *             return false;
-     *         }
-     *     });
-     * 
- * - * @param viewHolder The ViewHolder to start swiping. It must be a direct child of - * RecyclerView. - */ - public void startSwipe(@NonNull ViewHolder viewHolder) { - if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) { - Log.e(TAG, "Start swipe has been called but swiping is not enabled"); - return; - } - if (viewHolder.itemView.getParent() != mRecyclerView) { - Log.e(TAG, "Start swipe has been called with a view holder which is not a child of " - + "the RecyclerView controlled by this ItemTouchHelper."); - return; - } - obtainVelocityTracker(); - mDx = mDy = 0f; - select(viewHolder, ACTION_STATE_SWIPE); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - RecoverAnimation findAnimation(MotionEvent event) { - if (mRecoverAnimations.isEmpty()) { - return null; - } - View target = findChildView(event); - for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) { - final RecoverAnimation anim = mRecoverAnimations.get(i); - if (anim.mViewHolder.itemView == target) { - return anim; - } - } - return null; - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { - final float x = ev.getX(pointerIndex); - final float y = ev.getY(pointerIndex); - - // Calculate the distance moved - mDx = x - mInitialTouchX; - mDy = y - mInitialTouchY; - if ((directionFlags & LEFT) == 0) { - mDx = Math.max(0, mDx); - } - if ((directionFlags & RIGHT) == 0) { - mDx = Math.min(0, mDx); - } - if ((directionFlags & UP) == 0) { - mDy = Math.max(0, mDy); - } - if ((directionFlags & DOWN) == 0) { - mDy = Math.min(0, mDy); - } - } - - private int swipeIfNecessary(ViewHolder viewHolder) { - if (mActionState == ACTION_STATE_DRAG) { - return 0; - } - final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder); - final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection( - originalMovementFlags, - ViewCompat.getLayoutDirection(mRecyclerView)); - final int flags = (absoluteMovementFlags - & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT); - if (flags == 0) { - return 0; - } - final int originalFlags = (originalMovementFlags - & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT); - int swipeDir; - if (Math.abs(mDx) > Math.abs(mDy)) { - if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) { - // if swipe dir is not in original flags, it should be the relative direction - if ((originalFlags & swipeDir) == 0) { - // convert to relative - return Callback.convertToRelativeDirection(swipeDir, - ViewCompat.getLayoutDirection(mRecyclerView)); - } - return swipeDir; - } - if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) { - return swipeDir; - } - } else { - if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) { - return swipeDir; - } - if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) { - // if swipe dir is not in original flags, it should be the relative direction - if ((originalFlags & swipeDir) == 0) { - // convert to relative - return Callback.convertToRelativeDirection(swipeDir, - ViewCompat.getLayoutDirection(mRecyclerView)); - } - return swipeDir; - } - } - return 0; - } - - private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) { - if ((flags & (LEFT | RIGHT)) != 0) { - final int dirFlag = mDx > 0 ? RIGHT : LEFT; - if (mVelocityTracker != null && mActivePointerId > -1) { - mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, - mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); - final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId); - final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId); - final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT; - final float absXVelocity = Math.abs(xVelocity); - if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag - && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) - && absXVelocity > Math.abs(yVelocity)) { - return velDirFlag; - } - } - - final float threshold = mRecyclerView.getWidth() * mCallback - .getSwipeThreshold(viewHolder); - - if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) { - return dirFlag; - } - } - return 0; - } - - private int checkVerticalSwipe(ViewHolder viewHolder, int flags) { - if ((flags & (UP | DOWN)) != 0) { - final int dirFlag = mDy > 0 ? DOWN : UP; - if (mVelocityTracker != null && mActivePointerId > -1) { - mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, - mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); - final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId); - final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId); - final int velDirFlag = yVelocity > 0f ? DOWN : UP; - final float absYVelocity = Math.abs(yVelocity); - if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag - && absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) - && absYVelocity > Math.abs(xVelocity)) { - return velDirFlag; - } - } - - final float threshold = mRecyclerView.getHeight() * mCallback - .getSwipeThreshold(viewHolder); - if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) { - return dirFlag; - } - } - return 0; - } - - private void addChildDrawingOrderCallback() { - if (Build.VERSION.SDK_INT >= 21) { - return; // we use elevation on Lollipop - } - if (mChildDrawingOrderCallback == null) { - mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() { - @Override - public int onGetChildDrawingOrder(int childCount, int i) { - if (mOverdrawChild == null) { - return i; - } - int childPosition = mOverdrawChildPosition; - if (childPosition == -1) { - childPosition = mRecyclerView.indexOfChild(mOverdrawChild); - mOverdrawChildPosition = childPosition; - } - if (i == childCount - 1) { - return childPosition; - } - return i < childPosition ? i : i + 1; - } - }; - } - mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback); - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - void removeChildDrawingOrderCallbackIfNecessary(View view) { - if (view == mOverdrawChild) { - mOverdrawChild = null; - // only remove if we've added - if (mChildDrawingOrderCallback != null) { - mRecyclerView.setChildDrawingOrderCallback(null); - } - } - } - - /** - * An interface which can be implemented by LayoutManager for better integration with - * {@link ItemTouchHelper}. - */ - public interface ViewDropHandler { - - /** - * Called by the {@link ItemTouchHelper} after a View is dropped over another View. - *

- * A LayoutManager should implement this interface to get ready for the upcoming move - * operation. - *

- * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that - * the View under drag will be used as an anchor View while calculating the next layout, - * making layout stay consistent. - * - * @param view The View which is being dragged. It is very likely that user is still - * dragging this View so there might be other calls to - * {@code prepareForDrop()} after this one. - * @param target The target view which is being dropped on. - * @param x The left offset of the View that is being dragged. This value - * includes the movement caused by the user. - * @param y The top offset of the View that is being dragged. This value - * includes the movement caused by the user. - */ - void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y); - } - - /** - * This class is the contract between ItemTouchHelper and your application. It lets you control - * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user - * performs these actions. - *

- * To control which actions user can take on each view, you should override - * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set - * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END}, - * {@link #UP}, {@link #DOWN}). You can use - * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use - * {@link SimpleCallback}. - *

- * If user drags an item, ItemTouchHelper will call - * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder) - * onMove(recyclerView, dragged, target)}. - * Upon receiving this callback, you should move the item from the old position - * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()}) - * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}. - * To control where a View can be dropped, you can override - * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a - * dragging View overlaps multiple other views, Callback chooses the closest View with which - * dragged View might have changed positions. Although this approach works for many use cases, - * if you have a custom LayoutManager, you can override - * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a - * custom drop target. - *

- * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls - * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your - * adapter (e.g. remove the item) and call related Adapter#notify event. - */ - @SuppressWarnings("UnusedParameters") - public abstract static class Callback { - - @SuppressWarnings("WeakerAccess") - public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; - - @SuppressWarnings("WeakerAccess") - public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; - - static final int RELATIVE_DIR_FLAGS = START | END - | ((START | END) << DIRECTION_FLAG_COUNT) - | ((START | END) << (2 * DIRECTION_FLAG_COUNT)); - - private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT - | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) - | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT)); - - private static final Interpolator sDragScrollInterpolator = new Interpolator() { - @Override - public float getInterpolation(float t) { - return t * t * t * t * t; - } - }; - - private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() { - @Override - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - /** - * Drag scroll speed keeps accelerating until this many milliseconds before being capped. - */ - private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000; - - private int mCachedMaxScrollSpeed = -1; - - /** - * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for - * visual - * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different - * implementations for different platform versions. - *

- * By default, {@link Callback} applies these changes on - * {@link RecyclerView.ViewHolder#itemView}. - *

- * For example, if you have a use case where you only want the text to move when user - * swipes over the view, you can do the following: - *

-         *     public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
-         *         getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
-         *     }
-         *     public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
-         *         if (viewHolder != null){
-         *             getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
-         *         }
-         *     }
-         *     public void onChildDraw(Canvas c, RecyclerView recyclerView,
-         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
-         *             boolean isCurrentlyActive) {
-         *         getDefaultUIUtil().onDraw(c, recyclerView,
-         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
-         *                 actionState, isCurrentlyActive);
-         *         return true;
-         *     }
-         *     public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
-         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
-         *             boolean isCurrentlyActive) {
-         *         getDefaultUIUtil().onDrawOver(c, recyclerView,
-         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
-         *                 actionState, isCurrentlyActive);
-         *         return true;
-         *     }
-         * 
- * - * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback} - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public static ItemTouchUIUtil getDefaultUIUtil() { - return ItemTouchUIUtilImpl.INSTANCE; - } - - /** - * Replaces a movement direction with its relative version by taking layout direction into - * account. - * - * @param flags The flag value that include any number of movement flags. - * @param layoutDirection The layout direction of the View. Can be obtained from - * {@link ViewCompat#getLayoutDirection(android.view.View)}. - * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead - * of {@link #LEFT}, {@link #RIGHT}. - * @see #convertToAbsoluteDirection(int, int) - */ - @SuppressWarnings("WeakerAccess") - public static int convertToRelativeDirection(int flags, int layoutDirection) { - int masked = flags & ABS_HORIZONTAL_DIR_FLAGS; - if (masked == 0) { - return flags; // does not have any abs flags, good. - } - flags &= ~masked; //remove left / right. - if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { - // no change. just OR with 2 bits shifted mask and return - flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT. - return flags; - } else { - // add RIGHT flag as START - flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS); - // first clean RIGHT bit then add LEFT flag as END - flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2; - } - return flags; - } - - /** - * Convenience method to create movement flags. - *

- * For instance, if you want to let your items be drag & dropped vertically and swiped - * left to be dismissed, you can call this method with: - * makeMovementFlags(UP | DOWN, LEFT); - * - * @param dragFlags The directions in which the item can be dragged. - * @param swipeFlags The directions in which the item can be swiped. - * @return Returns an integer composed of the given drag and swipe flags. - */ - public static int makeMovementFlags(int dragFlags, int swipeFlags) { - return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) - | makeFlag(ACTION_STATE_SWIPE, swipeFlags) - | makeFlag(ACTION_STATE_DRAG, dragFlags); - } - - /** - * Shifts the given direction flags to the offset of the given action state. - * - * @param actionState The action state you want to get flags in. Should be one of - * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or - * {@link #ACTION_STATE_DRAG}. - * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN}, - * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}. - * @return And integer that represents the given directions in the provided actionState. - */ - @SuppressWarnings("WeakerAccess") - public static int makeFlag(int actionState, int directions) { - return directions << (actionState * DIRECTION_FLAG_COUNT); - } - - /** - * Should return a composite flag which defines the enabled move directions in each state - * (idle, swiping, dragging). - *

- * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int, - * int)} - * or {@link #makeFlag(int, int)}. - *

- * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next - * 8 bits are for SWIPE state and third 8 bits are for DRAG state. - * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in - * {@link ItemTouchHelper}. - *

- * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to - * swipe by swiping RIGHT, you can return: - *

-         *      makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
-         * 
- * This means, allow right movement while IDLE and allow right and left movement while - * swiping. - * - * @param recyclerView The RecyclerView to which ItemTouchHelper is attached. - * @param viewHolder The ViewHolder for which the movement information is necessary. - * @return flags specifying which movements are allowed on this ViewHolder. - * @see #makeMovementFlags(int, int) - * @see #makeFlag(int, int) - */ - public abstract int getMovementFlags(@NonNull RecyclerView recyclerView, - @NonNull ViewHolder viewHolder); - - /** - * Converts a given set of flags to absolution direction which means {@link #START} and - * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout - * direction. - * - * @param flags The flag value that include any number of movement flags. - * @param layoutDirection The layout direction of the RecyclerView. - * @return Updated flags which includes only absolute direction values. - */ - @SuppressWarnings("WeakerAccess") - public int convertToAbsoluteDirection(int flags, int layoutDirection) { - int masked = flags & RELATIVE_DIR_FLAGS; - if (masked == 0) { - return flags; // does not have any relative flags, good. - } - flags &= ~masked; //remove start / end - if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { - // no change. just OR with 2 bits shifted mask and return - flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT. - return flags; - } else { - // add START flag as RIGHT - flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS); - // first clean start bit then add END flag as LEFT - flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2; - } - return flags; - } - - final int getAbsoluteMovementFlags(RecyclerView recyclerView, - ViewHolder viewHolder) { - final int flags = getMovementFlags(recyclerView, viewHolder); - return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView)); - } - - boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) { - final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); - return (flags & ACTION_MODE_DRAG_MASK) != 0; - } - - boolean hasSwipeFlag(RecyclerView recyclerView, - ViewHolder viewHolder) { - final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); - return (flags & ACTION_MODE_SWIPE_MASK) != 0; - } - - /** - * Return true if the current ViewHolder can be dropped over the the target ViewHolder. - *

- * This method is used when selecting drop target for the dragged View. After Views are - * eliminated either via bounds check or via this method, resulting set of views will be - * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}. - *

- * Default implementation returns true. - * - * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to. - * @param current The ViewHolder that user is dragging. - * @param target The ViewHolder which is below the dragged ViewHolder. - * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false - * otherwise. - */ - @SuppressWarnings("WeakerAccess") - public boolean canDropOver(@NonNull RecyclerView recyclerView, @NonNull ViewHolder current, - @NonNull ViewHolder target) { - return true; - } - - /** - * Called when ItemTouchHelper wants to move the dragged item from its old position to - * the new position. - *

- * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved - * to the adapter position of {@code target} ViewHolder - * ({@link ViewHolder#getAbsoluteAdapterPosition() - * ViewHolder#getAdapterPositionInRecyclerView()}). - *

- * If you don't support drag & drop, this method will never be called. - * - * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to. - * @param viewHolder The ViewHolder which is being dragged by the user. - * @param target The ViewHolder over which the currently active item is being - * dragged. - * @return True if the {@code viewHolder} has been moved to the adapter position of - * {@code target}. - * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int) - */ - public abstract boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull ViewHolder viewHolder, @NonNull ViewHolder target); - - /** - * Returns whether ItemTouchHelper should start a drag and drop operation if an item is - * long pressed. - *

- * Default value returns true but you may want to disable this if you want to start - * dragging on a custom view touch using {@link #startDrag(ViewHolder)}. - * - * @return True if ItemTouchHelper should start dragging an item when it is long pressed, - * false otherwise. Default value is true. - * @see #startDrag(ViewHolder) - */ - public boolean isLongPressDragEnabled() { - return true; - } - - /** - * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped - * over the View. - *

- * Default value returns true but you may want to disable this if you want to start - * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}. - * - * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer - * over the View, false otherwise. Default value is true. - * @see #startSwipe(ViewHolder) - */ - public boolean isItemViewSwipeEnabled() { - return true; - } - - /** - * When finding views under a dragged view, by default, ItemTouchHelper searches for views - * that overlap with the dragged View. By overriding this method, you can extend or shrink - * the search box. - * - * @return The extra margin to be added to the hit box of the dragged View. - */ - @SuppressWarnings("WeakerAccess") - public int getBoundingBoxMargin() { - return 0; - } - - /** - * Returns the fraction that the user should move the View to be considered as swiped. - * The fraction is calculated with respect to RecyclerView's bounds. - *

- * Default value is .5f, which means, to swipe a View, user must move the View at least - * half of RecyclerView's width or height, depending on the swipe direction. - * - * @param viewHolder The ViewHolder that is being dragged. - * @return A float value that denotes the fraction of the View size. Default value - * is .5f . - */ - @SuppressWarnings("WeakerAccess") - public float getSwipeThreshold(@NonNull ViewHolder viewHolder) { - return .5f; - } - - /** - * Returns the fraction that the user should move the View to be considered as it is - * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views - * below it for a possible drop. - * - * @param viewHolder The ViewHolder that is being dragged. - * @return A float value that denotes the fraction of the View size. Default value is - * .5f . - */ - @SuppressWarnings("WeakerAccess") - public float getMoveThreshold(@NonNull ViewHolder viewHolder) { - return .5f; - } - - /** - * Defines the minimum velocity which will be considered as a swipe action by the user. - *

- * You can increase this value to make it harder to swipe or decrease it to make it easier. - * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure - * current direction velocity is larger then the perpendicular one. Otherwise, user's - * movement is ambiguous. You can change the threshold by overriding - * {@link #getSwipeVelocityThreshold(float)}. - *

- * The velocity is calculated in pixels per second. - *

- * The default framework value is passed as a parameter so that you can modify it with a - * multiplier. - * - * @param defaultValue The default value (in pixels per second) used by the - * ItemTouchHelper. - * @return The minimum swipe velocity. The default implementation returns the - * defaultValue parameter. - * @see #getSwipeVelocityThreshold(float) - * @see #getSwipeThreshold(ViewHolder) - */ - @SuppressWarnings("WeakerAccess") - public float getSwipeEscapeVelocity(float defaultValue) { - return defaultValue; - } - - /** - * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements. - *

- * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the - * perpendicular movement. If both directions reach to the max threshold, none of them will - * be considered as a swipe because it is usually an indication that user rather tried to - * scroll then swipe. - *

- * The velocity is calculated in pixels per second. - *

- * You can customize this behavior by changing this method. If you increase the value, it - * will be easier for the user to swipe diagonally and if you decrease the value, user will - * need to make a rather straight finger movement to trigger a swipe. - * - * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper. - * @return The velocity cap for pointer movements. The default implementation returns the - * defaultValue parameter. - * @see #getSwipeEscapeVelocity(float) - */ - @SuppressWarnings("WeakerAccess") - public float getSwipeVelocityThreshold(float defaultValue) { - return defaultValue; - } - - /** - * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that - * are under the dragged View. - *

- * Default implementation filters the View with which dragged item have changed position - * in the drag direction. For instance, if the view is dragged UP, it compares the - * view.getTop() of the two views before and after drag started. If that value - * is different, the target view passes the filter. - *

- * Among these Views which pass the test, the one closest to the dragged view is chosen. - *

- * This method is called on the main thread every time user moves the View. If you want to - * override it, make sure it does not do any expensive operations. - * - * @param selected The ViewHolder being dragged by the user. - * @param dropTargets The list of ViewHolder that are under the dragged View and - * candidate as a drop. - * @param curX The updated left value of the dragged View after drag translations - * are applied. This value does not include margins added by - * {@link RecyclerView.ItemDecoration}s. - * @param curY The updated top value of the dragged View after drag translations - * are applied. This value does not include margins added by - * {@link RecyclerView.ItemDecoration}s. - * @return A ViewHolder to whose position the dragged ViewHolder should be - * moved to. - */ - @SuppressWarnings("WeakerAccess") - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public ViewHolder chooseDropTarget(@NonNull ViewHolder selected, - @NonNull List dropTargets, int curX, int curY) { - int right = curX + selected.itemView.getWidth(); - int bottom = curY + selected.itemView.getHeight(); - ViewHolder winner = null; - int winnerScore = -1; - final int dx = curX - selected.itemView.getLeft(); - final int dy = curY - selected.itemView.getTop(); - final int targetsSize = dropTargets.size(); - for (int i = 0; i < targetsSize; i++) { - final ViewHolder target = dropTargets.get(i); - if (dx > 0) { - int diff = target.itemView.getRight() - right; - if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) { - final int score = Math.abs(diff); - if (score > winnerScore) { - winnerScore = score; - winner = target; - } - } - } - if (dx < 0) { - int diff = target.itemView.getLeft() - curX; - if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) { - final int score = Math.abs(diff); - if (score > winnerScore) { - winnerScore = score; - winner = target; - } - } - } - if (dy < 0) { - int diff = target.itemView.getTop() - curY; - if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) { - final int score = Math.abs(diff); - if (score > winnerScore) { - winnerScore = score; - winner = target; - } - } - } - - if (dy > 0) { - int diff = target.itemView.getBottom() - bottom; - if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) { - final int score = Math.abs(diff); - if (score > winnerScore) { - winnerScore = score; - winner = target; - } - } - } - } - return winner; - } - - /** - * Called when a ViewHolder is swiped by the user. - *

- * If you are returning relative directions ({@link #START} , {@link #END}) from the - * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method - * will also use relative directions. Otherwise, it will use absolute directions. - *

- * If you don't support swiping, this method will never be called. - *

- * ItemTouchHelper will keep a reference to the View until it is detached from - * RecyclerView. - * As soon as it is detached, ItemTouchHelper will call - * {@link #clearView(RecyclerView, ViewHolder)}. - * - * @param viewHolder The ViewHolder which has been swiped by the user. - * @param direction The direction to which the ViewHolder is swiped. It is one of - * {@link #UP}, {@link #DOWN}, - * {@link #LEFT} or {@link #RIGHT}. If your - * {@link #getMovementFlags(RecyclerView, ViewHolder)} - * method - * returned relative flags instead of {@link #LEFT} / {@link #RIGHT}; - * `direction` will be relative as well. ({@link #START} or {@link - * #END}). - */ - public abstract void onSwiped(@NonNull ViewHolder viewHolder, int direction); - - /** - * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed. - *

- * If you override this method, you should call super. - * - * @param viewHolder The new ViewHolder that is being swiped or dragged. Might be null if - * it is cleared. - * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE}, - * {@link ItemTouchHelper#ACTION_STATE_SWIPE} or - * {@link ItemTouchHelper#ACTION_STATE_DRAG}. - * @see #clearView(RecyclerView, RecyclerView.ViewHolder) - */ - public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) { - if (viewHolder != null) { - ItemTouchUIUtilImpl.INSTANCE.onSelected(viewHolder.itemView); - } - } - - private int getMaxDragScroll(RecyclerView recyclerView) { - if (mCachedMaxScrollSpeed == -1) { - mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize( - R.dimen.item_touch_helper_max_drag_scroll_per_frame); - } - return mCachedMaxScrollSpeed; - } - - /** - * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true. - *

- * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it - * modifies the existing View. Because of this reason, it is important that the View is - * still part of the layout after it is moved. This may not work as intended when swapped - * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views - * which were not eligible for dropping over). - *

- * This method is responsible to give necessary hint to the LayoutManager so that it will - * keep the View in visible area. For example, for LinearLayoutManager, this is as simple - * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}. - * - * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's - * new position is likely to be out of bounds. - *

- * It is important to ensure the ViewHolder will stay visible as otherwise, it might be - * removed by the LayoutManager if the move causes the View to go out of bounds. In that - * case, drag will end prematurely. - * - * @param recyclerView The RecyclerView controlled by the ItemTouchHelper. - * @param viewHolder The ViewHolder under user's control. - * @param fromPos The previous adapter position of the dragged item (before it was - * moved). - * @param target The ViewHolder on which the currently active item has been dropped. - * @param toPos The new adapter position of the dragged item. - * @param x The updated left value of the dragged View after drag translations - * are applied. This value does not include margins added by - * {@link RecyclerView.ItemDecoration}s. - * @param y The updated top value of the dragged View after drag translations - * are applied. This value does not include margins added by - * {@link RecyclerView.ItemDecoration}s. - */ - public void onMoved(@NonNull final RecyclerView recyclerView, - @NonNull final ViewHolder viewHolder, int fromPos, @NonNull final ViewHolder target, - int toPos, int x, int y) { - final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); - if (layoutManager instanceof ViewDropHandler) { - ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView, - target.itemView, x, y); - return; - } - - // if layout manager cannot handle it, do some guesswork - if (layoutManager.canScrollHorizontally()) { - final int minLeft = layoutManager.getDecoratedLeft(target.itemView); - if (minLeft <= recyclerView.getPaddingLeft()) { - recyclerView.scrollToPosition(toPos); - } - final int maxRight = layoutManager.getDecoratedRight(target.itemView); - if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) { - recyclerView.scrollToPosition(toPos); - } - } - - if (layoutManager.canScrollVertically()) { - final int minTop = layoutManager.getDecoratedTop(target.itemView); - if (minTop <= recyclerView.getPaddingTop()) { - recyclerView.scrollToPosition(toPos); - } - final int maxBottom = layoutManager.getDecoratedBottom(target.itemView); - if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) { - recyclerView.scrollToPosition(toPos); - } - } - } - - void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, - List recoverAnimationList, - int actionState, float dX, float dY) { - final int recoverAnimSize = recoverAnimationList.size(); - for (int i = 0; i < recoverAnimSize; i++) { - final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); - anim.update(); - final int count = c.save(); - onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, - false); - c.restoreToCount(count); - } - if (selected != null) { - final int count = c.save(); - onChildDraw(c, parent, selected, dX, dY, actionState, true); - c.restoreToCount(count); - } - } - - void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected, - List recoverAnimationList, - int actionState, float dX, float dY) { - final int recoverAnimSize = recoverAnimationList.size(); - for (int i = 0; i < recoverAnimSize; i++) { - final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); - final int count = c.save(); - onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, - false); - c.restoreToCount(count); - } - if (selected != null) { - final int count = c.save(); - onChildDrawOver(c, parent, selected, dX, dY, actionState, true); - c.restoreToCount(count); - } - boolean hasRunningAnimation = false; - for (int i = recoverAnimSize - 1; i >= 0; i--) { - final RecoverAnimation anim = recoverAnimationList.get(i); - if (anim.mEnded && !anim.mIsPendingCleanup) { - recoverAnimationList.remove(i); - } else if (!anim.mEnded) { - hasRunningAnimation = true; - } - } - if (hasRunningAnimation) { - parent.invalidate(); - } - } - - /** - * Called by the ItemTouchHelper when the user interaction with an element is over and it - * also completed its animation. - *

- * This is a good place to clear all changes on the View that was done in - * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)}, - * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, - * boolean)} or - * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}. - * - * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper. - * @param viewHolder The View that was interacted by the user. - */ - public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) { - ItemTouchUIUtilImpl.INSTANCE.clearView(viewHolder.itemView); - } - - /** - * Called by ItemTouchHelper on RecyclerView's onDraw callback. - *

- * If you would like to customize how your View's respond to user interactions, this is - * a good place to override. - *

- * Default implementation translates the child by the given dX, - * dY. - * ItemTouchHelper also takes care of drawing the child after other children if it is being - * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this - * is - * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L - * and after, it changes View's elevation value to be greater than all other children.) - * - * @param c The canvas which RecyclerView is drawing its children - * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to - * @param viewHolder The ViewHolder which is being interacted by the User or it was - * interacted and simply animating to its original position - * @param dX The amount of horizontal displacement caused by user's action - * @param dY The amount of vertical displacement caused by user's action - * @param actionState The type of interaction on the View. Is either {@link - * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}. - * @param isCurrentlyActive True if this view is currently being controlled by the user or - * false it is simply animating back to its original state. - * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, - * boolean) - */ - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, - @NonNull ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - ItemTouchUIUtilImpl.INSTANCE.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, - actionState, isCurrentlyActive); - } - - /** - * Called by ItemTouchHelper on RecyclerView's onDraw callback. - *

- * If you would like to customize how your View's respond to user interactions, this is - * a good place to override. - *

- * Default implementation translates the child by the given dX, - * dY. - * ItemTouchHelper also takes care of drawing the child after other children if it is being - * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this - * is - * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L - * and after, it changes View's elevation value to be greater than all other children.) - * - * @param c The canvas which RecyclerView is drawing its children - * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to - * @param viewHolder The ViewHolder which is being interacted by the User or it was - * interacted and simply animating to its original position - * @param dX The amount of horizontal displacement caused by user's action - * @param dY The amount of vertical displacement caused by user's action - * @param actionState The type of interaction on the View. Is either {@link - * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}. - * @param isCurrentlyActive True if this view is currently being controlled by the user or - * false it is simply animating back to its original state. - * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, - * boolean) - */ - public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) { - ItemTouchUIUtilImpl.INSTANCE.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, - actionState, isCurrentlyActive); - } - - /** - * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View - * will be animated to its final position. - *

- * Default implementation uses ItemAnimator's duration values. If - * animationType is {@link #ANIMATION_TYPE_DRAG}, it returns - * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns - * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have - * any {@link RecyclerView.ItemAnimator} attached, this method returns - * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION} - * depending on the animation type. - * - * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. - * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG}, - * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or - * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}. - * @param animateDx The horizontal distance that the animation will offset - * @param animateDy The vertical distance that the animation will offset - * @return The duration for the animation - */ - @SuppressWarnings("WeakerAccess") - public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, - float animateDx, float animateDy) { - final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator(); - if (itemAnimator == null) { - return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION - : DEFAULT_SWIPE_ANIMATION_DURATION; - } else { - return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration() - : itemAnimator.getRemoveDuration(); - } - } - - /** - * Called by the ItemTouchHelper when user is dragging a view out of bounds. - *

- * You can override this method to decide how much RecyclerView should scroll in response - * to this action. Default implementation calculates a value based on the amount of View - * out of bounds and the time it spent there. The longer user keeps the View out of bounds, - * the faster the list will scroll. Similarly, the larger portion of the View is out of - * bounds, the faster the RecyclerView will scroll. - * - * @param recyclerView The RecyclerView instance to which ItemTouchHelper is - * attached to. - * @param viewSize The total size of the View in scroll direction, excluding - * item decorations. - * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value - * is negative if the View is dragged towards left or top edge. - * @param totalSize The total size of RecyclerView in the scroll direction. - * @param msSinceStartScroll The time passed since View is kept out of bounds. - * @return The amount that RecyclerView should scroll. Keep in mind that this value will - * be passed to {@link RecyclerView#scrollBy(int, int)} method. - */ - @SuppressWarnings("WeakerAccess") - public int interpolateOutOfBoundsScroll(@NonNull RecyclerView recyclerView, - int viewSize, int viewSizeOutOfBounds, - int totalSize, long msSinceStartScroll) { - final int maxScroll = getMaxDragScroll(recyclerView); - final int absOutOfBounds = Math.abs(viewSizeOutOfBounds); - final int direction = (int) Math.signum(viewSizeOutOfBounds); - // might be negative if other direction - float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize); - final int cappedScroll = (int) (direction * maxScroll - * sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio)); - final float timeRatio; - if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) { - timeRatio = 1f; - } else { - timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS; - } - final int value = (int) (cappedScroll * sDragScrollInterpolator - .getInterpolation(timeRatio)); - if (value == 0) { - return viewSizeOutOfBounds > 0 ? 1 : -1; - } - return value; - } - } - - /** - * A simple wrapper to the default Callback which you can construct with drag and swipe - * directions and this class will handle the flag callbacks. You should still override onMove - * or - * onSwiped depending on your use case. - * - *

-     * ItemTouchHelper mIth = new ItemTouchHelper(
-     *     new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
-     *         ItemTouchHelper.LEFT) {
-     *         public boolean onMove(RecyclerView recyclerView,
-     *             ViewHolder viewHolder, ViewHolder target) {
-     *             final int fromPos = viewHolder.getAdapterPosition();
-     *             final int toPos = target.getAdapterPosition();
-     *             // move item in `fromPos` to `toPos` in adapter.
-     *             return true;// true if moved, false otherwise
-     *         }
-     *         public void onSwiped(ViewHolder viewHolder, int direction) {
-     *             // remove from adapter
-     *         }
-     * });
-     * 
- */ - public abstract static class SimpleCallback extends Callback { - - private int mDefaultSwipeDirs; - - private int mDefaultDragDirs; - - /** - * Creates a Callback for the given drag and swipe allowance. These values serve as - * defaults - * and if you want to customize behavior per ViewHolder, you can override - * {@link #getSwipeDirs(RecyclerView, ViewHolder)} - * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}. - * - * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be - * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link - * #END}, - * {@link #UP} and {@link #DOWN}. - * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be - * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link - * #END}, - * {@link #UP} and {@link #DOWN}. - */ - public SimpleCallback(int dragDirs, int swipeDirs) { - mDefaultSwipeDirs = swipeDirs; - mDefaultDragDirs = dragDirs; - } - - /** - * Updates the default swipe directions. For example, you can use this method to toggle - * certain directions depending on your use case. - * - * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped. - */ - @SuppressWarnings({"WeakerAccess", "unused"}) - public void setDefaultSwipeDirs(@SuppressWarnings("unused") int defaultSwipeDirs) { - mDefaultSwipeDirs = defaultSwipeDirs; - } - - /** - * Updates the default drag directions. For example, you can use this method to toggle - * certain directions depending on your use case. - * - * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged. - */ - @SuppressWarnings({"WeakerAccess", "unused"}) - public void setDefaultDragDirs(@SuppressWarnings("unused") int defaultDragDirs) { - mDefaultDragDirs = defaultDragDirs; - } - - /** - * Returns the swipe directions for the provided ViewHolder. - * Default implementation returns the swipe directions that was set via constructor or - * {@link #setDefaultSwipeDirs(int)}. - * - * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. - * @param viewHolder The ViewHolder for which the swipe direction is queried. - * @return A binary OR of direction flags. - */ - @SuppressWarnings("WeakerAccess") - public int getSwipeDirs(@SuppressWarnings("unused") @NonNull RecyclerView recyclerView, - @NonNull @SuppressWarnings("unused") ViewHolder viewHolder) { - return mDefaultSwipeDirs; - } - - /** - * Returns the drag directions for the provided ViewHolder. - * Default implementation returns the drag directions that was set via constructor or - * {@link #setDefaultDragDirs(int)}. - * - * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. - * @param viewHolder The ViewHolder for which the swipe direction is queried. - * @return A binary OR of direction flags. - */ - @SuppressWarnings("WeakerAccess") - public int getDragDirs(@SuppressWarnings("unused") @NonNull RecyclerView recyclerView, - @SuppressWarnings("unused") @NonNull ViewHolder viewHolder) { - return mDefaultDragDirs; - } - - @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, - @NonNull ViewHolder viewHolder) { - return makeMovementFlags(getDragDirs(recyclerView, viewHolder), - getSwipeDirs(recyclerView, viewHolder)); - } - } - - private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { - - /** - * Whether to execute code in response to the the invoking of - * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)}. - * - * It is necessary to control this here because - * {@link GestureDetector.SimpleOnGestureListener} can only be set on a - * {@link GestureDetector} in a GestureDetector's constructor, a GestureDetector will call - * onLongPress if an {@link MotionEvent#ACTION_DOWN} event is not followed by another event - * that would cancel it (like {@link MotionEvent#ACTION_UP} or - * {@link MotionEvent#ACTION_CANCEL}), the long press responding to the long press event - * needs to be cancellable to prevent unexpected behavior. - * - * @see #doNotReactToLongPress() - */ - private boolean mShouldReactToLongPress = true; - - ItemTouchHelperGestureListener() { - } - - /** - * Call to prevent executing code in response to - * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)} being called. - */ - void doNotReactToLongPress() { - mShouldReactToLongPress = false; - } - - @Override - public boolean onDown(MotionEvent e) { - return true; - } - - @Override - public void onLongPress(MotionEvent e) { - if (!mShouldReactToLongPress) { - return; - } - View child = findChildView(e); - if (child != null) { - ViewHolder vh = mRecyclerView.getChildViewHolder(child); - if (vh != null) { - if (!mCallback.hasDragFlag(mRecyclerView, vh)) { - return; - } - int pointerId = e.getPointerId(0); - // Long press is deferred. - // Check w/ active pointer id to avoid selecting after motion - // event is canceled. - if (pointerId == mActivePointerId) { - final int index = e.findPointerIndex(mActivePointerId); - final float x = e.getX(index); - final float y = e.getY(index); - mInitialTouchX = x; - mInitialTouchY = y; - mDx = mDy = 0f; - if (DEBUG) { - Log.d(TAG, - "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY); - } - if (mCallback.isLongPressDragEnabled()) { - select(vh, ACTION_STATE_DRAG); - } - } - } - } - } - } - - @VisibleForTesting - static class RecoverAnimation implements Animator.AnimatorListener { - - final float mStartDx; - - final float mStartDy; - - final float mTargetX; - - final float mTargetY; - - final ViewHolder mViewHolder; - - final int mActionState; - - @VisibleForTesting - final ValueAnimator mValueAnimator; - - final int mAnimationType; - - boolean mIsPendingCleanup; - - float mX; - - float mY; - - // if user starts touching a recovering view, we put it into interaction mode again, - // instantly. - boolean mOverridden = false; - - boolean mEnded = false; - - private float mFraction; - - RecoverAnimation(ViewHolder viewHolder, int animationType, - int actionState, float startDx, float startDy, float targetX, float targetY) { - mActionState = actionState; - mAnimationType = animationType; - mViewHolder = viewHolder; - mStartDx = startDx; - mStartDy = startDy; - mTargetX = targetX; - mTargetY = targetY; - mValueAnimator = ValueAnimator.ofFloat(0f, 1f); - mValueAnimator.addUpdateListener( - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setFraction(animation.getAnimatedFraction()); - } - }); - mValueAnimator.setTarget(viewHolder.itemView); - mValueAnimator.addListener(this); - setFraction(0f); - } - - public void setDuration(long duration) { - mValueAnimator.setDuration(duration); - } - - public void start() { - mViewHolder.setIsRecyclable(false); - mValueAnimator.start(); - } - - public void cancel() { - mValueAnimator.cancel(); - } - - public void setFraction(float fraction) { - mFraction = fraction; - } - - /** - * We run updates on onDraw method but use the fraction from animator callback. - * This way, we can sync translate x/y values w/ the animators to avoid one-off frames. - */ - public void update() { - if (mStartDx == mTargetX) { - mX = mViewHolder.itemView.getTranslationX(); - } else { - mX = mStartDx + mFraction * (mTargetX - mStartDx); - } - if (mStartDy == mTargetY) { - mY = mViewHolder.itemView.getTranslationY(); - } else { - mY = mStartDy + mFraction * (mTargetY - mStartDy); - } - } - - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - if (!mEnded) { - mViewHolder.setIsRecyclable(true); - } - mEnded = true; - } - - @Override - public void onAnimationCancel(Animator animation) { - setFraction(1f); //make sure we recover the view's state. - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtil.java b/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtil.java deleted file mode 100644 index 8f4f8f0616..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtil.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.graphics.Canvas; -import android.view.View; - -/** - * Utility class for {@link ItemTouchHelper} which handles item transformations for different - * API versions. - *

- * This class has methods that map to {@link ItemTouchHelper.Callback}'s drawing methods. Default - * implementations in {@link ItemTouchHelper.Callback} call these methods with - * {@link RecyclerView.ViewHolder#itemView} and {@link ItemTouchUIUtil} makes necessary changes - * on the View depending on the API level. You can access the instance of {@link ItemTouchUIUtil} - * via {@link ItemTouchHelper.Callback#getDefaultUIUtil()} and call its methods with the children - * of ViewHolder that you want to apply default effects. - * - * @see ItemTouchHelper.Callback#getDefaultUIUtil() - */ -public interface ItemTouchUIUtil { - - /** - * The default implementation for {@link ItemTouchHelper.Callback#onChildDraw(Canvas, - * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)} - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void onDraw(Canvas c, RecyclerView recyclerView, View view, - float dX, float dY, int actionState, boolean isCurrentlyActive); - - /** - * The default implementation for {@link ItemTouchHelper.Callback#onChildDrawOver(Canvas, - * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)} - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void onDrawOver(Canvas c, RecyclerView recyclerView, View view, - float dX, float dY, int actionState, boolean isCurrentlyActive); - - /** - * The default implementation for {@link ItemTouchHelper.Callback#clearView(RecyclerView, - * RecyclerView.ViewHolder)} - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void clearView(View view); - - /** - * The default implementation for {@link ItemTouchHelper.Callback#onSelectedChanged( - * RecyclerView.ViewHolder, int)} - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void onSelected(View view); -} - diff --git a/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java b/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java deleted file mode 100644 index c592e3e526..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.graphics.Canvas; -import android.os.Build; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.R; - -/** - * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them - * public API, which is not desired in this case. - */ -class ItemTouchUIUtilImpl implements ItemTouchUIUtil { - static final ItemTouchUIUtil INSTANCE = new ItemTouchUIUtilImpl(); - - @Override - public void onDraw( - @NonNull Canvas c, - @NonNull RecyclerView recyclerView, - @NonNull View view, - float dX, - float dY, - int actionState, - boolean isCurrentlyActive - ) { - if (Build.VERSION.SDK_INT >= 21) { - if (isCurrentlyActive) { - Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation); - if (originalElevation == null) { - originalElevation = ViewCompat.getElevation(view); - float newElevation = 1f + findMaxElevation(recyclerView, view); - ViewCompat.setElevation(view, newElevation); - view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation); - } - } - } - - view.setTranslationX(dX); - view.setTranslationY(dY); - } - - private static float findMaxElevation(RecyclerView recyclerView, View itemView) { - final int childCount = recyclerView.getChildCount(); - float max = 0; - for (int i = 0; i < childCount; i++) { - final View child = recyclerView.getChildAt(i); - if (child == itemView) { - continue; - } - final float elevation = ViewCompat.getElevation(child); - if (elevation > max) { - max = elevation; - } - } - return max; - } - - @Override - public void onDrawOver( - @NonNull Canvas c, - @NonNull RecyclerView recyclerView, - @NonNull View view, - float dX, - float dY, - int actionState, - boolean isCurrentlyActive - ) { - } - - @Override - public void clearView(@NonNull View view) { - if (Build.VERSION.SDK_INT >= 21) { - final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation); - if (tag instanceof Float) { - ViewCompat.setElevation(view, (Float) tag); - } - view.setTag(R.id.item_touch_helper_previous_elevation, null); - } - - view.setTranslationX(0f); - view.setTranslationY(0f); - } - - @Override - public void onSelected(@NonNull View view) { - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/LayoutState.java b/app/src/main/java/androidx/recyclerview/widget/LayoutState.java deleted file mode 100644 index 8805c1cc93..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/LayoutState.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.view.View; - -/** - * Helper class that keeps temporary state while {LayoutManager} is filling out the empty - * space. - */ -class LayoutState { - - static final int LAYOUT_START = -1; - - static final int LAYOUT_END = 1; - - static final int INVALID_LAYOUT = Integer.MIN_VALUE; - - static final int ITEM_DIRECTION_HEAD = -1; - - static final int ITEM_DIRECTION_TAIL = 1; - - /** - * We may not want to recycle children in some cases (e.g. layout) - */ - boolean mRecycle = true; - - /** - * Number of pixels that we should fill, in the layout direction. - */ - int mAvailable; - - /** - * Current position on the adapter to get the next item. - */ - int mCurrentPosition; - - /** - * Defines the direction in which the data adapter is traversed. - * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL} - */ - int mItemDirection; - - /** - * Defines the direction in which the layout is filled. - * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END} - */ - int mLayoutDirection; - - /** - * This is the target pixel closest to the start of the layout that we are trying to fill - */ - int mStartLine = 0; - - /** - * This is the target pixel closest to the end of the layout that we are trying to fill - */ - int mEndLine = 0; - - /** - * If true, layout should stop if a focusable view is added - */ - boolean mStopInFocusable; - - /** - * If the content is not wrapped with any value - */ - boolean mInfinite; - - /** - * @return true if there are more items in the data adapter - */ - boolean hasMore(RecyclerView.State state) { - return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); - } - - /** - * Gets the view for the next element that we should render. - * Also updates current item index to the next item, based on {@link #mItemDirection} - * - * @return The next element that we should render. - */ - View next(RecyclerView.Recycler recycler) { - final View view = recycler.getViewForPosition(mCurrentPosition); - mCurrentPosition += mItemDirection; - return view; - } - - @Override - public String toString() { - return "LayoutState{" - + "mAvailable=" + mAvailable - + ", mCurrentPosition=" + mCurrentPosition - + ", mItemDirection=" + mItemDirection - + ", mLayoutDirection=" + mLayoutDirection - + ", mStartLine=" + mStartLine - + ", mEndLine=" + mEndLine - + '}'; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java b/app/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java deleted file mode 100644 index 22fd533731..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java +++ /dev/null @@ -1,2624 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.PointF; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; -import androidx.core.os.TraceCompat; -import androidx.core.view.ViewCompat; - -import java.util.List; - -/** - * A {@link RecyclerView.LayoutManager} implementation which provides - * similar functionality to {@link android.widget.ListView}. - */ -public class LinearLayoutManager extends RecyclerView.LayoutManager implements - ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider { - - private static final String TAG = "LinearLayoutManager"; - - static final boolean DEBUG = false; - - public static final int HORIZONTAL = RecyclerView.HORIZONTAL; - - public static final int VERTICAL = RecyclerView.VERTICAL; - - public static final int INVALID_OFFSET = Integer.MIN_VALUE; - - - /** - * While trying to find next view to focus, LayoutManager will not try to scroll more - * than this factor times the total space of the list. If layout is vertical, total space is the - * height minus padding, if layout is horizontal, total space is the width minus padding. - */ - private static final float MAX_SCROLL_FACTOR = 1 / 3f; - - /** - * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} - */ - @RecyclerView.Orientation - int mOrientation = RecyclerView.DEFAULT_ORIENTATION; - - /** - * Helper class that keeps temporary layout state. - * It does not keep state after layout is complete but we still keep a reference to re-use - * the same object. - */ - private LayoutState mLayoutState; - - /** - * Many calculations are made depending on orientation. To keep it clean, this interface - * helps {@link LinearLayoutManager} make those decisions. - */ - OrientationHelper mOrientationHelper; - - /** - * We need to track this so that we can ignore current position when it changes. - */ - private boolean mLastStackFromEnd; - - - /** - * Defines if layout should be calculated from end to start. - * - * @see #mShouldReverseLayout - */ - private boolean mReverseLayout = false; - - /** - * This keeps the final value for how LayoutManager should start laying out views. - * It is calculated by checking {@link #getReverseLayout()} and View's layout direction. - * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run. - */ - boolean mShouldReverseLayout = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and - * it supports both orientations. - * see {@link android.widget.AbsListView#setStackFromBottom(boolean)} - */ - private boolean mStackFromEnd = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. - * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} - */ - private boolean mSmoothScrollbarEnabled = true; - - /** - * When LayoutManager needs to scroll to a position, it sets this variable and requests a - * layout which will check this variable and re-layout accordingly. - */ - int mPendingScrollPosition = RecyclerView.NO_POSITION; - - /** - * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is - * called. - */ - int mPendingScrollPositionOffset = INVALID_OFFSET; - - private boolean mRecycleChildrenOnDetach; - - SavedState mPendingSavedState = null; - - /** - * Re-used variable to keep anchor information on re-layout. - * Anchor position and coordinate defines the reference point for LLM while doing a layout. - */ - final AnchorInfo mAnchorInfo = new AnchorInfo(); - - /** - * Stashed to avoid allocation, currently only used in #fill() - */ - private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult(); - - /** - * Number of items to prefetch when first coming on screen with new data. - */ - private int mInitialPrefetchItemCount = 2; - - // Reusable int array to be passed to method calls that mutate it in order to "return" two ints. - // This should only be used used transiently and should not be used to retain any state over - // time. - private int[] mReusableIntPair = new int[2]; - - /** - * Creates a vertical LinearLayoutManager - * - * @param context Current context, will be used to access resources. - */ - public LinearLayoutManager( - // Suppressed because fixing it requires a source-incompatible change to a very - // commonly used constructor, for no benefit: the context parameter is unused - @SuppressLint("UnknownNullness") Context context - ) { - this(context, RecyclerView.DEFAULT_ORIENTATION, false); - } - - /** - * @param context Current context, will be used to access resources. - * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link - * #VERTICAL}. - * @param reverseLayout When set to true, layouts from end to start. - */ - public LinearLayoutManager( - // Suppressed because fixing it requires a source-incompatible change to a very - // commonly used constructor, for no benefit: the context parameter is unused - @SuppressLint("UnknownNullness") Context context, - @RecyclerView.Orientation int orientation, - boolean reverseLayout - ) { - setOrientation(orientation); - setReverseLayout(reverseLayout); - } - - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". Defaults to vertical orientation. - * - * {@link android.R.attr#orientation} - * {@link androidx.recyclerview.R.attr#reverseLayout} - * {@link androidx.recyclerview.R.attr#stackFromEnd} - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setOrientation(properties.orientation); - setReverseLayout(properties.reverseLayout); - setStackFromEnd(properties.stackFromEnd); - } - - @Override - public boolean isAutoMeasureEnabled() { - return true; - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - - /** - * Returns whether LayoutManager will recycle its children when it is detached from - * RecyclerView. - * - * @return true if LayoutManager will recycle its children when it is detached from - * RecyclerView. - */ - public boolean getRecycleChildrenOnDetach() { - return mRecycleChildrenOnDetach; - } - - /** - * Set whether LayoutManager will recycle its children when it is detached from - * RecyclerView. - *

- * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set - * this flag to true so that views will be available to other RecyclerViews - * immediately. - *

- * Note that, setting this flag will result in a performance drop if RecyclerView - * is restored. - * - * @param recycleChildrenOnDetach Whether children should be recycled in detach or not. - */ - public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) { - mRecycleChildrenOnDetach = recycleChildrenOnDetach; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { - super.onDetachedFromWindow(view, recycler); - if (mRecycleChildrenOnDetach) { - removeAndRecycleAllViews(recycler); - recycler.clear(); - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (getChildCount() > 0) { - event.setFromIndex(findFirstVisibleItemPosition()); - event.setToIndex(findLastVisibleItemPosition()); - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public Parcelable onSaveInstanceState() { - if (mPendingSavedState != null) { - return new SavedState(mPendingSavedState); - } - SavedState state = new SavedState(); - if (getChildCount() > 0) { - ensureLayoutState(); - boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout; - state.mAnchorLayoutFromEnd = didLayoutFromEnd; - if (didLayoutFromEnd) { - final View refChild = getChildClosestToEnd(); - state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(refChild); - state.mAnchorPosition = getPosition(refChild); - } else { - final View refChild = getChildClosestToStart(); - state.mAnchorPosition = getPosition(refChild); - state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) - - mOrientationHelper.getStartAfterPadding(); - } - } else { - state.invalidateAnchor(); - } - return state; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mPendingSavedState = (SavedState) state; - if (mPendingScrollPosition != RecyclerView.NO_POSITION) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - if (DEBUG) { - Log.d(TAG, "loaded saved state"); - } - } else if (DEBUG) { - Log.d(TAG, "invalid saved state class"); - } - } - - /** - * @return true if {@link #getOrientation()} is {@link #HORIZONTAL} - */ - @Override - public boolean canScrollHorizontally() { - return mOrientation == HORIZONTAL; - } - - /** - * @return true if {@link #getOrientation()} is {@link #VERTICAL} - */ - @Override - public boolean canScrollVertically() { - return mOrientation == VERTICAL; - } - - /** - * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)} - */ - public void setStackFromEnd(boolean stackFromEnd) { - assertNotInLayoutOrScroll(null); - if (mStackFromEnd == stackFromEnd) { - return; - } - mStackFromEnd = stackFromEnd; - requestLayout(); - } - - public boolean getStackFromEnd() { - return mStackFromEnd; - } - - /** - * Returns the current orientation of the layout. - * - * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL} - * @see #setOrientation(int) - */ - @RecyclerView.Orientation - public int getOrientation() { - return mOrientation; - } - - /** - * Sets the orientation of the layout. {@link LinearLayoutManager} - * will do its best to keep scroll position. - * - * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} - */ - public void setOrientation(@RecyclerView.Orientation int orientation) { - if (orientation != HORIZONTAL && orientation != VERTICAL) { - throw new IllegalArgumentException("invalid orientation:" + orientation); - } - - assertNotInLayoutOrScroll(null); - - if (orientation != mOrientation || mOrientationHelper == null) { - mOrientationHelper = - OrientationHelper.createOrientationHelper(this, orientation); - mAnchorInfo.mOrientationHelper = mOrientationHelper; - mOrientation = orientation; - requestLayout(); - } - } - - /** - * Calculates the view layout order. (e.g. from end to start or start to end) - * RTL layout support is applied automatically. So if layout is RTL and - * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. - */ - private void resolveShouldLayoutReverse() { - // A == B is the same result, but we rather keep it readable - if (mOrientation == VERTICAL || !isLayoutRTL()) { - mShouldReverseLayout = mReverseLayout; - } else { - mShouldReverseLayout = !mReverseLayout; - } - } - - /** - * Returns if views are laid out from the opposite direction of the layout. - * - * @return If layout is reversed or not. - * @see #setReverseLayout(boolean) - */ - public boolean getReverseLayout() { - return mReverseLayout; - } - - /** - * Used to reverse item traversal and layout order. - * This behaves similar to the layout change for RTL views. When set to true, first item is - * laid out at the end of the UI, second item is laid out before it etc. - * - * For horizontal layouts, it depends on the layout direction. - * When set to true, If {@link RecyclerView} is LTR, than it will - * layout from RTL, if {@link RecyclerView}} is RTL, it will layout - * from LTR. - * - * If you are looking for the exact same behavior of - * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use - * {@link #setStackFromEnd(boolean)} - */ - public void setReverseLayout(boolean reverseLayout) { - assertNotInLayoutOrScroll(null); - if (reverseLayout == mReverseLayout) { - return; - } - mReverseLayout = reverseLayout; - requestLayout(); - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public View findViewByPosition(int position) { - final int childCount = getChildCount(); - if (childCount == 0) { - return null; - } - final int firstChild = getPosition(getChildAt(0)); - final int viewPosition = position - firstChild; - if (viewPosition >= 0 && viewPosition < childCount) { - final View child = getChildAt(viewPosition); - if (getPosition(child) == position) { - return child; // in pre-layout, this may not match - } - } - // fallback to traversal. This might be necessary in pre-layout. - return super.findViewByPosition(position); - } - - /** - *

Returns the amount of extra space that should be laid out by LayoutManager.

- * - *

By default, {@link LinearLayoutManager} lays out 1 extra page - * of items while smooth scrolling and 0 otherwise. You can override this method to implement - * your custom layout pre-cache logic.

- * - *

Note:Laying out invisible elements generally comes with significant - * performance cost. It's typically only desirable in places like smooth scrolling to an unknown - * location, where 1) the extra content helps LinearLayoutManager know in advance when its - * target is approaching, so it can decelerate early and smoothly and 2) while motion is - * continuous.

- * - *

Extending the extra layout space is especially expensive if done while the user may change - * scrolling direction. Changing direction will cause the extra layout space to swap to the - * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large - * enough to handle it.

- * - * @return The extra space that should be laid out (in pixels). - * @deprecated Use {@link #calculateExtraLayoutSpace(RecyclerView.State, int[])} instead. - */ - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated - protected int getExtraLayoutSpace(RecyclerView.State state) { - if (state.hasTargetScrollPosition()) { - return mOrientationHelper.getTotalSpace(); - } else { - return 0; - } - } - - /** - *

Calculates the amount of extra space (in pixels) that should be laid out by {@link - * LinearLayoutManager} and stores the result in {@code extraLayoutSpace}. {@code - * extraLayoutSpace[0]} should be used for the extra space at the top/left, and {@code - * extraLayoutSpace[1]} should be used for the extra space at the bottom/right (depending on the - * orientation). Thus, the side where it is applied is unaffected by {@link - * #getLayoutDirection()} (LTR vs RTL), {@link #getStackFromEnd()} and {@link - * #getReverseLayout()}. Negative values are ignored.

- * - *

By default, {@code LinearLayoutManager} lays out 1 extra page of items while smooth - * scrolling, in the direction of the scroll, and no extra space is laid out in all other - * situations. You can override this method to implement your own custom pre-cache logic. Use - * {@link RecyclerView.State#hasTargetScrollPosition()} to find out if a smooth scroll to a - * position is in progress, and {@link RecyclerView.State#getTargetScrollPosition()} to find out - * which item it is scrolling to.

- * - *

Note:Laying out extra items generally comes with significant performance - * cost. It's typically only desirable in places like smooth scrolling to an unknown location, - * where 1) the extra content helps LinearLayoutManager know in advance when its target is - * approaching, so it can decelerate early and smoothly and 2) while motion is continuous.

- * - *

Extending the extra layout space is especially expensive if done while the user may change - * scrolling direction. In the default implementation, changing direction will cause the extra - * layout space to swap to the opposite side of the viewport, incurring many rebinds/recycles, - * unless the cache is large enough to handle it.

- */ - protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state, - @NonNull int[] extraLayoutSpace) { - int extraLayoutSpaceStart = 0; - int extraLayoutSpaceEnd = 0; - - // If calculateExtraLayoutSpace is not overridden, call the - // deprecated getExtraLayoutSpace for backwards compatibility - @SuppressWarnings("deprecation") - int extraScrollSpace = getExtraLayoutSpace(state); - if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - extraLayoutSpaceStart = extraScrollSpace; - } else { - extraLayoutSpaceEnd = extraScrollSpace; - } - - extraLayoutSpace[0] = extraLayoutSpaceStart; - extraLayoutSpace[1] = extraLayoutSpaceEnd; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(recyclerView.getContext()); - linearSmoothScroller.setTargetPosition(position); - startSmoothScroll(linearSmoothScroller); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public PointF computeScrollVectorForPosition(int targetPosition) { - if (getChildCount() == 0) { - return null; - } - final int firstChildPos = getPosition(getChildAt(0)); - final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1; - if (mOrientation == HORIZONTAL) { - return new PointF(direction, 0); - } else { - return new PointF(0, direction); - } - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - // layout algorithm: - // 1) by checking children and other variables, find an anchor coordinate and an anchor - // item position. - // 2) fill towards start, stacking from bottom - // 3) fill towards end, stacking from top - // 4) scroll to fulfill requirements like stack from bottom. - // create layout state - if (DEBUG) { - Log.d(TAG, "is pre layout:" + state.isPreLayout()); - } - if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) { - if (state.getItemCount() == 0) { - removeAndRecycleAllViews(recycler); - return; - } - } - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - } - - ensureLayoutState(); - mLayoutState.mRecycle = false; - // resolve layout direction - resolveShouldLayoutReverse(); - - final View focused = getFocusedChild(); - if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION - || mPendingSavedState != null) { - mAnchorInfo.reset(); - mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; - // calculate anchor position and coordinate - updateAnchorInfoForLayout(recycler, state, mAnchorInfo); - mAnchorInfo.mValid = true; - } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused) - >= mOrientationHelper.getEndAfterPadding() - || mOrientationHelper.getDecoratedEnd(focused) - <= mOrientationHelper.getStartAfterPadding())) { - // This case relates to when the anchor child is the focused view and due to layout - // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows - // up after tapping an EditText which shrinks RV causing the focused view (The tapped - // EditText which is the anchor child) to get kicked out of the screen. Will update the - // anchor coordinate in order to make sure that the focused view is laid out. Otherwise, - // the available space in layoutState will be calculated as negative preventing the - // focused view from being laid out in fill. - // Note that we won't update the anchor position between layout passes (refer to - // TestResizingRelayoutWithAutoMeasure), which happens if we were to call - // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference - // child which can change between layout passes). - mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); - } - if (DEBUG) { - Log.d(TAG, "Anchor info:" + mAnchorInfo); - } - - // LLM may decide to layout items for "extra" pixels to account for scrolling target, - // caching or predictive animations. - - mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0 - ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - calculateExtraLayoutSpace(state, mReusableIntPair); - int extraForStart = Math.max(0, mReusableIntPair[0]) - + mOrientationHelper.getStartAfterPadding(); - int extraForEnd = Math.max(0, mReusableIntPair[1]) - + mOrientationHelper.getEndPadding(); - if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION - && mPendingScrollPositionOffset != INVALID_OFFSET) { - // if the child is visible and we are going to move it around, we should layout - // extra items in the opposite direction to make sure new items animate nicely - // instead of just fading in - final View existing = findViewByPosition(mPendingScrollPosition); - if (existing != null) { - final int current; - final int upcomingOffset; - if (mShouldReverseLayout) { - current = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(existing); - upcomingOffset = current - mPendingScrollPositionOffset; - } else { - current = mOrientationHelper.getDecoratedStart(existing) - - mOrientationHelper.getStartAfterPadding(); - upcomingOffset = mPendingScrollPositionOffset - current; - } - if (upcomingOffset > 0) { - extraForStart += upcomingOffset; - } else { - extraForEnd -= upcomingOffset; - } - } - } - int startOffset; - int endOffset; - final int firstLayoutDirection; - if (mAnchorInfo.mLayoutFromEnd) { - firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL - : LayoutState.ITEM_DIRECTION_HEAD; - } else { - firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD - : LayoutState.ITEM_DIRECTION_TAIL; - } - - onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); - detachAndScrapAttachedViews(recycler); - mLayoutState.mInfinite = resolveIsInfinite(); - mLayoutState.mIsPreLayout = state.isPreLayout(); - // noRecycleSpace not needed: recycling doesn't happen in below's fill - // invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN - mLayoutState.mNoRecycleSpace = 0; - if (mAnchorInfo.mLayoutFromEnd) { - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtraFillSpace = extraForStart; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - final int firstElement = mLayoutState.mCurrentPosition; - if (mLayoutState.mAvailable > 0) { - extraForEnd += mLayoutState.mAvailable; - } - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtraFillSpace = extraForEnd; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - - if (mLayoutState.mAvailable > 0) { - // end could not consume all. add more items towards start - extraForStart = mLayoutState.mAvailable; - updateLayoutStateToFillStart(firstElement, startOffset); - mLayoutState.mExtraFillSpace = extraForStart; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - } - } else { - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtraFillSpace = extraForEnd; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - final int lastElement = mLayoutState.mCurrentPosition; - if (mLayoutState.mAvailable > 0) { - extraForStart += mLayoutState.mAvailable; - } - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtraFillSpace = extraForStart; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - - if (mLayoutState.mAvailable > 0) { - extraForEnd = mLayoutState.mAvailable; - // start could not consume all it should. add more items towards end - updateLayoutStateToFillEnd(lastElement, endOffset); - mLayoutState.mExtraFillSpace = extraForEnd; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - } - } - - // changes may cause gaps on the UI, try to fix them. - // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have - // changed - if (getChildCount() > 0) { - // because layout from end may be changed by scroll to position - // we re-calculate it. - // find which side we should check for gaps. - if (mShouldReverseLayout ^ mStackFromEnd) { - int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } else { - int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } - } - layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); - if (!state.isPreLayout()) { - mOrientationHelper.onLayoutComplete(); - } else { - mAnchorInfo.reset(); - } - mLastStackFromEnd = mStackFromEnd; - if (DEBUG) { - validateChildOrder(); - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onLayoutCompleted(RecyclerView.State state) { - super.onLayoutCompleted(state); - mPendingSavedState = null; // we don't need this anymore - mPendingScrollPosition = RecyclerView.NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - mAnchorInfo.reset(); - } - - /** - * Method called when Anchor position is decided. Extending class can setup accordingly or - * even update anchor info if necessary. - * - * @param recycler The recycler for the layout - * @param state The layout state - * @param anchorInfo The mutable POJO that keeps the position and offset. - * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter - * indices. - */ - void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo, int firstLayoutItemDirection) { - } - - /** - * If necessary, layouts new items for predictive animations - */ - private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler, - RecyclerView.State state, int startOffset, - int endOffset) { - // If there are scrap children that we did not layout, we need to find where they did go - // and layout them accordingly so that animations can work as expected. - // This case may happen if new views are added or an existing view expands and pushes - // another view out of bounds. - if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout() - || !supportsPredictiveItemAnimations()) { - return; - } - // to make the logic simpler, we calculate the size of children and call fill. - int scrapExtraStart = 0, scrapExtraEnd = 0; - final List scrapList = recycler.getScrapList(); - final int scrapSize = scrapList.size(); - final int firstChildPos = getPosition(getChildAt(0)); - for (int i = 0; i < scrapSize; i++) { - RecyclerView.ViewHolder scrap = scrapList.get(i); - if (scrap.isRemoved()) { - continue; - } - final int position = scrap.getLayoutPosition(); - final int direction = position < firstChildPos != mShouldReverseLayout - ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; - if (direction == LayoutState.LAYOUT_START) { - scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); - } else { - scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView); - } - } - - if (DEBUG) { - Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart - + " towards start and " + scrapExtraEnd + " towards end"); - } - mLayoutState.mScrapList = scrapList; - if (scrapExtraStart > 0) { - View anchor = getChildClosestToStart(); - updateLayoutStateToFillStart(getPosition(anchor), startOffset); - mLayoutState.mExtraFillSpace = scrapExtraStart; - mLayoutState.mAvailable = 0; - mLayoutState.assignPositionFromScrapList(); - fill(recycler, mLayoutState, state, false); - } - - if (scrapExtraEnd > 0) { - View anchor = getChildClosestToEnd(); - updateLayoutStateToFillEnd(getPosition(anchor), endOffset); - mLayoutState.mExtraFillSpace = scrapExtraEnd; - mLayoutState.mAvailable = 0; - mLayoutState.assignPositionFromScrapList(); - fill(recycler, mLayoutState, state, false); - } - mLayoutState.mScrapList = null; - } - - private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state, - AnchorInfo anchorInfo) { - if (updateAnchorFromPendingData(state, anchorInfo)) { - if (DEBUG) { - Log.d(TAG, "updated anchor info from pending information"); - } - return; - } - - if (updateAnchorFromChildren(recycler, state, anchorInfo)) { - if (DEBUG) { - Log.d(TAG, "updated anchor info from existing children"); - } - return; - } - if (DEBUG) { - Log.d(TAG, "deciding anchor info for fresh state"); - } - anchorInfo.assignCoordinateFromPadding(); - anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0; - } - - /** - * Finds an anchor child from existing Views. Most of the time, this is the view closest to - * start or end that has a valid position (e.g. not removed). - *

- * If a child has focus, it is given priority. - */ - private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler, - RecyclerView.State state, AnchorInfo anchorInfo) { - if (getChildCount() == 0) { - return false; - } - final View focused = getFocusedChild(); - if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) { - anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); - return true; - } - if (mLastStackFromEnd != mStackFromEnd) { - return false; - } - View referenceChild = - findReferenceChild( - recycler, - state, - anchorInfo.mLayoutFromEnd, - mStackFromEnd); - if (referenceChild != null) { - anchorInfo.assignFromView(referenceChild, getPosition(referenceChild)); - // If all visible views are removed in 1 pass, reference child might be out of bounds. - // If that is the case, offset it back to 0 so that we use these pre-layout children. - if (!state.isPreLayout() && supportsPredictiveItemAnimations()) { - // validate this child is at least partially visible. if not, offset it to start - final int childStart = mOrientationHelper.getDecoratedStart(referenceChild); - final int childEnd = mOrientationHelper.getDecoratedEnd(referenceChild); - final int boundsStart = mOrientationHelper.getStartAfterPadding(); - final int boundsEnd = mOrientationHelper.getEndAfterPadding(); - // b/148869110: usually if childStart >= boundsEnd the child is out of - // bounds, except if the child is 0 pixels! - boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart; - boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd; - if (outOfBoundsBefore || outOfBoundsAfter) { - anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd ? boundsEnd : boundsStart; - } - } - return true; - } - return false; - } - - /** - * If there is a pending scroll position or saved states, updates the anchor info from that - * data and returns true - */ - private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { - if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) { - return false; - } - // validate scroll position - if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { - mPendingScrollPosition = RecyclerView.NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - if (DEBUG) { - Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition); - } - return false; - } - - // if child is visible, try to make it a reference child and ensure it is fully visible. - // if child is not visible, align it depending on its virtual position. - anchorInfo.mPosition = mPendingScrollPosition; - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - // Anchor offset depends on how that child was laid out. Here, we update it - // according to our current view bounds - anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; - if (anchorInfo.mLayoutFromEnd) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - - mPendingSavedState.mAnchorOffset; - } else { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() - + mPendingSavedState.mAnchorOffset; - } - return true; - } - - if (mPendingScrollPositionOffset == INVALID_OFFSET) { - View child = findViewByPosition(mPendingScrollPosition); - if (child != null) { - final int childSize = mOrientationHelper.getDecoratedMeasurement(child); - if (childSize > mOrientationHelper.getTotalSpace()) { - // item does not fit. fix depending on layout direction - anchorInfo.assignCoordinateFromPadding(); - return true; - } - final int startGap = mOrientationHelper.getDecoratedStart(child) - - mOrientationHelper.getStartAfterPadding(); - if (startGap < 0) { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding(); - anchorInfo.mLayoutFromEnd = false; - return true; - } - final int endGap = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(child); - if (endGap < 0) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding(); - anchorInfo.mLayoutFromEnd = true; - return true; - } - anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd - ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper - .getTotalSpaceChange()) - : mOrientationHelper.getDecoratedStart(child); - } else { // item is not visible. - if (getChildCount() > 0) { - // get position of any child, does not matter - int pos = getPosition(getChildAt(0)); - anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos - == mShouldReverseLayout; - } - anchorInfo.assignCoordinateFromPadding(); - } - return true; - } - // override layout from end values for consistency - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - // if this changes, we should update prepareForDrop as well - if (mShouldReverseLayout) { - anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() - - mPendingScrollPositionOffset; - } else { - anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() - + mPendingScrollPositionOffset; - } - return true; - } - - /** - * @return The final offset amount for children - */ - private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler, - RecyclerView.State state, boolean canOffsetChildren) { - int gap = mOrientationHelper.getEndAfterPadding() - endOffset; - int fixOffset = 0; - if (gap > 0) { - fixOffset = -scrollBy(-gap, recycler, state); - } else { - return 0; // nothing to fix - } - // move offset according to scroll amount - endOffset += fixOffset; - if (canOffsetChildren) { - // re-calculate gap, see if we could fix it - gap = mOrientationHelper.getEndAfterPadding() - endOffset; - if (gap > 0) { - mOrientationHelper.offsetChildren(gap); - return gap + fixOffset; - } - } - return fixOffset; - } - - /** - * @return The final offset amount for children - */ - private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler, - RecyclerView.State state, boolean canOffsetChildren) { - int gap = startOffset - mOrientationHelper.getStartAfterPadding(); - int fixOffset = 0; - if (gap > 0) { - // check if we should fix this gap. - fixOffset = -scrollBy(gap, recycler, state); - } else { - return 0; // nothing to fix - } - startOffset += fixOffset; - if (canOffsetChildren) { - // re-calculate gap, see if we could fix it - gap = startOffset - mOrientationHelper.getStartAfterPadding(); - if (gap > 0) { - mOrientationHelper.offsetChildren(-gap); - return fixOffset - gap; - } - } - return fixOffset; - } - - private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) { - updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); - } - - private void updateLayoutStateToFillEnd(int itemPosition, int offset) { - mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset; - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : - LayoutState.ITEM_DIRECTION_TAIL; - mLayoutState.mCurrentPosition = itemPosition; - mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; - mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; - } - - private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) { - updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate); - } - - private void updateLayoutStateToFillStart(int itemPosition, int offset) { - mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding(); - mLayoutState.mCurrentPosition = itemPosition; - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : - LayoutState.ITEM_DIRECTION_HEAD; - mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START; - mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; - - } - - protected boolean isLayoutRTL() { - return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - void ensureLayoutState() { - if (mLayoutState == null) { - mLayoutState = createLayoutState(); - } - } - - /** - * Test overrides this to plug some tracking and verification. - * - * @return A new LayoutState - */ - LayoutState createLayoutState() { - return new LayoutState(); - } - - /** - *

Scroll the RecyclerView to make the position visible.

- * - *

RecyclerView will scroll the minimum amount that is necessary to make the - * target position visible. If you are looking for a similar behavior to - * {@link android.widget.ListView#setSelection(int)} or - * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use - * {@link #scrollToPositionWithOffset(int, int)}.

- * - *

Note that scroll position change will not be reflected until the next layout call.

- * - * @param position Scroll to this adapter position - * @see #scrollToPositionWithOffset(int, int) - */ - @Override - public void scrollToPosition(int position) { - mPendingScrollPosition = position; - mPendingScrollPositionOffset = INVALID_OFFSET; - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - } - - /** - * Scroll to the specified adapter position with the given offset from resolved layout - * start. Resolved layout start depends on {@link #getReverseLayout()}, - * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}. - *

- * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling - * scrollToPositionWithOffset(10, 20) will layout such that - * item[10]'s bottom is 20 pixels above the RecyclerView's bottom. - *

- * Note that scroll position change will not be reflected until the next layout call. - *

- * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. - * - * @param position Index (starting at 0) of the reference item. - * @param offset The distance (in pixels) between the start edge of the item view and - * start edge of the RecyclerView. - * @see #setReverseLayout(boolean) - * @see #scrollToPosition(int) - */ - public void scrollToPositionWithOffset(int position, int offset) { - mPendingScrollPosition = position; - mPendingScrollPositionOffset = offset; - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - } - - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return 0; - } - return scrollBy(dx, recycler, state); - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == HORIZONTAL) { - return 0; - } - return scrollBy(dy, recycler, state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeHorizontalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeVerticalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeHorizontalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeVerticalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeHorizontalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int computeVerticalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private int computeScrollOffset(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper, - findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), - findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), - this, mSmoothScrollbarEnabled, mShouldReverseLayout); - } - - private int computeScrollExtent(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper, - findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), - findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), - this, mSmoothScrollbarEnabled); - } - - private int computeScrollRange(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - ensureLayoutState(); - return ScrollbarHelper.computeScrollRange(state, mOrientationHelper, - findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true), - findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true), - this, mSmoothScrollbarEnabled); - } - - /** - * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed - * based on the number of visible pixels in the visible items. This however assumes that all - * list items have similar or equal widths or heights (depending on list orientation). - * If you use a list in which items have different dimensions, the scrollbar will change - * appearance as the user scrolls through the list. To avoid this issue, you need to disable - * this property. - * - * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based - * solely on the number of items in the adapter and the position of the visible items inside - * the adapter. This provides a stable scrollbar as the user navigates through a list of items - * with varying widths / heights. - * - * @param enabled Whether or not to enable smooth scrollbar. - * @see #setSmoothScrollbarEnabled(boolean) - */ - public void setSmoothScrollbarEnabled(boolean enabled) { - mSmoothScrollbarEnabled = enabled; - } - - /** - * Returns the current state of the smooth scrollbar feature. It is enabled by default. - * - * @return True if smooth scrollbar is enabled, false otherwise. - * @see #setSmoothScrollbarEnabled(boolean) - */ - public boolean isSmoothScrollbarEnabled() { - return mSmoothScrollbarEnabled; - } - - private void updateLayoutState(int layoutDirection, int requiredSpace, - boolean canUseExistingSpace, RecyclerView.State state) { - // If parent provides a hint, don't measure unlimited. - mLayoutState.mInfinite = resolveIsInfinite(); - mLayoutState.mLayoutDirection = layoutDirection; - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - calculateExtraLayoutSpace(state, mReusableIntPair); - int extraForStart = Math.max(0, mReusableIntPair[0]); - int extraForEnd = Math.max(0, mReusableIntPair[1]); - boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END; - mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart; - mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd; - int scrollingOffset; - if (layoutToEnd) { - mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding(); - // get the first child in the direction we are going - final View child = getChildClosestToEnd(); - // the direction in which we are traversing children - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD - : LayoutState.ITEM_DIRECTION_TAIL; - mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; - mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child); - // calculate how much we can scroll without adding new children (independent of layout) - scrollingOffset = mOrientationHelper.getDecoratedEnd(child) - - mOrientationHelper.getEndAfterPadding(); - - } else { - final View child = getChildClosestToStart(); - mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding(); - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL - : LayoutState.ITEM_DIRECTION_HEAD; - mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; - mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); - scrollingOffset = -mOrientationHelper.getDecoratedStart(child) - + mOrientationHelper.getStartAfterPadding(); - } - mLayoutState.mAvailable = requiredSpace; - if (canUseExistingSpace) { - mLayoutState.mAvailable -= scrollingOffset; - } - mLayoutState.mScrollingOffset = scrollingOffset; - } - - boolean resolveIsInfinite() { - return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED - && mOrientationHelper.getEnd() == 0; - } - - void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - final int pos = layoutState.mCurrentPosition; - if (pos >= 0 && pos < state.getItemCount()) { - layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset)); - } - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void collectInitialPrefetchPositions(int adapterItemCount, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - final boolean fromEnd; - final int anchorPos; - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - // use restored state, since it hasn't been resolved yet - fromEnd = mPendingSavedState.mAnchorLayoutFromEnd; - anchorPos = mPendingSavedState.mAnchorPosition; - } else { - resolveShouldLayoutReverse(); - fromEnd = mShouldReverseLayout; - if (mPendingScrollPosition == RecyclerView.NO_POSITION) { - anchorPos = fromEnd ? adapterItemCount - 1 : 0; - } else { - anchorPos = mPendingScrollPosition; - } - } - - final int direction = fromEnd - ? LayoutState.ITEM_DIRECTION_HEAD - : LayoutState.ITEM_DIRECTION_TAIL; - int targetPos = anchorPos; - for (int i = 0; i < mInitialPrefetchItemCount; i++) { - if (targetPos >= 0 && targetPos < adapterItemCount) { - layoutPrefetchRegistry.addPosition(targetPos, 0); - } else { - break; // no more to prefetch - } - targetPos += direction; - } - } - - /** - * Sets the number of items to prefetch in - * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines - * how many inner items should be prefetched when this LayoutManager's RecyclerView - * is nested inside another RecyclerView. - * - *

Set this value to the number of items this inner LayoutManager will display when it is - * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items - * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.

- * - *

For example, take a vertically scrolling RecyclerView with horizontally scrolling inner - * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing - * 4 to this method for each inner RecyclerView's LinearLayoutManager will enable - * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early, - * before it is scrolled on screen, instead of just the default 2.

- * - *

Calling this method does nothing unless the LayoutManager is in a RecyclerView - * nested in another RecyclerView.

- * - *

Note: Setting this value to be larger than the number of - * views that will be visible in this view can incur unnecessary bind work, and an increase to - * the number of Views created and in active use.

- * - * @param itemCount Number of items to prefetch - * @see #isItemPrefetchEnabled() - * @see #getInitialPrefetchItemCount() - * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) - */ - public void setInitialPrefetchItemCount(int itemCount) { - mInitialPrefetchItemCount = itemCount; - } - - /** - * Gets the number of items to prefetch in - * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines - * how many inner items should be prefetched when this LayoutManager's RecyclerView - * is nested inside another RecyclerView. - * - * @return number of items to prefetch. - * @see #isItemPrefetchEnabled() - * @see #setInitialPrefetchItemCount(int) - * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) - */ - public int getInitialPrefetchItemCount() { - return mInitialPrefetchItemCount; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - int delta = (mOrientation == HORIZONTAL) ? dx : dy; - if (getChildCount() == 0 || delta == 0) { - // can't support this scroll, so don't bother prefetching - return; - } - - ensureLayoutState(); - final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - final int absDelta = Math.abs(delta); - updateLayoutState(layoutDirection, absDelta, true, state); - collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry); - } - - int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getChildCount() == 0 || delta == 0) { - return 0; - } - ensureLayoutState(); - mLayoutState.mRecycle = true; - final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - final int absDelta = Math.abs(delta); - updateLayoutState(layoutDirection, absDelta, true, state); - final int consumed = mLayoutState.mScrollingOffset - + fill(recycler, mLayoutState, state, false); - if (consumed < 0) { - if (DEBUG) { - Log.d(TAG, "Don't have any more elements to scroll"); - } - return 0; - } - final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta; - mOrientationHelper.offsetChildren(-scrolled); - if (DEBUG) { - Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled); - } - mLayoutState.mLastScrollDelta = scrolled; - return scrolled; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void assertNotInLayoutOrScroll(String message) { - if (mPendingSavedState == null) { - super.assertNotInLayoutOrScroll(message); - } - } - - /** - * Recycles children between given indices. - * - * @param startIndex inclusive - * @param endIndex exclusive - */ - private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) { - if (startIndex == endIndex) { - return; - } - if (DEBUG) { - Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items"); - } - if (endIndex > startIndex) { - for (int i = endIndex - 1; i >= startIndex; i--) { - removeAndRecycleViewAt(i, recycler); - } - } else { - for (int i = startIndex; i > endIndex; i--) { - removeAndRecycleViewAt(i, recycler); - } - } - } - - /** - * Recycles views that went out of bounds after scrolling towards the end of the layout. - *

- * Checks both layout position and visible position to guarantee that the view is not visible. - * - * @param recycler Recycler instance of {@link RecyclerView} - * @param scrollingOffset This can be used to add additional padding to the visible area. This - * is used to detect children that will go out of bounds after scrolling, - * without actually moving them. - * @param noRecycleSpace Extra space that should be excluded from recycling. This is the space - * from {@code extraLayoutSpace[0]}, calculated in {@link - * #calculateExtraLayoutSpace}. - */ - private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset, - int noRecycleSpace) { - if (scrollingOffset < 0) { - if (DEBUG) { - Log.d(TAG, "Called recycle from start with a negative value. This might happen" - + " during layout changes but may be sign of a bug"); - } - return; - } - // ignore padding, ViewGroup may not clip children. - final int limit = scrollingOffset - noRecycleSpace; - final int childCount = getChildCount(); - if (mShouldReverseLayout) { - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit - || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { - // stop here - recycleChildren(recycler, childCount - 1, i); - return; - } - } - } else { - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit - || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { - // stop here - recycleChildren(recycler, 0, i); - return; - } - } - } - } - - - /** - * Recycles views that went out of bounds after scrolling towards the start of the layout. - *

- * Checks both layout position and visible position to guarantee that the view is not visible. - * - * @param recycler Recycler instance of {@link RecyclerView} - * @param scrollingOffset This can be used to add additional padding to the visible area. This - * is used to detect children that will go out of bounds after scrolling, - * without actually moving them. - * @param noRecycleSpace Extra space that should be excluded from recycling. This is the space - * from {@code extraLayoutSpace[1]}, calculated in {@link - * #calculateExtraLayoutSpace}. - */ - private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset, - int noRecycleSpace) { - final int childCount = getChildCount(); - if (scrollingOffset < 0) { - if (DEBUG) { - Log.d(TAG, "Called recycle from end with a negative value. This might happen" - + " during layout changes but may be sign of a bug"); - } - return; - } - final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace; - if (mShouldReverseLayout) { - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit - || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { - // stop here - recycleChildren(recycler, 0, i); - return; - } - } - } else { - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit - || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { - // stop here - recycleChildren(recycler, childCount - 1, i); - return; - } - } - } - } - - /** - * Helper method to call appropriate recycle method depending on current layout direction - * - * @param recycler Current recycler that is attached to RecyclerView - * @param layoutState Current layout state. Right now, this object does not change but - * we may consider moving it out of this view so passing around as a - * parameter for now, rather than accessing {@link #mLayoutState} - * @see #recycleViewsFromStart(RecyclerView.Recycler, int, int) - * @see #recycleViewsFromEnd(RecyclerView.Recycler, int, int) - * @see LinearLayoutManager.LayoutState#mLayoutDirection - */ - private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { - if (!layoutState.mRecycle || layoutState.mInfinite) { - return; - } - int scrollingOffset = layoutState.mScrollingOffset; - int noRecycleSpace = layoutState.mNoRecycleSpace; - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace); - } else { - recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace); - } - } - - /** - * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly - * independent from the rest of the {@link LinearLayoutManager} - * and with little change, can be made publicly available as a helper class. - * - * @param recycler Current recycler that is attached to RecyclerView - * @param layoutState Configuration on how we should fill out the available space. - * @param state Context passed by the RecyclerView to control scroll steps. - * @param stopOnFocusable If true, filling stops in the first focusable new child - * @return Number of pixels that it added. Useful for scroll functions. - */ - int fill(RecyclerView.Recycler recycler, LayoutState layoutState, - RecyclerView.State state, boolean stopOnFocusable) { - // max offset we should set is mFastScroll + available - final int start = layoutState.mAvailable; - if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { - // TODO ugly bug fix. should not happen - if (layoutState.mAvailable < 0) { - layoutState.mScrollingOffset += layoutState.mAvailable; - } - recycleByLayoutState(recycler, layoutState); - } - int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; - LayoutChunkResult layoutChunkResult = mLayoutChunkResult; - while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { - layoutChunkResult.resetInternal(); - if (RecyclerView.VERBOSE_TRACING) { - TraceCompat.beginSection("LLM LayoutChunk"); - } - layoutChunk(recycler, state, layoutState, layoutChunkResult); - if (RecyclerView.VERBOSE_TRACING) { - TraceCompat.endSection(); - } - if (layoutChunkResult.mFinished) { - break; - } - layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; - /** - * Consume the available space if: - * * layoutChunk did not request to be ignored - * * OR we are laying out scrap children - * * OR we are not doing pre-layout - */ - if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null - || !state.isPreLayout()) { - layoutState.mAvailable -= layoutChunkResult.mConsumed; - // we keep a separate remaining space because mAvailable is important for recycling - remainingSpace -= layoutChunkResult.mConsumed; - } - - if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { - layoutState.mScrollingOffset += layoutChunkResult.mConsumed; - if (layoutState.mAvailable < 0) { - layoutState.mScrollingOffset += layoutState.mAvailable; - } - recycleByLayoutState(recycler, layoutState); - } - if (stopOnFocusable && layoutChunkResult.mFocusable) { - break; - } - } - if (DEBUG) { - validateChildOrder(); - } - return start - layoutState.mAvailable; - } - - void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, - LayoutState layoutState, LayoutChunkResult result) { - View view = layoutState.next(recycler); - if (view == null) { - if (DEBUG && layoutState.mScrapList == null) { - throw new RuntimeException("received null view when unexpected"); - } - // if we are laying out views in scrap, this may return null which means there is - // no more items to layout. - result.mFinished = true; - return; - } - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); - if (layoutState.mScrapList == null) { - if (mShouldReverseLayout == (layoutState.mLayoutDirection - == LayoutState.LAYOUT_START)) { - addView(view); - } else { - addView(view, 0); - } - } else { - if (mShouldReverseLayout == (layoutState.mLayoutDirection - == LayoutState.LAYOUT_START)) { - addDisappearingView(view); - } else { - addDisappearingView(view, 0); - } - } - measureChildWithMargins(view, 0, 0); - result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); - int left, top, right, bottom; - if (mOrientation == VERTICAL) { - if (isLayoutRTL()) { - right = getWidth() - getPaddingRight(); - left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); - } else { - left = getPaddingLeft(); - right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); - } - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - bottom = layoutState.mOffset; - top = layoutState.mOffset - result.mConsumed; - } else { - top = layoutState.mOffset; - bottom = layoutState.mOffset + result.mConsumed; - } - } else { - top = getPaddingTop(); - bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); - - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - right = layoutState.mOffset; - left = layoutState.mOffset - result.mConsumed; - } else { - left = layoutState.mOffset; - right = layoutState.mOffset + result.mConsumed; - } - } - // We calculate everything with View's bounding box (which includes decor and margins) - // To calculate correct layout position, we subtract margins. - layoutDecoratedWithMargins(view, left, top, right, bottom); - if (DEBUG) { - Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" - + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" - + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); - } - // Consume the available space if the view is not removed OR changed - if (params.isItemRemoved() || params.isItemChanged()) { - result.mIgnoreConsumed = true; - } - result.mFocusable = view.hasFocusable(); - } - - @Override - boolean shouldMeasureTwice() { - return getHeightMode() != View.MeasureSpec.EXACTLY - && getWidthMode() != View.MeasureSpec.EXACTLY - && hasFlexibleChildInBothOrientations(); - } - - /** - * Converts a focusDirection to orientation. - * - * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * or 0 for not applicable - * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction - * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. - */ - int convertFocusDirectionToLayoutDirection(int focusDirection) { - switch (focusDirection) { - case View.FOCUS_BACKWARD: - if (mOrientation == VERTICAL) { - return LayoutState.LAYOUT_START; - } else if (isLayoutRTL()) { - return LayoutState.LAYOUT_END; - } else { - return LayoutState.LAYOUT_START; - } - case View.FOCUS_FORWARD: - if (mOrientation == VERTICAL) { - return LayoutState.LAYOUT_END; - } else if (isLayoutRTL()) { - return LayoutState.LAYOUT_START; - } else { - return LayoutState.LAYOUT_END; - } - case View.FOCUS_UP: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_DOWN: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_LEFT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_RIGHT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - default: - if (DEBUG) { - Log.d(TAG, "Unknown focus request:" + focusDirection); - } - return LayoutState.INVALID_LAYOUT; - } - - } - - /** - * Convenience method to find the child closes to start. Caller should check it has enough - * children. - * - * @return The child closes to start of the layout from user's perspective. - */ - private View getChildClosestToStart() { - return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0); - } - - /** - * Convenience method to find the child closes to end. Caller should check it has enough - * children. - * - * @return The child closes to end of the layout from user's perspective. - */ - private View getChildClosestToEnd() { - return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1); - } - - /** - * Convenience method to find the visible child closes to start. Caller should check if it has - * enough children. - * - * @param completelyVisible Whether child should be completely visible or not - * @return The first visible child closest to start of the layout from user's perspective. - */ - View findFirstVisibleChildClosestToStart(boolean completelyVisible, - boolean acceptPartiallyVisible) { - if (mShouldReverseLayout) { - return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible, - acceptPartiallyVisible); - } else { - return findOneVisibleChild(0, getChildCount(), completelyVisible, - acceptPartiallyVisible); - } - } - - /** - * Convenience method to find the visible child closes to end. Caller should check if it has - * enough children. - * - * @param completelyVisible Whether child should be completely visible or not - * @return The first visible child closest to end of the layout from user's perspective. - */ - View findFirstVisibleChildClosestToEnd(boolean completelyVisible, - boolean acceptPartiallyVisible) { - if (mShouldReverseLayout) { - return findOneVisibleChild(0, getChildCount(), completelyVisible, - acceptPartiallyVisible); - } else { - return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible, - acceptPartiallyVisible); - } - } - - // overridden by GridLayoutManager - - /** - * Finds a suitable anchor child. - *

- * Due to ambiguous adapter updates or children being removed, some children's positions may be - * invalid. This method is a best effort to find a position within adapter bounds if possible. - *

- * It also prioritizes children from best to worst in this order: - *

    - *
  1. An in bounds child. - *
  2. An out of bounds child. - *
  3. An invalid child. - *
- * - * @param layoutFromEnd True if the RV scrolls in the reverse direction, which is the same as - * (reverseLayout ^ stackFromEnd). - * @param traverseChildrenInReverseOrder True if the children should be traversed in reverse - * order (stackFromEnd). - * @return A View that can be used an an anchor View. - */ - View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) { - ensureLayoutState(); - - // Determine which direction through the view children we are going iterate. - int start = 0; - int end = getChildCount(); - int diff = 1; - if (traverseChildrenInReverseOrder) { - start = getChildCount() - 1; - end = -1; - diff = -1; - } - - int itemCount = state.getItemCount(); - - final int boundsStart = mOrientationHelper.getStartAfterPadding(); - final int boundsEnd = mOrientationHelper.getEndAfterPadding(); - - View invalidMatch = null; - View bestFirstFind = null; - View bestSecondFind = null; - - for (int i = start; i != end; i += diff) { - final View view = getChildAt(i); - final int position = getPosition(view); - final int childStart = mOrientationHelper.getDecoratedStart(view); - final int childEnd = mOrientationHelper.getDecoratedEnd(view); - if (position >= 0 && position < itemCount) { - if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) { - if (invalidMatch == null) { - invalidMatch = view; // removed item, least preferred - } - } else { - // b/148869110: usually if childStart >= boundsEnd the child is out of - // bounds, except if the child is 0 pixels! - boolean outOfBoundsBefore = childEnd <= boundsStart && childStart < boundsStart; - boolean outOfBoundsAfter = childStart >= boundsEnd && childEnd > boundsEnd; - if (outOfBoundsBefore || outOfBoundsAfter) { - // The item is out of bounds. - // We want to find the items closest to the in bounds items and because we - // are always going through the items linearly, the 2 items we want are the - // last out of bounds item on the side we start searching on, and the first - // out of bounds item on the side we are ending on. The side that we are - // ending on ultimately takes priority because we want items later in the - // layout to move forward if no in bounds anchors are found. - if (layoutFromEnd) { - if (outOfBoundsAfter) { - bestFirstFind = view; - } else if (bestSecondFind == null) { - bestSecondFind = view; - } - } else { - if (outOfBoundsBefore) { - bestFirstFind = view; - } else if (bestSecondFind == null) { - bestSecondFind = view; - } - } - } else { - // We found an in bounds item, greedily return it. - return view; - } - } - } - } - // We didn't find an in bounds item so we will settle for an item in this order: - // 1. bestSecondFind - // 2. bestFirstFind - // 3. invalidMatch - return bestSecondFind != null ? bestSecondFind : - (bestFirstFind != null ? bestFirstFind : invalidMatch); - } - - // returns the out-of-bound child view closest to RV's end bounds. An out-of-bound child is - // defined as a child that's either partially or fully invisible (outside RV's padding area). - private View findPartiallyOrCompletelyInvisibleChildClosestToEnd() { - return mShouldReverseLayout ? findFirstPartiallyOrCompletelyInvisibleChild() - : findLastPartiallyOrCompletelyInvisibleChild(); - } - - // returns the out-of-bound child view closest to RV's starting bounds. An out-of-bound child is - // defined as a child that's either partially or fully invisible (outside RV's padding area). - private View findPartiallyOrCompletelyInvisibleChildClosestToStart() { - return mShouldReverseLayout ? findLastPartiallyOrCompletelyInvisibleChild() : - findFirstPartiallyOrCompletelyInvisibleChild(); - } - - private View findFirstPartiallyOrCompletelyInvisibleChild() { - return findOnePartiallyOrCompletelyInvisibleChild(0, getChildCount()); - } - - private View findLastPartiallyOrCompletelyInvisibleChild() { - return findOnePartiallyOrCompletelyInvisibleChild(getChildCount() - 1, -1); - } - - /** - * Returns the adapter position of the first visible view. This position does not include - * adapter changes that were dispatched after the last layout pass. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * LayoutManager may pre-cache some views that are not necessarily visible. Those views - * are ignored in this method. - * - * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if - * there aren't any visible items. - * @see #findFirstCompletelyVisibleItemPosition() - * @see #findLastVisibleItemPosition() - */ - public int findFirstVisibleItemPosition() { - final View child = findOneVisibleChild(0, getChildCount(), false, true); - return child == null ? RecyclerView.NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the first fully visible view. This position does not include - * adapter changes that were dispatched after the last layout pass. - *

- * Note that bounds check is only performed in the current orientation. That means, if - * LayoutManager is horizontal, it will only check the view's left and right edges. - * - * @return The adapter position of the first fully visible item or - * {@link RecyclerView#NO_POSITION} if there aren't any visible items. - * @see #findFirstVisibleItemPosition() - * @see #findLastCompletelyVisibleItemPosition() - */ - public int findFirstCompletelyVisibleItemPosition() { - final View child = findOneVisibleChild(0, getChildCount(), true, false); - return child == null ? RecyclerView.NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last visible view. This position does not include - * adapter changes that were dispatched after the last layout pass. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * LayoutManager may pre-cache some views that are not necessarily visible. Those views - * are ignored in this method. - * - * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if - * there aren't any visible items. - * @see #findLastCompletelyVisibleItemPosition() - * @see #findFirstVisibleItemPosition() - */ - public int findLastVisibleItemPosition() { - final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true); - return child == null ? RecyclerView.NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last fully visible view. This position does not include - * adapter changes that were dispatched after the last layout pass. - *

- * Note that bounds check is only performed in the current orientation. That means, if - * LayoutManager is horizontal, it will only check the view's left and right edges. - * - * @return The adapter position of the last fully visible view or - * {@link RecyclerView#NO_POSITION} if there aren't any visible items. - * @see #findLastVisibleItemPosition() - * @see #findFirstCompletelyVisibleItemPosition() - */ - public int findLastCompletelyVisibleItemPosition() { - final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false); - return child == null ? RecyclerView.NO_POSITION : getPosition(child); - } - - // Returns the first child that is visible in the provided index range, i.e. either partially or - // fully visible depending on the arguments provided. Completely invisible children are not - // acceptable by this method, but could be returned - // using #findOnePartiallyOrCompletelyInvisibleChild - View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, - boolean acceptPartiallyVisible) { - ensureLayoutState(); - @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0; - @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; - if (completelyVisible) { - preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS - | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); - } else { - preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE - | ViewBoundsCheck.FLAG_CVE_GT_PVS); - } - if (acceptPartiallyVisible) { - acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE - | ViewBoundsCheck.FLAG_CVE_GT_PVS); - } - return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, - acceptableBoundsFlag) : mVerticalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, - acceptableBoundsFlag); - } - - View findOnePartiallyOrCompletelyInvisibleChild(int fromIndex, int toIndex) { - ensureLayoutState(); - final int next = toIndex > fromIndex ? 1 : (toIndex < fromIndex ? -1 : 0); - if (next == 0) { - return getChildAt(fromIndex); - } - @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0; - @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0; - if (mOrientationHelper.getDecoratedStart(getChildAt(fromIndex)) - < mOrientationHelper.getStartAfterPadding()) { - preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE - | ViewBoundsCheck.FLAG_CVE_GT_PVS); - acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS - | ViewBoundsCheck.FLAG_CVE_LT_PVE); - } else { - preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE | ViewBoundsCheck.FLAG_CVS_GT_PVS - | ViewBoundsCheck.FLAG_CVS_LT_PVE); - acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE - | ViewBoundsCheck.FLAG_CVS_GT_PVS); - } - return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, - acceptableBoundsFlag) : mVerticalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, - acceptableBoundsFlag); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public View onFocusSearchFailed(View focused, int direction, - RecyclerView.Recycler recycler, RecyclerView.State state) { - resolveShouldLayoutReverse(); - if (getChildCount() == 0) { - return null; - } - - final int layoutDir = convertFocusDirectionToLayoutDirection(direction); - if (layoutDir == LayoutState.INVALID_LAYOUT) { - return null; - } - ensureLayoutState(); - final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); - updateLayoutState(layoutDir, maxScroll, false, state); - mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; - mLayoutState.mRecycle = false; - fill(recycler, mLayoutState, state, true); - - // nextCandidate is the first child view in the layout direction that's partially - // within RV's bounds, i.e. part of it is visible or it's completely invisible but still - // touching RV's bounds. This will be the unfocusable candidate view to become visible onto - // the screen if no focusable views are found in the given layout direction. - final View nextCandidate; - if (layoutDir == LayoutState.LAYOUT_START) { - nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToStart(); - } else { - nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToEnd(); - } - // nextFocus is meaningful only if it refers to a focusable child, in which case it - // indicates the next view to gain focus. - final View nextFocus; - if (layoutDir == LayoutState.LAYOUT_START) { - nextFocus = getChildClosestToStart(); - } else { - nextFocus = getChildClosestToEnd(); - } - if (nextFocus.hasFocusable()) { - if (nextCandidate == null) { - return null; - } - return nextFocus; - } - return nextCandidate; - } - - /** - * Used for debugging. - * Logs the internal representation of children to default logger. - */ - private void logChildren() { - Log.d(TAG, "internal representation of views on the screen"); - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - Log.d(TAG, "item " + getPosition(child) + ", coord:" - + mOrientationHelper.getDecoratedStart(child)); - } - Log.d(TAG, "=============="); - } - - /** - * Used for debugging. - * Validates that child views are laid out in correct order. This is important because rest of - * the algorithm relies on this constraint. - * - * In default layout, child 0 should be closest to screen position 0 and last child should be - * closest to position WIDTH or HEIGHT. - * In reverse layout, last child should be closes to screen position 0 and first child should - * be closest to position WIDTH or HEIGHT - */ - void validateChildOrder() { - Log.d(TAG, "validating child count " + getChildCount()); - if (getChildCount() < 1) { - return; - } - int lastPos = getPosition(getChildAt(0)); - int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0)); - if (mShouldReverseLayout) { - for (int i = 1; i < getChildCount(); i++) { - View child = getChildAt(i); - int pos = getPosition(child); - int screenLoc = mOrientationHelper.getDecoratedStart(child); - if (pos < lastPos) { - logChildren(); - throw new RuntimeException("detected invalid position. loc invalid? " - + (screenLoc < lastScreenLoc)); - } - if (screenLoc > lastScreenLoc) { - logChildren(); - throw new RuntimeException("detected invalid location"); - } - } - } else { - for (int i = 1; i < getChildCount(); i++) { - View child = getChildAt(i); - int pos = getPosition(child); - int screenLoc = mOrientationHelper.getDecoratedStart(child); - if (pos < lastPos) { - logChildren(); - throw new RuntimeException("detected invalid position. loc invalid? " - + (screenLoc < lastScreenLoc)); - } - if (screenLoc < lastScreenLoc) { - logChildren(); - throw new RuntimeException("detected invalid location"); - } - } - } - } - - @Override - public boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd; - } - - /** - * {@inheritDoc} - */ - // This method is only intended to be called (and should only ever be called) by - // ItemTouchHelper. - @Override - public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) { - assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation"); - ensureLayoutState(); - resolveShouldLayoutReverse(); - final int myPos = getPosition(view); - final int targetPos = getPosition(target); - final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL - : LayoutState.ITEM_DIRECTION_HEAD; - if (mShouldReverseLayout) { - if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) { - scrollToPositionWithOffset(targetPos, - mOrientationHelper.getEndAfterPadding() - - (mOrientationHelper.getDecoratedStart(target) - + mOrientationHelper.getDecoratedMeasurement(view))); - } else { - scrollToPositionWithOffset(targetPos, - mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(target)); - } - } else { - if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) { - scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target)); - } else { - scrollToPositionWithOffset(targetPos, - mOrientationHelper.getDecoratedEnd(target) - - mOrientationHelper.getDecoratedMeasurement(view)); - } - } - } - - /** - * Helper class that keeps temporary state while {LayoutManager} is filling out the empty - * space. - */ - static class LayoutState { - - static final String TAG = "LLM#LayoutState"; - - static final int LAYOUT_START = -1; - - static final int LAYOUT_END = 1; - - static final int INVALID_LAYOUT = Integer.MIN_VALUE; - - static final int ITEM_DIRECTION_HEAD = -1; - - static final int ITEM_DIRECTION_TAIL = 1; - - static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE; - - /** - * We may not want to recycle children in some cases (e.g. layout) - */ - boolean mRecycle = true; - - /** - * Pixel offset where layout should start - */ - int mOffset; - - /** - * Number of pixels that we should fill, in the layout direction. - */ - int mAvailable; - - /** - * Current position on the adapter to get the next item. - */ - int mCurrentPosition; - - /** - * Defines the direction in which the data adapter is traversed. - * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL} - */ - int mItemDirection; - - /** - * Defines the direction in which the layout is filled. - * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END} - */ - int mLayoutDirection; - - /** - * Used when LayoutState is constructed in a scrolling state. - * It should be set the amount of scrolling we can make without creating a new view. - * Settings this is required for efficient view recycling. - */ - int mScrollingOffset; - - /** - * Used if you want to pre-layout items that are not yet visible. - * The difference with {@link #mAvailable} is that, when recycling, distance laid out for - * {@link #mExtraFillSpace} is not considered to avoid recycling visible children. - */ - int mExtraFillSpace = 0; - - /** - * Contains the {@link #calculateExtraLayoutSpace(RecyclerView.State, int[])} extra layout - * space} that should be excluded for recycling when cleaning up the tail of the list during - * a smooth scroll. - */ - int mNoRecycleSpace = 0; - - /** - * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value - * is set to true, we skip removed views since they should not be laid out in post layout - * step. - */ - boolean mIsPreLayout = false; - - /** - * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} - * amount. - */ - int mLastScrollDelta; - - /** - * When LLM needs to layout particular views, it sets this list in which case, LayoutState - * will only return views from this list and return null if it cannot find an item. - */ - List mScrapList = null; - - /** - * Used when there is no limit in how many views can be laid out. - */ - boolean mInfinite; - - /** - * @return true if there are more items in the data adapter - */ - boolean hasMore(RecyclerView.State state) { - return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); - } - - /** - * Gets the view for the next element that we should layout. - * Also updates current item index to the next item, based on {@link #mItemDirection} - * - * @return The next element that we should layout. - */ - View next(RecyclerView.Recycler recycler) { - if (mScrapList != null) { - return nextViewFromScrapList(); - } - final View view = recycler.getViewForPosition(mCurrentPosition); - mCurrentPosition += mItemDirection; - return view; - } - - /** - * Returns the next item from the scrap list. - *

- * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection - * - * @return View if an item in the current position or direction exists if not null. - */ - private View nextViewFromScrapList() { - final int size = mScrapList.size(); - for (int i = 0; i < size; i++) { - final View view = mScrapList.get(i).itemView; - final RecyclerView.LayoutParams lp = - (RecyclerView.LayoutParams) view.getLayoutParams(); - if (lp.isItemRemoved()) { - continue; - } - if (mCurrentPosition == lp.getViewLayoutPosition()) { - assignPositionFromScrapList(view); - return view; - } - } - return null; - } - - public void assignPositionFromScrapList() { - assignPositionFromScrapList(null); - } - - public void assignPositionFromScrapList(View ignore) { - final View closest = nextViewInLimitedList(ignore); - if (closest == null) { - mCurrentPosition = RecyclerView.NO_POSITION; - } else { - mCurrentPosition = ((RecyclerView.LayoutParams) closest.getLayoutParams()) - .getViewLayoutPosition(); - } - } - - public View nextViewInLimitedList(View ignore) { - int size = mScrapList.size(); - View closest = null; - int closestDistance = Integer.MAX_VALUE; - if (DEBUG && mIsPreLayout) { - throw new IllegalStateException("Scrap list cannot be used in pre layout"); - } - for (int i = 0; i < size; i++) { - View view = mScrapList.get(i).itemView; - final RecyclerView.LayoutParams lp = - (RecyclerView.LayoutParams) view.getLayoutParams(); - if (view == ignore || lp.isItemRemoved()) { - continue; - } - final int distance = (lp.getViewLayoutPosition() - mCurrentPosition) - * mItemDirection; - if (distance < 0) { - continue; // item is not in current direction - } - if (distance < closestDistance) { - closest = view; - closestDistance = distance; - if (distance == 0) { - break; - } - } - } - return closest; - } - - void log() { - Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" - + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection); - } - } - - /** - * @hide - */ - @RestrictTo(LIBRARY) - @SuppressLint("BanParcelableUsage") - public static class SavedState implements Parcelable { - - int mAnchorPosition; - - int mAnchorOffset; - - boolean mAnchorLayoutFromEnd; - - public SavedState() { - - } - - SavedState(Parcel in) { - mAnchorPosition = in.readInt(); - mAnchorOffset = in.readInt(); - mAnchorLayoutFromEnd = in.readInt() == 1; - } - - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public SavedState(SavedState other) { - mAnchorPosition = other.mAnchorPosition; - mAnchorOffset = other.mAnchorOffset; - mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; - } - - boolean hasValidAnchor() { - return mAnchorPosition >= 0; - } - - void invalidateAnchor() { - mAnchorPosition = RecyclerView.NO_POSITION; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAnchorPosition); - dest.writeInt(mAnchorOffset); - dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - * Simple data class to keep Anchor information - */ - static class AnchorInfo { - OrientationHelper mOrientationHelper; - int mPosition; - int mCoordinate; - boolean mLayoutFromEnd; - boolean mValid; - - AnchorInfo() { - reset(); - } - - void reset() { - mPosition = RecyclerView.NO_POSITION; - mCoordinate = INVALID_OFFSET; - mLayoutFromEnd = false; - mValid = false; - } - - /** - * assigns anchor coordinate from the RecyclerView's padding depending on current - * layoutFromEnd value - */ - void assignCoordinateFromPadding() { - mCoordinate = mLayoutFromEnd - ? mOrientationHelper.getEndAfterPadding() - : mOrientationHelper.getStartAfterPadding(); - } - - @Override - public String toString() { - return "AnchorInfo{" - + "mPosition=" + mPosition - + ", mCoordinate=" + mCoordinate - + ", mLayoutFromEnd=" + mLayoutFromEnd - + ", mValid=" + mValid - + '}'; - } - - boolean isViewValidAsAnchor(View child, RecyclerView.State state) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0 - && lp.getViewLayoutPosition() < state.getItemCount(); - } - - public void assignFromViewAndKeepVisibleRect(View child, int position) { - final int spaceChange = mOrientationHelper.getTotalSpaceChange(); - if (spaceChange >= 0) { - assignFromView(child, position); - return; - } - mPosition = position; - if (mLayoutFromEnd) { - final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange; - final int childEnd = mOrientationHelper.getDecoratedEnd(child); - final int previousEndMargin = prevLayoutEnd - childEnd; - mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin; - // ensure we did not push child's top out of bounds because of this - if (previousEndMargin > 0) { // we have room to shift bottom if necessary - final int childSize = mOrientationHelper.getDecoratedMeasurement(child); - final int estimatedChildStart = mCoordinate - childSize; - final int layoutStart = mOrientationHelper.getStartAfterPadding(); - final int previousStartMargin = mOrientationHelper.getDecoratedStart(child) - - layoutStart; - final int startReference = layoutStart + Math.min(previousStartMargin, 0); - final int startMargin = estimatedChildStart - startReference; - if (startMargin < 0) { - // offset to make top visible but not too much - mCoordinate += Math.min(previousEndMargin, -startMargin); - } - } - } else { - final int childStart = mOrientationHelper.getDecoratedStart(child); - final int startMargin = childStart - mOrientationHelper.getStartAfterPadding(); - mCoordinate = childStart; - if (startMargin > 0) { // we have room to fix end as well - final int estimatedEnd = childStart - + mOrientationHelper.getDecoratedMeasurement(child); - final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding() - - spaceChange; - final int previousEndMargin = previousLayoutEnd - - mOrientationHelper.getDecoratedEnd(child); - final int endReference = mOrientationHelper.getEndAfterPadding() - - Math.min(0, previousEndMargin); - final int endMargin = endReference - estimatedEnd; - if (endMargin < 0) { - mCoordinate -= Math.min(startMargin, -endMargin); - } - } - } - } - - public void assignFromView(View child, int position) { - if (mLayoutFromEnd) { - mCoordinate = mOrientationHelper.getDecoratedEnd(child) - + mOrientationHelper.getTotalSpaceChange(); - } else { - mCoordinate = mOrientationHelper.getDecoratedStart(child); - } - - mPosition = position; - } - } - - protected static class LayoutChunkResult { - public int mConsumed; - public boolean mFinished; - public boolean mIgnoreConsumed; - public boolean mFocusable; - - void resetInternal() { - mConsumed = 0; - mFinished = false; - mIgnoreConsumed = false; - mFocusable = false; - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/LinearSmoothScroller.java b/app/src/main/java/androidx/recyclerview/widget/LinearSmoothScroller.java deleted file mode 100644 index b4ba75fcdf..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/LinearSmoothScroller.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.PointF; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -/** - * {@link RecyclerView.SmoothScroller} implementation which uses a {@link LinearInterpolator} until - * the target position becomes a child of the RecyclerView and then uses a - * {@link DecelerateInterpolator} to slowly approach to target position. - *

- * If the {@link RecyclerView.LayoutManager} you are using does not implement the - * {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface, then you must override the - * {@link #computeScrollVectorForPosition(int)} method. All the LayoutManagers bundled with - * the support library implement this interface. - */ -public class LinearSmoothScroller extends RecyclerView.SmoothScroller { - - private static final boolean DEBUG = false; - - private static final float MILLISECONDS_PER_INCH = 25f; - - private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000; - - /** - * Align child view's left or top with parent view's left or top - * - * @see #calculateDtToFit(int, int, int, int, int) - * @see #calculateDxToMakeVisible(android.view.View, int) - * @see #calculateDyToMakeVisible(android.view.View, int) - */ - public static final int SNAP_TO_START = -1; - - /** - * Align child view's right or bottom with parent view's right or bottom - * - * @see #calculateDtToFit(int, int, int, int, int) - * @see #calculateDxToMakeVisible(android.view.View, int) - * @see #calculateDyToMakeVisible(android.view.View, int) - */ - public static final int SNAP_TO_END = 1; - - /** - *

Decides if the child should be snapped from start or end, depending on where it - * currently is in relation to its parent.

- *

For instance, if the view is virtually on the left of RecyclerView, using - * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}

- * - * @see #calculateDtToFit(int, int, int, int, int) - * @see #calculateDxToMakeVisible(android.view.View, int) - * @see #calculateDyToMakeVisible(android.view.View, int) - */ - public static final int SNAP_TO_ANY = 0; - - // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target - // view is not laid out until interim target position is reached, we can detect the case before - // scrolling slows down and reschedule another interim target scroll - private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f; - - protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); - - protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); - - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - protected PointF mTargetVector; - - private final DisplayMetrics mDisplayMetrics; - private boolean mHasCalculatedMillisPerPixel = false; - private float mMillisPerPixel; - - // Temporary variables to keep track of the interim scroll target. These values do not - // point to a real item position, rather point to an estimated location pixels. - protected int mInterimTargetDx = 0, mInterimTargetDy = 0; - - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public LinearSmoothScroller(Context context) { - mDisplayMetrics = context.getResources().getDisplayMetrics(); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onStart() { - - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { - final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference()); - final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference()); - final int distance = (int) Math.sqrt(dx * dx + dy * dy); - final int time = calculateTimeForDeceleration(distance); - if (time > 0) { - action.update(-dx, -dy, time, mDecelerateInterpolator); - } - } - - /** - * {@inheritDoc} - */ - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) { - // TODO(b/72745539): Is there ever a time when onSeekTargetStep should be called when - // getChildCount returns 0? Should this logic be extracted out of this method such that - // this method is not called if getChildCount() returns 0? - if (getChildCount() == 0) { - stop(); - return; - } - //noinspection PointlessBooleanExpression - if (DEBUG && mTargetVector != null - && (mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0)) { - throw new IllegalStateException("Scroll happened in the opposite direction" - + " of the target. Some calculations are wrong"); - } - mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx); - mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy); - - if (mInterimTargetDx == 0 && mInterimTargetDy == 0) { - updateActionForInterimTarget(action); - } // everything is valid, keep going - - } - - /** - * {@inheritDoc} - */ - @Override - protected void onStop() { - mInterimTargetDx = mInterimTargetDy = 0; - mTargetVector = null; - } - - /** - * Calculates the scroll speed. - * - *

By default, LinearSmoothScroller assumes this method always returns the same value and - * caches the result of calling it. - * - * @param displayMetrics DisplayMetrics to be used for real dimension calculations - * @return The time (in ms) it should take for each pixel. For instance, if returned value is - * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; - } - - private float getSpeedPerPixel() { - if (!mHasCalculatedMillisPerPixel) { - mMillisPerPixel = calculateSpeedPerPixel(mDisplayMetrics); - mHasCalculatedMillisPerPixel = true; - } - return mMillisPerPixel; - } - - /** - *

Calculates the time for deceleration so that transition from LinearInterpolator to - * DecelerateInterpolator looks smooth.

- * - * @param dx Distance to scroll - * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning - * from LinearInterpolation - */ - protected int calculateTimeForDeceleration(int dx) { - // we want to cover same area with the linear interpolator for the first 10% of the - // interpolation. After that, deceleration will take control. - // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x - // which gives 0.100028 when x = .3356 - // this is why we divide linear scrolling time with .3356 - return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356); - } - - /** - * Calculates the time it should take to scroll the given distance (in pixels) - * - * @param dx Distance in pixels that we want to scroll - * @return Time in milliseconds - * @see #calculateSpeedPerPixel(android.util.DisplayMetrics) - */ - protected int calculateTimeForScrolling(int dx) { - // In a case where dx is very small, rounding may return 0 although dx > 0. - // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive - // time. - return (int) Math.ceil(Math.abs(dx) * getSpeedPerPixel()); - } - - /** - * When scrolling towards a child view, this method defines whether we should align the left - * or the right edge of the child with the parent RecyclerView. - * - * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector - * @see #SNAP_TO_START - * @see #SNAP_TO_END - * @see #SNAP_TO_ANY - */ - protected int getHorizontalSnapPreference() { - return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY : - mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START; - } - - /** - * When scrolling towards a child view, this method defines whether we should align the top - * or the bottom edge of the child with the parent RecyclerView. - * - * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector - * @see #SNAP_TO_START - * @see #SNAP_TO_END - * @see #SNAP_TO_ANY - */ - protected int getVerticalSnapPreference() { - return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY : - mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START; - } - - /** - * When the target scroll position is not a child of the RecyclerView, this method calculates - * a direction vector towards that child and triggers a smooth scroll. - * - * @see #computeScrollVectorForPosition(int) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - protected void updateActionForInterimTarget(Action action) { - // find an interim target position - PointF scrollVector = computeScrollVectorForPosition(getTargetPosition()); - if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) { - final int target = getTargetPosition(); - action.jumpTo(target); - stop(); - return; - } - normalize(scrollVector); - mTargetVector = scrollVector; - - mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x); - mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y); - final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX); - // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the - // interim target. Since we track the distance travelled in onSeekTargetStep callback, it - // won't actually scroll more than what we need. - action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO), - (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO), - (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator); - } - - private int clampApplyScroll(int tmpDt, int dt) { - final int before = tmpDt; - tmpDt -= dt; - if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset - return 0; - } - return tmpDt; - } - - /** - * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and - * {@link #calculateDyToMakeVisible(android.view.View, int)} - */ - public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int - snapPreference) { - switch (snapPreference) { - case SNAP_TO_START: - return boxStart - viewStart; - case SNAP_TO_END: - return boxEnd - viewEnd; - case SNAP_TO_ANY: - final int dtStart = boxStart - viewStart; - if (dtStart > 0) { - return dtStart; - } - final int dtEnd = boxEnd - viewEnd; - if (dtEnd < 0) { - return dtEnd; - } - break; - default: - throw new IllegalArgumentException("snap preference should be one of the" - + " constants defined in SmoothScroller, starting with SNAP_"); - } - return 0; - } - - /** - * Calculates the vertical scroll amount necessary to make the given view fully visible - * inside the RecyclerView. - * - * @param view The view which we want to make fully visible - * @param snapPreference The edge which the view should snap to when entering the visible - * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or - * {@link #SNAP_TO_ANY}. - * @return The vertical scroll amount necessary to make the view visible with the given - * snap preference. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int calculateDyToMakeVisible(View view, int snapPreference) { - final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (layoutManager == null || !layoutManager.canScrollVertically()) { - return 0; - } - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - final int top = layoutManager.getDecoratedTop(view) - params.topMargin; - final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin; - final int start = layoutManager.getPaddingTop(); - final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom(); - return calculateDtToFit(top, bottom, start, end, snapPreference); - } - - /** - * Calculates the horizontal scroll amount necessary to make the given view fully visible - * inside the RecyclerView. - * - * @param view The view which we want to make fully visible - * @param snapPreference The edge which the view should snap to when entering the visible - * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or - * {@link #SNAP_TO_END} - * @return The vertical scroll amount necessary to make the view visible with the given - * snap preference. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int calculateDxToMakeVisible(View view, int snapPreference) { - final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (layoutManager == null || !layoutManager.canScrollHorizontally()) { - return 0; - } - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin; - final int right = layoutManager.getDecoratedRight(view) + params.rightMargin; - final int start = layoutManager.getPaddingLeft(); - final int end = layoutManager.getWidth() - layoutManager.getPaddingRight(); - return calculateDtToFit(left, right, start, end, snapPreference); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/LinearSnapHelper.java b/app/src/main/java/androidx/recyclerview/widget/LinearSnapHelper.java deleted file mode 100644 index 6481b877e5..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/LinearSnapHelper.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.graphics.PointF; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Implementation of the {@link SnapHelper} supporting snapping in either vertical or horizontal - * orientation. - *

- * The implementation will snap the center of the target child view to the center of - * the attached {@link RecyclerView}. If you intend to change this behavior then override - * {@link SnapHelper#calculateDistanceToFinalSnap}. - */ -public class LinearSnapHelper extends SnapHelper { - - private static final float INVALID_DISTANCE = 1f; - - // Orientation helpers are lazily created per LayoutManager. - @Nullable - private OrientationHelper mVerticalHelper; - @Nullable - private OrientationHelper mHorizontalHelper; - - @Override - public int[] calculateDistanceToFinalSnap( - @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { - int[] out = new int[2]; - if (layoutManager.canScrollHorizontally()) { - out[0] = distanceToCenter(targetView, - getHorizontalHelper(layoutManager)); - } else { - out[0] = 0; - } - - if (layoutManager.canScrollVertically()) { - out[1] = distanceToCenter(targetView, - getVerticalHelper(layoutManager)); - } else { - out[1] = 0; - } - return out; - } - - @Override - public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, - int velocityY) { - if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { - return RecyclerView.NO_POSITION; - } - - final int itemCount = layoutManager.getItemCount(); - if (itemCount == 0) { - return RecyclerView.NO_POSITION; - } - - final View currentView = findSnapView(layoutManager); - if (currentView == null) { - return RecyclerView.NO_POSITION; - } - - final int currentPosition = layoutManager.getPosition(currentView); - if (currentPosition == RecyclerView.NO_POSITION) { - return RecyclerView.NO_POSITION; - } - - RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = - (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; - // deltaJumps sign comes from the velocity which may not match the order of children in - // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to - // get the direction. - PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); - if (vectorForEnd == null) { - // cannot get a vector for the given position. - return RecyclerView.NO_POSITION; - } - - int vDeltaJump, hDeltaJump; - if (layoutManager.canScrollHorizontally()) { - hDeltaJump = estimateNextPositionDiffForFling(layoutManager, - getHorizontalHelper(layoutManager), velocityX, 0); - if (vectorForEnd.x < 0) { - hDeltaJump = -hDeltaJump; - } - } else { - hDeltaJump = 0; - } - if (layoutManager.canScrollVertically()) { - vDeltaJump = estimateNextPositionDiffForFling(layoutManager, - getVerticalHelper(layoutManager), 0, velocityY); - if (vectorForEnd.y < 0) { - vDeltaJump = -vDeltaJump; - } - } else { - vDeltaJump = 0; - } - - int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump; - if (deltaJump == 0) { - return RecyclerView.NO_POSITION; - } - - int targetPos = currentPosition + deltaJump; - if (targetPos < 0) { - targetPos = 0; - } - if (targetPos >= itemCount) { - targetPos = itemCount - 1; - } - return targetPos; - } - - @Override - public View findSnapView(RecyclerView.LayoutManager layoutManager) { - if (layoutManager.canScrollVertically()) { - return findCenterView(layoutManager, getVerticalHelper(layoutManager)); - } else if (layoutManager.canScrollHorizontally()) { - return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); - } - return null; - } - - private int distanceToCenter(@NonNull View targetView, OrientationHelper helper) { - final int childCenter = helper.getDecoratedStart(targetView) - + (helper.getDecoratedMeasurement(targetView) / 2); - final int containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; - return childCenter - containerCenter; - } - - /** - * Estimates a position to which SnapHelper will try to scroll to in response to a fling. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * @param helper The {@link OrientationHelper} that is created from the LayoutManager. - * @param velocityX The velocity on the x axis. - * @param velocityY The velocity on the y axis. - * - * @return The diff between the target scroll position and the current position. - */ - private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, - OrientationHelper helper, int velocityX, int velocityY) { - int[] distances = calculateScrollDistance(velocityX, velocityY); - float distancePerChild = computeDistancePerChild(layoutManager, helper); - if (distancePerChild <= 0) { - return 0; - } - int distance = - Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1]; - return (int) Math.round(distance / distancePerChild); - } - - /** - * Return the child view that is currently closest to the center of this parent. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. - * - * @return the child view that is currently closest to the center of this parent. - */ - @Nullable - private View findCenterView(RecyclerView.LayoutManager layoutManager, - OrientationHelper helper) { - int childCount = layoutManager.getChildCount(); - if (childCount == 0) { - return null; - } - - View closestChild = null; - final int center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; - int absClosest = Integer.MAX_VALUE; - - for (int i = 0; i < childCount; i++) { - final View child = layoutManager.getChildAt(i); - int childCenter = helper.getDecoratedStart(child) - + (helper.getDecoratedMeasurement(child) / 2); - int absDistance = Math.abs(childCenter - center); - - /** if child center is closer than previous closest, set it as closest **/ - if (absDistance < absClosest) { - absClosest = absDistance; - closestChild = child; - } - } - return closestChild; - } - - /** - * Computes an average pixel value to pass a single child. - *

- * Returns a negative value if it cannot be calculated. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * @param helper The relevant {@link OrientationHelper} for the attached - * {@link RecyclerView.LayoutManager}. - * - * @return A float value that is the average number of pixels needed to scroll by one view in - * the relevant direction. - */ - private float computeDistancePerChild(RecyclerView.LayoutManager layoutManager, - OrientationHelper helper) { - View minPosView = null; - View maxPosView = null; - int minPos = Integer.MAX_VALUE; - int maxPos = Integer.MIN_VALUE; - int childCount = layoutManager.getChildCount(); - if (childCount == 0) { - return INVALID_DISTANCE; - } - - for (int i = 0; i < childCount; i++) { - View child = layoutManager.getChildAt(i); - final int pos = layoutManager.getPosition(child); - if (pos == RecyclerView.NO_POSITION) { - continue; - } - if (pos < minPos) { - minPos = pos; - minPosView = child; - } - if (pos > maxPos) { - maxPos = pos; - maxPosView = child; - } - } - if (minPosView == null || maxPosView == null) { - return INVALID_DISTANCE; - } - int start = Math.min(helper.getDecoratedStart(minPosView), - helper.getDecoratedStart(maxPosView)); - int end = Math.max(helper.getDecoratedEnd(minPosView), - helper.getDecoratedEnd(maxPosView)); - int distance = end - start; - if (distance == 0) { - return INVALID_DISTANCE; - } - return 1f * distance / ((maxPos - minPos) + 1); - } - - @NonNull - private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { - if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { - mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); - } - return mVerticalHelper; - } - - @NonNull - private OrientationHelper getHorizontalHelper( - @NonNull RecyclerView.LayoutManager layoutManager) { - if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { - mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); - } - return mHorizontalHelper; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ListAdapter.java b/app/src/main/java/androidx/recyclerview/widget/ListAdapter.java deleted file mode 100644 index 6b1ad73c13..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ListAdapter.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.List; - -/** - * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting List data in a - * {@link RecyclerView}, including computing diffs between Lists on a background thread. - *

- * This class is a convenience wrapper around {@link AsyncListDiffer} that implements Adapter common - * default behavior for item access and counting. - *

- * While using a LiveData<List> is an easy way to provide data to the adapter, it isn't required - * - you can use {@link #submitList(List)} when new lists are available. - *

- * A complete usage pattern with Room would look like this: - *

- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract LiveData<List<User>> usersByLastName();
- * }
- *
- * class MyViewModel extends ViewModel {
- *     public final LiveData<List<User>> usersList;
- *     public MyViewModel(UserDao userDao) {
- *         usersList = userDao.usersByLastName();
- *     }
- * }
- *
- * class MyActivity extends AppCompatActivity {
- *     {@literal @}Override
- *     public void onCreate(Bundle savedState) {
- *         super.onCreate(savedState);
- *         MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
- *         RecyclerView recyclerView = findViewById(R.id.user_list);
- *         UserAdapter<User> adapter = new UserAdapter();
- *         viewModel.usersList.observe(this, list -> adapter.submitList(list));
- *         recyclerView.setAdapter(adapter);
- *     }
- * }
- *
- * class UserAdapter extends ListAdapter<User, UserViewHolder> {
- *     public UserAdapter() {
- *         super(User.DIFF_CALLBACK);
- *     }
- *     {@literal @}Override
- *     public void onBindViewHolder(UserViewHolder holder, int position) {
- *         holder.bindTo(getItem(position));
- *     }
- *     public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
- *             new DiffUtil.ItemCallback<User>() {
- *         {@literal @}Override
- *         public boolean areItemsTheSame(
- *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *             // User properties may have changed if reloaded from the DB, but ID is fixed
- *             return oldUser.getId() == newUser.getId();
- *         }
- *         {@literal @}Override
- *         public boolean areContentsTheSame(
- *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- *             // NOTE: if you use equals, your object must properly override Object#equals()
- *             // Incorrectly returning false here will result in too many animations.
- *             return oldUser.equals(newUser);
- *         }
- *     }
- * }
- * - * Advanced users that wish for more control over adapter behavior, or to provide a specific base - * class should refer to {@link AsyncListDiffer}, which provides custom mapping from diff events - * to adapter positions. - * - * @param Type of the Lists this Adapter will receive. - * @param A class that extends ViewHolder that will be used by the adapter. - */ -public abstract class ListAdapter - extends RecyclerView.Adapter { - final AsyncListDiffer mDiffer; - private final AsyncListDiffer.ListListener mListener = - new AsyncListDiffer.ListListener() { - @Override - public void onCurrentListChanged( - @NonNull List previousList, @NonNull List currentList) { - ListAdapter.this.onCurrentListChanged(previousList, currentList); - } - }; - - @SuppressWarnings("unused") - protected ListAdapter(@NonNull DiffUtil.ItemCallback diffCallback) { - mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), - new AsyncDifferConfig.Builder<>(diffCallback).build()); - mDiffer.addListListener(mListener); - } - - @SuppressWarnings("unused") - protected ListAdapter(@NonNull AsyncDifferConfig config) { - mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config); - mDiffer.addListListener(mListener); - } - - /** - * Submits a new list to be diffed, and displayed. - *

- * If a list is already being displayed, a diff will be computed on a background thread, which - * will dispatch Adapter.notifyItem events on the main thread. - * - * @param list The new list to be displayed. - */ - public void submitList(@Nullable List list) { - mDiffer.submitList(list); - } - - /** - * Set the new list to be displayed. - *

- * If a List is already being displayed, a diff will be computed on a background thread, which - * will dispatch Adapter.notifyItem events on the main thread. - *

- * The commit callback can be used to know when the List is committed, but note that it - * may not be executed. If List B is submitted immediately after List A, and is - * committed directly, the callback associated with List A will not be run. - * - * @param list The new list to be displayed. - * @param commitCallback Optional runnable that is executed when the List is committed, if - * it is committed. - */ - public void submitList(@Nullable List list, @Nullable final Runnable commitCallback) { - mDiffer.submitList(list, commitCallback); - } - - protected T getItem(int position) { - return mDiffer.getCurrentList().get(position); - } - - @Override - public int getItemCount() { - return mDiffer.getCurrentList().size(); - } - - /** - * Get the current List - any diffing to present this list has already been computed and - * dispatched via the ListUpdateCallback. - *

- * If a null List, or no List has been submitted, an empty list will be returned. - *

- * The returned list may not be mutated - mutations to content must be done through - * {@link #submitList(List)}. - * - * @return The list currently being displayed. - * - * @see #onCurrentListChanged(List, List) - */ - @NonNull - public List getCurrentList() { - return mDiffer.getCurrentList(); - } - - /** - * Called when the current List is updated. - *

- * If a null List is passed to {@link #submitList(List)}, or no List has been - * submitted, the current List is represented as an empty List. - * - * @param previousList List that was displayed previously. - * @param currentList new List being displayed, will be empty if {@code null} was passed to - * {@link #submitList(List)}. - * - * @see #getCurrentList() - */ - public void onCurrentListChanged(@NonNull List previousList, @NonNull List currentList) { - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ListUpdateCallback.java b/app/src/main/java/androidx/recyclerview/widget/ListUpdateCallback.java deleted file mode 100644 index ed8e7fc676..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ListUpdateCallback.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import androidx.annotation.Nullable; - -/** - * An interface that can receive Update operations that are applied to a list. - *

- * This class can be used together with DiffUtil to detect changes between two lists. - */ -public interface ListUpdateCallback { - /** - * Called when {@code count} number of items are inserted at the given position. - * - * @param position The position of the new item. - * @param count The number of items that have been added. - */ - void onInserted(int position, int count); - - /** - * Called when {@code count} number of items are removed from the given position. - * - * @param position The position of the item which has been removed. - * @param count The number of items which have been removed. - */ - void onRemoved(int position, int count); - - /** - * Called when an item changes its position in the list. - * - * @param fromPosition The previous position of the item before the move. - * @param toPosition The new position of the item. - */ - void onMoved(int fromPosition, int toPosition); - - /** - * Called when {@code count} number of items are updated at the given position. - * - * @param position The position of the item which has been updated. - * @param count The number of items which has changed. - */ - void onChanged(int position, int count, @Nullable Object payload); -} diff --git a/app/src/main/java/androidx/recyclerview/widget/MessageThreadUtil.java b/app/src/main/java/androidx/recyclerview/widget/MessageThreadUtil.java deleted file mode 100644 index 4286cd639c..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/MessageThreadUtil.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -class MessageThreadUtil implements ThreadUtil { - - @Override - public MainThreadCallback getMainThreadProxy(final MainThreadCallback callback) { - return new MainThreadCallback() { - final MessageQueue mQueue = new MessageQueue(); - final private Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - - static final int UPDATE_ITEM_COUNT = 1; - static final int ADD_TILE = 2; - static final int REMOVE_TILE = 3; - - @Override - public void updateItemCount(int generation, int itemCount) { - sendMessage(SyncQueueItem.obtainMessage(UPDATE_ITEM_COUNT, generation, itemCount)); - } - - @Override - public void addTile(int generation, TileList.Tile tile) { - sendMessage(SyncQueueItem.obtainMessage(ADD_TILE, generation, tile)); - } - - @Override - public void removeTile(int generation, int position) { - sendMessage(SyncQueueItem.obtainMessage(REMOVE_TILE, generation, position)); - } - - private void sendMessage(SyncQueueItem msg) { - mQueue.sendMessage(msg); - mMainThreadHandler.post(mMainThreadRunnable); - } - - private Runnable mMainThreadRunnable = new Runnable() { - @Override - public void run() { - SyncQueueItem msg = mQueue.next(); - while (msg != null) { - switch (msg.what) { - case UPDATE_ITEM_COUNT: - callback.updateItemCount(msg.arg1, msg.arg2); - break; - case ADD_TILE: - @SuppressWarnings("unchecked") - TileList.Tile tile = (TileList.Tile) msg.data; - callback.addTile(msg.arg1, tile); - break; - case REMOVE_TILE: - callback.removeTile(msg.arg1, msg.arg2); - break; - default: - Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); - } - msg = mQueue.next(); - } - } - }; - }; - } - - @SuppressWarnings("deprecation") /* AsyncTask */ - @Override - public BackgroundCallback getBackgroundProxy(final BackgroundCallback callback) { - return new BackgroundCallback() { - final MessageQueue mQueue = new MessageQueue(); - private final Executor mExecutor = android.os.AsyncTask.THREAD_POOL_EXECUTOR; - AtomicBoolean mBackgroundRunning = new AtomicBoolean(false); - - static final int REFRESH = 1; - static final int UPDATE_RANGE = 2; - static final int LOAD_TILE = 3; - static final int RECYCLE_TILE = 4; - - @Override - public void refresh(int generation) { - sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(REFRESH, generation, null)); - } - - @Override - public void updateRange(int rangeStart, int rangeEnd, - int extRangeStart, int extRangeEnd, int scrollHint) { - sendMessageAtFrontOfQueue(SyncQueueItem.obtainMessage(UPDATE_RANGE, - rangeStart, rangeEnd, extRangeStart, extRangeEnd, scrollHint, null)); - } - - @Override - public void loadTile(int position, int scrollHint) { - sendMessage(SyncQueueItem.obtainMessage(LOAD_TILE, position, scrollHint)); - } - - @Override - public void recycleTile(TileList.Tile tile) { - sendMessage(SyncQueueItem.obtainMessage(RECYCLE_TILE, 0, tile)); - } - - private void sendMessage(SyncQueueItem msg) { - mQueue.sendMessage(msg); - maybeExecuteBackgroundRunnable(); - } - - private void sendMessageAtFrontOfQueue(SyncQueueItem msg) { - mQueue.sendMessageAtFrontOfQueue(msg); - maybeExecuteBackgroundRunnable(); - } - - private void maybeExecuteBackgroundRunnable() { - if (mBackgroundRunning.compareAndSet(false, true)) { - mExecutor.execute(mBackgroundRunnable); - } - } - - private Runnable mBackgroundRunnable = new Runnable() { - @Override - public void run() { - while (true) { - SyncQueueItem msg = mQueue.next(); - if (msg == null) { - break; - } - switch (msg.what) { - case REFRESH: - mQueue.removeMessages(REFRESH); - callback.refresh(msg.arg1); - break; - case UPDATE_RANGE: - mQueue.removeMessages(UPDATE_RANGE); - mQueue.removeMessages(LOAD_TILE); - callback.updateRange( - msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5); - break; - case LOAD_TILE: - callback.loadTile(msg.arg1, msg.arg2); - break; - case RECYCLE_TILE: - @SuppressWarnings("unchecked") - TileList.Tile tile = (TileList.Tile) msg.data; - callback.recycleTile(tile); - break; - default: - Log.e("ThreadUtil", "Unsupported message, what=" + msg.what); - } - } - mBackgroundRunning.set(false); - } - }; - }; - } - - /** - * Replica of android.os.Message. Unfortunately, cannot use it without a Handler and don't want - * to create a thread just for this component. - */ - static class SyncQueueItem { - - private static SyncQueueItem sPool; - private static final Object sPoolLock = new Object(); - SyncQueueItem next; - public int what; - public int arg1; - public int arg2; - public int arg3; - public int arg4; - public int arg5; - public Object data; - - void recycle() { - next = null; - what = arg1 = arg2 = arg3 = arg4 = arg5 = 0; - data = null; - synchronized (sPoolLock) { - if (sPool != null) { - next = sPool; - } - sPool = this; - } - } - - static SyncQueueItem obtainMessage(int what, int arg1, int arg2, int arg3, int arg4, - int arg5, Object data) { - synchronized (sPoolLock) { - final SyncQueueItem item; - if (sPool == null) { - item = new SyncQueueItem(); - } else { - item = sPool; - sPool = sPool.next; - item.next = null; - } - item.what = what; - item.arg1 = arg1; - item.arg2 = arg2; - item.arg3 = arg3; - item.arg4 = arg4; - item.arg5 = arg5; - item.data = data; - return item; - } - } - - static SyncQueueItem obtainMessage(int what, int arg1, int arg2) { - return obtainMessage(what, arg1, arg2, 0, 0, 0, null); - } - - static SyncQueueItem obtainMessage(int what, int arg1, Object data) { - return obtainMessage(what, arg1, 0, 0, 0, 0, data); - } - } - - static class MessageQueue { - - private SyncQueueItem mRoot; - private final Object mLock = new Object(); - - SyncQueueItem next() { - synchronized (mLock) { - if (mRoot == null) { - return null; - } - final SyncQueueItem next = mRoot; - mRoot = mRoot.next; - return next; - } - } - - void sendMessageAtFrontOfQueue(SyncQueueItem item) { - synchronized (mLock) { - item.next = mRoot; - mRoot = item; - } - } - - void sendMessage(SyncQueueItem item) { - synchronized (mLock) { - if (mRoot == null) { - mRoot = item; - return; - } - SyncQueueItem last = mRoot; - while (last.next != null) { - last = last.next; - } - last.next = item; - } - } - - void removeMessages(int what) { - synchronized (mLock) { - while (mRoot != null && mRoot.what == what) { - SyncQueueItem item = mRoot; - mRoot = mRoot.next; - item.recycle(); - } - if (mRoot != null) { - SyncQueueItem prev = mRoot; - SyncQueueItem item = prev.next; - while (item != null) { - SyncQueueItem next = item.next; - if (item.what == what) { - prev.next = next; - item.recycle(); - } else { - prev = item; - } - item = next; - } - } - } - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java b/app/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java deleted file mode 100644 index a6b9a300e3..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/NestedAdapterWrapper.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY; - -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.util.Preconditions; -import androidx.recyclerview.widget.RecyclerView.Adapter; -import androidx.recyclerview.widget.RecyclerView.ViewHolder; - -/** - * Wrapper for each adapter in {@link ConcatAdapter}. - */ -class NestedAdapterWrapper { - @NonNull - private final ViewTypeStorage.ViewTypeLookup mViewTypeLookup; - @NonNull - private final StableIdStorage.StableIdLookup mStableIdLookup; - public final Adapter adapter; - @SuppressWarnings("WeakerAccess") - final Callback mCallback; - // we cache this value so that we can know the previous size when change happens - // this is also important as getting real size while an adapter is dispatching possibly a - // a chain of events might create inconsistencies (as it happens in DiffUtil). - // Instead, we always calculate this value based on notify events. - @SuppressWarnings("WeakerAccess") - int mCachedItemCount; - - private RecyclerView.AdapterDataObserver mAdapterObserver = - new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - mCachedItemCount = adapter.getItemCount(); - mCallback.onChanged(NestedAdapterWrapper.this); - } - - @Override - public void onItemRangeChanged(int positionStart, int itemCount) { - mCallback.onItemRangeChanged( - NestedAdapterWrapper.this, - positionStart, - itemCount, - null - ); - } - - @Override - public void onItemRangeChanged(int positionStart, int itemCount, - @Nullable Object payload) { - mCallback.onItemRangeChanged( - NestedAdapterWrapper.this, - positionStart, - itemCount, - payload - ); - } - - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - mCachedItemCount += itemCount; - mCallback.onItemRangeInserted( - NestedAdapterWrapper.this, - positionStart, - itemCount); - if (mCachedItemCount > 0 - && adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) { - mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this); - } - } - - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - mCachedItemCount -= itemCount; - mCallback.onItemRangeRemoved( - NestedAdapterWrapper.this, - positionStart, - itemCount - ); - if (mCachedItemCount < 1 - && adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) { - mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this); - } - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - Preconditions.checkArgument(itemCount == 1, - "moving more than 1 item is not supported in RecyclerView"); - mCallback.onItemRangeMoved( - NestedAdapterWrapper.this, - fromPosition, - toPosition - ); - } - - @Override - public void onStateRestorationPolicyChanged() { - mCallback.onStateRestorationPolicyChanged( - NestedAdapterWrapper.this - ); - } - }; - - NestedAdapterWrapper( - Adapter adapter, - final Callback callback, - ViewTypeStorage viewTypeStorage, - StableIdStorage.StableIdLookup stableIdLookup) { - this.adapter = adapter; - mCallback = callback; - mViewTypeLookup = viewTypeStorage.createViewTypeWrapper(this); - mStableIdLookup = stableIdLookup; - mCachedItemCount = this.adapter.getItemCount(); - this.adapter.registerAdapterDataObserver(mAdapterObserver); - } - - - void dispose() { - adapter.unregisterAdapterDataObserver(mAdapterObserver); - mViewTypeLookup.dispose(); - } - - int getCachedItemCount() { - return mCachedItemCount; - } - - int getItemViewType(int localPosition) { - return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition)); - } - - ViewHolder onCreateViewHolder( - ViewGroup parent, - int globalViewType) { - int localType = mViewTypeLookup.globalToLocal(globalViewType); - return adapter.onCreateViewHolder(parent, localType); - } - - void onBindViewHolder(ViewHolder viewHolder, int localPosition) { - adapter.bindViewHolder(viewHolder, localPosition); - } - - public long getItemId(int localPosition) { - long localItemId = adapter.getItemId(localPosition); - return mStableIdLookup.localToGlobal(localItemId); - } - - interface Callback { - void onChanged(@NonNull NestedAdapterWrapper wrapper); - - void onItemRangeChanged( - @NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, - int itemCount - ); - - void onItemRangeChanged( - @NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, - int itemCount, - @Nullable Object payload - ); - - void onItemRangeInserted( - @NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, - int itemCount); - - void onItemRangeRemoved( - @NonNull NestedAdapterWrapper nestedAdapterWrapper, - int positionStart, - int itemCount - ); - - void onItemRangeMoved( - @NonNull NestedAdapterWrapper nestedAdapterWrapper, - int fromPosition, - int toPosition - ); - - void onStateRestorationPolicyChanged(NestedAdapterWrapper nestedAdapterWrapper); - } - -} diff --git a/app/src/main/java/androidx/recyclerview/widget/OpReorderer.java b/app/src/main/java/androidx/recyclerview/widget/OpReorderer.java deleted file mode 100644 index 722960c82e..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/OpReorderer.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import java.util.List; - -class OpReorderer { - - final Callback mCallback; - - OpReorderer(Callback callback) { - mCallback = callback; - } - - void reorderOps(List ops) { - // since move operations breaks continuity, their effects on ADD/RM are hard to handle. - // we push them to the end of the list so that they can be handled easily. - int badMove; - while ((badMove = getLastMoveOutOfOrder(ops)) != -1) { - swapMoveOp(ops, badMove, badMove + 1); - } - } - - private void swapMoveOp(List list, int badMove, int next) { - final AdapterHelper.UpdateOp moveOp = list.get(badMove); - final AdapterHelper.UpdateOp nextOp = list.get(next); - switch (nextOp.cmd) { - case AdapterHelper.UpdateOp.REMOVE: - swapMoveRemove(list, badMove, moveOp, next, nextOp); - break; - case AdapterHelper.UpdateOp.ADD: - swapMoveAdd(list, badMove, moveOp, next, nextOp); - break; - case AdapterHelper.UpdateOp.UPDATE: - swapMoveUpdate(list, badMove, moveOp, next, nextOp); - break; - } - } - - void swapMoveRemove(List list, int movePos, AdapterHelper.UpdateOp moveOp, - int removePos, AdapterHelper.UpdateOp removeOp) { - AdapterHelper.UpdateOp extraRm = null; - // check if move is nulled out by remove - boolean revertedMove = false; - final boolean moveIsBackwards; - - if (moveOp.positionStart < moveOp.itemCount) { - moveIsBackwards = false; - if (removeOp.positionStart == moveOp.positionStart - && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) { - revertedMove = true; - } - } else { - moveIsBackwards = true; - if (removeOp.positionStart == moveOp.itemCount + 1 - && removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) { - revertedMove = true; - } - } - - // going in reverse, first revert the effect of add - if (moveOp.itemCount < removeOp.positionStart) { - removeOp.positionStart--; - } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) { - // move is removed. - removeOp.itemCount--; - moveOp.cmd = AdapterHelper.UpdateOp.REMOVE; - moveOp.itemCount = 1; - if (removeOp.itemCount == 0) { - list.remove(removePos); - mCallback.recycleUpdateOp(removeOp); - } - // no need to swap, it is already a remove - return; - } - - // now affect of add is consumed. now apply effect of first remove - if (moveOp.positionStart <= removeOp.positionStart) { - removeOp.positionStart++; - } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) { - final int remaining = removeOp.positionStart + removeOp.itemCount - - moveOp.positionStart; - extraRm = mCallback.obtainUpdateOp(AdapterHelper.UpdateOp.REMOVE, moveOp.positionStart + 1, remaining, null); - removeOp.itemCount = moveOp.positionStart - removeOp.positionStart; - } - - // if effects of move is reverted by remove, we are done. - if (revertedMove) { - list.set(movePos, removeOp); - list.remove(removePos); - mCallback.recycleUpdateOp(moveOp); - return; - } - - // now find out the new locations for move actions - if (moveIsBackwards) { - if (extraRm != null) { - if (moveOp.positionStart > extraRm.positionStart) { - moveOp.positionStart -= extraRm.itemCount; - } - if (moveOp.itemCount > extraRm.positionStart) { - moveOp.itemCount -= extraRm.itemCount; - } - } - if (moveOp.positionStart > removeOp.positionStart) { - moveOp.positionStart -= removeOp.itemCount; - } - if (moveOp.itemCount > removeOp.positionStart) { - moveOp.itemCount -= removeOp.itemCount; - } - } else { - if (extraRm != null) { - if (moveOp.positionStart >= extraRm.positionStart) { - moveOp.positionStart -= extraRm.itemCount; - } - if (moveOp.itemCount >= extraRm.positionStart) { - moveOp.itemCount -= extraRm.itemCount; - } - } - if (moveOp.positionStart >= removeOp.positionStart) { - moveOp.positionStart -= removeOp.itemCount; - } - if (moveOp.itemCount >= removeOp.positionStart) { - moveOp.itemCount -= removeOp.itemCount; - } - } - - list.set(movePos, removeOp); - if (moveOp.positionStart != moveOp.itemCount) { - list.set(removePos, moveOp); - } else { - list.remove(removePos); - } - if (extraRm != null) { - list.add(movePos, extraRm); - } - } - - private void swapMoveAdd(List list, int move, AdapterHelper.UpdateOp moveOp, int add, - AdapterHelper.UpdateOp addOp) { - int offset = 0; - // going in reverse, first revert the effect of add - if (moveOp.itemCount < addOp.positionStart) { - offset--; - } - if (moveOp.positionStart < addOp.positionStart) { - offset++; - } - if (addOp.positionStart <= moveOp.positionStart) { - moveOp.positionStart += addOp.itemCount; - } - if (addOp.positionStart <= moveOp.itemCount) { - moveOp.itemCount += addOp.itemCount; - } - addOp.positionStart += offset; - list.set(move, addOp); - list.set(add, moveOp); - } - - void swapMoveUpdate(List list, int move, AdapterHelper.UpdateOp moveOp, int update, - AdapterHelper.UpdateOp updateOp) { - AdapterHelper.UpdateOp extraUp1 = null; - AdapterHelper.UpdateOp extraUp2 = null; - // going in reverse, first revert the effect of add - if (moveOp.itemCount < updateOp.positionStart) { - updateOp.positionStart--; - } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) { - // moved item is updated. add an update for it - updateOp.itemCount--; - extraUp1 = mCallback.obtainUpdateOp(AdapterHelper.UpdateOp.UPDATE, moveOp.positionStart, 1, updateOp.payload); - } - // now affect of add is consumed. now apply effect of first remove - if (moveOp.positionStart <= updateOp.positionStart) { - updateOp.positionStart++; - } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) { - final int remaining = updateOp.positionStart + updateOp.itemCount - - moveOp.positionStart; - extraUp2 = mCallback.obtainUpdateOp( - AdapterHelper.UpdateOp.UPDATE, moveOp.positionStart + 1, remaining, - updateOp.payload); - updateOp.itemCount -= remaining; - } - list.set(update, moveOp); - if (updateOp.itemCount > 0) { - list.set(move, updateOp); - } else { - list.remove(move); - mCallback.recycleUpdateOp(updateOp); - } - if (extraUp1 != null) { - list.add(move, extraUp1); - } - if (extraUp2 != null) { - list.add(move, extraUp2); - } - } - - private int getLastMoveOutOfOrder(List list) { - boolean foundNonMove = false; - for (int i = list.size() - 1; i >= 0; i--) { - final AdapterHelper.UpdateOp op1 = list.get(i); - if (op1.cmd == AdapterHelper.UpdateOp.MOVE) { - if (foundNonMove) { - return i; - } - } else { - foundNonMove = true; - } - } - return -1; - } - - interface Callback { - - AdapterHelper.UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount, Object payload); - - void recycleUpdateOp(AdapterHelper.UpdateOp op); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/OrientationHelper.java b/app/src/main/java/androidx/recyclerview/widget/OrientationHelper.java deleted file mode 100644 index f94e0dd162..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/OrientationHelper.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.graphics.Rect; -import android.view.View; - -/** - * Helper class for LayoutManagers to abstract measurements depending on the View's orientation. - *

- * It is developed to easily support vertical and horizontal orientations in a LayoutManager but - * can also be used to abstract calls around view bounds and child measurements with margins and - * decorations. - * - * @see #createHorizontalHelper(RecyclerView.LayoutManager) - * @see #createVerticalHelper(RecyclerView.LayoutManager) - */ -public abstract class OrientationHelper { - - private static final int INVALID_SIZE = Integer.MIN_VALUE; - - protected final RecyclerView.LayoutManager mLayoutManager; - - public static final int HORIZONTAL = RecyclerView.HORIZONTAL; - - public static final int VERTICAL = RecyclerView.VERTICAL; - - private int mLastTotalSpace = INVALID_SIZE; - - final Rect mTmpRect = new Rect(); - - private OrientationHelper(RecyclerView.LayoutManager layoutManager) { - mLayoutManager = layoutManager; - } - - /** - * Returns the {@link RecyclerView.LayoutManager LayoutManager} that - * is associated with this OrientationHelper. - */ - public RecyclerView.LayoutManager getLayoutManager() { - return mLayoutManager; - } - - /** - * Call this method after onLayout method is complete if state is NOT pre-layout. - * This method records information like layout bounds that might be useful in the next layout - * calculations. - */ - public void onLayoutComplete() { - mLastTotalSpace = getTotalSpace(); - } - - /** - * Returns the layout space change between the previous layout pass and current layout pass. - *

- * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's - * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, - * RecyclerView.State)} method. - * - * @return The difference between the current total space and previous layout's total space. - * @see #onLayoutComplete() - */ - public int getTotalSpaceChange() { - return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace; - } - - /** - * Returns the start of the view including its decoration and margin. - *

- * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left - * decoration and 3px left margin, returned value will be 15px. - * - * @param view The view element to check - * @return The first pixel of the element - * @see #getDecoratedEnd(android.view.View) - */ - public abstract int getDecoratedStart(View view); - - /** - * Returns the end of the view including its decoration and margin. - *

- * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right - * decoration and 3px right margin, returned value will be 205. - * - * @param view The view element to check - * @return The last pixel of the element - * @see #getDecoratedStart(android.view.View) - */ - public abstract int getDecoratedEnd(View view); - - /** - * Returns the end of the View after its matrix transformations are applied to its layout - * position. - *

- * This method is useful when trying to detect the visible edge of a View. - *

- * It includes the decorations but does not include the margins. - * - * @param view The view whose transformed end will be returned - * @return The end of the View after its decor insets and transformation matrix is applied to - * its position - * - * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) - */ - public abstract int getTransformedEndWithDecoration(View view); - - /** - * Returns the start of the View after its matrix transformations are applied to its layout - * position. - *

- * This method is useful when trying to detect the visible edge of a View. - *

- * It includes the decorations but does not include the margins. - * - * @param view The view whose transformed start will be returned - * @return The start of the View after its decor insets and transformation matrix is applied to - * its position - * - * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) - */ - public abstract int getTransformedStartWithDecoration(View view); - - /** - * Returns the space occupied by this View in the current orientation including decorations and - * margins. - * - * @param view The view element to check - * @return Total space occupied by this view - * @see #getDecoratedMeasurementInOther(View) - */ - public abstract int getDecoratedMeasurement(View view); - - /** - * Returns the space occupied by this View in the perpendicular orientation including - * decorations and margins. - * - * @param view The view element to check - * @return Total space occupied by this view in the perpendicular orientation to current one - * @see #getDecoratedMeasurement(View) - */ - public abstract int getDecoratedMeasurementInOther(View view); - - /** - * Returns the start position of the layout after the start padding is added. - * - * @return The very first pixel we can draw. - */ - public abstract int getStartAfterPadding(); - - /** - * Returns the end position of the layout after the end padding is removed. - * - * @return The end boundary for this layout. - */ - public abstract int getEndAfterPadding(); - - /** - * Returns the end position of the layout without taking padding into account. - * - * @return The end boundary for this layout without considering padding. - */ - public abstract int getEnd(); - - /** - * Offsets all children's positions by the given amount. - * - * @param amount Value to add to each child's layout parameters - */ - public abstract void offsetChildren(int amount); - - /** - * Returns the total space to layout. This number is the difference between - * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}. - * - * @return Total space to layout children - */ - public abstract int getTotalSpace(); - - /** - * Offsets the child in this orientation. - * - * @param view View to offset - * @param offset offset amount - */ - public abstract void offsetChild(View view, int offset); - - /** - * Returns the padding at the end of the layout. For horizontal helper, this is the right - * padding and for vertical helper, this is the bottom padding. This method does not check - * whether the layout is RTL or not. - * - * @return The padding at the end of the layout. - */ - public abstract int getEndPadding(); - - /** - * Returns the MeasureSpec mode for the current orientation from the LayoutManager. - * - * @return The current measure spec mode. - * - * @see View.MeasureSpec - * @see RecyclerView.LayoutManager#getWidthMode() - * @see RecyclerView.LayoutManager#getHeightMode() - */ - public abstract int getMode(); - - /** - * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager. - * - * @return The current measure spec mode. - * - * @see View.MeasureSpec - * @see RecyclerView.LayoutManager#getWidthMode() - * @see RecyclerView.LayoutManager#getHeightMode() - */ - public abstract int getModeInOther(); - - /** - * Creates an OrientationHelper for the given LayoutManager and orientation. - * - * @param layoutManager LayoutManager to attach to - * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL} - * @return A new OrientationHelper - */ - public static OrientationHelper createOrientationHelper( - RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation) { - switch (orientation) { - case HORIZONTAL: - return createHorizontalHelper(layoutManager); - case VERTICAL: - return createVerticalHelper(layoutManager); - } - throw new IllegalArgumentException("invalid orientation"); - } - - /** - * Creates a horizontal OrientationHelper for the given LayoutManager. - * - * @param layoutManager The LayoutManager to attach to. - * @return A new OrientationHelper - */ - public static OrientationHelper createHorizontalHelper( - RecyclerView.LayoutManager layoutManager) { - return new OrientationHelper(layoutManager) { - @Override - public int getEndAfterPadding() { - return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight(); - } - - @Override - public int getEnd() { - return mLayoutManager.getWidth(); - } - - @Override - public void offsetChildren(int amount) { - mLayoutManager.offsetChildrenHorizontal(amount); - } - - @Override - public int getStartAfterPadding() { - return mLayoutManager.getPaddingLeft(); - } - - @Override - public int getDecoratedMeasurement(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin - + params.rightMargin; - } - - @Override - public int getDecoratedMeasurementInOther(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin - + params.bottomMargin; - } - - @Override - public int getDecoratedEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedRight(view) + params.rightMargin; - } - - @Override - public int getDecoratedStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedLeft(view) - params.leftMargin; - } - - @Override - public int getTransformedEndWithDecoration(View view) { - mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); - return mTmpRect.right; - } - - @Override - public int getTransformedStartWithDecoration(View view) { - mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); - return mTmpRect.left; - } - - @Override - public int getTotalSpace() { - return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() - - mLayoutManager.getPaddingRight(); - } - - @Override - public void offsetChild(View view, int offset) { - view.offsetLeftAndRight(offset); - } - - @Override - public int getEndPadding() { - return mLayoutManager.getPaddingRight(); - } - - @Override - public int getMode() { - return mLayoutManager.getWidthMode(); - } - - @Override - public int getModeInOther() { - return mLayoutManager.getHeightMode(); - } - }; - } - - /** - * Creates a vertical OrientationHelper for the given LayoutManager. - * - * @param layoutManager The LayoutManager to attach to. - * @return A new OrientationHelper - */ - public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) { - return new OrientationHelper(layoutManager) { - @Override - public int getEndAfterPadding() { - return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); - } - - @Override - public int getEnd() { - return mLayoutManager.getHeight(); - } - - @Override - public void offsetChildren(int amount) { - mLayoutManager.offsetChildrenVertical(amount); - } - - @Override - public int getStartAfterPadding() { - return mLayoutManager.getPaddingTop(); - } - - @Override - public int getDecoratedMeasurement(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin - + params.bottomMargin; - } - - @Override - public int getDecoratedMeasurementInOther(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin - + params.rightMargin; - } - - @Override - public int getDecoratedEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin; - } - - @Override - public int getDecoratedStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return mLayoutManager.getDecoratedTop(view) - params.topMargin; - } - - @Override - public int getTransformedEndWithDecoration(View view) { - mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); - return mTmpRect.bottom; - } - - @Override - public int getTransformedStartWithDecoration(View view) { - mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); - return mTmpRect.top; - } - - @Override - public int getTotalSpace() { - return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() - - mLayoutManager.getPaddingBottom(); - } - - @Override - public void offsetChild(View view, int offset) { - view.offsetTopAndBottom(offset); - } - - @Override - public int getEndPadding() { - return mLayoutManager.getPaddingBottom(); - } - - @Override - public int getMode() { - return mLayoutManager.getHeightMode(); - } - - @Override - public int getModeInOther() { - return mLayoutManager.getWidthMode(); - } - }; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java b/app/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java deleted file mode 100644 index 3d97cdf552..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/PagerSnapHelper.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.graphics.PointF; -import android.util.DisplayMetrics; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Implementation of the {@link SnapHelper} supporting pager style snapping in either vertical or - * horizontal orientation. - * - *

- * - * PagerSnapHelper can help achieve a similar behavior to - * {@link androidx.viewpager.widget.ViewPager}. Set both {@link RecyclerView} and the items of the - * {@link RecyclerView.Adapter} to have - * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} height and width and then attach - * PagerSnapHelper to the {@link RecyclerView} using {@link #attachToRecyclerView(RecyclerView)}. - */ -public class PagerSnapHelper extends SnapHelper { - private static final int MAX_SCROLL_ON_FLING_DURATION = 100; // ms - - // Orientation helpers are lazily created per LayoutManager. - @Nullable - private OrientationHelper mVerticalHelper; - @Nullable - private OrientationHelper mHorizontalHelper; - - @Nullable - @Override - public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, - @NonNull View targetView) { - int[] out = new int[2]; - if (layoutManager.canScrollHorizontally()) { - out[0] = distanceToCenter(targetView, - getHorizontalHelper(layoutManager)); - } else { - out[0] = 0; - } - - if (layoutManager.canScrollVertically()) { - out[1] = distanceToCenter(targetView, - getVerticalHelper(layoutManager)); - } else { - out[1] = 0; - } - return out; - } - - @Nullable - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public View findSnapView(RecyclerView.LayoutManager layoutManager) { - if (layoutManager.canScrollVertically()) { - return findCenterView(layoutManager, getVerticalHelper(layoutManager)); - } else if (layoutManager.canScrollHorizontally()) { - return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); - } - return null; - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, - int velocityY) { - final int itemCount = layoutManager.getItemCount(); - if (itemCount == 0) { - return RecyclerView.NO_POSITION; - } - - final OrientationHelper orientationHelper = getOrientationHelper(layoutManager); - if (orientationHelper == null) { - return RecyclerView.NO_POSITION; - } - - // A child that is exactly in the center is eligible for both before and after - View closestChildBeforeCenter = null; - int distanceBefore = Integer.MIN_VALUE; - View closestChildAfterCenter = null; - int distanceAfter = Integer.MAX_VALUE; - - // Find the first view before the center, and the first view after the center - final int childCount = layoutManager.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = layoutManager.getChildAt(i); - if (child == null) { - continue; - } - final int distance = distanceToCenter(child, orientationHelper); - - if (distance <= 0 && distance > distanceBefore) { - // Child is before the center and closer then the previous best - distanceBefore = distance; - closestChildBeforeCenter = child; - } - if (distance >= 0 && distance < distanceAfter) { - // Child is after the center and closer then the previous best - distanceAfter = distance; - closestChildAfterCenter = child; - } - } - - // Return the position of the first child from the center, in the direction of the fling - final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY); - if (forwardDirection && closestChildAfterCenter != null) { - return layoutManager.getPosition(closestChildAfterCenter); - } else if (!forwardDirection && closestChildBeforeCenter != null) { - return layoutManager.getPosition(closestChildBeforeCenter); - } - - // There is no child in the direction of the fling. Either it doesn't exist (start/end of - // the list), or it is not yet attached (very rare case when children are larger then the - // viewport). Extrapolate from the child that is visible to get the position of the view to - // snap to. - View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter; - if (visibleView == null) { - return RecyclerView.NO_POSITION; - } - int visiblePosition = layoutManager.getPosition(visibleView); - int snapToPosition = visiblePosition - + (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1); - - if (snapToPosition < 0 || snapToPosition >= itemCount) { - return RecyclerView.NO_POSITION; - } - return snapToPosition; - } - - private boolean isForwardFling(RecyclerView.LayoutManager layoutManager, int velocityX, - int velocityY) { - if (layoutManager.canScrollHorizontally()) { - return velocityX > 0; - } else { - return velocityY > 0; - } - } - - private boolean isReverseLayout(RecyclerView.LayoutManager layoutManager) { - final int itemCount = layoutManager.getItemCount(); - if ((layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { - RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = - (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; - PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); - if (vectorForEnd != null) { - return vectorForEnd.x < 0 || vectorForEnd.y < 0; - } - } - return false; - } - - @Nullable - @Override - protected RecyclerView.SmoothScroller createScroller( - @NonNull RecyclerView.LayoutManager layoutManager) { - if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { - return null; - } - return new LinearSmoothScroller(mRecyclerView.getContext()) { - @Override - protected void onTargetFound(@NonNull View targetView, - @NonNull RecyclerView.State state, @NonNull Action action) { - int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), - targetView); - final int dx = snapDistances[0]; - final int dy = snapDistances[1]; - final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); - if (time > 0) { - action.update(dx, dy, time, mDecelerateInterpolator); - } - } - - @Override - protected float calculateSpeedPerPixel(@NonNull DisplayMetrics displayMetrics) { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; - } - - @Override - protected int calculateTimeForScrolling(int dx) { - return Math.min(MAX_SCROLL_ON_FLING_DURATION, super.calculateTimeForScrolling(dx)); - } - }; - } - - private int distanceToCenter(@NonNull View targetView, OrientationHelper helper) { - final int childCenter = helper.getDecoratedStart(targetView) - + (helper.getDecoratedMeasurement(targetView) / 2); - final int containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; - return childCenter - containerCenter; - } - - /** - * Return the child view that is currently closest to the center of this parent. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. - * - * @return the child view that is currently closest to the center of this parent. - */ - @Nullable - private View findCenterView(RecyclerView.LayoutManager layoutManager, - OrientationHelper helper) { - int childCount = layoutManager.getChildCount(); - if (childCount == 0) { - return null; - } - - View closestChild = null; - final int center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; - int absClosest = Integer.MAX_VALUE; - - for (int i = 0; i < childCount; i++) { - final View child = layoutManager.getChildAt(i); - int childCenter = helper.getDecoratedStart(child) - + (helper.getDecoratedMeasurement(child) / 2); - int absDistance = Math.abs(childCenter - center); - - /* if child center is closer than previous closest, set it as closest */ - if (absDistance < absClosest) { - absClosest = absDistance; - closestChild = child; - } - } - return closestChild; - } - - @Nullable - private OrientationHelper getOrientationHelper(RecyclerView.LayoutManager layoutManager) { - if (layoutManager.canScrollVertically()) { - return getVerticalHelper(layoutManager); - } else if (layoutManager.canScrollHorizontally()) { - return getHorizontalHelper(layoutManager); - } else { - return null; - } - } - - @NonNull - private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { - if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { - mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); - } - return mVerticalHelper; - } - - @NonNull - private OrientationHelper getHorizontalHelper( - @NonNull RecyclerView.LayoutManager layoutManager) { - if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { - mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); - } - return mHorizontalHelper; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/app/src/main/java/androidx/recyclerview/widget/RecyclerView.java deleted file mode 100644 index 9265d56d01..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/RecyclerView.java +++ /dev/null @@ -1,14454 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package androidx.recyclerview.widget; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; -import static androidx.core.util.Preconditions.checkArgument; -import static androidx.core.view.ViewCompat.TYPE_NON_TOUCH; -import static androidx.core.view.ViewCompat.TYPE_TOUCH; - -import android.animation.LayoutTransition; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.Observable; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; -import android.hardware.SensorManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseArray; -import android.view.Display; -import android.view.FocusFinder; -import android.view.InputDevice; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.Interpolator; -import android.widget.EdgeEffect; -import android.widget.LinearLayout; -import android.widget.OverScroller; - -import androidx.annotation.CallSuper; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.Px; -import androidx.annotation.RestrictTo; -import androidx.annotation.VisibleForTesting; -import androidx.core.os.TraceCompat; -import androidx.core.util.Preconditions; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.InputDeviceCompat; -import androidx.core.view.MotionEventCompat; -import androidx.core.view.NestedScrollingChild2; -import androidx.core.view.NestedScrollingChild3; -import androidx.core.view.NestedScrollingChildHelper; -import androidx.core.view.ScrollingView; -import androidx.core.view.ViewCompat; -import androidx.core.view.ViewConfigurationCompat; -import androidx.core.view.accessibility.AccessibilityEventCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.widget.EdgeEffectCompat; -import androidx.customview.poolingcontainer.PoolingContainer; -import androidx.customview.poolingcontainer.PoolingContainerListener; -import androidx.customview.view.AbsSavedState; -import androidx.recyclerview.R; -import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Set; - -/** - * A flexible view for providing a limited window into a large data set. - * - *

Glossary of terms:

- * - *
    - *
  • Adapter: A subclass of {@link Adapter} responsible for providing views - * that represent items in a data set.
  • - *
  • Position: The position of a data item within an Adapter.
  • - *
  • Index: The index of an attached child view as used in a call to - * {@link ViewGroup#getChildAt}. Contrast with Position.
  • - *
  • Binding: The process of preparing a child view to display data corresponding - * to a position within the adapter.
  • - *
  • Recycle (view): A view previously used to display data for a specific adapter - * position may be placed in a cache for later reuse to display the same type of data again - * later. This can drastically improve performance by skipping initial layout inflation - * or construction.
  • - *
  • Scrap (view): A child view that has entered into a temporarily detached - * state during layout. Scrap views may be reused without becoming fully detached - * from the parent RecyclerView, either unmodified if no rebinding is required or modified - * by the adapter if the view was considered dirty.
  • - *
  • Dirty (view): A child view that must be rebound by the adapter before - * being displayed.
  • - *
- * - *

Positions in RecyclerView:

- *

- * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and - * {@link LayoutManager} to be able to detect data set changes in batches during a layout - * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. - * It also helps with performance because all view bindings happen at the same time and unnecessary - * bindings are avoided. - *

- * For this reason, there are two types of position related methods in RecyclerView: - *

    - *
  • layout position: Position of an item in the latest layout calculation. This is the - * position from the LayoutManager's perspective.
  • - *
  • adapter position: Position of an item in the adapter. This is the position from - * the Adapter's perspective.
  • - *
- *

- * These two positions are the same except the time between dispatching adapter.notify* - * events and calculating the updated layout. - *

- * Methods that return or receive *LayoutPosition* use position as of the latest - * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, - * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the - * last layout calculation. You can rely on these positions to be consistent with what user is - * currently seeing on the screen. For example, if you have a list of items on the screen and user - * asks for the 5th element, you should use these methods as they'll match what user - * is seeing. - *

- * The other set of position related methods are in the form of - * *AdapterPosition*. (e.g. {@link ViewHolder#getAbsoluteAdapterPosition()}, - * {@link ViewHolder#getBindingAdapterPosition()}, - * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to - * work with up-to-date adapter positions even if they may not have been reflected to layout yet. - * For example, if you want to access the item in the adapter on a ViewHolder click, you should use - * {@link ViewHolder#getBindingAdapterPosition()}. Beware that these methods may not be able to - * calculate adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new - * layout has not yet been calculated. For this reasons, you should carefully handle - * {@link #NO_POSITION} or null results from these methods. - *

- * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when - * writing an {@link Adapter}, you probably want to use adapter positions. - *

- *

Presenting Dynamic Data

- * To display updatable data in a RecyclerView, your adapter needs to signal inserts, moves, and - * deletions to RecyclerView. You can build this yourself by manually calling - * {@code adapter.notify*} methods when content changes, or you can use one of the easier solutions - * RecyclerView provides: - *

- *

List diffing with DiffUtil

- * If your RecyclerView is displaying a list that is re-fetched from scratch for each update (e.g. - * from the network, or from a database), {@link DiffUtil} can calculate the difference between - * versions of the list. {@code DiffUtil} takes both lists as input and computes the difference, - * which can be passed to RecyclerView to trigger minimal animations and updates to keep your UI - * performant, and animations meaningful. This approach requires that each list is represented in - * memory with immutable content, and relies on receiving updates as new instances of lists. This - * approach is also ideal if your UI layer doesn't implement sorting, it just presents the data in - * the order it's given. - *

- * The best part of this approach is that it extends to any arbitrary changes - item updates, - * moves, addition and removal can all be computed and handled the same way. Though you do have - * to keep two copies of the list in memory while diffing, and must avoid mutating them, it's - * possible to share unmodified elements between list versions. - *

- * There are three primary ways to do this for RecyclerView. We recommend you start with - * {@link ListAdapter}, the higher-level API that builds in {@link List} diffing on a background - * thread, with minimal code. {@link AsyncListDiffer} also provides this behavior, but without - * defining an Adapter to subclass. If you want more control, {@link DiffUtil} is the lower-level - * API you can use to compute the diffs yourself. Each approach allows you to specify how diffs - * should be computed based on item data. - *

- *

List mutation with SortedList

- * If your RecyclerView receives updates incrementally, e.g. item X is inserted, or item Y is - * removed, you can use {@link SortedList} to manage your list. You define how to order items, - * and it will automatically trigger update signals that RecyclerView can use. SortedList works - * if you only need to handle insert and remove events, and has the benefit that you only ever - * need to have a single copy of the list in memory. It can also compute differences with - * {@link SortedList#replaceAll(Object[])}, but this method is more limited than the list diffing - * behavior above. - *

- *

Paging Library

- * The Paging - * library extends the diff-based approach to additionally support paged loading. It provides - * the {@link androidx.paging.PagedList} class that operates as a self-loading list, provided a - * source of data like a database, or paginated network API. It provides convenient list diffing - * support out of the box, similar to {@code ListAdapter} and {@code AsyncListDiffer}. For more - * information about the Paging library, see the - * library - * documentation. - * - * {@link androidx.recyclerview.R.attr#layoutManager} - */ -public class RecyclerView extends ViewGroup implements ScrollingView, - NestedScrollingChild2, NestedScrollingChild3 { - - static final String TAG = "RecyclerView"; - - static boolean sDebugAssertionsEnabled = false; - static boolean sVerboseLoggingEnabled = false; - - static final boolean VERBOSE_TRACING = false; - - private static final int[] NESTED_SCROLLING_ATTRS = - {16843830 /* android.R.attr.nestedScrollingEnabled */}; - - /** - * The following are copied from OverScroller to determine how far a fling will go. - */ - private static final float SCROLL_FRICTION = 0.015f; - private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) - private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); - private final float mPhysicalCoef; - - /** - * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if - * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by - * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler - * recursively traverses itemView and invalidates display list for each ViewGroup that matches - * this criteria. - */ - static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 - || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; - /** - * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, - * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to - * 0 when mode is unspecified. - */ - static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; - - static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16; - - /** - * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to - * RenderThread but before the next frame begins. We schedule prefetch work in this window. - */ - static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; - - /** - * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction. - * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT. - */ - private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15; - - /** - * on API 15-, a focused child can still be considered a focused child of RV even after - * it's being removed or its focusable flag is set to false. This is because when this focused - * child is detached, the reference to this child is not removed in clearFocus. API 16 and above - * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus - * to request focus on a new child, which will clear the focus on the old (detached) child as a - * side-effect. - */ - private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15; - - /** - * When flinging the stretch towards scrolling content, it should destretch quicker than the - * fling would normally do. The visual effect of flinging the stretch looks strange as little - * appears to happen at first and then when the stretch disappears, the content starts - * scrolling quickly. - */ - private static final float FLING_DESTRETCH_FACTOR = 4f; - - static final boolean DISPATCH_TEMP_DETACH = false; - - /** @hide */ - @RestrictTo(LIBRARY_GROUP_PREFIX) - @IntDef({HORIZONTAL, VERTICAL}) - @Retention(RetentionPolicy.SOURCE) - public @interface Orientation { - } - - public static final int HORIZONTAL = LinearLayout.HORIZONTAL; - public static final int VERTICAL = LinearLayout.VERTICAL; - - static final int DEFAULT_ORIENTATION = VERTICAL; - public static final int NO_POSITION = -1; - public static final long NO_ID = -1; - public static final int INVALID_TYPE = -1; - - /** - * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates - * that the RecyclerView should use the standard touch slop for smooth, - * continuous scrolling. - */ - public static final int TOUCH_SLOP_DEFAULT = 0; - - /** - * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates - * that the RecyclerView should use the standard touch slop for scrolling - * widgets that snap to a page or other coarse-grained barrier. - */ - public static final int TOUCH_SLOP_PAGING = 1; - - /** - * Constant that represents that a duration has not been defined. - */ - public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; - - static final int MAX_SCROLL_DURATION = 2000; - - /** - * RecyclerView is calculating a scroll. - * If there are too many of these in Systrace, some Views inside RecyclerView might be causing - * it. Try to avoid using EditText, focusable views or handle them with care. - */ - static final String TRACE_SCROLL_TAG = "RV Scroll"; - - /** - * OnLayout has been called by the View system. - * If this shows up too many times in Systrace, make sure the children of RecyclerView do not - * update themselves directly. This will cause a full re-layout but when it happens via the - * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation. - */ - private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout"; - - /** - * NotifyDataSetChanged or equal has been called. - * If this is taking a long time, try sending granular notify adapter changes instead of just - * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter - * might help. - */ - private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate"; - - /** - * RecyclerView is doing a layout for partial adapter updates (we know what has changed) - * If this is taking a long time, you may have dispatched too many Adapter updates causing too - * many Views being rebind. Make sure all are necessary and also prefer using notify*Range - * methods. - */ - private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate"; - - /** - * RecyclerView is rebinding a View. - * If this is taking a lot of time, consider optimizing your layout or make sure you are not - * doing extra operations in onBindViewHolder call. - */ - static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; - - /** - * RecyclerView is attempting to pre-populate off screen views. - */ - static final String TRACE_PREFETCH_TAG = "RV Prefetch"; - - /** - * RecyclerView is attempting to pre-populate off screen itemviews within an off screen - * RecyclerView. - */ - static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch"; - - /** - * RecyclerView is creating a new View. - * If too many of these present in Systrace: - * - There might be a problem in Recycling (e.g. custom Animations that set transient state and - * prevent recycling or ItemAnimator not implementing the contract properly. ({@link - * > Adapter#onFailedToRecycleView(ViewHolder)}) - * - * - There might be too many item view types. - * > Try merging them - * - * - There might be too many itemChange animations and not enough space in RecyclerPool. - * >Try increasing your pool size and item cache size. - */ - static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; - private static final Class[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = - new Class[]{Context.class, AttributeSet.class, int.class, int.class}; - - /** - * Enable internal assertions about RecyclerView's state and throw exceptions if the - * assertions are violated. - *

- * This is primarily intended to diagnose problems with RecyclerView, and - * should not be enabled in production unless you have a specific reason to - * do so. - *

- * Enabling this may negatively affect performance and/or stability. - * - * @param debugAssertionsEnabled true to enable assertions; false to disable them - */ - public static void setDebugAssertionsEnabled(boolean debugAssertionsEnabled) { - RecyclerView.sDebugAssertionsEnabled = debugAssertionsEnabled; - } - - /** - * Enable verbose logging within RecyclerView itself. - *

- * Enabling this may negatively affect performance and reduce the utility of logcat due to - * high-volume logging. This generally should not be enabled in production - * unless you have a specific reason for doing so. - * - * @param verboseLoggingEnabled true to enable logging; false to disable it - */ - public static void setVerboseLoggingEnabled(boolean verboseLoggingEnabled) { - RecyclerView.sVerboseLoggingEnabled = verboseLoggingEnabled; - } - - private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); - - final Recycler mRecycler = new Recycler(); - - SavedState mPendingSavedState; - - /** - * Handles adapter updates - */ - AdapterHelper mAdapterHelper; - - /** - * Handles abstraction between LayoutManager children and RecyclerView children - */ - ChildHelper mChildHelper; - - /** - * Keeps data about views to be used for animations - */ - final ViewInfoStore mViewInfoStore = new ViewInfoStore(); - - /** - * Prior to L, there is no way to query this variable which is why we override the setter and - * track it here. - */ - boolean mClipToPadding; - - /** - * Note: this Runnable is only ever posted if: - * 1) We've been through first layout - * 2) We know we have a fixed size (mHasFixedSize) - * 3) We're attached - */ - final Runnable mUpdateChildViewsRunnable = new Runnable() { - @Override - public void run() { - if (!mFirstLayoutComplete || isLayoutRequested()) { - // a layout request will happen, we should not do layout here. - return; - } - if (!mIsAttached) { - requestLayout(); - // if we are not attached yet, mark us as requiring layout and skip - return; - } - if (mLayoutSuppressed) { - mLayoutWasDefered = true; - return; //we'll process updates when ice age ends. - } - consumePendingUpdateOperations(); - } - }; - - final Rect mTempRect = new Rect(); - private final Rect mTempRect2 = new Rect(); - final RectF mTempRectF = new RectF(); - Adapter mAdapter; - @VisibleForTesting - LayoutManager mLayout; - // TODO: Remove this once setRecyclerListener has been removed. - RecyclerListener mRecyclerListener; - // default access to avoid the need for synthetic accessors for Recycler inner class. - final List mRecyclerListeners = new ArrayList<>(); - final ArrayList mItemDecorations = new ArrayList<>(); - private final ArrayList mOnItemTouchListeners = - new ArrayList<>(); - private OnItemTouchListener mInterceptingOnItemTouchListener; - boolean mIsAttached; - boolean mHasFixedSize; - boolean mEnableFastScroller; - @VisibleForTesting - boolean mFirstLayoutComplete; - - /** - * The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of - * calls to {@link #startInterceptRequestLayout()} - number of calls to - * {@link #stopInterceptRequestLayout(boolean)} . This is used to signal whether we - * should defer layout operations caused by layout requests from children of - * {@link RecyclerView}. - */ - private int mInterceptRequestLayoutDepth = 0; - - /** - * True if a call to requestLayout was intercepted and prevented from executing like normal and - * we plan on continuing with normal execution later. - */ - boolean mLayoutWasDefered; - - boolean mLayoutSuppressed; - private boolean mIgnoreMotionEventTillDown; - - // binary OR of change events that were eaten during a layout or scroll. - private int mEatenAccessibilityChangeFlags; - boolean mAdapterUpdateDuringMeasure; - - private final AccessibilityManager mAccessibilityManager; - private List mOnChildAttachStateListeners; - - /** - * True after an event occurs that signals that the entire data set has changed. In that case, - * we cannot run any animations since we don't know what happened until layout. - * - * Attached items are invalid until next layout, at which point layout will animate/replace - * items as necessary, building up content from the (effectively) new adapter from scratch. - * - * Cached items must be discarded when setting this to true, so that the cache may be freely - * used by prefetching until the next layout occurs. - * - * @see #processDataSetCompletelyChanged(boolean) - */ - boolean mDataSetHasChangedAfterLayout = false; - - /** - * True after the data set has completely changed and - * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent - * measure/layout. - * - * @see #processDataSetCompletelyChanged(boolean) - */ - boolean mDispatchItemsChangedEvent = false; - - /** - * This variable is incremented during a dispatchLayout and/or scroll. - * Some methods should not be called during these periods (e.g. adapter data change). - * Doing so will create hard to find bugs so we better check it and throw an exception. - * - * @see #assertInLayoutOrScroll(String) - * @see #assertNotInLayoutOrScroll(String) - */ - private int mLayoutOrScrollCounter = 0; - - /** - * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception - * (for API compatibility). - *

- * It is a bad practice for a developer to update the data in a scroll callback since it is - * potentially called during a layout. - */ - private int mDispatchScrollCounter = 0; - - @NonNull - private EdgeEffectFactory mEdgeEffectFactory = sDefaultEdgeEffectFactory; - private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; - - ItemAnimator mItemAnimator = new DefaultItemAnimator(); - - private static final int INVALID_POINTER = -1; - - /** - * The RecyclerView is not currently scrolling. - * - * @see #getScrollState() - */ - public static final int SCROLL_STATE_IDLE = 0; - - /** - * The RecyclerView is currently being dragged by outside input such as user touch input. - * - * @see #getScrollState() - */ - public static final int SCROLL_STATE_DRAGGING = 1; - - /** - * The RecyclerView is currently animating to a final position while not under - * outside control. - * - * @see #getScrollState() - */ - public static final int SCROLL_STATE_SETTLING = 2; - - static final long FOREVER_NS = Long.MAX_VALUE; - - // Touch/scrolling handling - - private int mScrollState = SCROLL_STATE_IDLE; - private int mScrollPointerId = INVALID_POINTER; - private VelocityTracker mVelocityTracker; - private int mInitialTouchX; - private int mInitialTouchY; - private int mLastTouchX; - private int mLastTouchY; - private int mTouchSlop; - private OnFlingListener mOnFlingListener; - private final int mMinFlingVelocity; - private final int mMaxFlingVelocity; - - // This value is used when handling rotary encoder generic motion events. - private float mScaledHorizontalScrollFactor = Float.MIN_VALUE; - private float mScaledVerticalScrollFactor = Float.MIN_VALUE; - - private boolean mPreserveFocusAfterLayout = true; - - final ViewFlinger mViewFlinger = new ViewFlinger(); - - GapWorker mGapWorker; - GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = - ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; - - final State mState = new State(); - - private OnScrollListener mScrollListener; - private List mScrollListeners; - - // For use in item animations - boolean mItemsAddedOrRemoved = false; - boolean mItemsChanged = false; - private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = - new ItemAnimatorRestoreListener(); - boolean mPostedAnimatorRunner = false; - RecyclerViewAccessibilityDelegate mAccessibilityDelegate; - private ChildDrawingOrderCallback mChildDrawingOrderCallback; - - // simple array to keep min and max child position during a layout calculation - // preserved not to create a new one in each layout pass - private final int[] mMinMaxLayoutPositions = new int[2]; - - private NestedScrollingChildHelper mScrollingChildHelper; - private final int[] mScrollOffset = new int[2]; - private final int[] mNestedOffsets = new int[2]; - - // Reusable int array to be passed to method calls that mutate it in order to "return" two ints. - final int[] mReusableIntPair = new int[2]; - - /** - * These are views that had their a11y importance changed during a layout. We defer these events - * until the end of the layout because a11y service may make sync calls back to the RV while - * the View's state is undefined. - */ - @VisibleForTesting - final List mPendingAccessibilityImportanceChange = new ArrayList<>(); - - private Runnable mItemAnimatorRunner = new Runnable() { - @Override - public void run() { - if (mItemAnimator != null) { - mItemAnimator.runPendingAnimations(); - } - mPostedAnimatorRunner = false; - } - }; - - static final Interpolator sQuinticInterpolator = new Interpolator() { - @Override - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - static final StretchEdgeEffectFactory sDefaultEdgeEffectFactory = - new StretchEdgeEffectFactory(); - - // These fields are only used to track whether we need to layout and measure RV children in - // onLayout. - // - // We track this information because there is an optimized path such that when - // LayoutManager#isAutoMeasureEnabled() returns true and we are measured with - // MeasureSpec.EXACTLY in both dimensions, we skip measuring and layout children till the - // layout phase. - // - // However, there are times when we are first measured with something other than - // MeasureSpec.EXACTLY in both dimensions, in which case we measure and layout children during - // onMeasure. Then if we are measured again with EXACTLY, and we skip measurement, we will - // get laid out with a different size than we were last aware of being measured with. If - // that happens and we don't check for it, we may not remeasure children, which would be a bug. - // - // mLastAutoMeasureNonExactMeasureResult tracks our last known measurements in this case, and - // mLastAutoMeasureSkippedDueToExact tracks whether or not we skipped. So, whenever we - // layout, we can see if our last known measurement information is different from our actual - // laid out size, and if it is, only then do we remeasure and relayout children. - private boolean mLastAutoMeasureSkippedDueToExact; - private int mLastAutoMeasureNonExactMeasuredWidth = 0; - private int mLastAutoMeasureNonExactMeasuredHeight = 0; - - /** - * The callback to convert view info diffs into animations. - */ - private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = - new ViewInfoStore.ProcessCallback() { - @Override - public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, - @Nullable ItemHolderInfo postInfo) { - mRecycler.unscrapView(viewHolder); - animateDisappearance(viewHolder, info, postInfo); - } - - @Override - public void processAppeared(ViewHolder viewHolder, - ItemHolderInfo preInfo, ItemHolderInfo info) { - animateAppearance(viewHolder, preInfo, info); - } - - @Override - public void processPersistent(ViewHolder viewHolder, - @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { - viewHolder.setIsRecyclable(false); - if (mDataSetHasChangedAfterLayout) { - // since it was rebound, use change instead as we'll be mapping them from - // stable ids. If stable ids were false, we would not be running any - // animations - if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, - postInfo)) { - postAnimationRunner(); - } - } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { - postAnimationRunner(); - } - } - - @Override - public void unused(ViewHolder viewHolder) { - mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); - } - }; - - public RecyclerView(@NonNull Context context) { - this(context, null); - } - - public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, R.attr.recyclerViewStyle); - } - - public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setScrollContainer(true); - setFocusableInTouchMode(true); - - final ViewConfiguration vc = ViewConfiguration.get(context); - mTouchSlop = vc.getScaledTouchSlop(); - mScaledHorizontalScrollFactor = - ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); - mScaledVerticalScrollFactor = - ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); - mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); - mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); - final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; - mPhysicalCoef = SensorManager.GRAVITY_EARTH // g (m/s^2) - * 39.37f // inch/meter - * ppi - * 0.84f; // look and feel tuning - setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); - - mItemAnimator.setListener(mItemAnimatorListener); - initAdapterManager(); - initChildrenHelper(); - initAutofill(); - // If not explicitly specified this view is important for accessibility. - if (ViewCompat.getImportantForAccessibility(this) - == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - ViewCompat.setImportantForAccessibility(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - mAccessibilityManager = (AccessibilityManager) getContext() - .getSystemService(Context.ACCESSIBILITY_SERVICE); - setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, - defStyleAttr, 0); - - ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.RecyclerView, - attrs, a, defStyleAttr, 0); - String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); - int descendantFocusability = a.getInt( - R.styleable.RecyclerView_android_descendantFocusability, -1); - if (descendantFocusability == -1) { - setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - } - mClipToPadding = a.getBoolean(R.styleable.RecyclerView_android_clipToPadding, true); - mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); - if (mEnableFastScroller) { - StateListDrawable verticalThumbDrawable = (StateListDrawable) a - .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); - Drawable verticalTrackDrawable = a - .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); - StateListDrawable horizontalThumbDrawable = (StateListDrawable) a - .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); - Drawable horizontalTrackDrawable = a - .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); - initFastScroller(verticalThumbDrawable, verticalTrackDrawable, - horizontalThumbDrawable, horizontalTrackDrawable); - } - a.recycle(); - - // Create the layoutManager if specified. - createLayoutManager(context, layoutManagerName, attrs, defStyleAttr, 0); - - boolean nestedScrollingEnabled = true; - if (Build.VERSION.SDK_INT >= 21) { - a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, - defStyleAttr, 0); - ViewCompat.saveAttributeDataForStyleable(this, - context, NESTED_SCROLLING_ATTRS, attrs, a, defStyleAttr, 0); - nestedScrollingEnabled = a.getBoolean(0, true); - a.recycle(); - } - // Re-set whether nested scrolling is enabled so that it is set on all API levels - setNestedScrollingEnabled(nestedScrollingEnabled); - PoolingContainer.setPoolingContainer(this, true); - } - - /** - * Label appended to all public exception strings, used to help find which RV in an app is - * hitting an exception. - */ - String exceptionLabel() { - return " " + super.toString() - + ", adapter:" + mAdapter - + ", layout:" + mLayout - + ", context:" + getContext(); - } - - /** - * If not explicitly specified, this view and its children don't support autofill. - *

- * This is done because autofill's means of uniquely identifying views doesn't work out of the - * box with View recycling. - */ - @SuppressLint("InlinedApi") - private void initAutofill() { - if (ViewCompat.getImportantForAutofill(this) == View.IMPORTANT_FOR_AUTOFILL_AUTO) { - ViewCompat.setImportantForAutofill(this, - View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS); - } - } - - /** - * Returns the accessibility delegate compatibility implementation used by the RecyclerView. - * - * @return An instance of AccessibilityDelegateCompat used by RecyclerView - */ - @Nullable - public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { - return mAccessibilityDelegate; - } - - /** - * Sets the accessibility delegate compatibility implementation used by RecyclerView. - * - * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. - */ - public void setAccessibilityDelegateCompat( - @Nullable RecyclerViewAccessibilityDelegate accessibilityDelegate) { - mAccessibilityDelegate = accessibilityDelegate; - ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); - } - - @Override - public CharSequence getAccessibilityClassName() { - return "androidx.recyclerview.widget.RecyclerView"; - } - - /** - * Instantiate and set a LayoutManager, if specified in the attributes. - */ - private void createLayoutManager(Context context, String className, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - if (className != null) { - className = className.trim(); - if (!className.isEmpty()) { - className = getFullClassName(context, className); - try { - ClassLoader classLoader; - if (isInEditMode()) { - // Stupid layoutlib cannot handle simple class loaders. - classLoader = this.getClass().getClassLoader(); - } else { - classLoader = context.getClassLoader(); - } - Class layoutManagerClass = - Class.forName(className, false, classLoader) - .asSubclass(LayoutManager.class); - Constructor constructor; - Object[] constructorArgs = null; - try { - constructor = layoutManagerClass - .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE); - constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes}; - } catch (NoSuchMethodException e) { - try { - constructor = layoutManagerClass.getConstructor(); - } catch (NoSuchMethodException e1) { - e1.initCause(e); - throw new IllegalStateException(attrs.getPositionDescription() - + ": Error creating LayoutManager " + className, e1); - } - } - constructor.setAccessible(true); - setLayoutManager(constructor.newInstance(constructorArgs)); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(attrs.getPositionDescription() - + ": Unable to find LayoutManager " + className, e); - } catch (InvocationTargetException e) { - throw new IllegalStateException(attrs.getPositionDescription() - + ": Could not instantiate the LayoutManager: " + className, e); - } catch (InstantiationException e) { - throw new IllegalStateException(attrs.getPositionDescription() - + ": Could not instantiate the LayoutManager: " + className, e); - } catch (IllegalAccessException e) { - throw new IllegalStateException(attrs.getPositionDescription() - + ": Cannot access non-public constructor " + className, e); - } catch (ClassCastException e) { - throw new IllegalStateException(attrs.getPositionDescription() - + ": Class is not a LayoutManager " + className, e); - } - } - } - } - - private String getFullClassName(Context context, String className) { - if (className.charAt(0) == '.') { - return context.getPackageName() + className; - } - if (className.contains(".")) { - return className; - } - return RecyclerView.class.getPackage().getName() + '.' + className; - } - - private void initChildrenHelper() { - mChildHelper = new ChildHelper(new ChildHelper.Callback() { - @Override - public int getChildCount() { - return RecyclerView.this.getChildCount(); - } - - @Override - public void addView(View child, int index) { - if (VERBOSE_TRACING) { - TraceCompat.beginSection("RV addView"); - } - RecyclerView.this.addView(child, index); - if (VERBOSE_TRACING) { - TraceCompat.endSection(); - } - dispatchChildAttached(child); - } - - @Override - public int indexOfChild(View view) { - return RecyclerView.this.indexOfChild(view); - } - - @Override - public void removeViewAt(int index) { - final View child = RecyclerView.this.getChildAt(index); - if (child != null) { - dispatchChildDetached(child); - - // Clear any android.view.animation.Animation that may prevent the item from - // detaching when being removed. If a child is re-added before the - // lazy detach occurs, it will receive invalid attach/detach sequencing. - child.clearAnimation(); - } - if (VERBOSE_TRACING) { - TraceCompat.beginSection("RV removeViewAt"); - } - RecyclerView.this.removeViewAt(index); - if (VERBOSE_TRACING) { - TraceCompat.endSection(); - } - } - - @Override - public View getChildAt(int offset) { - return RecyclerView.this.getChildAt(offset); - } - - @Override - public void removeAllViews() { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - dispatchChildDetached(child); - - // Clear any android.view.animation.Animation that may prevent the item from - // detaching when being removed. If a child is re-added before the - // lazy detach occurs, it will receive invalid attach/detach sequencing. - child.clearAnimation(); - } - RecyclerView.this.removeAllViews(); - } - - @Override - public ViewHolder getChildViewHolder(View view) { - return getChildViewHolderInt(view); - } - - @Override - public void attachViewToParent(View child, int index, - ViewGroup.LayoutParams layoutParams) { - final ViewHolder vh = getChildViewHolderInt(child); - if (vh != null) { - if (!vh.isTmpDetached() && !vh.shouldIgnore()) { - throw new IllegalArgumentException("Called attach on a child which is not" - + " detached: " + vh + exceptionLabel()); - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "reAttach " + vh); - } - vh.clearTmpDetachFlag(); - } else { - if (sDebugAssertionsEnabled) { - throw new IllegalArgumentException( - "No ViewHolder found for child: " + child + ", index: " + index - + exceptionLabel()); - } - } - RecyclerView.this.attachViewToParent(child, index, layoutParams); - } - - @Override - public void detachViewFromParent(int offset) { - final View view = getChildAt(offset); - if (view != null) { - final ViewHolder vh = getChildViewHolderInt(view); - if (vh != null) { - if (vh.isTmpDetached() && !vh.shouldIgnore()) { - throw new IllegalArgumentException("called detach on an already" - + " detached child " + vh + exceptionLabel()); - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "tmpDetach " + vh); - } - vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); - } - } else { - if (sDebugAssertionsEnabled) { - throw new IllegalArgumentException( - "No view at offset " + offset + exceptionLabel()); - } - } - RecyclerView.this.detachViewFromParent(offset); - } - - @Override - public void onEnteredHiddenState(View child) { - final ViewHolder vh = getChildViewHolderInt(child); - if (vh != null) { - vh.onEnteredHiddenState(RecyclerView.this); - } - } - - @Override - public void onLeftHiddenState(View child) { - final ViewHolder vh = getChildViewHolderInt(child); - if (vh != null) { - vh.onLeftHiddenState(RecyclerView.this); - } - } - }); - } - - void initAdapterManager() { - mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { - @Override - public ViewHolder findViewHolder(int position) { - final ViewHolder vh = findViewHolderForPosition(position, true); - if (vh == null) { - return null; - } - // ensure it is not hidden because for adapter helper, the only thing matter is that - // LM thinks view is a child. - if (mChildHelper.isHidden(vh.itemView)) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "assuming view holder cannot be find because it is hidden"); - } - return null; - } - return vh; - } - - @Override - public void offsetPositionsForRemovingInvisible(int start, int count) { - offsetPositionRecordsForRemove(start, count, true); - mItemsAddedOrRemoved = true; - mState.mDeletedInvisibleItemCountSincePreviousLayout += count; - } - - @Override - public void offsetPositionsForRemovingLaidOutOrNewView( - int positionStart, int itemCount) { - offsetPositionRecordsForRemove(positionStart, itemCount, false); - mItemsAddedOrRemoved = true; - } - - - @Override - public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { - viewRangeUpdate(positionStart, itemCount, payload); - mItemsChanged = true; - } - - @Override - public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { - dispatchUpdate(op); - } - - void dispatchUpdate(AdapterHelper.UpdateOp op) { - switch (op.cmd) { - case AdapterHelper.UpdateOp.ADD: - mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); - break; - case AdapterHelper.UpdateOp.REMOVE: - mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); - break; - case AdapterHelper.UpdateOp.UPDATE: - mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, - op.payload); - break; - case AdapterHelper.UpdateOp.MOVE: - mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); - break; - } - } - - @Override - public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { - dispatchUpdate(op); - } - - @Override - public void offsetPositionsForAdd(int positionStart, int itemCount) { - offsetPositionRecordsForInsert(positionStart, itemCount); - mItemsAddedOrRemoved = true; - } - - @Override - public void offsetPositionsForMove(int from, int to) { - offsetPositionRecordsForMove(from, to); - // should we create mItemsMoved ? - mItemsAddedOrRemoved = true; - } - }); - } - - /** - * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's - * size is not affected by the adapter contents. RecyclerView can still change its size based - * on other factors (e.g. its parent's size) but this size calculation cannot depend on the - * size of its children or contents of its adapter (except the number of items in the adapter). - *

- * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow - * RecyclerView to avoid invalidating the whole layout when its adapter contents change. - * - * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. - */ - public void setHasFixedSize(boolean hasFixedSize) { - mHasFixedSize = hasFixedSize; - } - - /** - * @return true if the app has specified that changes in adapter content cannot change - * the size of the RecyclerView itself. - */ - public boolean hasFixedSize() { - return mHasFixedSize; - } - - @Override - public void setClipToPadding(boolean clipToPadding) { - if (clipToPadding != mClipToPadding) { - invalidateGlows(); - } - mClipToPadding = clipToPadding; - super.setClipToPadding(clipToPadding); - if (mFirstLayoutComplete) { - requestLayout(); - } - } - - /** - * Returns whether this RecyclerView will clip its children to its padding, and resize (but - * not clip) any EdgeEffect to the padded region, if padding is present. - *

- * By default, children are clipped to the padding of their parent - * RecyclerView. This clipping behavior is only enabled if padding is non-zero. - * - * @return true if this RecyclerView clips children to its padding and resizes (but doesn't - * clip) any EdgeEffect to the padded region, false otherwise. - * @attr name android:clipToPadding - */ - @Override - public boolean getClipToPadding() { - return mClipToPadding; - } - - /** - * Configure the scrolling touch slop for a specific use case. - * - * Set up the RecyclerView's scrolling motion threshold based on common usages. - * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. - * - * @param slopConstant One of the TOUCH_SLOP_ constants representing - * the intended usage of this RecyclerView - */ - public void setScrollingTouchSlop(int slopConstant) { - final ViewConfiguration vc = ViewConfiguration.get(getContext()); - switch (slopConstant) { - default: - Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " - + slopConstant + "; using default value"); - // fall-through - case TOUCH_SLOP_DEFAULT: - mTouchSlop = vc.getScaledTouchSlop(); - break; - - case TOUCH_SLOP_PAGING: - mTouchSlop = vc.getScaledPagingTouchSlop(); - break; - } - } - - /** - * Swaps the current adapter with the provided one. It is similar to - * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same - * {@link ViewHolder} and does not clear the RecycledViewPool. - *

- * Note that it still calls onAdapterChanged callbacks. - * - * @param adapter The new adapter to set, or null to set no adapter. - * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing - * Views. If adapters have stable ids and/or you want to - * animate the disappearing views, you may prefer to set - * this to false. - * @see #setAdapter(Adapter) - */ - public void swapAdapter(@Nullable Adapter adapter, boolean removeAndRecycleExistingViews) { - // bail out if layout is frozen - setLayoutFrozen(false); - setAdapterInternal(adapter, true, removeAndRecycleExistingViews); - processDataSetCompletelyChanged(true); - requestLayout(); - } - - /** - * Set a new adapter to provide child views on demand. - *

- * When adapter is changed, all existing views are recycled back to the pool. If the pool has - * only one adapter, it will be cleared. - * - * @param adapter The new adapter to set, or null to set no adapter. - * @see #swapAdapter(Adapter, boolean) - */ - public void setAdapter(@Nullable Adapter adapter) { - // bail out if layout is frozen - setLayoutFrozen(false); - setAdapterInternal(adapter, false, true); - processDataSetCompletelyChanged(false); - requestLayout(); - } - - /** - * Removes and recycles all views - both those currently attached, and those in the Recycler. - */ - void removeAndRecycleViews() { - // end all running animations - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - } - // Since animations are ended, mLayout.children should be equal to - // recyclerView.children. This may not be true if item animator's end does not work as - // expected. (e.g. not release children instantly). It is safer to use mLayout's child - // count. - if (mLayout != null) { - mLayout.removeAndRecycleAllViews(mRecycler); - mLayout.removeAndRecycleScrapInt(mRecycler); - } - // we should clear it here before adapters are swapped to ensure correct callbacks. - mRecycler.clear(); - } - - /** - * Replaces the current adapter with the new one and triggers listeners. - * - * @param adapter The new adapter - * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and - * item types with the current adapter (helps us avoid cache - * invalidation). - * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If - * compatibleWithPrevious is false, this parameter is ignored. - */ - private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, - boolean removeAndRecycleViews) { - if (mAdapter != null) { - mAdapter.unregisterAdapterDataObserver(mObserver); - mAdapter.onDetachedFromRecyclerView(this); - } - if (!compatibleWithPrevious || removeAndRecycleViews) { - removeAndRecycleViews(); - } - mAdapterHelper.reset(); - final Adapter oldAdapter = mAdapter; - mAdapter = adapter; - if (adapter != null) { - adapter.registerAdapterDataObserver(mObserver); - adapter.onAttachedToRecyclerView(this); - } - if (mLayout != null) { - mLayout.onAdapterChanged(oldAdapter, mAdapter); - } - mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); - mState.mStructureChanged = true; - } - - /** - * Retrieves the previously set adapter or null if no adapter is set. - * - * @return The previously set adapter - * @see #setAdapter(Adapter) - */ - @Nullable - public Adapter getAdapter() { - return mAdapter; - } - - /** - * Register a listener that will be notified whenever a child view is recycled. - * - *

This listener will be called when a LayoutManager or the RecyclerView decides - * that a child view is no longer needed. If an application associates expensive - * or heavyweight data with item views, this may be a good place to release - * or free those resources.

- * - * @param listener Listener to register, or null to clear - * @deprecated Use {@link #addRecyclerListener(RecyclerListener)} and - * {@link #removeRecyclerListener(RecyclerListener)} - */ - @Deprecated - public void setRecyclerListener(@Nullable RecyclerListener listener) { - mRecyclerListener = listener; - } - - /** - * Register a listener that will be notified whenever a child view is recycled. - * - *

The listeners will be called when a LayoutManager or the RecyclerView decides - * that a child view is no longer needed. If an application associates data with - * the item views being recycled, this may be a good place to release - * or free those resources.

- * - * @param listener Listener to register. - */ - public void addRecyclerListener(@NonNull RecyclerListener listener) { - checkArgument(listener != null, "'listener' arg cannot " - + "be null."); - mRecyclerListeners.add(listener); - } - - /** - * Removes the provided listener from RecyclerListener list. - * - * @param listener Listener to unregister. - */ - public void removeRecyclerListener(@NonNull RecyclerListener listener) { - mRecyclerListeners.remove(listener); - } - - /** - *

Return the offset of the RecyclerView's text baseline from the its top - * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, - * this method returns -1.

- * - * @return the offset of the baseline within the RecyclerView's bounds or -1 - * if baseline alignment is not supported - */ - @Override - public int getBaseline() { - if (mLayout != null) { - return mLayout.getBaseline(); - } else { - return super.getBaseline(); - } - } - - /** - * Register a listener that will be notified whenever a child view is attached to or detached - * from RecyclerView. - * - *

This listener will be called when a LayoutManager or the RecyclerView decides - * that a child view is no longer needed. If an application associates expensive - * or heavyweight data with item views, this may be a good place to release - * or free those resources.

- * - * @param listener Listener to register - */ - public void addOnChildAttachStateChangeListener( - @NonNull OnChildAttachStateChangeListener listener) { - if (mOnChildAttachStateListeners == null) { - mOnChildAttachStateListeners = new ArrayList<>(); - } - mOnChildAttachStateListeners.add(listener); - } - - /** - * Removes the provided listener from child attached state listeners list. - * - * @param listener Listener to unregister - */ - public void removeOnChildAttachStateChangeListener( - @NonNull OnChildAttachStateChangeListener listener) { - if (mOnChildAttachStateListeners == null) { - return; - } - mOnChildAttachStateListeners.remove(listener); - } - - /** - * Removes all listeners that were added via - * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}. - */ - public void clearOnChildAttachStateChangeListeners() { - if (mOnChildAttachStateListeners != null) { - mOnChildAttachStateListeners.clear(); - } - } - - /** - * Set the {@link LayoutManager} that this RecyclerView will use. - * - *

In contrast to other adapter-backed views such as {@link android.widget.ListView} - * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom - * layout arrangements for child views. These arrangements are controlled by the - * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.

- * - *

Several default strategies are provided for common uses such as lists and grids.

- * - * @param layout LayoutManager to use - */ - public void setLayoutManager(@Nullable LayoutManager layout) { - if (layout == mLayout) { - return; - } - stopScroll(); - // TODO We should do this switch a dispatchLayout pass and animate children. There is a good - // chance that LayoutManagers will re-use views. - if (mLayout != null) { - // end all running animations - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - } - mLayout.removeAndRecycleAllViews(mRecycler); - mLayout.removeAndRecycleScrapInt(mRecycler); - mRecycler.clear(); - - if (mIsAttached) { - mLayout.dispatchDetachedFromWindow(this, mRecycler); - } - mLayout.setRecyclerView(null); - mLayout = null; - } else { - mRecycler.clear(); - } - // this is just a defensive measure for faulty item animators. - mChildHelper.removeAllViewsUnfiltered(); - mLayout = layout; - if (layout != null) { - if (layout.mRecyclerView != null) { - throw new IllegalArgumentException("LayoutManager " + layout - + " is already attached to a RecyclerView:" - + layout.mRecyclerView.exceptionLabel()); - } - mLayout.setRecyclerView(this); - if (mIsAttached) { - mLayout.dispatchAttachedToWindow(this); - } - } - mRecycler.updateViewCacheSize(); - requestLayout(); - } - - /** - * Set a {@link OnFlingListener} for this {@link RecyclerView}. - *

- * If the {@link OnFlingListener} is set then it will receive - * calls to {@link #fling(int, int)} and will be able to intercept them. - * - * @param onFlingListener The {@link OnFlingListener} instance. - */ - public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { - mOnFlingListener = onFlingListener; - } - - /** - * Get the current {@link OnFlingListener} from this {@link RecyclerView}. - * - * @return The {@link OnFlingListener} instance currently set (can be null). - */ - @Nullable - public OnFlingListener getOnFlingListener() { - return mOnFlingListener; - } - - @Override - protected Parcelable onSaveInstanceState() { - SavedState state = new SavedState(super.onSaveInstanceState()); - if (mPendingSavedState != null) { - state.copyFrom(mPendingSavedState); - } else if (mLayout != null) { - state.mLayoutState = mLayout.onSaveInstanceState(); - } else { - state.mLayoutState = null; - } - - return state; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - mPendingSavedState = (SavedState) state; - super.onRestoreInstanceState(mPendingSavedState.getSuperState()); - // Historically, some app developers have used onRestoreInstanceState(State) in ways it - // was never intended. For example, some devs have used it to manually set a state they - // updated themselves such that passing the state here would cause a LayoutManager to - // receive it and update its internal state accordingly, even if state was already - // previously restored. Therefore, it is necessary to always call requestLayout to retain - // the functionality even if it otherwise seems like a strange thing to do. - // ¯\_(ツ)_/¯ - requestLayout(); - } - - /** - * Override to prevent freezing of any views created by the adapter. - */ - @Override - protected void dispatchSaveInstanceState(SparseArray container) { - dispatchFreezeSelfOnly(container); - } - - /** - * Override to prevent thawing of any views created by the adapter. - */ - @Override - protected void dispatchRestoreInstanceState(SparseArray container) { - dispatchThawSelfOnly(container); - } - - /** - * Adds a view to the animatingViews list. - * mAnimatingViews holds the child views that are currently being kept around - * purely for the purpose of being animated out of view. They are drawn as a regular - * part of the child list of the RecyclerView, but they are invisible to the LayoutManager - * as they are managed separately from the regular child views. - * - * @param viewHolder The ViewHolder to be removed - */ - private void addAnimatingView(ViewHolder viewHolder) { - final View view = viewHolder.itemView; - final boolean alreadyParented = view.getParent() == this; - mRecycler.unscrapView(getChildViewHolder(view)); - if (viewHolder.isTmpDetached()) { - // re-attach - mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); - } else if (!alreadyParented) { - mChildHelper.addView(view, true); - } else { - mChildHelper.hide(view); - } - } - - /** - * Removes a view from the animatingViews list. - * - * @param view The view to be removed - * @return true if an animating view is removed - * @see #addAnimatingView(RecyclerView.ViewHolder) - */ - boolean removeAnimatingView(View view) { - startInterceptRequestLayout(); - final boolean removed = mChildHelper.removeViewIfHidden(view); - if (removed) { - final ViewHolder viewHolder = getChildViewHolderInt(view); - mRecycler.unscrapView(viewHolder); - mRecycler.recycleViewHolderInternal(viewHolder); - if (sVerboseLoggingEnabled) { - Log.d(TAG, "after removing animated view: " + view + ", " + this); - } - } - // only clear request eaten flag if we removed the view. - stopInterceptRequestLayout(!removed); - return removed; - } - - /** - * Return the {@link LayoutManager} currently responsible for - * layout policy for this RecyclerView. - * - * @return The currently bound LayoutManager - */ - @Nullable - public LayoutManager getLayoutManager() { - return mLayout; - } - - /** - * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; - * if no pool is set for this view a new one will be created. See - * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. - * - * @return The pool used to store recycled item views for reuse. - * @see #setRecycledViewPool(RecycledViewPool) - */ - @NonNull - public RecycledViewPool getRecycledViewPool() { - return mRecycler.getRecycledViewPool(); - } - - /** - * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. - * This can be useful if you have multiple RecyclerViews with adapters that use the same - * view types, for example if you have several data sets with the same kinds of item views - * displayed by a {@link androidx.viewpager.widget.ViewPager}. - * - * @param pool Pool to set. If this parameter is null a new pool will be created and used. - */ - public void setRecycledViewPool(@Nullable RecycledViewPool pool) { - mRecycler.setRecycledViewPool(pool); - } - - /** - * Sets a new {@link ViewCacheExtension} to be used by the Recycler. - * - * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. - * @see ViewCacheExtension#getViewForPositionAndType(Recycler, int, int) - */ - public void setViewCacheExtension(@Nullable ViewCacheExtension extension) { - mRecycler.setViewCacheExtension(extension); - } - - /** - * Set the number of offscreen views to retain before adding them to the potentially shared - * {@link #getRecycledViewPool() recycled view pool}. - * - *

The offscreen view cache stays aware of changes in the attached adapter, allowing - * a LayoutManager to reuse those views unmodified without needing to return to the adapter - * to rebind them.

- * - * @param size Number of views to cache offscreen before returning them to the general - * recycled view pool - */ - public void setItemViewCacheSize(int size) { - mRecycler.setViewCacheSize(size); - } - - /** - * Return the current scrolling state of the RecyclerView. - * - * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or - * {@link #SCROLL_STATE_SETTLING} - */ - public int getScrollState() { - return mScrollState; - } - - void setScrollState(int state) { - if (state == mScrollState) { - return; - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, - new Exception()); - } - mScrollState = state; - if (state != SCROLL_STATE_SETTLING) { - stopScrollersInternal(); - } - dispatchOnScrollStateChanged(state); - } - - /** - * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can - * affect both measurement and drawing of individual item views. - * - *

Item decorations are ordered. Decorations placed earlier in the list will - * be run/queried/drawn first for their effects on item views. Padding added to views - * will be nested; a padding added by an earlier decoration will mean further - * item decorations in the list will be asked to draw/pad within the previous decoration's - * given area.

- * - * @param decor Decoration to add - * @param index Position in the decoration chain to insert this decoration at. If this value - * is negative the decoration will be added at the end. - */ - public void addItemDecoration(@NonNull ItemDecoration decor, int index) { - if (mLayout != null) { - mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" - + " layout"); - } - if (mItemDecorations.isEmpty()) { - setWillNotDraw(false); - } - if (index < 0) { - mItemDecorations.add(decor); - } else { - mItemDecorations.add(index, decor); - } - markItemDecorInsetsDirty(); - requestLayout(); - } - - /** - * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can - * affect both measurement and drawing of individual item views. - * - *

Item decorations are ordered. Decorations placed earlier in the list will - * be run/queried/drawn first for their effects on item views. Padding added to views - * will be nested; a padding added by an earlier decoration will mean further - * item decorations in the list will be asked to draw/pad within the previous decoration's - * given area.

- * - * @param decor Decoration to add - */ - public void addItemDecoration(@NonNull ItemDecoration decor) { - addItemDecoration(decor, -1); - } - - /** - * Returns an {@link ItemDecoration} previously added to this RecyclerView. - * - * @param index The index position of the desired ItemDecoration. - * @return the ItemDecoration at index position - * @throws IndexOutOfBoundsException on invalid index - */ - @NonNull - public ItemDecoration getItemDecorationAt(int index) { - final int size = getItemDecorationCount(); - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); - } - - return mItemDecorations.get(index); - } - - /** - * Returns the number of {@link ItemDecoration} currently added to this RecyclerView. - * - * @return number of ItemDecorations currently added added to this RecyclerView. - */ - public int getItemDecorationCount() { - return mItemDecorations.size(); - } - - /** - * Removes the {@link ItemDecoration} associated with the supplied index position. - * - * @param index The index position of the ItemDecoration to be removed. - */ - public void removeItemDecorationAt(int index) { - final int size = getItemDecorationCount(); - if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size); - } - - removeItemDecoration(getItemDecorationAt(index)); - } - - /** - * Remove an {@link ItemDecoration} from this RecyclerView. - * - *

The given decoration will no longer impact the measurement and drawing of - * item views.

- * - * @param decor Decoration to remove - * @see #addItemDecoration(ItemDecoration) - */ - public void removeItemDecoration(@NonNull ItemDecoration decor) { - if (mLayout != null) { - mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" - + " layout"); - } - mItemDecorations.remove(decor); - if (mItemDecorations.isEmpty()) { - setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); - } - markItemDecorInsetsDirty(); - requestLayout(); - } - - /** - * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children. - *

- * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will - * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be - * true if childDrawingOrderCallback is not null, false otherwise. - *

- * Note that child drawing order may be overridden by View's elevation. - * - * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing - * system. - */ - public void setChildDrawingOrderCallback( - @Nullable ChildDrawingOrderCallback childDrawingOrderCallback) { - if (childDrawingOrderCallback == mChildDrawingOrderCallback) { - return; - } - mChildDrawingOrderCallback = childDrawingOrderCallback; - setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null); - } - - /** - * Set a listener that will be notified of any changes in scroll state or position. - * - * @param listener Listener to set or null to clear - * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and - * {@link #removeOnScrollListener(OnScrollListener)} - */ - @Deprecated - public void setOnScrollListener(@Nullable OnScrollListener listener) { - mScrollListener = listener; - } - - /** - * Add a listener that will be notified of any changes in scroll state or position. - * - *

Components that add a listener should take care to remove it when finished. - * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} - * to remove all attached listeners.

- * - * @param listener listener to set - */ - public void addOnScrollListener(@NonNull OnScrollListener listener) { - if (mScrollListeners == null) { - mScrollListeners = new ArrayList<>(); - } - mScrollListeners.add(listener); - } - - /** - * Remove a listener that was notified of any changes in scroll state or position. - * - * @param listener listener to set or null to clear - */ - public void removeOnScrollListener(@NonNull OnScrollListener listener) { - if (mScrollListeners != null) { - mScrollListeners.remove(listener); - } - } - - /** - * Remove all secondary listener that were notified of any changes in scroll state or position. - */ - public void clearOnScrollListeners() { - if (mScrollListeners != null) { - mScrollListeners.clear(); - } - } - - /** - * Convenience method to scroll to a certain position. - * - * RecyclerView does not implement scrolling logic, rather forwards the call to - * {@link RecyclerView.LayoutManager#scrollToPosition(int)} - * - * @param position Scroll to this adapter position - * @see RecyclerView.LayoutManager#scrollToPosition(int) - */ - public void scrollToPosition(int position) { - if (mLayoutSuppressed) { - return; - } - stopScroll(); - if (mLayout == null) { - Log.e(TAG, "Cannot scroll to position a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return; - } - mLayout.scrollToPosition(position); - awakenScrollBars(); - } - - void jumpToPositionForSmoothScroller(int position) { - if (mLayout == null) { - return; - } - - // If we are jumping to a position, we are in fact scrolling the contents of the RV, so - // we should be sure that we are in the settling state. - setScrollState(SCROLL_STATE_SETTLING); - mLayout.scrollToPosition(position); - awakenScrollBars(); - } - - /** - * Starts a smooth scroll to an adapter position. - *

- * To support smooth scrolling, you must override - * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a - * {@link SmoothScroller}. - *

- * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to - * provide a custom smooth scroll logic, override - * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your - * LayoutManager. - * - * @param position The adapter position to scroll to - * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) - */ - public void smoothScrollToPosition(int position) { - if (mLayoutSuppressed) { - return; - } - if (mLayout == null) { - Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return; - } - mLayout.smoothScrollToPosition(this, mState, position); - } - - @Override - public void scrollTo(int x, int y) { - Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " - + "Use scrollToPosition instead"); - } - - @Override - public void scrollBy(int x, int y) { - if (mLayout == null) { - Log.e(TAG, "Cannot scroll without a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return; - } - if (mLayoutSuppressed) { - return; - } - final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); - final boolean canScrollVertical = mLayout.canScrollVertically(); - if (canScrollHorizontal || canScrollVertical) { - scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null, - TYPE_TOUCH); - } - } - - /** - * Same as {@link RecyclerView#scrollBy(int, int)}, but also participates in nested scrolling. - * @param x The amount of horizontal scroll requested - * @param y The amount of vertical scroll requested - * @see androidx.core.view.NestedScrollingChild - */ - public void nestedScrollBy(int x, int y) { - nestedScrollByInternal(x, y, null, TYPE_NON_TOUCH); - } - - /** - * Similar to {@link RecyclerView#scrollByInternal(int, int, MotionEvent, int)}, but fully - * participates in nested scrolling "end to end", meaning that it will start nested scrolling, - * participate in nested scrolling, and then end nested scrolling all within one call. - * @param x The amount of horizontal scroll requested. - * @param y The amount of vertical scroll requested. - * @param motionEvent The originating MotionEvent if any. - * @param type The type of nested scrolling to engage in (TYPE_TOUCH or TYPE_NON_TOUCH). - */ - @SuppressWarnings("SameParameterValue") - private void nestedScrollByInternal(int x, int y, @Nullable MotionEvent motionEvent, int type) { - - if (mLayout == null) { - Log.e(TAG, "Cannot scroll without a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return; - } - if (mLayoutSuppressed) { - return; - } - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); - final boolean canScrollVertical = mLayout.canScrollVertically(); - - int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; - if (canScrollHorizontal) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; - } - if (canScrollVertical) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; - } - - // If there is no MotionEvent, treat it as center-aligned edge effect: - float verticalDisplacement = motionEvent == null ? getHeight() / 2f : motionEvent.getY(); - float horizontalDisplacement = motionEvent == null ? getWidth() / 2f : motionEvent.getX(); - x -= releaseHorizontalGlow(x, verticalDisplacement); - y -= releaseVerticalGlow(y, horizontalDisplacement); - startNestedScroll(nestedScrollAxis, type); - if (dispatchNestedPreScroll( - canScrollHorizontal ? x : 0, - canScrollVertical ? y : 0, - mReusableIntPair, mScrollOffset, type - )) { - x -= mReusableIntPair[0]; - y -= mReusableIntPair[1]; - } - - scrollByInternal( - canScrollHorizontal ? x : 0, - canScrollVertical ? y : 0, - motionEvent, type); - if (mGapWorker != null && (x != 0 || y != 0)) { - mGapWorker.postFromTraversal(this, x, y); - } - stopNestedScroll(type); - } - - /** - * Scrolls the RV by 'dx' and 'dy' via calls to - * {@link LayoutManager#scrollHorizontallyBy(int, Recycler, State)} and - * {@link LayoutManager#scrollVerticallyBy(int, Recycler, State)}. - * - * Also sets how much of the scroll was actually consumed in 'consumed' parameter (indexes 0 and - * 1 for the x axis and y axis, respectively). - * - * This method should only be called in the context of an existing scroll operation such that - * any other necessary operations (such as a call to {@link #consumePendingUpdateOperations()}) - * is already handled. - */ - void scrollStep(int dx, int dy, @Nullable int[] consumed) { - startInterceptRequestLayout(); - onEnterLayoutOrScroll(); - - TraceCompat.beginSection(TRACE_SCROLL_TAG); - fillRemainingScrollValues(mState); - - int consumedX = 0; - int consumedY = 0; - if (dx != 0) { - consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); - } - if (dy != 0) { - consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); - } - - TraceCompat.endSection(); - repositionShadowingViews(); - - onExitLayoutOrScroll(); - stopInterceptRequestLayout(false); - - if (consumed != null) { - consumed[0] = consumedX; - consumed[1] = consumedY; - } - } - - /** - * Helper method reflect data changes to the state. - *

- * Adapter changes during a scroll may trigger a crash because scroll assumes no data change - * but data actually changed. - *

- * This method consumes all deferred changes to avoid that case. - */ - void consumePendingUpdateOperations() { - if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { - TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); - dispatchLayout(); - TraceCompat.endSection(); - return; - } - if (!mAdapterHelper.hasPendingUpdates()) { - return; - } - - // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any - // of the visible items is affected and if not, just ignore the change. - if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper - .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE - | AdapterHelper.UpdateOp.MOVE)) { - TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); - startInterceptRequestLayout(); - onEnterLayoutOrScroll(); - mAdapterHelper.preProcess(); - if (!mLayoutWasDefered) { - if (hasUpdatedView()) { - dispatchLayout(); - } else { - // no need to layout, clean state - mAdapterHelper.consumePostponedUpdates(); - } - } - stopInterceptRequestLayout(true); - onExitLayoutOrScroll(); - TraceCompat.endSection(); - } else if (mAdapterHelper.hasPendingUpdates()) { - TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); - dispatchLayout(); - TraceCompat.endSection(); - } - } - - /** - * @return True if an existing view holder needs to be updated - */ - private boolean hasUpdatedView() { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder == null || holder.shouldIgnore()) { - continue; - } - if (holder.isUpdated()) { - return true; - } - } - return false; - } - - /** - * Does not perform bounds checking. Used by internal methods that have already validated input. - *

- * It also reports any unused scroll request to the related EdgeEffect. - * - * @param x The amount of horizontal scroll request - * @param y The amount of vertical scroll request - * @param ev The originating MotionEvent, or null if not from a touch event. - * @param type NestedScrollType, TOUCH or NON_TOUCH. - * @return Whether any scroll was consumed in either direction. - */ - boolean scrollByInternal(int x, int y, MotionEvent ev, int type) { - int unconsumedX = 0; - int unconsumedY = 0; - int consumedX = 0; - int consumedY = 0; - - consumePendingUpdateOperations(); - if (mAdapter != null) { - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - scrollStep(x, y, mReusableIntPair); - consumedX = mReusableIntPair[0]; - consumedY = mReusableIntPair[1]; - unconsumedX = x - consumedX; - unconsumedY = y - consumedY; - } - if (!mItemDecorations.isEmpty()) { - invalidate(); - } - - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, - type, mReusableIntPair); - unconsumedX -= mReusableIntPair[0]; - unconsumedY -= mReusableIntPair[1]; - boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0; - - // Update the last touch co-ords, taking any scroll offset into account - mLastTouchX -= mScrollOffset[0]; - mLastTouchY -= mScrollOffset[1]; - mNestedOffsets[0] += mScrollOffset[0]; - mNestedOffsets[1] += mScrollOffset[1]; - - if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { - if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) { - pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); - } - considerReleasingGlowsOnScroll(x, y); - } - if (consumedX != 0 || consumedY != 0) { - dispatchOnScrolled(consumedX, consumedY); - } - if (!awakenScrollBars()) { - invalidate(); - } - return consumedNestedScroll || consumedX != 0 || consumedY != 0; - } - - /** - * If either of the horizontal edge glows are currently active, this consumes part or all of - * deltaX on the edge glow. - * - * @param deltaX The pointer motion, in pixels, in the horizontal direction, positive - * for moving down and negative for moving up. - * @param y The vertical position of the pointer. - * @return The amount of deltaX that has been consumed by the - * edge glow. - */ - private int releaseHorizontalGlow(int deltaX, float y) { - // First allow releasing existing overscroll effect: - float consumed = 0; - float displacement = y / getHeight(); - float pullDistance = (float) deltaX / getWidth(); - if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0) { - if (canScrollHorizontally(-1)) { - mLeftGlow.onRelease(); - } else { - consumed = -EdgeEffectCompat.onPullDistance(mLeftGlow, -pullDistance, - 1 - displacement); - if (EdgeEffectCompat.getDistance(mLeftGlow) == 0) { - mLeftGlow.onRelease(); - } - } - invalidate(); - } else if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0) { - if (canScrollHorizontally(1)) { - mRightGlow.onRelease(); - } else { - consumed = EdgeEffectCompat.onPullDistance(mRightGlow, pullDistance, displacement); - if (EdgeEffectCompat.getDistance(mRightGlow) == 0) { - mRightGlow.onRelease(); - } - } - invalidate(); - } - return Math.round(consumed * getWidth()); - } - - /** - * If either of the vertical edge glows are currently active, this consumes part or all of - * deltaY on the edge glow. - * - * @param deltaY The pointer motion, in pixels, in the vertical direction, positive - * for moving down and negative for moving up. - * @param x The vertical position of the pointer. - * @return The amount of deltaY that has been consumed by the - * edge glow. - */ - private int releaseVerticalGlow(int deltaY, float x) { - // First allow releasing existing overscroll effect: - float consumed = 0; - float displacement = x / getWidth(); - float pullDistance = (float) deltaY / getHeight(); - if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0) { - if (canScrollVertically(-1)) { - mTopGlow.onRelease(); - } else { - consumed = -EdgeEffectCompat.onPullDistance(mTopGlow, -pullDistance, displacement); - if (EdgeEffectCompat.getDistance(mTopGlow) == 0) { - mTopGlow.onRelease(); - } - } - invalidate(); - } else if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0) { - if (canScrollVertically(1)) { - mBottomGlow.onRelease(); - } else { - consumed = EdgeEffectCompat.onPullDistance(mBottomGlow, pullDistance, - 1 - displacement); - if (EdgeEffectCompat.getDistance(mBottomGlow) == 0) { - mBottomGlow.onRelease(); - } - } - invalidate(); - } - return Math.round(consumed * getHeight()); - } - - /** - *

Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal - * range. This value is used to compute the length of the thumb within the scrollbar's track. - *

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The horizontal offset of the scrollbar's thumb - * @see RecyclerView.LayoutManager#computeHorizontalScrollOffset - * (RecyclerView.State) - */ - @Override - public int computeHorizontalScrollOffset() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; - } - - /** - *

Compute the horizontal extent of the horizontal scrollbar's thumb within the - * horizontal range. This value is used to compute the length of the thumb within the - * scrollbar's track.

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The horizontal extent of the scrollbar's thumb - * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) - */ - @Override - public int computeHorizontalScrollExtent() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; - } - - /** - *

Compute the horizontal range that the horizontal scrollbar represents.

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The total horizontal range represented by the vertical scrollbar - * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) - */ - @Override - public int computeHorizontalScrollRange() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; - } - - /** - *

Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. - * This value is used to compute the length of the thumb within the scrollbar's track.

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The vertical offset of the scrollbar's thumb - * @see RecyclerView.LayoutManager#computeVerticalScrollOffset - * (RecyclerView.State) - */ - @Override - public int computeVerticalScrollOffset() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; - } - - /** - *

Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. - * This value is used to compute the length of the thumb within the scrollbar's track.

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The vertical extent of the scrollbar's thumb - * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) - */ - @Override - public int computeVerticalScrollExtent() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; - } - - /** - *

Compute the vertical range that the vertical scrollbar represents.

- * - *

The range is expressed in arbitrary units that must be the same as the units used by - * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.

- * - *

Default implementation returns 0.

- * - *

If you want to support scroll bars, override - * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your - * LayoutManager.

- * - * @return The total vertical range represented by the vertical scrollbar - * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) - */ - @Override - public int computeVerticalScrollRange() { - if (mLayout == null) { - return 0; - } - return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; - } - - /** - * This method should be called before any code that may trigger a child view to cause a call to - * {@link RecyclerView#requestLayout()}. Doing so enables {@link RecyclerView} to avoid - * reacting to additional redundant calls to {@link #requestLayout()}. - *

- * A call to this method must always be accompanied by a call to - * {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a - * child View to cause a call to {@link RecyclerView#requestLayout()}. - * - * @see #stopInterceptRequestLayout(boolean) - */ - void startInterceptRequestLayout() { - mInterceptRequestLayoutDepth++; - if (mInterceptRequestLayoutDepth == 1 && !mLayoutSuppressed) { - mLayoutWasDefered = false; - } - } - - /** - * This method should be called after any code that may trigger a child view to cause a call to - * {@link RecyclerView#requestLayout()}. - *

- * A call to this method must always be accompanied by a call to - * {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child - * View to cause a call to {@link RecyclerView#requestLayout()}. - * - * @see #startInterceptRequestLayout() - */ - void stopInterceptRequestLayout(boolean performLayoutChildren) { - if (mInterceptRequestLayoutDepth < 1) { - //noinspection PointlessBooleanExpression - if (sDebugAssertionsEnabled) { - throw new IllegalStateException("stopInterceptRequestLayout was called more " - + "times than startInterceptRequestLayout." - + exceptionLabel()); - } - mInterceptRequestLayoutDepth = 1; - } - if (!performLayoutChildren && !mLayoutSuppressed) { - // Reset the layout request eaten counter. - // This is necessary since eatRequest calls can be nested in which case the other - // call will override the inner one. - // for instance: - // eat layout for process adapter updates - // eat layout for dispatchLayout - // a bunch of req layout calls arrive - - mLayoutWasDefered = false; - } - if (mInterceptRequestLayoutDepth == 1) { - // when layout is frozen we should delay dispatchLayout() - if (performLayoutChildren && mLayoutWasDefered && !mLayoutSuppressed - && mLayout != null && mAdapter != null) { - dispatchLayout(); - } - if (!mLayoutSuppressed) { - mLayoutWasDefered = false; - } - } - mInterceptRequestLayoutDepth--; - } - - /** - * Tells this RecyclerView to suppress all layout and scroll calls until layout - * suppression is disabled with a later call to suppressLayout(false). - * When layout suppression is disabled, a requestLayout() call is sent - * if requestLayout() was attempted while layout was being suppressed. - *

- * In addition to the layout suppression {@link #smoothScrollBy(int, int)}, - * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and - * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are - * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be - * called. - * - *

- * suppressLayout(true) does not prevent app from directly calling {@link - * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( - *RecyclerView, State, int)}. - *

- * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically - * stop suppressing. - *

- * Note: Running ItemAnimator is not stopped automatically, it's caller's - * responsibility to call ItemAnimator.end(). - * - * @param suppress true to suppress layout and scroll, false to re-enable. - */ - @Override - public final void suppressLayout(boolean suppress) { - if (suppress != mLayoutSuppressed) { - assertNotInLayoutOrScroll("Do not suppressLayout in layout or scroll"); - if (!suppress) { - mLayoutSuppressed = false; - if (mLayoutWasDefered && mLayout != null && mAdapter != null) { - requestLayout(); - } - mLayoutWasDefered = false; - } else { - final long now = SystemClock.uptimeMillis(); - MotionEvent cancelEvent = MotionEvent.obtain(now, now, - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); - onTouchEvent(cancelEvent); - mLayoutSuppressed = true; - mIgnoreMotionEventTillDown = true; - stopScroll(); - } - } - } - - /** - * Returns whether layout and scroll calls on this container are currently being - * suppressed, due to an earlier call to {@link #suppressLayout(boolean)}. - * - * @return true if layout and scroll are currently suppressed, false otherwise. - */ - @Override - public final boolean isLayoutSuppressed() { - return mLayoutSuppressed; - } - - /** - * Enable or disable layout and scroll. After setLayoutFrozen(true) is called, - * Layout requests will be postponed until setLayoutFrozen(false) is called; - * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)}, - * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and - * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are - * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be - * called. - * - *

- * setLayoutFrozen(true) does not prevent app from directly calling {@link - * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( - *RecyclerView, State, int)}. - *

- * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically - * stop frozen. - *

- * Note: Running ItemAnimator is not stopped automatically, it's caller's - * responsibility to call ItemAnimator.end(). - * - * @param frozen true to freeze layout and scroll, false to re-enable. - * @deprecated Use {@link #suppressLayout(boolean)}. - */ - @Deprecated - public void setLayoutFrozen(boolean frozen) { - suppressLayout(frozen); - } - - /** - * @return true if layout and scroll are frozen - * @deprecated Use {@link #isLayoutSuppressed()}. - */ - @Deprecated - public boolean isLayoutFrozen() { - return isLayoutSuppressed(); - } - - /** - * @deprecated Use {@link #setItemAnimator(ItemAnimator)} ()}. - */ - @Deprecated - @Override - public void setLayoutTransition(LayoutTransition transition) { - if (Build.VERSION.SDK_INT < 18) { - // Transitions on APIs below 18 are using an empty LayoutTransition as a replacement - // for suppressLayout(true) and null LayoutTransition to then unsuppress it. - // We can detect this cases and use our suppressLayout() implementation instead. - if (transition == null) { - suppressLayout(false); - return; - } else { - int layoutTransitionChanging = 4; // LayoutTransition.CHANGING (Added in API 16) - if (transition.getAnimator(LayoutTransition.CHANGE_APPEARING) == null - && transition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING) == null - && transition.getAnimator(LayoutTransition.APPEARING) == null - && transition.getAnimator(LayoutTransition.DISAPPEARING) == null - && transition.getAnimator(layoutTransitionChanging) == null) { - suppressLayout(true); - return; - } - } - } - - if (transition == null) { - super.setLayoutTransition(null); - } else { - throw new IllegalArgumentException("Providing a LayoutTransition into RecyclerView is " - + "not supported. Please use setItemAnimator() instead for animating changes " - + "to the items in this RecyclerView"); - } - } - - /** - * Animate a scroll by the given amount of pixels along either axis. - * - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - */ - public void smoothScrollBy(@Px int dx, @Px int dy) { - smoothScrollBy(dx, dy, null); - } - - /** - * Animate a scroll by the given amount of pixels along either axis. - * - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param interpolator {@link Interpolator} to be used for scrolling. If it is - * {@code null}, RecyclerView will use an internal default interpolator. - */ - public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) { - smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION); - } - - /** - * Smooth scrolls the RecyclerView by a given distance. - * - * @param dx x distance in pixels. - * @param dy y distance in pixels. - * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null}, - * RecyclerView will use an internal default interpolator. - * @param duration Duration of the animation in milliseconds. Set to - * {@link #UNDEFINED_DURATION} - * to have the duration be automatically calculated based on an internally - * defined standard initial velocity. A duration less than 1 (that does not - * equal UNDEFINED_DURATION), will result in a call to - * {@link #scrollBy(int, int)}. - */ - public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator, - int duration) { - smoothScrollBy(dx, dy, interpolator, duration, false); - } - - /** - * Internal smooth scroll by implementation that currently has some tricky logic related to it's - * parameters. - *

    - *
  • For scrolling to occur, on either dimension, dx or dy must not be equal to 0 and the - * {@link LayoutManager} must support scrolling in a direction for which the value is not 0. - *
  • For smooth scrolling to occur, scrolling must occur and the duration must be equal to - * {@link #UNDEFINED_DURATION} or greater than 0. - *
  • For scrolling to occur with nested scrolling, smooth scrolling must occur and - * {@code withNestedScrolling} must be {@code true}. This could be updated, but it would - * require that {@link #scrollBy(int, int)} be implemented such that it too can handle nested - * scrolling. - *
- * - * @param dx x distance in pixels. - * @param dy y distance in pixels. - * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code - * null}, - * RecyclerView will use an internal default interpolator. - * @param duration Duration of the animation in milliseconds. Set to - * {@link #UNDEFINED_DURATION} - * to have the duration be automatically calculated based on an - * internally - * defined standard initial velocity. A duration less than 1 (that - * does not - * equal UNDEFINED_DURATION), will result in a call to - * {@link #scrollBy(int, int)}. - * @param withNestedScrolling True to perform the smooth scroll with nested scrolling. If - * {@code duration} is less than 0 and not equal to - * {@link #UNDEFINED_DURATION}, smooth scrolling will not occur and - * thus no nested scrolling will occur. - */ - // Should be considered private. Not private to avoid synthetic accessor. - void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator, - int duration, boolean withNestedScrolling) { - if (mLayout == null) { - Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return; - } - if (mLayoutSuppressed) { - return; - } - if (!mLayout.canScrollHorizontally()) { - dx = 0; - } - if (!mLayout.canScrollVertically()) { - dy = 0; - } - if (dx != 0 || dy != 0) { - boolean durationSuggestsAnimation = duration == UNDEFINED_DURATION || duration > 0; - if (durationSuggestsAnimation) { - if (withNestedScrolling) { - int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; - if (dx != 0) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; - } - if (dy != 0) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; - } - startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH); - } - mViewFlinger.smoothScrollBy(dx, dy, duration, interpolator); - } else { - scrollBy(dx, dy); - } - } - } - - /** - * Begin a standard fling with an initial velocity along each axis in pixels per second. - * If the velocity given is below the system-defined minimum this method will return false - * and no fling will occur. - * - * @param velocityX Initial horizontal velocity in pixels per second - * @param velocityY Initial vertical velocity in pixels per second - * @return true if the fling was started, false if the velocity was too low to fling or - * LayoutManager does not support scrolling in the axis fling is issued. - * @see LayoutManager#canScrollVertically() - * @see LayoutManager#canScrollHorizontally() - */ - public boolean fling(int velocityX, int velocityY) { - if (mLayout == null) { - Log.e(TAG, "Cannot fling without a LayoutManager set. " - + "Call setLayoutManager with a non-null argument."); - return false; - } - if (mLayoutSuppressed) { - return false; - } - - final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); - final boolean canScrollVertical = mLayout.canScrollVertically(); - - if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { - velocityX = 0; - } - if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { - velocityY = 0; - } - if (velocityX == 0 && velocityY == 0) { - // If we don't have any velocity, return false - return false; - } - - // Flinging while the edge effect is active should affect the edge effect, - // not scrolling. - int flingX = 0; - int flingY = 0; - if (velocityX != 0) { - if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0) { - if (shouldAbsorb(mLeftGlow, -velocityX, getWidth())) { - mLeftGlow.onAbsorb(-velocityX); - } else { - flingX = velocityX; - } - velocityX = 0; - } else if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0) { - if (shouldAbsorb(mRightGlow, velocityX, getWidth())) { - mRightGlow.onAbsorb(velocityX); - } else { - flingX = velocityX; - } - velocityX = 0; - } - } - if (velocityY != 0) { - if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0) { - if (shouldAbsorb(mTopGlow, -velocityY, getHeight())) { - mTopGlow.onAbsorb(-velocityY); - } else { - flingY = velocityY; - } - velocityY = 0; - } else if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0) { - if (shouldAbsorb(mBottomGlow, velocityY, getHeight())) { - mBottomGlow.onAbsorb(velocityY); - } else { - flingY = velocityY; - } - velocityY = 0; - } - } - if (flingX != 0 || flingY != 0) { - flingX = Math.max(-mMaxFlingVelocity, Math.min(flingX, mMaxFlingVelocity)); - flingY = Math.max(-mMaxFlingVelocity, Math.min(flingY, mMaxFlingVelocity)); - mViewFlinger.fling(flingX, flingY); - } - if (velocityX == 0 && velocityY == 0) { - return flingX != 0 || flingY != 0; - } - - if (!dispatchNestedPreFling(velocityX, velocityY)) { - final boolean canScroll = canScrollHorizontal || canScrollVertical; - dispatchNestedFling(velocityX, velocityY, canScroll); - - if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { - return true; - } - - if (canScroll) { - int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; - if (canScrollHorizontal) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; - } - if (canScrollVertical) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; - } - startNestedScroll(nestedScrollAxis, TYPE_NON_TOUCH); - - velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); - velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); - mViewFlinger.fling(velocityX, velocityY); - return true; - } - } - return false; - } - - /** - * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should - * animate with a fling. It will animate with a fling if the velocity will remove the - * EdgeEffect through its normal operation. - * - * @param edgeEffect The EdgeEffect that might absorb the velocity. - * @param velocity The velocity of the fling motion - * @param size The width or height of the RecyclerView, depending on the edge that the - * EdgeEffect is on. - * @return true if the velocity should be absorbed or false if it should be flung. - */ - private boolean shouldAbsorb(@NonNull EdgeEffect edgeEffect, int velocity, int size) { - if (velocity > 0) { - return true; - } - float distance = EdgeEffectCompat.getDistance(edgeEffect) * size; - - // This is flinging without the spring, so let's see if it will fling past the overscroll - float flingDistance = getSplineFlingDistance(-velocity); - - return flingDistance < distance; - } - - /** - * If mLeftGlow or mRightGlow is currently active and the motion will remove some of the - * stretch, this will consume any of unconsumedX that the glow can. If the motion would - * increase the stretch, or the EdgeEffect isn't a stretch, then nothing will be consumed. - * - * @param unconsumedX The horizontal delta that might be consumed by the horizontal EdgeEffects - * @return The remaining unconsumed delta after the edge effects have consumed. - */ - int consumeFlingInHorizontalStretch(int unconsumedX) { - return consumeFlingInStretch(unconsumedX, mLeftGlow, mRightGlow, getWidth()); - } - - /** - * If mTopGlow or mBottomGlow is currently active and the motion will remove some of the - * stretch, this will consume any of unconsumedY that the glow can. If the motion would - * increase the stretch, or the EdgeEffect isn't a stretch, then nothing will be consumed. - * - * @param unconsumedY The vertical delta that might be consumed by the vertical EdgeEffects - * @return The remaining unconsumed delta after the edge effects have consumed. - */ - int consumeFlingInVerticalStretch(int unconsumedY) { - return consumeFlingInStretch(unconsumedY, mTopGlow, mBottomGlow, getHeight()); - } - - /** - * Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for - * consuming deltas from EdgeEffects - * @param unconsumed The unconsumed delta that the EdgeEffets may consume - * @param startGlow The start (top or left) EdgeEffect - * @param endGlow The end (bottom or right) EdgeEffect - * @param size The width or height of the container, depending on whether this is for - * horizontal or vertical EdgeEffects - * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. - */ - private int consumeFlingInStretch( - int unconsumed, - EdgeEffect startGlow, - EdgeEffect endGlow, - int size - ) { - if (unconsumed > 0 && startGlow != null && EdgeEffectCompat.getDistance(startGlow) != 0f) { - float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; - int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR - * EdgeEffectCompat.onPullDistance(startGlow, deltaDistance, 0.5f)); - if (consumed != unconsumed) { - startGlow.finish(); - } - return unconsumed - consumed; - } - if (unconsumed < 0 && endGlow != null && EdgeEffectCompat.getDistance(endGlow) != 0f) { - float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size; - int consumed = Math.round(size / FLING_DESTRETCH_FACTOR - * EdgeEffectCompat.onPullDistance(endGlow, deltaDistance, 0.5f)); - if (consumed != unconsumed) { - endGlow.finish(); - } - return unconsumed - consumed; - } - return unconsumed; - } - - /** - * Stop any current scroll in progress, such as one started by - * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. - */ - public void stopScroll() { - setScrollState(SCROLL_STATE_IDLE); - stopScrollersInternal(); - } - - /** - * Similar to {@link #stopScroll()} but does not set the state. - */ - private void stopScrollersInternal() { - mViewFlinger.stop(); - if (mLayout != null) { - mLayout.stopSmoothScroller(); - } - } - - /** - * Returns the minimum velocity to start a fling. - * - * @return The minimum velocity to start a fling - */ - public int getMinFlingVelocity() { - return mMinFlingVelocity; - } - - - /** - * Returns the maximum fling velocity used by this RecyclerView. - * - * @return The maximum fling velocity used by this RecyclerView. - */ - public int getMaxFlingVelocity() { - return mMaxFlingVelocity; - } - - /** - * Apply a pull to relevant overscroll glow effects - */ - private void pullGlows(float x, float overscrollX, float y, float overscrollY) { - boolean invalidate = false; - if (overscrollX < 0) { - ensureLeftGlow(); - EdgeEffectCompat.onPullDistance(mLeftGlow, -overscrollX / getWidth(), - 1f - y / getHeight()); - invalidate = true; - } else if (overscrollX > 0) { - ensureRightGlow(); - EdgeEffectCompat.onPullDistance(mRightGlow, overscrollX / getWidth(), y / getHeight()); - invalidate = true; - } - - if (overscrollY < 0) { - ensureTopGlow(); - EdgeEffectCompat.onPullDistance(mTopGlow, -overscrollY / getHeight(), x / getWidth()); - invalidate = true; - } else if (overscrollY > 0) { - ensureBottomGlow(); - EdgeEffectCompat.onPullDistance(mBottomGlow, overscrollY / getHeight(), - 1f - x / getWidth()); - invalidate = true; - } - - if (invalidate || overscrollX != 0 || overscrollY != 0) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private void releaseGlows() { - boolean needsInvalidate = false; - if (mLeftGlow != null) { - mLeftGlow.onRelease(); - needsInvalidate = mLeftGlow.isFinished(); - } - if (mTopGlow != null) { - mTopGlow.onRelease(); - needsInvalidate |= mTopGlow.isFinished(); - } - if (mRightGlow != null) { - mRightGlow.onRelease(); - needsInvalidate |= mRightGlow.isFinished(); - } - if (mBottomGlow != null) { - mBottomGlow.onRelease(); - needsInvalidate |= mBottomGlow.isFinished(); - } - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void considerReleasingGlowsOnScroll(int dx, int dy) { - boolean needsInvalidate = false; - if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { - mLeftGlow.onRelease(); - needsInvalidate = mLeftGlow.isFinished(); - } - if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { - mRightGlow.onRelease(); - needsInvalidate |= mRightGlow.isFinished(); - } - if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { - mTopGlow.onRelease(); - needsInvalidate |= mTopGlow.isFinished(); - } - if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { - mBottomGlow.onRelease(); - needsInvalidate |= mBottomGlow.isFinished(); - } - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void absorbGlows(int velocityX, int velocityY) { - if (velocityX < 0) { - ensureLeftGlow(); - if (mLeftGlow.isFinished()) { - mLeftGlow.onAbsorb(-velocityX); - } - } else if (velocityX > 0) { - ensureRightGlow(); - if (mRightGlow.isFinished()) { - mRightGlow.onAbsorb(velocityX); - } - } - - if (velocityY < 0) { - ensureTopGlow(); - if (mTopGlow.isFinished()) { - mTopGlow.onAbsorb(-velocityY); - } - } else if (velocityY > 0) { - ensureBottomGlow(); - if (mBottomGlow.isFinished()) { - mBottomGlow.onAbsorb(velocityY); - } - } - - if (velocityX != 0 || velocityY != 0) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void ensureLeftGlow() { - if (mLeftGlow != null) { - return; - } - mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT); - if (mClipToPadding) { - mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), - getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); - } else { - mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); - } - } - - void ensureRightGlow() { - if (mRightGlow != null) { - return; - } - mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT); - if (mClipToPadding) { - mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), - getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); - } else { - mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); - } - } - - void ensureTopGlow() { - if (mTopGlow != null) { - return; - } - mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP); - if (mClipToPadding) { - mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); - } else { - mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); - } - - } - - void ensureBottomGlow() { - if (mBottomGlow != null) { - return; - } - mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM); - if (mClipToPadding) { - mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); - } else { - mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); - } - } - - void invalidateGlows() { - mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; - } - - /** - * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}. - *

- * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared - * and new effects are created as needed using - * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)} - * - * @param edgeEffectFactory The {@link EdgeEffectFactory} instance. - */ - public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) { - Preconditions.checkNotNull(edgeEffectFactory); - mEdgeEffectFactory = edgeEffectFactory; - invalidateGlows(); - } - - /** - * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing - * was set. - * - * @return The previously set {@link EdgeEffectFactory} - * @see #setEdgeEffectFactory(EdgeEffectFactory) - */ - @NonNull - public EdgeEffectFactory getEdgeEffectFactory() { - return mEdgeEffectFactory; - } - - /** - * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are - * in the Adapter but not visible in the UI), it employs a more involved focus search strategy - * that differs from other ViewGroups. - *

- * It first does a focus search within the RecyclerView. If this search finds a View that is in - * the focus direction with respect to the currently focused View, RecyclerView returns that - * child as the next focus target. When it cannot find such child, it calls - * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views - * in the focus search direction. If LayoutManager adds a View that matches the - * focus search criteria, it will be returned as the focus search result. Otherwise, - * RecyclerView will call parent to handle the focus search like a regular ViewGroup. - *

- * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that - * is not in the focus direction is still valid focus target which may not be the desired - * behavior if the Adapter has more children in the focus direction. To handle this case, - * RecyclerView converts the focus direction to an absolute direction and makes a preliminary - * focus search in that direction. If there are no Views to gain focus, it will call - * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a - * focus search with the original (relative) direction. This allows RecyclerView to provide - * better candidates to the focus search while still allowing the view system to take focus from - * the RecyclerView and give it to a more suitable child if such child exists. - * - * @param focused The view that currently has focus - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_FORWARD}, - * {@link View#FOCUS_BACKWARD} or 0 for not applicable. - * @return A new View that can be the next focus after the focused View - */ - @Override - public View focusSearch(View focused, int direction) { - View result = mLayout.onInterceptFocusSearch(focused, direction); - if (result != null) { - return result; - } - final boolean canRunFocusFailure = mAdapter != null && mLayout != null - && !isComputingLayout() && !mLayoutSuppressed; - - final FocusFinder ff = FocusFinder.getInstance(); - if (canRunFocusFailure - && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { - // convert direction to absolute direction and see if we have a view there and if not - // tell LayoutManager to add if it can. - boolean needsFocusFailureLayout = false; - if (mLayout.canScrollVertically()) { - final int absDir = - direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; - final View found = ff.findNextFocus(this, focused, absDir); - needsFocusFailureLayout = found == null; - if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { - // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. - direction = absDir; - } - } - if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { - boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; - final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl - ? View.FOCUS_RIGHT : View.FOCUS_LEFT; - final View found = ff.findNextFocus(this, focused, absDir); - needsFocusFailureLayout = found == null; - if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { - // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. - direction = absDir; - } - } - if (needsFocusFailureLayout) { - consumePendingUpdateOperations(); - final View focusedItemView = findContainingItemView(focused); - if (focusedItemView == null) { - // panic, focused view is not a child anymore, cannot call super. - return null; - } - startInterceptRequestLayout(); - mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); - stopInterceptRequestLayout(false); - } - result = ff.findNextFocus(this, focused, direction); - } else { - result = ff.findNextFocus(this, focused, direction); - if (result == null && canRunFocusFailure) { - consumePendingUpdateOperations(); - final View focusedItemView = findContainingItemView(focused); - if (focusedItemView == null) { - // panic, focused view is not a child anymore, cannot call super. - return null; - } - startInterceptRequestLayout(); - result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); - stopInterceptRequestLayout(false); - } - } - if (result != null && !result.hasFocusable()) { - if (getFocusedChild() == null) { - // Scrolling to this unfocusable view is not meaningful since there is no currently - // focused view which RV needs to keep visible. - return super.focusSearch(focused, direction); - } - // If the next view returned by onFocusSearchFailed in layout manager has no focusable - // views, we still scroll to that view in order to make it visible on the screen. - // If it's focusable, framework already calls RV's requestChildFocus which handles - // bringing this newly focused item onto the screen. - requestChildOnScreen(result, null); - return focused; - } - return isPreferredNextFocus(focused, result, direction) - ? result : super.focusSearch(focused, direction); - } - - /** - * Checks if the new focus candidate is a good enough candidate such that RecyclerView will - * assign it as the next focus View instead of letting view hierarchy decide. - * A good candidate means a View that is aligned in the focus direction wrt the focused View - * and is not the RecyclerView itself. - * When this method returns false, RecyclerView will let the parent make the decision so the - * same View may still get the focus as a result of that search. - */ - private boolean isPreferredNextFocus(View focused, View next, int direction) { - if (next == null || next == this || next == focused) { - return false; - } - // panic, result view is not a child anymore, maybe workaround b/37864393 - if (findContainingItemView(next) == null) { - return false; - } - if (focused == null) { - return true; - } - // panic, focused view is not a child anymore, maybe workaround b/37864393 - if (findContainingItemView(focused) == null) { - return true; - } - - mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); - mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); - offsetDescendantRectToMyCoords(focused, mTempRect); - offsetDescendantRectToMyCoords(next, mTempRect2); - final int rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL ? -1 : 1; - int rightness = 0; - if ((mTempRect.left < mTempRect2.left - || mTempRect.right <= mTempRect2.left) - && mTempRect.right < mTempRect2.right) { - rightness = 1; - } else if ((mTempRect.right > mTempRect2.right - || mTempRect.left >= mTempRect2.right) - && mTempRect.left > mTempRect2.left) { - rightness = -1; - } - int downness = 0; - if ((mTempRect.top < mTempRect2.top - || mTempRect.bottom <= mTempRect2.top) - && mTempRect.bottom < mTempRect2.bottom) { - downness = 1; - } else if ((mTempRect.bottom > mTempRect2.bottom - || mTempRect.top >= mTempRect2.bottom) - && mTempRect.top > mTempRect2.top) { - downness = -1; - } - switch (direction) { - case View.FOCUS_LEFT: - return rightness < 0; - case View.FOCUS_RIGHT: - return rightness > 0; - case View.FOCUS_UP: - return downness < 0; - case View.FOCUS_DOWN: - return downness > 0; - case View.FOCUS_FORWARD: - return downness > 0 || (downness == 0 && rightness * rtl > 0); - case View.FOCUS_BACKWARD: - return downness < 0 || (downness == 0 && rightness * rtl < 0); - } - throw new IllegalArgumentException("Invalid direction: " + direction + exceptionLabel()); - } - - @Override - public void requestChildFocus(View child, View focused) { - if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { - requestChildOnScreen(child, focused); - } - super.requestChildFocus(child, focused); - } - - /** - * Requests that the given child of the RecyclerView be positioned onto the screen. This method - * can be called for both unfocusable and focusable child views. For unfocusable child views, - * the {@param focused} parameter passed is null, whereas for a focusable child, this parameter - * indicates the actual descendant view within this child view that holds the focus. - * - * @param child The child view of this RecyclerView that wants to come onto the screen. - * @param focused The descendant view that actually has the focus if child is focusable, null - * otherwise. - */ - private void requestChildOnScreen(@NonNull View child, @Nullable View focused) { - View rectView = (focused != null) ? focused : child; - mTempRect.set(0, 0, rectView.getWidth(), rectView.getHeight()); - - // get item decor offsets w/o refreshing. If they are invalid, there will be another - // layout pass to fix them, then it is LayoutManager's responsibility to keep focused - // View in viewport. - final ViewGroup.LayoutParams focusedLayoutParams = rectView.getLayoutParams(); - if (focusedLayoutParams instanceof LayoutParams) { - // if focused child has item decors, use them. Otherwise, ignore. - final LayoutParams lp = (LayoutParams) focusedLayoutParams; - if (!lp.mInsetsDirty) { - final Rect insets = lp.mDecorInsets; - mTempRect.left -= insets.left; - mTempRect.right += insets.right; - mTempRect.top -= insets.top; - mTempRect.bottom += insets.bottom; - } - } - - if (focused != null) { - offsetDescendantRectToMyCoords(focused, mTempRect); - offsetRectIntoDescendantCoords(child, mTempRect); - } - mLayout.requestChildRectangleOnScreen(this, child, mTempRect, !mFirstLayoutComplete, - (focused == null)); - } - - @Override - public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { - return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); - } - - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { - super.addFocusables(views, direction, focusableMode); - } - } - - @Override - protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - if (isComputingLayout()) { - // if we are in the middle of a layout calculation, don't let any child take focus. - // RV will handle it after layout calculation is finished. - return false; - } - return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mLayoutOrScrollCounter = 0; - mIsAttached = true; - mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); - - mRecycler.onAttachedToWindow(); - - if (mLayout != null) { - mLayout.dispatchAttachedToWindow(this); - } - mPostedAnimatorRunner = false; - - if (ALLOW_THREAD_GAP_WORK) { - // Register with gap worker - mGapWorker = GapWorker.sGapWorker.get(); - if (mGapWorker == null) { - mGapWorker = new GapWorker(); - - // break 60 fps assumption if data from display appears valid - // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) - Display display = ViewCompat.getDisplay(this); - float refreshRate = 60.0f; - if (!isInEditMode() && display != null) { - float displayRefreshRate = display.getRefreshRate(); - if (displayRefreshRate >= 30.0f) { - refreshRate = displayRefreshRate; - } - } - mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); - GapWorker.sGapWorker.set(mGapWorker); - } - mGapWorker.add(this); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - } - stopScroll(); - mIsAttached = false; - if (mLayout != null) { - mLayout.dispatchDetachedFromWindow(this, mRecycler); - } - mPendingAccessibilityImportanceChange.clear(); - removeCallbacks(mItemAnimatorRunner); - mViewInfoStore.onDetach(); - mRecycler.onDetachedFromWindow(); - - PoolingContainer.callPoolingContainerOnReleaseForChildren(this); - - if (ALLOW_THREAD_GAP_WORK && mGapWorker != null) { - // Unregister with gap worker - mGapWorker.remove(this); - mGapWorker = null; - } - } - - /** - * Returns true if RecyclerView is attached to window. - */ - @Override - public boolean isAttachedToWindow() { - return mIsAttached; - } - - /** - * Checks if RecyclerView is in the middle of a layout or scroll and throws an - * {@link IllegalStateException} if it is not. - * - * @param message The message for the exception. Can be null. - * @see #assertNotInLayoutOrScroll(String) - */ - void assertInLayoutOrScroll(String message) { - if (!isComputingLayout()) { - if (message == null) { - throw new IllegalStateException("Cannot call this method unless RecyclerView is " - + "computing a layout or scrolling" + exceptionLabel()); - } - throw new IllegalStateException(message + exceptionLabel()); - - } - } - - /** - * Checks if RecyclerView is in the middle of a layout or scroll and throws an - * {@link IllegalStateException} if it is. - * - * @param message The message for the exception. Can be null. - * @see #assertInLayoutOrScroll(String) - */ - void assertNotInLayoutOrScroll(String message) { - if (isComputingLayout()) { - if (message == null) { - throw new IllegalStateException("Cannot call this method while RecyclerView is " - + "computing a layout or scrolling" + exceptionLabel()); - } - throw new IllegalStateException(message); - } - if (mDispatchScrollCounter > 0) { - Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might" - + "be run during a measure & layout pass where you cannot change the" - + "RecyclerView data. Any method call that might change the structure" - + "of the RecyclerView or the adapter contents should be postponed to" - + "the next frame.", - new IllegalStateException("" + exceptionLabel())); - } - } - - /** - * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched - * to child views or this view's standard scrolling behavior. - * - *

Client code may use listeners to implement item manipulation behavior. Once a listener - * returns true from - * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its - * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called - * for each incoming MotionEvent until the end of the gesture.

- * - * @param listener Listener to add - * @see SimpleOnItemTouchListener - */ - public void addOnItemTouchListener(@NonNull OnItemTouchListener listener) { - mOnItemTouchListeners.add(listener); - } - - /** - * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. - * - * @param listener Listener to remove - */ - public void removeOnItemTouchListener(@NonNull OnItemTouchListener listener) { - mOnItemTouchListeners.remove(listener); - if (mInterceptingOnItemTouchListener == listener) { - mInterceptingOnItemTouchListener = null; - } - } - - /** - * Dispatches the motion event to the intercepting OnItemTouchListener or provides opportunity - * for OnItemTouchListeners to intercept. - * - * @param e The MotionEvent - * @return True if handled by an intercepting OnItemTouchListener. - */ - private boolean dispatchToOnItemTouchListeners(MotionEvent e) { - - // OnItemTouchListeners should receive calls to their methods in the same pattern that - // ViewGroups do. That pattern is a bit confusing, which in turn makes the below code a - // bit confusing. Here are rules for the pattern: - // - // 1. A single MotionEvent should not be passed to either OnInterceptTouchEvent or - // OnTouchEvent twice. - // 2. ACTION_DOWN MotionEvents may be passed to both onInterceptTouchEvent and - // onTouchEvent. - // 3. All other MotionEvents should be passed to either onInterceptTouchEvent or - // onTouchEvent, not both. - - // Side Note: We don't currently perfectly mimic how MotionEvents work in the view system. - // If we were to do so, for every MotionEvent, any OnItemTouchListener that is before the - // intercepting OnItemTouchEvent should still have a chance to intercept, and if it does, - // the previously intercepting OnItemTouchEvent should get an ACTION_CANCEL event. - - if (mInterceptingOnItemTouchListener == null) { - if (e.getAction() == MotionEvent.ACTION_DOWN) { - return false; - } - return findInterceptingOnItemTouchListener(e); - } else { - mInterceptingOnItemTouchListener.onTouchEvent(this, e); - final int action = e.getAction(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mInterceptingOnItemTouchListener = null; - } - return true; - } - } - - /** - * Looks for an OnItemTouchListener that wants to intercept. - * - *

Calls {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} on each - * of the registered {@link OnItemTouchListener}s, passing in the - * MotionEvent. If one returns true and the action is not ACTION_CANCEL, saves the intercepting - * OnItemTouchListener to be called for future {@link RecyclerView#onTouchEvent(MotionEvent)} - * and immediately returns true. If none want to intercept or the action is ACTION_CANCEL, - * returns false. - * - * @param e The MotionEvent - * @return true if an OnItemTouchListener is saved as intercepting. - */ - private boolean findInterceptingOnItemTouchListener(MotionEvent e) { - int action = e.getAction(); - final int listenerCount = mOnItemTouchListeners.size(); - for (int i = 0; i < listenerCount; i++) { - final OnItemTouchListener listener = mOnItemTouchListeners.get(i); - if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { - mInterceptingOnItemTouchListener = listener; - return true; - } - } - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - if (mLayoutSuppressed) { - // When layout is suppressed, RV does not intercept the motion event. - // A child view e.g. a button may still get the click. - return false; - } - - // Clear the active onInterceptTouchListener. None should be set at this time, and if one - // is, it's because some other code didn't follow the standard contract. - mInterceptingOnItemTouchListener = null; - if (findInterceptingOnItemTouchListener(e)) { - cancelScroll(); - return true; - } - - if (mLayout == null) { - return false; - } - - final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); - final boolean canScrollVertically = mLayout.canScrollVertically(); - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(e); - - final int action = e.getActionMasked(); - final int actionIndex = e.getActionIndex(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - if (mIgnoreMotionEventTillDown) { - mIgnoreMotionEventTillDown = false; - } - mScrollPointerId = e.getPointerId(0); - mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); - - if (stopGlowAnimations(e) || mScrollState == SCROLL_STATE_SETTLING) { - getParent().requestDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - stopNestedScroll(TYPE_NON_TOUCH); - } - - // Clear the nested offsets - mNestedOffsets[0] = mNestedOffsets[1] = 0; - - int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; - if (canScrollHorizontally) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; - } - if (canScrollVertically) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; - } - startNestedScroll(nestedScrollAxis, TYPE_TOUCH); - break; - - case MotionEvent.ACTION_POINTER_DOWN: - mScrollPointerId = e.getPointerId(actionIndex); - mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); - break; - - case MotionEvent.ACTION_MOVE: { - final int index = e.findPointerIndex(mScrollPointerId); - if (index < 0) { - Log.e(TAG, "Error processing scroll; pointer index for id " - + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); - return false; - } - - final int x = (int) (e.getX(index) + 0.5f); - final int y = (int) (e.getY(index) + 0.5f); - if (mScrollState != SCROLL_STATE_DRAGGING) { - final int dx = x - mInitialTouchX; - final int dy = y - mInitialTouchY; - boolean startScroll = false; - if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { - mLastTouchX = x; - startScroll = true; - } - if (canScrollVertically && Math.abs(dy) > mTouchSlop) { - mLastTouchY = y; - startScroll = true; - } - if (startScroll) { - setScrollState(SCROLL_STATE_DRAGGING); - } - } - } - break; - - case MotionEvent.ACTION_POINTER_UP: { - onPointerUp(e); - } - break; - - case MotionEvent.ACTION_UP: { - mVelocityTracker.clear(); - stopNestedScroll(TYPE_TOUCH); - } - break; - - case MotionEvent.ACTION_CANCEL: { - cancelScroll(); - } - } - return mScrollState == SCROLL_STATE_DRAGGING; - } - - /** - * This stops any edge glow animation that is currently running by applying a - * 0 length pull at the displacement given by the provided MotionEvent. On pre-S devices, - * this method does nothing, allowing any animating edge effect to continue animating and - * returning false always. - * - * @param e The motion event to use to indicate the finger position for the displacement of - * the current pull. - * @return true if any edge effect had an existing effect to be drawn ond the - * animation was stopped or false if no edge effect had a value to display. - */ - private boolean stopGlowAnimations(MotionEvent e) { - boolean stopped = false; - if (mLeftGlow != null && EdgeEffectCompat.getDistance(mLeftGlow) != 0 - && !canScrollHorizontally(-1)) { - EdgeEffectCompat.onPullDistance(mLeftGlow, 0, 1 - (e.getY() / getHeight())); - stopped = true; - } - if (mRightGlow != null && EdgeEffectCompat.getDistance(mRightGlow) != 0 - && !canScrollHorizontally(1)) { - EdgeEffectCompat.onPullDistance(mRightGlow, 0, e.getY() / getHeight()); - stopped = true; - } - if (mTopGlow != null && EdgeEffectCompat.getDistance(mTopGlow) != 0 - && !canScrollVertically(-1)) { - EdgeEffectCompat.onPullDistance(mTopGlow, 0, e.getX() / getWidth()); - stopped = true; - } - if (mBottomGlow != null && EdgeEffectCompat.getDistance(mBottomGlow) != 0 - && !canScrollVertically(1)) { - EdgeEffectCompat.onPullDistance(mBottomGlow, 0, 1 - e.getX() / getWidth()); - stopped = true; - } - return stopped; - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - final int listenerCount = mOnItemTouchListeners.size(); - for (int i = 0; i < listenerCount; i++) { - final OnItemTouchListener listener = mOnItemTouchListeners.get(i); - listener.onRequestDisallowInterceptTouchEvent(disallowIntercept); - } - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (mLayoutSuppressed || mIgnoreMotionEventTillDown) { - return false; - } - if (dispatchToOnItemTouchListeners(e)) { - cancelScroll(); - return true; - } - - if (mLayout == null) { - return false; - } - - final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); - final boolean canScrollVertically = mLayout.canScrollVertically(); - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - boolean eventAddedToVelocityTracker = false; - - final int action = e.getActionMasked(); - final int actionIndex = e.getActionIndex(); - - if (action == MotionEvent.ACTION_DOWN) { - mNestedOffsets[0] = mNestedOffsets[1] = 0; - } - final MotionEvent vtev = MotionEvent.obtain(e); - vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - mScrollPointerId = e.getPointerId(0); - mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); - - int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; - if (canScrollHorizontally) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; - } - if (canScrollVertically) { - nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; - } - startNestedScroll(nestedScrollAxis, TYPE_TOUCH); - } - break; - - case MotionEvent.ACTION_POINTER_DOWN: { - mScrollPointerId = e.getPointerId(actionIndex); - mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); - } - break; - - case MotionEvent.ACTION_MOVE: { - final int index = e.findPointerIndex(mScrollPointerId); - if (index < 0) { - Log.e(TAG, "Error processing scroll; pointer index for id " - + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); - return false; - } - - final int x = (int) (e.getX(index) + 0.5f); - final int y = (int) (e.getY(index) + 0.5f); - int dx = mLastTouchX - x; - int dy = mLastTouchY - y; - - if (mScrollState != SCROLL_STATE_DRAGGING) { - boolean startScroll = false; - if (canScrollHorizontally) { - if (dx > 0) { - dx = Math.max(0, dx - mTouchSlop); - } else { - dx = Math.min(0, dx + mTouchSlop); - } - if (dx != 0) { - startScroll = true; - } - } - if (canScrollVertically) { - if (dy > 0) { - dy = Math.max(0, dy - mTouchSlop); - } else { - dy = Math.min(0, dy + mTouchSlop); - } - if (dy != 0) { - startScroll = true; - } - } - if (startScroll) { - setScrollState(SCROLL_STATE_DRAGGING); - } - } - - if (mScrollState == SCROLL_STATE_DRAGGING) { - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - dx -= releaseHorizontalGlow(dx, e.getY()); - dy -= releaseVerticalGlow(dy, e.getX()); - - if (dispatchNestedPreScroll( - canScrollHorizontally ? dx : 0, - canScrollVertically ? dy : 0, - mReusableIntPair, mScrollOffset, TYPE_TOUCH - )) { - dx -= mReusableIntPair[0]; - dy -= mReusableIntPair[1]; - // Updated the nested offsets - mNestedOffsets[0] += mScrollOffset[0]; - mNestedOffsets[1] += mScrollOffset[1]; - // Scroll has initiated, prevent parents from intercepting - getParent().requestDisallowInterceptTouchEvent(true); - } - - mLastTouchX = x - mScrollOffset[0]; - mLastTouchY = y - mScrollOffset[1]; - - if (scrollByInternal( - canScrollHorizontally ? dx : 0, - canScrollVertically ? dy : 0, - e, TYPE_TOUCH)) { - getParent().requestDisallowInterceptTouchEvent(true); - } - if (mGapWorker != null && (dx != 0 || dy != 0)) { - mGapWorker.postFromTraversal(this, dx, dy); - } - } - } - break; - - case MotionEvent.ACTION_POINTER_UP: { - onPointerUp(e); - } - break; - - case MotionEvent.ACTION_UP: { - mVelocityTracker.addMovement(vtev); - eventAddedToVelocityTracker = true; - mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); - final float xvel = canScrollHorizontally - ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; - final float yvel = canScrollVertically - ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; - if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { - setScrollState(SCROLL_STATE_IDLE); - } - resetScroll(); - } - break; - - case MotionEvent.ACTION_CANCEL: { - cancelScroll(); - } - break; - } - - if (!eventAddedToVelocityTracker) { - mVelocityTracker.addMovement(vtev); - } - vtev.recycle(); - - return true; - } - - private void resetScroll() { - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - stopNestedScroll(TYPE_TOUCH); - releaseGlows(); - } - - private void cancelScroll() { - resetScroll(); - setScrollState(SCROLL_STATE_IDLE); - } - - private void onPointerUp(MotionEvent e) { - final int actionIndex = e.getActionIndex(); - if (e.getPointerId(actionIndex) == mScrollPointerId) { - // Pick a new pointer to pick up the slack. - final int newIndex = actionIndex == 0 ? 1 : 0; - mScrollPointerId = e.getPointerId(newIndex); - mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); - } - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (mLayout == null) { - return false; - } - if (mLayoutSuppressed) { - return false; - } - if (event.getAction() == MotionEvent.ACTION_SCROLL) { - final float vScroll, hScroll; - if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { - if (mLayout.canScrollVertically()) { - // Inverse the sign of the vertical scroll to align the scroll orientation - // with AbsListView. - vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); - } else { - vScroll = 0f; - } - if (mLayout.canScrollHorizontally()) { - hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); - } else { - hScroll = 0f; - } - } else if ((event.getSource() & InputDeviceCompat.SOURCE_ROTARY_ENCODER) != 0) { - final float axisScroll = event.getAxisValue(MotionEventCompat.AXIS_SCROLL); - if (mLayout.canScrollVertically()) { - // Invert the sign of the vertical scroll to align the scroll orientation - // with AbsListView. - vScroll = -axisScroll; - hScroll = 0f; - } else if (mLayout.canScrollHorizontally()) { - vScroll = 0f; - hScroll = axisScroll; - } else { - vScroll = 0f; - hScroll = 0f; - } - } else { - vScroll = 0f; - hScroll = 0f; - } - - if (vScroll != 0 || hScroll != 0) { - nestedScrollByInternal((int) (hScroll * mScaledHorizontalScrollFactor), - (int) (vScroll * mScaledVerticalScrollFactor), event, TYPE_NON_TOUCH); - } - } - return false; - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - if (mLayout == null) { - defaultOnMeasure(widthSpec, heightSpec); - return; - } - if (mLayout.isAutoMeasureEnabled()) { - final int widthMode = MeasureSpec.getMode(widthSpec); - final int heightMode = MeasureSpec.getMode(heightSpec); - - /** - * This specific call should be considered deprecated and replaced with - * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could - * break existing third party code but all documentation directs developers to not - * override {@link LayoutManager#onMeasure(int, int)} when - * {@link LayoutManager#isAutoMeasureEnabled()} returns true. - */ - mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); - - // Calculate and track whether we should skip measurement here because the MeasureSpec - // modes in both dimensions are EXACTLY. - mLastAutoMeasureSkippedDueToExact = - widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; - if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) { - return; - } - - if (mState.mLayoutStep == State.STEP_START) { - dispatchLayoutStep1(); - } - // set dimensions in 2nd step. Pre-layout should happen with old dimensions for - // consistency - mLayout.setMeasureSpecs(widthSpec, heightSpec); - mState.mIsMeasuring = true; - dispatchLayoutStep2(); - - // now we can get the width and height from the children. - mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); - - // if RecyclerView has non-exact width and height and if there is at least one child - // which also has non-exact width & height, we have to re-measure. - if (mLayout.shouldMeasureTwice()) { - mLayout.setMeasureSpecs( - MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - mState.mIsMeasuring = true; - dispatchLayoutStep2(); - // now we can get the width and height from the children. - mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); - } - - mLastAutoMeasureNonExactMeasuredWidth = getMeasuredWidth(); - mLastAutoMeasureNonExactMeasuredHeight = getMeasuredHeight(); - } else { - if (mHasFixedSize) { - mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); - return; - } - // custom onMeasure - if (mAdapterUpdateDuringMeasure) { - startInterceptRequestLayout(); - onEnterLayoutOrScroll(); - processAdapterUpdatesAndSetAnimationFlags(); - onExitLayoutOrScroll(); - - if (mState.mRunPredictiveAnimations) { - mState.mInPreLayout = true; - } else { - // consume remaining updates to provide a consistent state with the layout pass. - mAdapterHelper.consumeUpdatesInOnePass(); - mState.mInPreLayout = false; - } - mAdapterUpdateDuringMeasure = false; - stopInterceptRequestLayout(false); - } else if (mState.mRunPredictiveAnimations) { - // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: - // this means there is already an onMeasure() call performed to handle the pending - // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout - // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time - // because getViewForPosition() will crash when LM uses a child to measure. - setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); - return; - } - - if (mAdapter != null) { - mState.mItemCount = mAdapter.getItemCount(); - } else { - mState.mItemCount = 0; - } - startInterceptRequestLayout(); - mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); - stopInterceptRequestLayout(false); - mState.mInPreLayout = false; // clear - } - } - - /** - * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios - * where this RecyclerView is otherwise lacking better information. - */ - void defaultOnMeasure(int widthSpec, int heightSpec) { - // calling LayoutManager here is not pretty but that API is already public and it is better - // than creating another method since this is internal. - final int width = LayoutManager.chooseSize(widthSpec, - getPaddingLeft() + getPaddingRight(), - ViewCompat.getMinimumWidth(this)); - final int height = LayoutManager.chooseSize(heightSpec, - getPaddingTop() + getPaddingBottom(), - ViewCompat.getMinimumHeight(this)); - - setMeasuredDimension(width, height); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (w != oldw || h != oldh) { - invalidateGlows(); - // layout's w/h are updated during measure/layout steps. - } - } - - /** - * Sets the {@link ItemAnimator} that will handle animations involving changes - * to the items in this RecyclerView. By default, RecyclerView instantiates and - * uses an instance of {@link DefaultItemAnimator}. Whether item animations are - * enabled for the RecyclerView depends on the ItemAnimator and whether - * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() - * supports item animations}. - * - * @param animator The ItemAnimator being set. If null, no animations will occur - * when changes occur to the items in this RecyclerView. - */ - public void setItemAnimator(@Nullable ItemAnimator animator) { - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - mItemAnimator.setListener(null); - } - mItemAnimator = animator; - if (mItemAnimator != null) { - mItemAnimator.setListener(mItemAnimatorListener); - } - } - - void onEnterLayoutOrScroll() { - mLayoutOrScrollCounter++; - } - - void onExitLayoutOrScroll() { - onExitLayoutOrScroll(true); - } - - void onExitLayoutOrScroll(boolean enableChangeEvents) { - mLayoutOrScrollCounter--; - if (mLayoutOrScrollCounter < 1) { - if (sDebugAssertionsEnabled && mLayoutOrScrollCounter < 0) { - throw new IllegalStateException("layout or scroll counter cannot go below zero." - + "Some calls are not matching" + exceptionLabel()); - } - mLayoutOrScrollCounter = 0; - if (enableChangeEvents) { - dispatchContentChangedIfNecessary(); - dispatchPendingImportantForAccessibilityChanges(); - } - } - } - - boolean isAccessibilityEnabled() { - return mAccessibilityManager != null && mAccessibilityManager.isEnabled(); - } - - private void dispatchContentChangedIfNecessary() { - final int flags = mEatenAccessibilityChangeFlags; - mEatenAccessibilityChangeFlags = 0; - if (flags != 0 && isAccessibilityEnabled()) { - final AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - AccessibilityEventCompat.setContentChangeTypes(event, flags); - sendAccessibilityEventUnchecked(event); - } - } - - /** - * Returns whether RecyclerView is currently computing a layout. - *

- * If this method returns true, it means that RecyclerView is in a lockdown state and any - * attempt to update adapter contents will result in an exception because adapter contents - * cannot be changed while RecyclerView is trying to compute the layout. - *

- * It is very unlikely that your code will be running during this state as it is - * called by the framework when a layout traversal happens or RecyclerView starts to scroll - * in response to system events (touch, accessibility etc). - *

- * This case may happen if you have some custom logic to change adapter contents in - * response to a View callback (e.g. focus change callback) which might be triggered during a - * layout calculation. In these cases, you should just postpone the change using a Handler or a - * similar mechanism. - * - * @return true if RecyclerView is currently computing a layout, false - * otherwise - */ - public boolean isComputingLayout() { - return mLayoutOrScrollCounter > 0; - } - - /** - * Returns true if an accessibility event should not be dispatched now. This happens when an - * accessibility request arrives while RecyclerView does not have a stable state which is very - * hard to handle for a LayoutManager. Instead, this method records necessary information about - * the event and dispatches a window change event after the critical section is finished. - * - * @return True if the accessibility event should be postponed. - */ - boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) { - if (isComputingLayout()) { - int type = 0; - if (event != null) { - type = AccessibilityEventCompat.getContentChangeTypes(event); - } - if (type == 0) { - type = AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED; - } - mEatenAccessibilityChangeFlags |= type; - return true; - } - return false; - } - - @Override - public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { - if (shouldDeferAccessibilityEvent(event)) { - return; - } - super.sendAccessibilityEventUnchecked(event); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - onPopulateAccessibilityEvent(event); - return true; - } - - /** - * Gets the current ItemAnimator for this RecyclerView. A null return value - * indicates that there is no animator and that item changes will happen without - * any animations. By default, RecyclerView instantiates and - * uses an instance of {@link DefaultItemAnimator}. - * - * @return ItemAnimator The current ItemAnimator. If null, no animations will occur - * when changes occur to the items in this RecyclerView. - */ - @Nullable - public ItemAnimator getItemAnimator() { - return mItemAnimator; - } - - /** - * Post a runnable to the next frame to run pending item animations. Only the first such - * request will be posted, governed by the mPostedAnimatorRunner flag. - */ - void postAnimationRunner() { - if (!mPostedAnimatorRunner && mIsAttached) { - ViewCompat.postOnAnimation(this, mItemAnimatorRunner); - mPostedAnimatorRunner = true; - } - } - - private boolean predictiveItemAnimationsEnabled() { - return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); - } - - /** - * Consumes adapter updates and calculates which type of animations we want to run. - * Called in onMeasure and dispatchLayout. - *

- * This method may process only the pre-layout state of updates or all of them. - */ - private void processAdapterUpdatesAndSetAnimationFlags() { - if (mDataSetHasChangedAfterLayout) { - // Processing these items have no value since data set changed unexpectedly. - // Instead, we just reset it. - mAdapterHelper.reset(); - if (mDispatchItemsChangedEvent) { - mLayout.onItemsChanged(this); - } - } - // simple animations are a subset of advanced animations (which will cause a - // pre-layout step) - // If layout supports predictive animations, pre-process to decide if we want to run them - if (predictiveItemAnimationsEnabled()) { - mAdapterHelper.preProcess(); - } else { - mAdapterHelper.consumeUpdatesInOnePass(); - } - boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; - mState.mRunSimpleAnimations = mFirstLayoutComplete - && mItemAnimator != null - && (mDataSetHasChangedAfterLayout - || animationTypeSupported - || mLayout.mRequestedSimpleAnimations) - && (!mDataSetHasChangedAfterLayout - || mAdapter.hasStableIds()); - mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations - && animationTypeSupported - && !mDataSetHasChangedAfterLayout - && predictiveItemAnimationsEnabled(); - } - - /** - * Wrapper around layoutChildren() that handles animating changes caused by layout. - * Animations work on the assumption that there are five different kinds of items - * in play: - * PERSISTENT: items are visible before and after layout - * REMOVED: items were visible before layout and were removed by the app - * ADDED: items did not exist before layout and were added by the app - * DISAPPEARING: items exist in the data set before/after, but changed from - * visible to non-visible in the process of layout (they were moved off - * screen as a side-effect of other changes) - * APPEARING: items exist in the data set before/after, but changed from - * non-visible to visible in the process of layout (they were moved on - * screen as a side-effect of other changes) - * The overall approach figures out what items exist before/after layout and - * infers one of the five above states for each of the items. Then the animations - * are set up accordingly: - * PERSISTENT views are animated via - * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} - * DISAPPEARING views are animated via - * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} - * APPEARING views are animated via - * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} - * and changed views are animated via - * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. - */ - void dispatchLayout() { - if (mAdapter == null) { - Log.w(TAG, "No adapter attached; skipping layout"); - // leave the state in START - return; - } - if (mLayout == null) { - Log.e(TAG, "No layout manager attached; skipping layout"); - // leave the state in START - return; - } - mState.mIsMeasuring = false; - - // If the last time we measured children in onMeasure, we skipped the measurement and layout - // of RV children because the MeasureSpec in both dimensions was EXACTLY, and current - // dimensions of the RV are not equal to the last measured dimensions of RV, we need to - // measure and layout children one last time. - boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact - && (mLastAutoMeasureNonExactMeasuredWidth != getWidth() - || mLastAutoMeasureNonExactMeasuredHeight != getHeight()); - mLastAutoMeasureNonExactMeasuredWidth = 0; - mLastAutoMeasureNonExactMeasuredHeight = 0; - mLastAutoMeasureSkippedDueToExact = false; - - if (mState.mLayoutStep == State.STEP_START) { - dispatchLayoutStep1(); - mLayout.setExactMeasureSpecsFrom(this); - dispatchLayoutStep2(); - } else if (mAdapterHelper.hasUpdates() - || needsRemeasureDueToExactSkip - || mLayout.getWidth() != getWidth() - || mLayout.getHeight() != getHeight()) { - // First 2 steps are done in onMeasure but looks like we have to run again due to - // changed size. - - // TODO(shepshapard): Worth a note that I believe - // "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is - // not actually correct, causes unnecessary work to be done, and should be - // removed. Removing causes many tests to fail and I didn't have the time to - // investigate. Just a note for the a future reader or bug fixer. - mLayout.setExactMeasureSpecsFrom(this); - dispatchLayoutStep2(); - } else { - // always make sure we sync them (to ensure mode is exact) - mLayout.setExactMeasureSpecsFrom(this); - } - dispatchLayoutStep3(); - } - - private void saveFocusInfo() { - View child = null; - if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { - child = getFocusedChild(); - } - - final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); - if (focusedVh == null) { - resetFocusInfo(); - } else { - mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; - // mFocusedItemPosition should hold the current adapter position of the previously - // focused item. If the item is removed, we store the previous adapter position of the - // removed item. - mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION - : (focusedVh.isRemoved() ? focusedVh.mOldPosition - : focusedVh.getAbsoluteAdapterPosition()); - mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); - } - } - - private void resetFocusInfo() { - mState.mFocusedItemId = NO_ID; - mState.mFocusedItemPosition = NO_POSITION; - mState.mFocusedSubChildId = View.NO_ID; - } - - /** - * Finds the best view candidate to request focus on using mFocusedItemPosition index of the - * previously focused item. It first traverses the adapter forward to find a focusable candidate - * and if no such candidate is found, it reverses the focus search direction for the items - * before the mFocusedItemPosition'th index; - * - * @return The best candidate to request focus on, or null if no such candidate exists. Null - * indicates all the existing adapter items are unfocusable. - */ - @Nullable - private View findNextViewToFocus() { - int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition - : 0; - ViewHolder nextFocus; - final int itemCount = mState.getItemCount(); - for (int i = startFocusSearchIndex; i < itemCount; i++) { - nextFocus = findViewHolderForAdapterPosition(i); - if (nextFocus == null) { - break; - } - if (nextFocus.itemView.hasFocusable()) { - return nextFocus.itemView; - } - } - final int limit = Math.min(itemCount, startFocusSearchIndex); - for (int i = limit - 1; i >= 0; i--) { - nextFocus = findViewHolderForAdapterPosition(i); - if (nextFocus == null) { - return null; - } - if (nextFocus.itemView.hasFocusable()) { - return nextFocus.itemView; - } - } - return null; - } - - private void recoverFocusFromState() { - if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() - || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS - || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { - // No-op if either of these cases happens: - // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus - // before its children and is focused (i.e. it already stole the focus away from its - // descendants). - return; - } - // only recover focus if RV itself has the focus or the focused view is hidden - if (!isFocused()) { - final View focusedChild = getFocusedChild(); - if (IGNORE_DETACHED_FOCUSED_CHILD - && (focusedChild.getParent() == null || !focusedChild.hasFocus())) { - // Special handling of API 15-. A focused child can be invalid because mFocus is not - // cleared when the child is detached (mParent = null), - // This happens because clearFocus on API 15- does not invalidate mFocus of its - // parent when this child is detached. - // For API 16+, this is not an issue because requestFocus takes care of clearing the - // prior detached focused child. For API 15- the problem happens in 2 cases because - // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called - // for the current focused item which calls clearChild or 2. when the prior focused - // child is removed, removeDetachedView called in layout step 3 which calls - // clearChild. We should ignore this invalid focused child in all our calculations - // for the next view to receive focus, and apply the focus recovery logic instead. - if (mChildHelper.getChildCount() == 0) { - // No children left. Request focus on the RV itself since one of its children - // was holding focus previously. - requestFocus(); - return; - } - } else if (!mChildHelper.isHidden(focusedChild)) { - // If the currently focused child is hidden, apply the focus recovery logic. - // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. - return; - } - } - ViewHolder focusTarget = null; - // RV first attempts to locate the previously focused item to request focus on using - // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to - // find the next best candidate to request focus on based on mFocusedItemPosition. - if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { - focusTarget = findViewHolderForItemId(mState.mFocusedItemId); - } - View viewToFocus = null; - if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) - || !focusTarget.itemView.hasFocusable()) { - if (mChildHelper.getChildCount() > 0) { - // At this point, RV has focus and either of these conditions are true: - // 1. There's no previously focused item either because RV received focused before - // layout, or the previously focused item was removed, or RV doesn't have stable IDs - // 2. Previous focus child is hidden, or 3. Previous focused child is no longer - // focusable. In either of these cases, we make sure that RV still passes down the - // focus to one of its focusable children using a best-effort algorithm. - viewToFocus = findNextViewToFocus(); - } - } else { - // looks like the focused item has been replaced with another view that represents the - // same item in the adapter. Request focus on that. - viewToFocus = focusTarget.itemView; - } - - if (viewToFocus != null) { - if (mState.mFocusedSubChildId != NO_ID) { - View child = viewToFocus.findViewById(mState.mFocusedSubChildId); - if (child != null && child.isFocusable()) { - viewToFocus = child; - } - } - viewToFocus.requestFocus(); - } - } - - private int getDeepestFocusedViewWithId(View view) { - int lastKnownId = view.getId(); - while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { - view = ((ViewGroup) view).getFocusedChild(); - final int id = view.getId(); - if (id != View.NO_ID) { - lastKnownId = view.getId(); - } - } - return lastKnownId; - } - - final void fillRemainingScrollValues(State state) { - if (getScrollState() == SCROLL_STATE_SETTLING) { - final OverScroller scroller = mViewFlinger.mOverScroller; - state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX(); - state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY(); - } else { - state.mRemainingScrollHorizontal = 0; - state.mRemainingScrollVertical = 0; - } - } - - /** - * The first step of a layout where we; - * - process adapter updates - * - decide which animation should run - * - save information about current views - * - If necessary, run predictive layout and save its information - */ - private void dispatchLayoutStep1() { - mState.assertLayoutStep(State.STEP_START); - fillRemainingScrollValues(mState); - mState.mIsMeasuring = false; - startInterceptRequestLayout(); - mViewInfoStore.clear(); - onEnterLayoutOrScroll(); - processAdapterUpdatesAndSetAnimationFlags(); - saveFocusInfo(); - mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; - mItemsAddedOrRemoved = mItemsChanged = false; - mState.mInPreLayout = mState.mRunPredictiveAnimations; - mState.mItemCount = mAdapter.getItemCount(); - findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); - - if (mState.mRunSimpleAnimations) { - // Step 0: Find out where all non-removed items are, pre-layout - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { - continue; - } - final ItemHolderInfo animationInfo = mItemAnimator - .recordPreLayoutInformation(mState, holder, - ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), - holder.getUnmodifiedPayloads()); - mViewInfoStore.addToPreLayout(holder, animationInfo); - if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() - && !holder.shouldIgnore() && !holder.isInvalid()) { - long key = getChangedHolderKey(holder); - // This is NOT the only place where a ViewHolder is added to old change holders - // list. There is another case where: - // * A VH is currently hidden but not deleted - // * The hidden item is changed in the adapter - // * Layout manager decides to layout the item in the pre-Layout pass (step1) - // When this case is detected, RV will un-hide that view and add to the old - // change holders list. - mViewInfoStore.addToOldChangeHolders(key, holder); - } - } - } - if (mState.mRunPredictiveAnimations) { - // Step 1: run prelayout: This will use the old positions of items. The layout manager - // is expected to layout everything, even removed items (though not to add removed - // items back to the container). This gives the pre-layout position of APPEARING views - // which come into existence as part of the real layout. - - // Save old positions so that LayoutManager can run its mapping logic. - saveOldPositions(); - final boolean didStructureChange = mState.mStructureChanged; - mState.mStructureChanged = false; - // temporarily disable flag because we are asking for previous layout - mLayout.onLayoutChildren(mRecycler, mState); - mState.mStructureChanged = didStructureChange; - - for (int i = 0; i < mChildHelper.getChildCount(); ++i) { - final View child = mChildHelper.getChildAt(i); - final ViewHolder viewHolder = getChildViewHolderInt(child); - if (viewHolder.shouldIgnore()) { - continue; - } - if (!mViewInfoStore.isInPreLayout(viewHolder)) { - int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); - boolean wasHidden = viewHolder - .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); - if (!wasHidden) { - flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; - } - final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( - mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); - if (wasHidden) { - recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); - } else { - mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); - } - } - } - // we don't process disappearing list because they may re-appear in post layout pass. - clearOldPositions(); - } else { - clearOldPositions(); - } - onExitLayoutOrScroll(); - stopInterceptRequestLayout(false); - mState.mLayoutStep = State.STEP_LAYOUT; - } - - /** - * The second layout step where we do the actual layout of the views for the final state. - * This step might be run multiple times if necessary (e.g. measure). - */ - private void dispatchLayoutStep2() { - startInterceptRequestLayout(); - onEnterLayoutOrScroll(); - mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); - mAdapterHelper.consumeUpdatesInOnePass(); - mState.mItemCount = mAdapter.getItemCount(); - mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; - if (mPendingSavedState != null && mAdapter.canRestoreState()) { - if (mPendingSavedState.mLayoutState != null) { - mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); - } - mPendingSavedState = null; - } - // Step 2: Run layout - mState.mInPreLayout = false; - mLayout.onLayoutChildren(mRecycler, mState); - - mState.mStructureChanged = false; - - // onLayoutChildren may have caused client code to disable item animations; re-check - mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; - mState.mLayoutStep = State.STEP_ANIMATIONS; - onExitLayoutOrScroll(); - stopInterceptRequestLayout(false); - } - - /** - * The final step of the layout where we save the information about views for animations, - * trigger animations and do any necessary cleanup. - */ - private void dispatchLayoutStep3() { - mState.assertLayoutStep(State.STEP_ANIMATIONS); - startInterceptRequestLayout(); - onEnterLayoutOrScroll(); - mState.mLayoutStep = State.STEP_START; - if (mState.mRunSimpleAnimations) { - // Step 3: Find out where things are now, and process change animations. - // traverse list in reverse because we may call animateChange in the loop which may - // remove the target view holder. - for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { - ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.shouldIgnore()) { - continue; - } - long key = getChangedHolderKey(holder); - final ItemHolderInfo animationInfo = mItemAnimator - .recordPostLayoutInformation(mState, holder); - ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); - if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { - // run a change animation - - // If an Item is CHANGED but the updated version is disappearing, it creates - // a conflicting case. - // Since a view that is marked as disappearing is likely to be going out of - // bounds, we run a change animation. Both views will be cleaned automatically - // once their animations finish. - // On the other hand, if it is the same view holder instance, we run a - // disappearing animation instead because we are not going to rebind the updated - // VH unless it is enforced by the layout manager. - final boolean oldDisappearing = mViewInfoStore.isDisappearing( - oldChangeViewHolder); - final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); - if (oldDisappearing && oldChangeViewHolder == holder) { - // run disappear animation instead of change - mViewInfoStore.addToPostLayout(holder, animationInfo); - } else { - final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( - oldChangeViewHolder); - // we add and remove so that any post info is merged. - mViewInfoStore.addToPostLayout(holder, animationInfo); - ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); - if (preInfo == null) { - handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); - } else { - animateChange(oldChangeViewHolder, holder, preInfo, postInfo, - oldDisappearing, newDisappearing); - } - } - } else { - mViewInfoStore.addToPostLayout(holder, animationInfo); - } - } - - // Step 4: Process view info lists and trigger animations - mViewInfoStore.process(mViewInfoProcessCallback); - } - - mLayout.removeAndRecycleScrapInt(mRecycler); - mState.mPreviousLayoutItemCount = mState.mItemCount; - mDataSetHasChangedAfterLayout = false; - mDispatchItemsChangedEvent = false; - mState.mRunSimpleAnimations = false; - - mState.mRunPredictiveAnimations = false; - mLayout.mRequestedSimpleAnimations = false; - if (mRecycler.mChangedScrap != null) { - mRecycler.mChangedScrap.clear(); - } - if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { - // Initial prefetch has expanded cache, so reset until next prefetch. - // This prevents initial prefetches from expanding the cache permanently. - mLayout.mPrefetchMaxCountObserved = 0; - mLayout.mPrefetchMaxObservedInInitialPrefetch = false; - mRecycler.updateViewCacheSize(); - } - - mLayout.onLayoutCompleted(mState); - onExitLayoutOrScroll(); - stopInterceptRequestLayout(false); - mViewInfoStore.clear(); - if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { - dispatchOnScrolled(0, 0); - } - recoverFocusFromState(); - resetFocusInfo(); - } - - /** - * This handles the case where there is an unexpected VH missing in the pre-layout map. - *

- * We might be able to detect the error in the application which will help the developer to - * resolve the issue. - *

- * If it is not an expected error, we at least print an error to notify the developer and ignore - * the animation. - * - * https://code.google.com/p/android/issues/detail?id=193958 - * - * @param key The change key - * @param holder Current ViewHolder - * @param oldChangeViewHolder Changed ViewHolder - */ - private void handleMissingPreInfoForChangeError(long key, - ViewHolder holder, ViewHolder oldChangeViewHolder) { - // check if two VH have the same key, if so, print that as an error - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - View view = mChildHelper.getChildAt(i); - ViewHolder other = getChildViewHolderInt(view); - if (other == holder) { - continue; - } - final long otherKey = getChangedHolderKey(other); - if (otherKey == key) { - if (mAdapter != null && mAdapter.hasStableIds()) { - throw new IllegalStateException("Two different ViewHolders have the same stable" - + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" - + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder - + exceptionLabel()); - } else { - throw new IllegalStateException("Two different ViewHolders have the same change" - + " ID. This might happen due to inconsistent Adapter update events or" - + " if the LayoutManager lays out the same View multiple times." - + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder - + exceptionLabel()); - } - } - } - // Very unlikely to happen but if it does, notify the developer. - Log.e(TAG, "Problem while matching changed view holders with the new" - + "ones. The pre-layout information for the change holder " + oldChangeViewHolder - + " cannot be found but it is necessary for " + holder + exceptionLabel()); - } - - /** - * Records the animation information for a view holder that was bounced from hidden list. It - * also clears the bounce back flag. - */ - void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, - ItemHolderInfo animationInfo) { - // looks like this view bounced back from hidden list! - viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); - if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() - && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { - long key = getChangedHolderKey(viewHolder); - mViewInfoStore.addToOldChangeHolders(key, viewHolder); - } - mViewInfoStore.addToPreLayout(viewHolder, animationInfo); - } - - private void findMinMaxChildLayoutPositions(int[] into) { - final int count = mChildHelper.getChildCount(); - if (count == 0) { - into[0] = NO_POSITION; - into[1] = NO_POSITION; - return; - } - int minPositionPreLayout = Integer.MAX_VALUE; - int maxPositionPreLayout = Integer.MIN_VALUE; - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.shouldIgnore()) { - continue; - } - final int pos = holder.getLayoutPosition(); - if (pos < minPositionPreLayout) { - minPositionPreLayout = pos; - } - if (pos > maxPositionPreLayout) { - maxPositionPreLayout = pos; - } - } - into[0] = minPositionPreLayout; - into[1] = maxPositionPreLayout; - } - - private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { - findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); - return mMinMaxLayoutPositions[0] != minPositionPreLayout - || mMinMaxLayoutPositions[1] != maxPositionPreLayout; - } - - @Override - protected void removeDetachedView(View child, boolean animate) { - ViewHolder vh = getChildViewHolderInt(child); - if (vh != null) { - if (vh.isTmpDetached()) { - vh.clearTmpDetachFlag(); - } else if (!vh.shouldIgnore()) { - throw new IllegalArgumentException("Called removeDetachedView with a view which" - + " is not flagged as tmp detached." + vh + exceptionLabel()); - } - } else { - if (sDebugAssertionsEnabled) { - throw new IllegalArgumentException( - "No ViewHolder found for child: " + child + exceptionLabel()); - } - } - - // Clear any android.view.animation.Animation that may prevent the item from - // detaching when being removed. If a child is re-added before the - // lazy detach occurs, it will receive invalid attach/detach sequencing. - child.clearAnimation(); - - dispatchChildDetached(child); - super.removeDetachedView(child, animate); - } - - /** - * Returns a unique key to be used while handling change animations. - * It might be child's position or stable id depending on the adapter type. - */ - long getChangedHolderKey(ViewHolder holder) { - return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; - } - - void animateAppearance(@NonNull ViewHolder itemHolder, - @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { - itemHolder.setIsRecyclable(false); - if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { - postAnimationRunner(); - } - } - - void animateDisappearance(@NonNull ViewHolder holder, - @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { - addAnimatingView(holder); - holder.setIsRecyclable(false); - if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { - postAnimationRunner(); - } - } - - private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, - @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, - boolean oldHolderDisappearing, boolean newHolderDisappearing) { - oldHolder.setIsRecyclable(false); - if (oldHolderDisappearing) { - addAnimatingView(oldHolder); - } - if (oldHolder != newHolder) { - if (newHolderDisappearing) { - addAnimatingView(newHolder); - } - oldHolder.mShadowedHolder = newHolder; - // old holder should disappear after animation ends - addAnimatingView(oldHolder); - mRecycler.unscrapView(oldHolder); - newHolder.setIsRecyclable(false); - newHolder.mShadowingHolder = oldHolder; - } - if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { - postAnimationRunner(); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); - dispatchLayout(); - TraceCompat.endSection(); - mFirstLayoutComplete = true; - } - - @Override - public void requestLayout() { - if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) { - super.requestLayout(); - } else { - mLayoutWasDefered = true; - } - } - - void markItemDecorInsetsDirty() { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = mChildHelper.getUnfilteredChildAt(i); - ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; - } - mRecycler.markItemDecorInsetsDirty(); - } - - @Override - public void draw(Canvas c) { - super.draw(c); - - final int count = mItemDecorations.size(); - for (int i = 0; i < count; i++) { - mItemDecorations.get(i).onDrawOver(c, this, mState); - } - // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we - // need find children closest to edges. Not sure if it is worth the effort. - boolean needsInvalidate = false; - if (mLeftGlow != null && !mLeftGlow.isFinished()) { - final int restore = c.save(); - final int padding = mClipToPadding ? getPaddingBottom() : 0; - c.rotate(270); - c.translate(-getHeight() + padding, 0); - needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); - c.restoreToCount(restore); - } - if (mTopGlow != null && !mTopGlow.isFinished()) { - final int restore = c.save(); - if (mClipToPadding) { - c.translate(getPaddingLeft(), getPaddingTop()); - } - needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); - c.restoreToCount(restore); - } - if (mRightGlow != null && !mRightGlow.isFinished()) { - final int restore = c.save(); - final int width = getWidth(); - final int padding = mClipToPadding ? getPaddingTop() : 0; - c.rotate(90); - c.translate(padding, -width); - needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); - c.restoreToCount(restore); - } - if (mBottomGlow != null && !mBottomGlow.isFinished()) { - final int restore = c.save(); - c.rotate(180); - if (mClipToPadding) { - c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); - } else { - c.translate(-getWidth(), -getHeight()); - } - needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); - c.restoreToCount(restore); - } - - // If some views are animating, ItemDecorators are likely to move/change with them. - // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's - // display lists are not invalidated. - if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 - && mItemAnimator.isRunning()) { - needsInvalidate = true; - } - - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - public void onDraw(Canvas c) { - super.onDraw(c); - - final int count = mItemDecorations.size(); - for (int i = 0; i < count; i++) { - mItemDecorations.get(i).onDraw(c, this, mState); - } - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - if (mLayout == null) { - throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); - } - return mLayout.generateDefaultLayoutParams(); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - if (mLayout == null) { - throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); - } - return mLayout.generateLayoutParams(getContext(), attrs); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - if (mLayout == null) { - throw new IllegalStateException("RecyclerView has no LayoutManager" + exceptionLabel()); - } - return mLayout.generateLayoutParams(p); - } - - /** - * Returns true if RecyclerView is currently running some animations. - *

- * If you want to be notified when animations are finished, use - * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}. - * - * @return True if there are some item animations currently running or waiting to be started. - */ - public boolean isAnimating() { - return mItemAnimator != null && mItemAnimator.isRunning(); - } - - void saveOldPositions() { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (sDebugAssertionsEnabled && holder.mPosition == -1 && !holder.isRemoved()) { - throw new IllegalStateException("view holder cannot have position -1 unless it" - + " is removed" + exceptionLabel()); - } - if (!holder.shouldIgnore()) { - holder.saveOldPosition(); - } - } - } - - void clearOldPositions() { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (!holder.shouldIgnore()) { - holder.clearOldPosition(); - } - } - mRecycler.clearOldPositions(); - } - - void offsetPositionRecordsForMove(int from, int to) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - final int start, end, inBetweenOffset; - if (from < to) { - start = from; - end = to; - inBetweenOffset = -1; - } else { - start = to; - end = from; - inBetweenOffset = 1; - } - - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder == null || holder.mPosition < start || holder.mPosition > end) { - continue; - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " - + holder); - } - if (holder.mPosition == from) { - holder.offsetPosition(to - from, false); - } else { - holder.offsetPosition(inBetweenOffset, false); - } - - mState.mStructureChanged = true; - } - mRecycler.offsetPositionRecordsForMove(from, to); - requestLayout(); - } - - void offsetPositionRecordsForInsert(int positionStart, int itemCount) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " - + holder + " now at position " + (holder.mPosition + itemCount)); - } - holder.offsetPosition(itemCount, false); - mState.mStructureChanged = true; - } - } - mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); - requestLayout(); - } - - void offsetPositionRecordsForRemove(int positionStart, int itemCount, - boolean applyToPreLayout) { - final int positionEnd = positionStart + itemCount; - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.shouldIgnore()) { - if (holder.mPosition >= positionEnd) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i - + " holder " + holder + " now at position " - + (holder.mPosition - itemCount)); - } - holder.offsetPosition(-itemCount, applyToPreLayout); - mState.mStructureChanged = true; - } else if (holder.mPosition >= positionStart) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i - + " holder " + holder + " now REMOVED"); - } - holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, - applyToPreLayout); - mState.mStructureChanged = true; - } - } - } - mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); - requestLayout(); - } - - /** - * Rebind existing views for the given range, or create as needed. - * - * @param positionStart Adapter position to start at - * @param itemCount Number of views that must explicitly be rebound - */ - void viewRangeUpdate(int positionStart, int itemCount, Object payload) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - final int positionEnd = positionStart + itemCount; - - for (int i = 0; i < childCount; i++) { - final View child = mChildHelper.getUnfilteredChildAt(i); - final ViewHolder holder = getChildViewHolderInt(child); - if (holder == null || holder.shouldIgnore()) { - continue; - } - if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { - // We re-bind these view holders after pre-processing is complete so that - // ViewHolders have their final positions assigned. - holder.addFlags(ViewHolder.FLAG_UPDATE); - holder.addChangePayload(payload); - // lp cannot be null since we get ViewHolder from it. - ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; - } - } - mRecycler.viewRangeUpdate(positionStart, itemCount); - } - - boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { - return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, - viewHolder.getUnmodifiedPayloads()); - } - - /** - * Processes the fact that, as far as we can tell, the data set has completely changed. - * - *

    - *
  • Once layout occurs, all attached items should be discarded or animated. - *
  • Attached items are labeled as invalid. - *
  • Because items may still be prefetched between a "data set completely changed" - * event and a layout event, all cached items are discarded. - *
- * - * @param dispatchItemsChanged Whether to call - * {@link LayoutManager#onItemsChanged(RecyclerView)} during - * measure/layout. - */ - void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { - mDispatchItemsChangedEvent |= dispatchItemsChanged; - mDataSetHasChangedAfterLayout = true; - markKnownViewsInvalid(); - } - - /** - * Mark all known views as invalid. Used in response to a, "the whole world might have changed" - * data change event. - */ - void markKnownViewsInvalid() { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.shouldIgnore()) { - holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); - } - } - markItemDecorInsetsDirty(); - mRecycler.markKnownViewsInvalid(); - } - - /** - * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method - * will trigger a {@link #requestLayout()} call. - */ - public void invalidateItemDecorations() { - if (mItemDecorations.size() == 0) { - return; - } - if (mLayout != null) { - mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" - + " or layout"); - } - markItemDecorInsetsDirty(); - requestLayout(); - } - - /** - * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's - * focus even if the View representing the Item is replaced during a layout calculation. - *

- * By default, this value is {@code true}. - * - * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses - * focus. - * @see #setPreserveFocusAfterLayout(boolean) - */ - public boolean getPreserveFocusAfterLayout() { - return mPreserveFocusAfterLayout; - } - - /** - * Set whether the RecyclerView should try to keep the same Item focused after a layout - * calculation or not. - *

- * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, - * views may lose focus during a layout calculation as their state changes or they are replaced - * with another view due to type change or animation. In these cases, RecyclerView can request - * focus on the new view automatically. - * - * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a - * layout calculations. Defaults to true. - * @see #getPreserveFocusAfterLayout() - */ - public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { - mPreserveFocusAfterLayout = preserveFocusAfterLayout; - } - - /** - * Retrieve the {@link ViewHolder} for the given child view. - * - * @param child Child of this RecyclerView to query for its ViewHolder - * @return The child view's ViewHolder - */ - public ViewHolder getChildViewHolder(@NonNull View child) { - final ViewParent parent = child.getParent(); - if (parent != null && parent != this) { - throw new IllegalArgumentException("View " + child + " is not a direct child of " - + this); - } - return getChildViewHolderInt(child); - } - - /** - * Traverses the ancestors of the given view and returns the item view that contains it and - * also a direct child of the RecyclerView. This returned view can be used to get the - * ViewHolder by calling {@link #getChildViewHolder(View)}. - * - * @param view The view that is a descendant of the RecyclerView. - * @return The direct child of the RecyclerView which contains the given view or null if the - * provided view is not a descendant of this RecyclerView. - * @see #getChildViewHolder(View) - * @see #findContainingViewHolder(View) - */ - @Nullable - public View findContainingItemView(@NonNull View view) { - ViewParent parent = view.getParent(); - while (parent != null && parent != this && parent instanceof View) { - view = (View) parent; - parent = view.getParent(); - } - return parent == this ? view : null; - } - - /** - * Returns the ViewHolder that contains the given view. - * - * @param view The view that is a descendant of the RecyclerView. - * @return The ViewHolder that contains the given view or null if the provided view is not a - * descendant of this RecyclerView. - */ - @Nullable - public ViewHolder findContainingViewHolder(@NonNull View view) { - View itemView = findContainingItemView(view); - return itemView == null ? null : getChildViewHolder(itemView); - } - - - static ViewHolder getChildViewHolderInt(View child) { - if (child == null) { - return null; - } - return ((LayoutParams) child.getLayoutParams()).mViewHolder; - } - - /** - * @deprecated use {@link #getChildAdapterPosition(View)} or - * {@link #getChildLayoutPosition(View)}. - */ - @Deprecated - public int getChildPosition(@NonNull View child) { - return getChildAdapterPosition(child); - } - - /** - * Return the adapter position that the given child view corresponds to. - * - * @param child Child View to query - * @return Adapter position corresponding to the given view or {@link #NO_POSITION} - */ - public int getChildAdapterPosition(@NonNull View child) { - final ViewHolder holder = getChildViewHolderInt(child); - return holder != null ? holder.getAbsoluteAdapterPosition() : NO_POSITION; - } - - /** - * Return the adapter position of the given child view as of the latest completed layout pass. - *

- * This position may not be equal to Item's adapter position if there are pending changes - * in the adapter which have not been reflected to the layout yet. - * - * @param child Child View to query - * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if - * the View is representing a removed item. - */ - public int getChildLayoutPosition(@NonNull View child) { - final ViewHolder holder = getChildViewHolderInt(child); - return holder != null ? holder.getLayoutPosition() : NO_POSITION; - } - - /** - * Return the stable item id that the given child view corresponds to. - * - * @param child Child View to query - * @return Item id corresponding to the given view or {@link #NO_ID} - */ - public long getChildItemId(@NonNull View child) { - if (mAdapter == null || !mAdapter.hasStableIds()) { - return NO_ID; - } - final ViewHolder holder = getChildViewHolderInt(child); - return holder != null ? holder.getItemId() : NO_ID; - } - - /** - * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or - * {@link #findViewHolderForAdapterPosition(int)} - */ - @Deprecated - @Nullable - public ViewHolder findViewHolderForPosition(int position) { - return findViewHolderForPosition(position, false); - } - - /** - * Return the ViewHolder for the item in the given position of the data set as of the latest - * layout pass. - *

- * This method checks only the children of RecyclerView. If the item at the given - * position is not laid out, it will not create a new one. - *

- * Note that when Adapter contents change, ViewHolder positions are not updated until the - * next layout calculation. If there are pending adapter updates, the return value of this - * method may not match your adapter contents. You can use - * #{@link ViewHolder#getBindingAdapterPosition()} to get the current adapter position - * of a ViewHolder. If the {@link Adapter} that is assigned to the RecyclerView is an adapter - * that combines other adapters (e.g. {@link ConcatAdapter}), you can use the - * {@link ViewHolder#getBindingAdapter()}) to find the position relative to the {@link Adapter} - * that bound the {@link ViewHolder}. - *

- * When the ItemAnimator is running a change animation, there might be 2 ViewHolders - * with the same layout position representing the same Item. In this case, the updated - * ViewHolder will be returned. - * - * @param position The position of the item in the data set of the adapter - * @return The ViewHolder at position or null if there is no such item - */ - @Nullable - public ViewHolder findViewHolderForLayoutPosition(int position) { - return findViewHolderForPosition(position, false); - } - - /** - * Return the ViewHolder for the item in the given position of the data set. Unlike - * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending - * adapter changes that may not be reflected to the layout yet. On the other hand, if - * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been - * calculated yet, this method will return null since the new positions of views - * are unknown until the layout is calculated. - *

- * This method checks only the children of RecyclerView. If the item at the given - * position is not laid out, it will not create a new one. - *

- * When the ItemAnimator is running a change animation, there might be 2 ViewHolders - * representing the same Item. In this case, the updated ViewHolder will be returned. - * - * @param position The position of the item in the data set of the adapter - * @return The ViewHolder at position or null if there is no such item - */ - @Nullable - public ViewHolder findViewHolderForAdapterPosition(int position) { - if (mDataSetHasChangedAfterLayout) { - return null; - } - final int childCount = mChildHelper.getUnfilteredChildCount(); - // hidden VHs are not preferred but if that is the only one we find, we rather return it - ViewHolder hidden = null; - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.isRemoved() - && getAdapterPositionInRecyclerView(holder) == position) { - if (mChildHelper.isHidden(holder.itemView)) { - hidden = holder; - } else { - return holder; - } - } - } - return hidden; - } - - @Nullable - ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - ViewHolder hidden = null; - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.isRemoved()) { - if (checkNewPosition) { - if (holder.mPosition != position) { - continue; - } - } else if (holder.getLayoutPosition() != position) { - continue; - } - if (mChildHelper.isHidden(holder.itemView)) { - hidden = holder; - } else { - return holder; - } - } - } - // This method should not query cached views. It creates a problem during adapter updates - // when we are dealing with already laid out views. Also, for the public method, it is more - // reasonable to return null if position is not laid out. - return hidden; - } - - /** - * Return the ViewHolder for the item with the given id. The RecyclerView must - * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to - * return a non-null value. - *

- * This method checks only the children of RecyclerView. If the item with the given - * id is not laid out, it will not create a new one. - * - * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the - * same id. In this case, the updated ViewHolder will be returned. - * - * @param id The id for the requested item - * @return The ViewHolder with the given id or null if there is no such item - */ - public ViewHolder findViewHolderForItemId(long id) { - if (mAdapter == null || !mAdapter.hasStableIds()) { - return null; - } - final int childCount = mChildHelper.getUnfilteredChildCount(); - ViewHolder hidden = null; - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { - if (mChildHelper.isHidden(holder.itemView)) { - hidden = holder; - } else { - return holder; - } - } - } - return hidden; - } - - /** - * Find the topmost view under the given point. - * - * @param x Horizontal position in pixels to search - * @param y Vertical position in pixels to search - * @return The child view under (x, y) or null if no matching child is found - */ - @Nullable - public View findChildViewUnder(float x, float y) { - final int count = mChildHelper.getChildCount(); - for (int i = count - 1; i >= 0; i--) { - final View child = mChildHelper.getChildAt(i); - final float translationX = child.getTranslationX(); - final float translationY = child.getTranslationY(); - if (x >= child.getLeft() + translationX - && x <= child.getRight() + translationX - && y >= child.getTop() + translationY - && y <= child.getBottom() + translationY) { - return child; - } - } - return null; - } - - @Override - public boolean drawChild(Canvas canvas, View child, long drawingTime) { - return super.drawChild(canvas, child, drawingTime); - } - - /** - * Offset the bounds of all child views by dy pixels. - * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. - * - * @param dy Vertical pixel offset to apply to the bounds of all child views - */ - public void offsetChildrenVertical(@Px int dy) { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - mChildHelper.getChildAt(i).offsetTopAndBottom(dy); - } - } - - /** - * Called when an item view is attached to this RecyclerView. - * - *

Subclasses of RecyclerView may want to perform extra bookkeeping or modifications - * of child views as they become attached. This will be called before a - * {@link LayoutManager} measures or lays out the view and is a good time to perform these - * changes.

- * - * @param child Child view that is now attached to this RecyclerView and its associated window - */ - public void onChildAttachedToWindow(@NonNull View child) { - } - - /** - * Called when an item view is detached from this RecyclerView. - * - *

Subclasses of RecyclerView may want to perform extra bookkeeping or modifications - * of child views as they become detached. This will be called as a - * {@link LayoutManager} fully detaches the child view from the parent and its window.

- * - * @param child Child view that is now detached from this RecyclerView and its associated window - */ - public void onChildDetachedFromWindow(@NonNull View child) { - } - - /** - * Offset the bounds of all child views by dx pixels. - * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. - * - * @param dx Horizontal pixel offset to apply to the bounds of all child views - */ - public void offsetChildrenHorizontal(@Px int dx) { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - mChildHelper.getChildAt(i).offsetLeftAndRight(dx); - } - } - - /** - * Returns the bounds of the view including its decoration and margins. - * - * @param view The view element to check - * @param outBounds A rect that will receive the bounds of the element including its - * decoration and margins. - */ - public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { - getDecoratedBoundsWithMarginsInt(view, outBounds); - } - - static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final Rect insets = lp.mDecorInsets; - outBounds.set(view.getLeft() - insets.left - lp.leftMargin, - view.getTop() - insets.top - lp.topMargin, - view.getRight() + insets.right + lp.rightMargin, - view.getBottom() + insets.bottom + lp.bottomMargin); - } - - Rect getItemDecorInsetsForChild(View child) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.mInsetsDirty) { - return lp.mDecorInsets; - } - - if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { - // changed/invalid items should not be updated until they are rebound. - return lp.mDecorInsets; - } - final Rect insets = lp.mDecorInsets; - insets.set(0, 0, 0, 0); - final int decorCount = mItemDecorations.size(); - for (int i = 0; i < decorCount; i++) { - mTempRect.set(0, 0, 0, 0); - mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); - insets.left += mTempRect.left; - insets.top += mTempRect.top; - insets.right += mTempRect.right; - insets.bottom += mTempRect.bottom; - } - lp.mInsetsDirty = false; - return insets; - } - - /** - * Called when the scroll position of this RecyclerView changes. Subclasses should use - * this method to respond to scrolling within the adapter's data set instead of an explicit - * listener. - * - *

This method will always be invoked before listeners. If a subclass needs to perform - * any additional upkeep or bookkeeping after scrolling but before listeners run, - * this is a good place to do so.

- * - *

This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives - * the distance scrolled in either direction within the adapter's data set instead of absolute - * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from - * any arbitrary point in the data set, onScrollChanged will always receive - * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which - * do not correspond to the data set scroll position. However, some subclasses may choose - * to use these fields as special offsets.

- * - * @param dx horizontal distance scrolled in pixels - * @param dy vertical distance scrolled in pixels - */ - public void onScrolled(@Px int dx, @Px int dy) { - // Do nothing - } - - void dispatchOnScrolled(int hresult, int vresult) { - mDispatchScrollCounter++; - // Pass the current scrollX/scrollY values as current values. No actual change in these - // properties occurred. Pass negative hresult and vresult as old values so that - // postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt) in onScrollChanged - // sends the scrolled accessibility event correctly. - final int scrollX = getScrollX(); - final int scrollY = getScrollY(); - onScrollChanged(scrollX, scrollY, scrollX - hresult, scrollY - vresult); - - // Pass the real deltas to onScrolled, the RecyclerView-specific method. - onScrolled(hresult, vresult); - - // Invoke listeners last. Subclassed view methods always handle the event first. - // All internal state is consistent by the time listeners are invoked. - if (mScrollListener != null) { - mScrollListener.onScrolled(this, hresult, vresult); - } - if (mScrollListeners != null) { - for (int i = mScrollListeners.size() - 1; i >= 0; i--) { - mScrollListeners.get(i).onScrolled(this, hresult, vresult); - } - } - mDispatchScrollCounter--; - } - - /** - * Called when the scroll state of this RecyclerView changes. Subclasses should use this - * method to respond to state changes instead of an explicit listener. - * - *

This method will always be invoked before listeners, but after the LayoutManager - * responds to the scroll state change.

- * - * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, - * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} - */ - public void onScrollStateChanged(int state) { - // Do nothing - } - - /** - * Copied from OverScroller, this returns the distance that a fling with the given velocity - * will go. - * @param velocity The velocity of the fling - * @return The distance that will be traveled by a fling of the given velocity. - */ - private float getSplineFlingDistance(int velocity) { - final double l = - Math.log(INFLEXION * Math.abs(velocity) / (SCROLL_FRICTION * mPhysicalCoef)); - final double decelMinusOne = DECELERATION_RATE - 1.0; - return (float) (SCROLL_FRICTION * mPhysicalCoef - * Math.exp(DECELERATION_RATE / decelMinusOne * l)); - } - - void dispatchOnScrollStateChanged(int state) { - // Let the LayoutManager go first; this allows it to bring any properties into - // a consistent state before the RecyclerView subclass responds. - if (mLayout != null) { - mLayout.onScrollStateChanged(state); - } - - // Let the RecyclerView subclass handle this event next; any LayoutManager property - // changes will be reflected by this time. - onScrollStateChanged(state); - - // Listeners go last. All other internal state is consistent by this point. - if (mScrollListener != null) { - mScrollListener.onScrollStateChanged(this, state); - } - if (mScrollListeners != null) { - for (int i = mScrollListeners.size() - 1; i >= 0; i--) { - mScrollListeners.get(i).onScrollStateChanged(this, state); - } - } - } - - /** - * Returns whether there are pending adapter updates which are not yet applied to the layout. - *

- * If this method returns true, it means that what user is currently seeing may not - * reflect them adapter contents (depending on what has changed). - * You may use this information to defer or cancel some operations. - *

- * This method returns true if RecyclerView has not yet calculated the first layout after it is - * attached to the Window or the Adapter has been replaced. - * - * @return True if there are some adapter updates which are not yet reflected to layout or false - * if layout is up to date. - */ - public boolean hasPendingAdapterUpdates() { - return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout - || mAdapterHelper.hasPendingUpdates(); - } - - // Effectively private. Set to default to avoid synthetic accessor. - class ViewFlinger implements Runnable { - private int mLastFlingX; - private int mLastFlingY; - OverScroller mOverScroller; - Interpolator mInterpolator = sQuinticInterpolator; - - // When set to true, postOnAnimation callbacks are delayed until the run method completes - private boolean mEatRunOnAnimationRequest = false; - - // Tracks if postAnimationCallback should be re-attached when it is done - private boolean mReSchedulePostAnimationCallback = false; - - ViewFlinger() { - mOverScroller = new OverScroller(getContext(), sQuinticInterpolator); - } - - @Override - public void run() { - if (mLayout == null) { - stop(); - return; // no layout, cannot scroll. - } - - mReSchedulePostAnimationCallback = false; - mEatRunOnAnimationRequest = true; - - consumePendingUpdateOperations(); - - // TODO(72745539): After reviewing the code, it seems to me we may actually want to - // update the reference to the OverScroller after onAnimation. It looks to me like - // it is possible that a new OverScroller could be created (due to a new Interpolator - // being used), when the current OverScroller knows it's done after - // scroller.computeScrollOffset() is called. If that happens, and we don't update the - // reference, it seems to me that we could prematurely stop the newly created scroller - // due to setScrollState(SCROLL_STATE_IDLE) being called below. - - // Keep a local reference so that if it is changed during onAnimation method, it won't - // cause unexpected behaviors - final OverScroller scroller = mOverScroller; - if (scroller.computeScrollOffset()) { - final int x = scroller.getCurrX(); - final int y = scroller.getCurrY(); - int unconsumedX = x - mLastFlingX; - int unconsumedY = y - mLastFlingY; - mLastFlingX = x; - mLastFlingY = y; - - unconsumedX = consumeFlingInHorizontalStretch(unconsumedX); - unconsumedY = consumeFlingInVerticalStretch(unconsumedY); - - int consumedX = 0; - int consumedY = 0; - - // Nested Pre Scroll - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - if (dispatchNestedPreScroll(unconsumedX, unconsumedY, mReusableIntPair, null, - TYPE_NON_TOUCH)) { - unconsumedX -= mReusableIntPair[0]; - unconsumedY -= mReusableIntPair[1]; - } - - // Based on movement, we may want to trigger the hiding of existing over scroll - // glows. - if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { - considerReleasingGlowsOnScroll(unconsumedX, unconsumedY); - } - - // Local Scroll - if (mAdapter != null) { - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - scrollStep(unconsumedX, unconsumedY, mReusableIntPair); - consumedX = mReusableIntPair[0]; - consumedY = mReusableIntPair[1]; - unconsumedX -= consumedX; - unconsumedY -= consumedY; - - // If SmoothScroller exists, this ViewFlinger was started by it, so we must - // report back to SmoothScroller. - SmoothScroller smoothScroller = mLayout.mSmoothScroller; - if (smoothScroller != null && !smoothScroller.isPendingInitialRun() - && smoothScroller.isRunning()) { - final int adapterSize = mState.getItemCount(); - if (adapterSize == 0) { - smoothScroller.stop(); - } else if (smoothScroller.getTargetPosition() >= adapterSize) { - smoothScroller.setTargetPosition(adapterSize - 1); - smoothScroller.onAnimation(consumedX, consumedY); - } else { - smoothScroller.onAnimation(consumedX, consumedY); - } - } - } - - if (!mItemDecorations.isEmpty()) { - invalidate(); - } - - // Nested Post Scroll - mReusableIntPair[0] = 0; - mReusableIntPair[1] = 0; - dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, null, - TYPE_NON_TOUCH, mReusableIntPair); - unconsumedX -= mReusableIntPair[0]; - unconsumedY -= mReusableIntPair[1]; - - if (consumedX != 0 || consumedY != 0) { - dispatchOnScrolled(consumedX, consumedY); - } - - if (!awakenScrollBars()) { - invalidate(); - } - - // We are done scrolling if scroller is finished, or for both the x and y dimension, - // we are done scrolling or we can't scroll further (we know we can't scroll further - // when we have unconsumed scroll distance). It's possible that we don't need - // to also check for scroller.isFinished() at all, but no harm in doing so in case - // of old bugs in Overscroller. - boolean scrollerFinishedX = scroller.getCurrX() == scroller.getFinalX(); - boolean scrollerFinishedY = scroller.getCurrY() == scroller.getFinalY(); - final boolean doneScrolling = scroller.isFinished() - || ((scrollerFinishedX || unconsumedX != 0) - && (scrollerFinishedY || unconsumedY != 0)); - - // Get the current smoothScroller. It may have changed by this point and we need to - // make sure we don't stop scrolling if it has changed and it's pending an initial - // run. - SmoothScroller smoothScroller = mLayout.mSmoothScroller; - boolean smoothScrollerPending = - smoothScroller != null && smoothScroller.isPendingInitialRun(); - - if (!smoothScrollerPending && doneScrolling) { - // If we are done scrolling and the layout's SmoothScroller is not pending, - // do the things we do at the end of a scroll and don't postOnAnimation. - - if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { - final int vel = (int) scroller.getCurrVelocity(); - int velX = unconsumedX < 0 ? -vel : unconsumedX > 0 ? vel : 0; - int velY = unconsumedY < 0 ? -vel : unconsumedY > 0 ? vel : 0; - absorbGlows(velX, velY); - } - - if (ALLOW_THREAD_GAP_WORK) { - mPrefetchRegistry.clearPrefetchPositions(); - } - } else { - // Otherwise continue the scroll. - - postOnAnimation(); - if (mGapWorker != null) { - mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY); - } - } - } - - SmoothScroller smoothScroller = mLayout.mSmoothScroller; - // call this after the onAnimation is complete not to have inconsistent callbacks etc. - if (smoothScroller != null && smoothScroller.isPendingInitialRun()) { - smoothScroller.onAnimation(0, 0); - } - - mEatRunOnAnimationRequest = false; - if (mReSchedulePostAnimationCallback) { - internalPostOnAnimation(); - } else { - setScrollState(SCROLL_STATE_IDLE); - stopNestedScroll(TYPE_NON_TOUCH); - } - } - - void postOnAnimation() { - if (mEatRunOnAnimationRequest) { - mReSchedulePostAnimationCallback = true; - } else { - internalPostOnAnimation(); - } - } - - private void internalPostOnAnimation() { - removeCallbacks(this); - ViewCompat.postOnAnimation(RecyclerView.this, this); - } - - public void fling(int velocityX, int velocityY) { - setScrollState(SCROLL_STATE_SETTLING); - mLastFlingX = mLastFlingY = 0; - // Because you can't define a custom interpolator for flinging, we should make sure we - // reset ourselves back to the teh default interpolator in case a different call - // changed our interpolator. - if (mInterpolator != sQuinticInterpolator) { - mInterpolator = sQuinticInterpolator; - mOverScroller = new OverScroller(getContext(), sQuinticInterpolator); - } - mOverScroller.fling(0, 0, velocityX, velocityY, - Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); - postOnAnimation(); - } - - /** - * Smooth scrolls the RecyclerView by a given distance. - * - * @param dx x distance in pixels. - * @param dy y distance in pixels. - * @param duration Duration of the animation in milliseconds. Set to - * {@link #UNDEFINED_DURATION} to have the duration automatically - * calculated - * based on an internally defined standard velocity. - * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null}, - * RecyclerView will use an internal default interpolator. - */ - public void smoothScrollBy(int dx, int dy, int duration, - @Nullable Interpolator interpolator) { - - // Handle cases where parameter values aren't defined. - if (duration == UNDEFINED_DURATION) { - duration = computeScrollDuration(dx, dy); - } - if (interpolator == null) { - interpolator = sQuinticInterpolator; - } - - // If the Interpolator has changed, create a new OverScroller with the new - // interpolator. - if (mInterpolator != interpolator) { - mInterpolator = interpolator; - mOverScroller = new OverScroller(getContext(), interpolator); - } - - // Reset the last fling information. - mLastFlingX = mLastFlingY = 0; - - // Set to settling state and start scrolling. - setScrollState(SCROLL_STATE_SETTLING); - mOverScroller.startScroll(0, 0, dx, dy, duration); - - if (Build.VERSION.SDK_INT < 23) { - // b/64931938 before API 23, startScroll() does not reset getCurX()/getCurY() - // to start values, which causes fillRemainingScrollValues() put in obsolete values - // for LayoutManager.onLayoutChildren(). - mOverScroller.computeScrollOffset(); - } - - postOnAnimation(); - } - - /** - * Computes of an animated scroll in milliseconds. - * @param dx x distance in pixels. - * @param dy y distance in pixels. - * @return The duration of the animated scroll in milliseconds. - */ - private int computeScrollDuration(int dx, int dy) { - final int absDx = Math.abs(dx); - final int absDy = Math.abs(dy); - final boolean horizontal = absDx > absDy; - final int containerSize = horizontal ? getWidth() : getHeight(); - - float absDelta = (float) (horizontal ? absDx : absDy); - final int duration = (int) (((absDelta / containerSize) + 1) * 300); - - return Math.min(duration, MAX_SCROLL_DURATION); - } - - public void stop() { - removeCallbacks(this); - mOverScroller.abortAnimation(); - } - - } - - void repositionShadowingViews() { - // Fix up shadow views used by change animations - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; i++) { - View view = mChildHelper.getChildAt(i); - ViewHolder holder = getChildViewHolder(view); - if (holder != null && holder.mShadowingHolder != null) { - View shadowingView = holder.mShadowingHolder.itemView; - int left = view.getLeft(); - int top = view.getTop(); - if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { - shadowingView.layout(left, top, - left + shadowingView.getWidth(), - top + shadowingView.getHeight()); - } - } - } - } - - private class RecyclerViewDataObserver extends AdapterDataObserver { - RecyclerViewDataObserver() { - } - - @Override - public void onChanged() { - assertNotInLayoutOrScroll(null); - mState.mStructureChanged = true; - - processDataSetCompletelyChanged(true); - if (!mAdapterHelper.hasPendingUpdates()) { - requestLayout(); - } - } - - @Override - public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { - assertNotInLayoutOrScroll(null); - if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { - triggerUpdateProcessor(); - } - } - - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - assertNotInLayoutOrScroll(null); - if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { - triggerUpdateProcessor(); - } - } - - @Override - public void onItemRangeRemoved(int positionStart, int itemCount) { - assertNotInLayoutOrScroll(null); - if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { - triggerUpdateProcessor(); - } - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - assertNotInLayoutOrScroll(null); - if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { - triggerUpdateProcessor(); - } - } - - void triggerUpdateProcessor() { - if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { - ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); - } else { - mAdapterUpdateDuringMeasure = true; - requestLayout(); - } - } - - @Override - public void onStateRestorationPolicyChanged() { - if (mPendingSavedState == null) { - return; - } - // If there is a pending saved state and the new mode requires us to restore it, - // we'll request a layout which will call the adapter to see if it can restore state - // and trigger state restoration - Adapter adapter = mAdapter; - if (adapter != null && adapter.canRestoreState()) { - requestLayout(); - } - } - } - - /** - * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews. - * - * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory) - */ - public static class EdgeEffectFactory { - - @Retention(RetentionPolicy.SOURCE) - @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM}) - public @interface EdgeDirection { - } - - /** - * Direction constant for the left edge - */ - public static final int DIRECTION_LEFT = 0; - - /** - * Direction constant for the top edge - */ - public static final int DIRECTION_TOP = 1; - - /** - * Direction constant for the right edge - */ - public static final int DIRECTION_RIGHT = 2; - - /** - * Direction constant for the bottom edge - */ - public static final int DIRECTION_BOTTOM = 3; - - /** - * Create a new EdgeEffect for the provided direction. - */ - protected @NonNull - EdgeEffect createEdgeEffect(@NonNull RecyclerView view, - @EdgeDirection int direction) { - return new EdgeEffect(view.getContext()); - } - } - - /** - * The default EdgeEffectFactory sets the edge effect type of the EdgeEffect. - */ - static class StretchEdgeEffectFactory extends EdgeEffectFactory { - @NonNull - @Override - protected EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) { - return new EdgeEffect(view.getContext()); - } - } - - /** - * RecycledViewPool lets you share Views between multiple RecyclerViews. - *

- * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool - * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. - *

- * RecyclerView automatically creates a pool for itself if you don't provide one. - */ - public static class RecycledViewPool { - private static final int DEFAULT_MAX_SCRAP = 5; - - /** - * Tracks both pooled holders, as well as create/bind timing metadata for the given type. - * - * Note that this tracks running averages of create/bind time across all RecyclerViews - * (and, indirectly, Adapters) that use this pool. - * - * 1) This enables us to track average create and bind times across multiple adapters. Even - * though create (and especially bind) may behave differently for different Adapter - * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. - * - * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return - * false for all other views of its type for the same deadline. This prevents items - * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. - */ - static class ScrapData { - final ArrayList mScrapHeap = new ArrayList<>(); - int mMaxScrap = DEFAULT_MAX_SCRAP; - long mCreateRunningAverageNs = 0; - long mBindRunningAverageNs = 0; - } - - SparseArray mScrap = new SparseArray<>(); - - /** - * Attach counts for clearing (that is, emptying the pool when there are no adapters - * attached) and for PoolingContainer release are tracked separately to maintain the - * historical behavior of this functionality. - * - * The count for clearing is inaccurate in certain scenarios: for instance, if a - * RecyclerView is removed from the view hierarchy and thrown away to be GCed, the - * attach count will never be correspondingly decreased. However, it has been this way - * for years without any complaints, so we are not going to potentially increase the - * number of scenarios where the pool would be cleared. - * - * The attached adapters for PoolingContainer purposes strives to be more accurate, as - * it will be decremented whenever a RecyclerView is detached from the window. This - * could potentially be inaccurate in the unlikely event that someone is manually driving - * a detached RecyclerView by calling measure, layout, draw, etc. However, the - * implementation of {@link RecyclerView#onDetachedFromWindow()} suggests this is not the - * only unexpected behavior that doing so might provoke, so this should be acceptable. - */ - int mAttachCountForClearing = 0; - - /** - * The set of adapters for PoolingContainer release purposes - * - * @see #mAttachCountForClearing - */ - Set> mAttachedAdaptersForPoolingContainer = - Collections.newSetFromMap(new IdentityHashMap<>()); - - /** - * Discard all ViewHolders. - */ - public void clear() { - for (int i = 0; i < mScrap.size(); i++) { - ScrapData data = mScrap.valueAt(i); - for (ViewHolder scrap: data.mScrapHeap) { - PoolingContainer.callPoolingContainerOnRelease(scrap.itemView); - } - data.mScrapHeap.clear(); - } - } - - /** - * Sets the maximum number of ViewHolders to hold in the pool before discarding. - * - * @param viewType ViewHolder Type - * @param max Maximum number - */ - public void setMaxRecycledViews(int viewType, int max) { - ScrapData scrapData = getScrapDataForType(viewType); - scrapData.mMaxScrap = max; - final ArrayList scrapHeap = scrapData.mScrapHeap; - while (scrapHeap.size() > max) { - scrapHeap.remove(scrapHeap.size() - 1); - } - } - - /** - * Returns the current number of Views held by the RecycledViewPool of the given view type. - */ - public int getRecycledViewCount(int viewType) { - return getScrapDataForType(viewType).mScrapHeap.size(); - } - - /** - * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are - * present. - * - * @param viewType ViewHolder type. - * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none - * are present. - */ - @Nullable - public ViewHolder getRecycledView(int viewType) { - final ScrapData scrapData = mScrap.get(viewType); - if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { - final ArrayList scrapHeap = scrapData.mScrapHeap; - for (int i = scrapHeap.size() - 1; i >= 0; i--) { - if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) { - return scrapHeap.remove(i); - } - } - } - return null; - } - - /** - * Total number of ViewHolders held by the pool. - * - * @return Number of ViewHolders held by the pool. - */ - int size() { - int count = 0; - for (int i = 0; i < mScrap.size(); i++) { - ArrayList viewHolders = mScrap.valueAt(i).mScrapHeap; - if (viewHolders != null) { - count += viewHolders.size(); - } - } - return count; - } - - /** - * Add a scrap ViewHolder to the pool. - *

- * If the pool is already full for that ViewHolder's type, it will be immediately discarded. - * - * @param scrap ViewHolder to be added to the pool. - */ - public void putRecycledView(ViewHolder scrap) { - final int viewType = scrap.getItemViewType(); - final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap; - if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { - PoolingContainer.callPoolingContainerOnRelease(scrap.itemView); - return; - } - if (sDebugAssertionsEnabled && scrapHeap.contains(scrap)) { - throw new IllegalArgumentException("this scrap item already exists"); - } - scrap.resetInternal(); - scrapHeap.add(scrap); - } - - long runningAverage(long oldAverage, long newValue) { - if (oldAverage == 0) { - return newValue; - } - return (oldAverage / 4 * 3) + (newValue / 4); - } - - void factorInCreateTime(int viewType, long createTimeNs) { - ScrapData scrapData = getScrapDataForType(viewType); - scrapData.mCreateRunningAverageNs = runningAverage( - scrapData.mCreateRunningAverageNs, createTimeNs); - } - - void factorInBindTime(int viewType, long bindTimeNs) { - ScrapData scrapData = getScrapDataForType(viewType); - scrapData.mBindRunningAverageNs = runningAverage( - scrapData.mBindRunningAverageNs, bindTimeNs); - } - - boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { - long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; - return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); - } - - boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { - long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; - return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); - } - - void attach() { - mAttachCountForClearing++; - } - - void detach() { - mAttachCountForClearing--; - } - - /** - * Adds this adapter to the set of adapters being tracked for PoolingContainer release - * purposes. This method may validly be called multiple times for a given adapter. - * Additional calls to this method for an already-attached adapter are a no-op. - * - * @param adapter the adapter to ensure is in the set - */ - void attachForPoolingContainer(@NonNull Adapter adapter) { - mAttachedAdaptersForPoolingContainer.add(adapter); - } - - /** - * Removes this adapter from the set of adapters being tracked for PoolingContainer - * release purposes. This method may validly be called multiple times for a given adapter. - + Additional calls to this method for an already-detached adapter are a no-op. - * - * @param adapter the adapter to be removed from the set - * @param isBeingReplaced {@code true} if this detach is immediately preceding a call to - * {@link #attachForPoolingContainer(Adapter)} and - * {@link PoolingContainerListener#onRelease()} should not be triggered, or false otherwise - */ - void detachForPoolingContainer(@NonNull Adapter adapter, boolean isBeingReplaced) { - mAttachedAdaptersForPoolingContainer.remove(adapter); - if (mAttachedAdaptersForPoolingContainer.size() == 0 && !isBeingReplaced) { - for (int keyIndex = 0; keyIndex < mScrap.size(); keyIndex++) { - ArrayList scrapHeap = mScrap.get(mScrap.keyAt(keyIndex)).mScrapHeap; - for (int i = 0; i < scrapHeap.size(); i++) { - PoolingContainer.callPoolingContainerOnRelease( - scrapHeap.get(i).itemView - ); - } - } - } - } - - /** - * Detaches the old adapter and attaches the new one. - *

- * RecycledViewPool will clear its cache if it has only one adapter attached and the new - * adapter uses a different ViewHolder than the oldAdapter. - * - * @param oldAdapter The previous adapter instance. Will be detached. - * @param newAdapter The new adapter instance. Will be attached. - * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same - * ViewHolder and view types. - */ - void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, - boolean compatibleWithPrevious) { - if (oldAdapter != null) { - detach(); - } - if (!compatibleWithPrevious && mAttachCountForClearing == 0) { - clear(); - } - if (newAdapter != null) { - attach(); - } - } - - private ScrapData getScrapDataForType(int viewType) { - ScrapData scrapData = mScrap.get(viewType); - if (scrapData == null) { - scrapData = new ScrapData(); - mScrap.put(viewType, scrapData); - } - return scrapData; - } - } - - /** - * Utility method for finding an internal RecyclerView, if present - */ - @Nullable - static RecyclerView findNestedRecyclerView(@NonNull View view) { - if (!(view instanceof ViewGroup)) { - return null; - } - if (view instanceof RecyclerView) { - return (RecyclerView) view; - } - final ViewGroup parent = (ViewGroup) view; - final int count = parent.getChildCount(); - for (int i = 0; i < count; i++) { - final View child = parent.getChildAt(i); - final RecyclerView descendant = findNestedRecyclerView(child); - if (descendant != null) { - return descendant; - } - } - return null; - } - - /** - * Utility method for clearing holder's internal RecyclerView, if present - */ - static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { - if (holder.mNestedRecyclerView != null) { - View item = holder.mNestedRecyclerView.get(); - while (item != null) { - if (item == holder.itemView) { - return; // match found, don't need to clear - } - - ViewParent parent = item.getParent(); - if (parent instanceof View) { - item = (View) parent; - } else { - item = null; - } - } - holder.mNestedRecyclerView = null; // not nested - } - } - - /** - * Time base for deadline-aware work scheduling. Overridable for testing. - * - * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling - * isn't relevant. - */ - long getNanoTime() { - if (ALLOW_THREAD_GAP_WORK) { - return System.nanoTime(); - } else { - return 0; - } - } - - /** - * A Recycler is responsible for managing scrapped or detached item views for reuse. - * - *

A "scrapped" view is a view that is still attached to its parent RecyclerView but - * that has been marked for removal or reuse.

- * - *

Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for - * an adapter's data set representing the data at a given position or item ID. - * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. - * If not, the view can be quickly reused by the LayoutManager with no further work. - * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} - * may be repositioned by a LayoutManager without remeasurement.

- */ - public final class Recycler { - final ArrayList mAttachedScrap = new ArrayList<>(); - ArrayList mChangedScrap = null; - - final ArrayList mCachedViews = new ArrayList(); - - private final List - mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); - - private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; - int mViewCacheMax = DEFAULT_CACHE_SIZE; - - RecycledViewPool mRecyclerPool; - - private ViewCacheExtension mViewCacheExtension; - - static final int DEFAULT_CACHE_SIZE = 2; - - /** - * Clear scrap views out of this recycler. Detached views contained within a - * recycled view pool will remain. - */ - public void clear() { - mAttachedScrap.clear(); - recycleAndClearCachedViews(); - } - - /** - * Set the maximum number of detached, valid views we should retain for later use. - * - * @param viewCount Number of views to keep before sending views to the shared pool - */ - public void setViewCacheSize(int viewCount) { - mRequestedCacheMax = viewCount; - updateViewCacheSize(); - } - - void updateViewCacheSize() { - int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; - mViewCacheMax = mRequestedCacheMax + extraCache; - - // first, try the views that can be recycled - for (int i = mCachedViews.size() - 1; - i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { - recycleCachedViewAt(i); - } - } - - /** - * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. - * - * @return List of ViewHolders in the scrap list. - */ - @NonNull - public List getScrapList() { - return mUnmodifiableAttachedScrap; - } - - /** - * Helper method for getViewForPosition. - *

- * Checks whether a given view holder can be used for the provided position. - * - * @param holder ViewHolder - * @return true if ViewHolder matches the provided position, false otherwise - */ - boolean validateViewHolderForOffsetPosition(ViewHolder holder) { - // if it is a removed holder, nothing to verify since we cannot ask adapter anymore - // if it is not removed, verify the type and id. - if (holder.isRemoved()) { - if (sDebugAssertionsEnabled && !mState.isPreLayout()) { - throw new IllegalStateException("should not receive a removed view unless it" - + " is pre layout" + exceptionLabel()); - } - return mState.isPreLayout(); - } - if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { - throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " - + "adapter position" + holder + exceptionLabel()); - } - if (!mState.isPreLayout()) { - // don't check type if it is pre-layout. - final int type = mAdapter.getItemViewType(holder.mPosition); - if (type != holder.getItemViewType()) { - return false; - } - } - if (mAdapter.hasStableIds()) { - return holder.getItemId() == mAdapter.getItemId(holder.mPosition); - } - return true; - } - - /** - * Attempts to bind view, and account for relevant timing information. If - * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. - * - * @param holder Holder to be bound. - * @param offsetPosition Position of item to be bound. - * @param position Pre-layout position of item to be bound. - * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should - * complete. If FOREVER_NS is passed, this method will not fail to - * bind the holder. - */ - @SuppressWarnings("unchecked") - private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, - int position, long deadlineNs) { - holder.mBindingAdapter = null; - holder.mOwnerRecyclerView = RecyclerView.this; - final int viewType = holder.getItemViewType(); - long startBindNs = getNanoTime(); - if (deadlineNs != FOREVER_NS - && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { - // abort - we have a deadline we can't meet - return false; - } - - // Holders being bound should be either fully attached or fully detached. - // We don't want to bind with views that are temporarily detached, because that - // creates a situation in which they are unable to reason about their attach state - // properly. - // For example, isAttachedToWindow will return true, but the itemView will lack a - // parent. This breaks, among other possible issues, anything involving traversing - // the view tree, such as ViewTreeLifecycleOwner. - // Thus, we temporarily reattach any temp-detached holders for the bind operation. - // See https://issuetracker.google.com/265347515 for additional details on problems - // resulting from this - boolean reattachedForBind = false; - if (holder.isTmpDetached()) { - attachViewToParent(holder.itemView, getChildCount(), - holder.itemView.getLayoutParams()); - reattachedForBind = true; - } - - mAdapter.bindViewHolder(holder, offsetPosition); - - if (reattachedForBind) { - detachViewFromParent(holder.itemView); - } - - long endBindNs = getNanoTime(); - mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); - attachAccessibilityDelegateOnBind(holder); - if (mState.isPreLayout()) { - holder.mPreLayoutPosition = position; - } - return true; - } - - /** - * Binds the given View to the position. The View can be a View previously retrieved via - * {@link #getViewForPosition(int)} or created by - * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. - *

- * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} - * and let the RecyclerView handle caching. This is a helper method for LayoutManager who - * wants to handle its own recycling logic. - *

- * Note that, {@link #getViewForPosition(int)} already binds the View to the position so - * you don't need to call this method unless you want to bind this View to another position. - * - * @param view The view to update. - * @param position The position of the item to bind to this View. - */ - public void bindViewToPosition(@NonNull View view, int position) { - ViewHolder holder = getChildViewHolderInt(view); - if (holder == null) { - throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" - + " pass arbitrary views to this method, they should be created by the " - + "Adapter" + exceptionLabel()); - } - final int offsetPosition = mAdapterHelper.findPositionOffset(position); - if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { - throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " - + "position " + position + "(offset:" + offsetPosition + ")." - + "state:" + mState.getItemCount() + exceptionLabel()); - } - tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); - - final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); - final LayoutParams rvLayoutParams; - if (lp == null) { - rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); - holder.itemView.setLayoutParams(rvLayoutParams); - } else if (!checkLayoutParams(lp)) { - rvLayoutParams = (LayoutParams) generateLayoutParams(lp); - holder.itemView.setLayoutParams(rvLayoutParams); - } else { - rvLayoutParams = (LayoutParams) lp; - } - - rvLayoutParams.mInsetsDirty = true; - rvLayoutParams.mViewHolder = holder; - rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; - } - - /** - * RecyclerView provides artificial position range (item count) in pre-layout state and - * automatically maps these positions to {@link Adapter} positions when - * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. - *

- * Usually, LayoutManager does not need to worry about this. However, in some cases, your - * LayoutManager may need to call some custom component with item positions in which - * case you need the actual adapter position instead of the pre layout position. You - * can use this method to convert a pre-layout position to adapter (post layout) position. - *

- * Note that if the provided position belongs to a deleted ViewHolder, this method will - * return -1. - *

- * Calling this method in post-layout state returns the same value back. - * - * @param position The pre-layout position to convert. Must be greater or equal to 0 and - * less than {@link State#getItemCount()}. - */ - public int convertPreLayoutPositionToPostLayout(int position) { - if (position < 0 || position >= mState.getItemCount()) { - throw new IndexOutOfBoundsException("invalid position " + position + ". State " - + "item count is " + mState.getItemCount() + exceptionLabel()); - } - if (!mState.isPreLayout()) { - return position; - } - return mAdapterHelper.findPositionOffset(position); - } - - /** - * Obtain a view initialized for the given position. - * - * This method should be used by {@link LayoutManager} implementations to obtain - * views to represent data from an {@link Adapter}. - *

- * The Recycler may reuse a scrap or detached view from a shared pool if one is - * available for the correct view type. If the adapter has not indicated that the - * data at the given position has changed, the Recycler will attempt to hand back - * a scrap view that was previously initialized for that data without rebinding. - * - * @param position Position to obtain a view for - * @return A view representing the data at position from adapter - */ - @NonNull - public View getViewForPosition(int position) { - return getViewForPosition(position, false); - } - - View getViewForPosition(int position, boolean dryRun) { - return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; - } - - /** - * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, - * cache, the RecycledViewPool, or creating it directly. - *

- * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return - * rather than constructing or binding a ViewHolder if it doesn't think it has time. - * If a ViewHolder must be constructed and not enough time remains, null is returned. If a - * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is - * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. - * - * @param position Position of ViewHolder to be returned. - * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ - * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should - * complete. If FOREVER_NS is passed, this method will not fail to - * create/bind the holder if needed. - * @return ViewHolder for requested position - */ - @Nullable - ViewHolder tryGetViewHolderForPositionByDeadline(int position, - boolean dryRun, long deadlineNs) { - if (position < 0 || position >= mState.getItemCount()) { - throw new IndexOutOfBoundsException("Invalid item position " + position - + "(" + position + "). Item count:" + mState.getItemCount() - + exceptionLabel()); - } - boolean fromScrapOrHiddenOrCache = false; - ViewHolder holder = null; - // 0) If there is a changed scrap, try to find from there - if (mState.isPreLayout()) { - holder = getChangedScrapViewForPosition(position); - fromScrapOrHiddenOrCache = holder != null; - } - // 1) Find by position from scrap/hidden list/cache - if (holder == null) { - holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); - if (holder != null) { - if (!validateViewHolderForOffsetPosition(holder)) { - // recycle holder (and unscrap if relevant) since it can't be used - if (!dryRun) { - // we would like to recycle this but need to make sure it is not used by - // animation logic etc. - holder.addFlags(ViewHolder.FLAG_INVALID); - if (holder.isScrap()) { - removeDetachedView(holder.itemView, false); - holder.unScrap(); - } else if (holder.wasReturnedFromScrap()) { - holder.clearReturnedFromScrapFlag(); - } - recycleViewHolderInternal(holder); - } - holder = null; - } else { - fromScrapOrHiddenOrCache = true; - } - } - } - if (holder == null) { - final int offsetPosition = mAdapterHelper.findPositionOffset(position); - if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { - throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " - + "position " + position + "(offset:" + offsetPosition + ")." - + "state:" + mState.getItemCount() + exceptionLabel()); - } - - final int type = mAdapter.getItemViewType(offsetPosition); - // 2) Find from scrap/cache via stable ids, if exists - if (mAdapter.hasStableIds()) { - holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), - type, dryRun); - if (holder != null) { - // update position - holder.mPosition = offsetPosition; - fromScrapOrHiddenOrCache = true; - } - } - if (holder == null && mViewCacheExtension != null) { - // We are NOT sending the offsetPosition because LayoutManager does not - // know it. - final View view = mViewCacheExtension - .getViewForPositionAndType(this, position, type); - if (view != null) { - holder = getChildViewHolder(view); - if (holder == null) { - throw new IllegalArgumentException("getViewForPositionAndType returned" - + " a view which does not have a ViewHolder" - + exceptionLabel()); - } else if (holder.shouldIgnore()) { - throw new IllegalArgumentException("getViewForPositionAndType returned" - + " a view that is ignored. You must call stopIgnoring before" - + " returning this view." + exceptionLabel()); - } - } - } - if (holder == null) { // fallback to pool - if (sVerboseLoggingEnabled) { - Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" - + position + ") fetching from shared pool"); - } - holder = getRecycledViewPool().getRecycledView(type); - if (holder != null) { - holder.resetInternal(); - if (FORCE_INVALIDATE_DISPLAY_LIST) { - invalidateDisplayListInt(holder); - } - } - } - if (holder == null) { - long start = getNanoTime(); - if (deadlineNs != FOREVER_NS - && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { - // abort - we have a deadline we can't meet - return null; - } - holder = mAdapter.createViewHolder(RecyclerView.this, type); - if (ALLOW_THREAD_GAP_WORK) { - // only bother finding nested RV if prefetching - RecyclerView innerView = findNestedRecyclerView(holder.itemView); - if (innerView != null) { - holder.mNestedRecyclerView = new WeakReference<>(innerView); - } - } - - long end = getNanoTime(); - mRecyclerPool.factorInCreateTime(type, end - start); - if (sVerboseLoggingEnabled) { - Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); - } - } - } - - // This is very ugly but the only place we can grab this information - // before the View is rebound and returned to the LayoutManager for post layout ops. - // We don't need this in pre-layout since the VH is not updated by the LM. - if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder - .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { - holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); - if (mState.mRunSimpleAnimations) { - int changeFlags = ItemAnimator - .buildAdapterChangeFlagsForAnimations(holder); - changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; - final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, - holder, changeFlags, holder.getUnmodifiedPayloads()); - recordAnimationInfoIfBouncedHiddenView(holder, info); - } - } - - boolean bound = false; - if (mState.isPreLayout() && holder.isBound()) { - // do not update unless we absolutely have to. - holder.mPreLayoutPosition = position; - } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { - if (sDebugAssertionsEnabled && holder.isRemoved()) { - throw new IllegalStateException("Removed holder should be bound and it should" - + " come here only in pre-layout. Holder: " + holder - + exceptionLabel()); - } - final int offsetPosition = mAdapterHelper.findPositionOffset(position); - bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); - } - - final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); - final LayoutParams rvLayoutParams; - if (lp == null) { - rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); - holder.itemView.setLayoutParams(rvLayoutParams); - } else if (!checkLayoutParams(lp)) { - rvLayoutParams = (LayoutParams) generateLayoutParams(lp); - holder.itemView.setLayoutParams(rvLayoutParams); - } else { - rvLayoutParams = (LayoutParams) lp; - } - rvLayoutParams.mViewHolder = holder; - rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; - return holder; - } - - private void attachAccessibilityDelegateOnBind(ViewHolder holder) { - if (isAccessibilityEnabled()) { - final View itemView = holder.itemView; - if (ViewCompat.getImportantForAccessibility(itemView) - == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - ViewCompat.setImportantForAccessibility(itemView, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - if (mAccessibilityDelegate == null) { - return; - } - AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate(); - if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) { - // If there was already an a11y delegate set on the itemView, store it in the - // itemDelegate and then set the itemDelegate as the a11y delegate. - ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate) - .saveOriginalDelegate(itemView); - } - ViewCompat.setAccessibilityDelegate(itemView, itemDelegate); - } - } - - private void invalidateDisplayListInt(ViewHolder holder) { - if (holder.itemView instanceof ViewGroup) { - invalidateDisplayListInt((ViewGroup) holder.itemView, false); - } - } - - private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { - for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { - final View view = viewGroup.getChildAt(i); - if (view instanceof ViewGroup) { - invalidateDisplayListInt((ViewGroup) view, true); - } - } - if (!invalidateThis) { - return; - } - // we need to force it to become invisible - if (viewGroup.getVisibility() == View.INVISIBLE) { - viewGroup.setVisibility(View.VISIBLE); - viewGroup.setVisibility(View.INVISIBLE); - } else { - final int visibility = viewGroup.getVisibility(); - viewGroup.setVisibility(View.INVISIBLE); - viewGroup.setVisibility(visibility); - } - } - - /** - * Recycle a detached view. The specified view will be added to a pool of views - * for later rebinding and reuse. - * - *

A view must be fully detached (removed from parent) before it may be recycled. If the - * View is scrapped, it will be removed from scrap list.

- * - * @param view Removed view for recycling - * @see LayoutManager#removeAndRecycleView(View, Recycler) - */ - public void recycleView(@NonNull View view) { - // This public recycle method tries to make view recycle-able since layout manager - // intended to recycle this view (e.g. even if it is in scrap or change cache) - ViewHolder holder = getChildViewHolderInt(view); - if (holder.isTmpDetached()) { - removeDetachedView(view, false); - } - if (holder.isScrap()) { - holder.unScrap(); - } else if (holder.wasReturnedFromScrap()) { - holder.clearReturnedFromScrapFlag(); - } - recycleViewHolderInternal(holder); - // In most cases we dont need call endAnimation() because when view is detached, - // ViewPropertyAnimation will end. But if the animation is based on ObjectAnimator or - // if the ItemAnimator uses "pending runnable" and the ViewPropertyAnimation has not - // started yet, the ItemAnimatior on the view may not be cleared. - // In b/73552923, the View is removed by scroll pass while it's waiting in - // the "pending moving" list of DefaultItemAnimator and DefaultItemAnimator later in - // a post runnable, incorrectly performs postDelayed() on the detached view. - // To fix the issue, we issue endAnimation() here to make sure animation of this view - // finishes. - // - // Note the order: we must call endAnimation() after recycleViewHolderInternal() - // to avoid recycle twice. If ViewHolder isRecyclable is false, - // recycleViewHolderInternal() will not recycle it, endAnimation() will reset - // isRecyclable flag and recycle the view. - if (mItemAnimator != null && !holder.isRecyclable()) { - mItemAnimator.endAnimation(holder); - } - } - - void recycleAndClearCachedViews() { - final int count = mCachedViews.size(); - for (int i = count - 1; i >= 0; i--) { - recycleCachedViewAt(i); - } - mCachedViews.clear(); - if (ALLOW_THREAD_GAP_WORK) { - mPrefetchRegistry.clearPrefetchPositions(); - } - } - - /** - * Recycles a cached view and removes the view from the list. Views are added to cache - * if and only if they are recyclable, so this method does not check it again. - *

- * A small exception to this rule is when the view does not have an animator reference - * but transient state is true (due to animations created outside ItemAnimator). In that - * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is - * still recyclable since Adapter wants to do so. - * - * @param cachedViewIndex The index of the view in cached views list - */ - void recycleCachedViewAt(int cachedViewIndex) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); - } - ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); - if (sVerboseLoggingEnabled) { - Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); - } - addViewHolderToRecycledViewPool(viewHolder, true); - mCachedViews.remove(cachedViewIndex); - } - - /** - * internal implementation checks if view is scrapped or attached and throws an exception - * if so. - * Public version un-scraps before calling recycle. - */ - void recycleViewHolderInternal(ViewHolder holder) { - if (holder.isScrap() || holder.itemView.getParent() != null) { - throw new IllegalArgumentException( - "Scrapped or attached views may not be recycled. isScrap:" - + holder.isScrap() + " isAttached:" - + (holder.itemView.getParent() != null) + exceptionLabel()); - } - - if (holder.isTmpDetached()) { - throw new IllegalArgumentException("Tmp detached view should be removed " - + "from RecyclerView before it can be recycled: " + holder - + exceptionLabel()); - } - - if (holder.shouldIgnore()) { - throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" - + " should first call stopIgnoringView(view) before calling recycle." - + exceptionLabel()); - } - final boolean transientStatePreventsRecycling = holder - .doesTransientStatePreventRecycling(); - @SuppressWarnings("unchecked") final boolean forceRecycle = mAdapter != null - && transientStatePreventsRecycling - && mAdapter.onFailedToRecycleView(holder); - boolean cached = false; - boolean recycled = false; - if (sDebugAssertionsEnabled && mCachedViews.contains(holder)) { - throw new IllegalArgumentException("cached view received recycle internal? " - + holder + exceptionLabel()); - } - if (forceRecycle || holder.isRecyclable()) { - if (mViewCacheMax > 0 - && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID - | ViewHolder.FLAG_REMOVED - | ViewHolder.FLAG_UPDATE - | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { - // Retire oldest cached view - int cachedViewSize = mCachedViews.size(); - if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { - recycleCachedViewAt(0); - cachedViewSize--; - } - - int targetCacheIndex = cachedViewSize; - if (ALLOW_THREAD_GAP_WORK - && cachedViewSize > 0 - && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { - // when adding the view, skip past most recently prefetched views - int cacheIndex = cachedViewSize - 1; - while (cacheIndex >= 0) { - int cachedPos = mCachedViews.get(cacheIndex).mPosition; - if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { - break; - } - cacheIndex--; - } - targetCacheIndex = cacheIndex + 1; - } - mCachedViews.add(targetCacheIndex, holder); - cached = true; - } - if (!cached) { - addViewHolderToRecycledViewPool(holder, true); - recycled = true; - } - } else { - // NOTE: A view can fail to be recycled when it is scrolled off while an animation - // runs. In this case, the item is eventually recycled by - // ItemAnimatorRestoreListener#onAnimationFinished. - - // TODO: consider cancelling an animation when an item is removed scrollBy, - // to return it to the pool faster - if (sVerboseLoggingEnabled) { - Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " - + "re-visit here. We are still removing it from animation lists" - + exceptionLabel()); - } - } - // even if the holder is not removed, we still call this method so that it is removed - // from view holder lists. - mViewInfoStore.removeViewHolder(holder); - if (!cached && !recycled && transientStatePreventsRecycling) { - PoolingContainer.callPoolingContainerOnRelease(holder.itemView); - holder.mBindingAdapter = null; - holder.mOwnerRecyclerView = null; - } - } - - /** - * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. - * - * Pass false to dispatchRecycled for views that have not been bound. - * - * @param holder Holder to be added to the pool. - * @param dispatchRecycled True to dispatch View recycled callbacks. - */ - void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) { - clearNestedRecyclerViewIfNotNested(holder); - View itemView = holder.itemView; - if (mAccessibilityDelegate != null) { - AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate(); - AccessibilityDelegateCompat originalDelegate = null; - if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) { - originalDelegate = - ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate) - .getAndRemoveOriginalDelegateForItem(itemView); - } - // Set the a11y delegate back to whatever the original delegate was. - ViewCompat.setAccessibilityDelegate(itemView, originalDelegate); - } - if (dispatchRecycled) { - dispatchViewRecycled(holder); - } - holder.mBindingAdapter = null; - holder.mOwnerRecyclerView = null; - getRecycledViewPool().putRecycledView(holder); - } - - /** - * Used as a fast path for unscrapping and recycling a view during a bulk operation. - * The caller must call {@link #clearScrap()} when it's done to update the recycler's - * internal bookkeeping. - */ - void quickRecycleScrapView(View view) { - final ViewHolder holder = getChildViewHolderInt(view); - holder.mScrapContainer = null; - holder.mInChangeScrap = false; - holder.clearReturnedFromScrapFlag(); - recycleViewHolderInternal(holder); - } - - /** - * Mark an attached view as scrap. - * - *

"Scrap" views are still attached to their parent RecyclerView but are eligible - * for rebinding and reuse. Requests for a view for a given position may return a - * reused or rebound scrap view instance.

- * - * @param view View to scrap - */ - void scrapView(View view) { - final ViewHolder holder = getChildViewHolderInt(view); - if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) - || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { - if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { - throw new IllegalArgumentException("Called scrap view with an invalid view." - + " Invalid views cannot be reused from scrap, they should rebound from" - + " recycler pool." + exceptionLabel()); - } - holder.setScrapContainer(this, false); - mAttachedScrap.add(holder); - } else { - if (mChangedScrap == null) { - mChangedScrap = new ArrayList(); - } - holder.setScrapContainer(this, true); - mChangedScrap.add(holder); - } - } - - /** - * Remove a previously scrapped view from the pool of eligible scrap. - * - *

This view will no longer be eligible for reuse until re-scrapped or - * until it is explicitly removed and recycled.

- */ - void unscrapView(ViewHolder holder) { - if (holder.mInChangeScrap) { - mChangedScrap.remove(holder); - } else { - mAttachedScrap.remove(holder); - } - holder.mScrapContainer = null; - holder.mInChangeScrap = false; - holder.clearReturnedFromScrapFlag(); - } - - int getScrapCount() { - return mAttachedScrap.size(); - } - - View getScrapViewAt(int index) { - return mAttachedScrap.get(index).itemView; - } - - void clearScrap() { - mAttachedScrap.clear(); - if (mChangedScrap != null) { - mChangedScrap.clear(); - } - } - - ViewHolder getChangedScrapViewForPosition(int position) { - // If pre-layout, check the changed scrap for an exact match. - final int changedScrapSize; - if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { - return null; - } - // find by position - for (int i = 0; i < changedScrapSize; i++) { - final ViewHolder holder = mChangedScrap.get(i); - if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { - holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); - return holder; - } - } - // find by id - if (mAdapter.hasStableIds()) { - final int offsetPosition = mAdapterHelper.findPositionOffset(position); - if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { - final long id = mAdapter.getItemId(offsetPosition); - for (int i = 0; i < changedScrapSize; i++) { - final ViewHolder holder = mChangedScrap.get(i); - if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { - holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); - return holder; - } - } - } - } - return null; - } - - /** - * Returns a view for the position either from attach scrap, hidden children, or cache. - * - * @param position Item position - * @param dryRun Does a dry run, finds the ViewHolder but does not remove - * @return a ViewHolder that can be re-used for this position. - */ - ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { - final int scrapCount = mAttachedScrap.size(); - - // Try first for an exact, non-invalid match from scrap. - for (int i = 0; i < scrapCount; i++) { - final ViewHolder holder = mAttachedScrap.get(i); - if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position - && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { - holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); - return holder; - } - } - - if (!dryRun) { - View view = mChildHelper.findHiddenNonRemovedView(position); - if (view != null) { - // This View is good to be used. We just need to unhide, detach and move to the - // scrap list. - final ViewHolder vh = getChildViewHolderInt(view); - mChildHelper.unhide(view); - int layoutIndex = mChildHelper.indexOfChild(view); - if (layoutIndex == RecyclerView.NO_POSITION) { - throw new IllegalStateException("layout index should not be -1 after " - + "unhiding a view:" + vh + exceptionLabel()); - } - mChildHelper.detachViewFromParent(layoutIndex); - scrapView(view); - vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP - | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); - return vh; - } - } - - // Search in our first-level recycled view cache. - final int cacheSize = mCachedViews.size(); - for (int i = 0; i < cacheSize; i++) { - final ViewHolder holder = mCachedViews.get(i); - // invalid view holders may be in cache if adapter has stable ids as they can be - // retrieved via getScrapOrCachedViewForId - if (!holder.isInvalid() && holder.getLayoutPosition() == position - && !holder.isAttachedToTransitionOverlay()) { - if (!dryRun) { - mCachedViews.remove(i); - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position - + ") found match in cache: " + holder); - } - return holder; - } - } - return null; - } - - ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { - // Look in our attached views first - final int count = mAttachedScrap.size(); - for (int i = count - 1; i >= 0; i--) { - final ViewHolder holder = mAttachedScrap.get(i); - if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { - if (type == holder.getItemViewType()) { - holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); - if (holder.isRemoved()) { - // this might be valid in two cases: - // > item is removed but we are in pre-layout pass - // >> do nothing. return as is. make sure we don't rebind - // > item is removed then added to another position and we are in - // post layout. - // >> remove removed and invalid flags, add update flag to rebind - // because item was invisible to us and we don't know what happened in - // between. - if (!mState.isPreLayout()) { - holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE - | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); - } - } - return holder; - } else if (!dryRun) { - // if we are running animations, it is actually better to keep it in scrap - // but this would force layout manager to lay it out which would be bad. - // Recycle this scrap. Type mismatch. - mAttachedScrap.remove(i); - removeDetachedView(holder.itemView, false); - quickRecycleScrapView(holder.itemView); - } - } - } - - // Search the first-level cache - final int cacheSize = mCachedViews.size(); - for (int i = cacheSize - 1; i >= 0; i--) { - final ViewHolder holder = mCachedViews.get(i); - if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) { - if (type == holder.getItemViewType()) { - if (!dryRun) { - mCachedViews.remove(i); - } - return holder; - } else if (!dryRun) { - recycleCachedViewAt(i); - return null; - } - } - } - return null; - } - - @SuppressWarnings("unchecked") - void dispatchViewRecycled(@NonNull ViewHolder holder) { - // TODO: Remove this once setRecyclerListener (currently deprecated) is deleted. - if (mRecyclerListener != null) { - mRecyclerListener.onViewRecycled(holder); - } - - final int listenerCount = mRecyclerListeners.size(); - for (int i = 0; i < listenerCount; i++) { - mRecyclerListeners.get(i).onViewRecycled(holder); - } - if (mAdapter != null) { - mAdapter.onViewRecycled(holder); - } - if (mState != null) { - mViewInfoStore.removeViewHolder(holder); - } - if (sVerboseLoggingEnabled) Log.d(TAG, "dispatchViewRecycled: " + holder); - } - - void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, - boolean compatibleWithPrevious) { - clear(); - poolingContainerDetach(oldAdapter, true); - getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, - compatibleWithPrevious); - maybeSendPoolingContainerAttach(); - } - - void offsetPositionRecordsForMove(int from, int to) { - final int start, end, inBetweenOffset; - if (from < to) { - start = from; - end = to; - inBetweenOffset = -1; - } else { - start = to; - end = from; - inBetweenOffset = 1; - } - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - if (holder == null || holder.mPosition < start || holder.mPosition > end) { - continue; - } - if (holder.mPosition == from) { - holder.offsetPosition(to - from, false); - } else { - holder.offsetPosition(inBetweenOffset, false); - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " - + holder); - } - } - } - - void offsetPositionRecordsForInsert(int insertedAt, int count) { - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - if (holder != null && holder.mPosition >= insertedAt) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " - + holder + " now at position " + (holder.mPosition + count)); - } - // insertions only affect post layout hence don't apply them to pre-layout. - holder.offsetPosition(count, false); - } - } - } - - /** - * @param removedFrom Remove start index - * @param count Remove count - * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if - * false, they'll be applied before the second layout pass - */ - void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { - final int removedEnd = removedFrom + count; - final int cachedCount = mCachedViews.size(); - for (int i = cachedCount - 1; i >= 0; i--) { - final ViewHolder holder = mCachedViews.get(i); - if (holder != null) { - if (holder.mPosition >= removedEnd) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "offsetPositionRecordsForRemove cached " + i - + " holder " + holder + " now at position " - + (holder.mPosition - count)); - } - holder.offsetPosition(-count, applyToPreLayout); - } else if (holder.mPosition >= removedFrom) { - // Item for this view was removed. Dump it from the cache. - holder.addFlags(ViewHolder.FLAG_REMOVED); - recycleCachedViewAt(i); - } - } - } - } - - void setViewCacheExtension(ViewCacheExtension extension) { - mViewCacheExtension = extension; - } - - void setRecycledViewPool(RecycledViewPool pool) { - poolingContainerDetach(mAdapter); - if (mRecyclerPool != null) { - mRecyclerPool.detach(); - } - mRecyclerPool = pool; - if (mRecyclerPool != null && getAdapter() != null) { - mRecyclerPool.attach(); - } - maybeSendPoolingContainerAttach(); - } - - private void maybeSendPoolingContainerAttach() { - if (mRecyclerPool != null - && mAdapter != null - && isAttachedToWindow()) { - mRecyclerPool.attachForPoolingContainer(mAdapter); - } - } - - private void poolingContainerDetach(Adapter adapter) { - poolingContainerDetach(adapter, false); - } - - private void poolingContainerDetach(Adapter adapter, boolean isBeingReplaced) { - if (mRecyclerPool != null) { - mRecyclerPool.detachForPoolingContainer(adapter, isBeingReplaced); - } - } - - void onAttachedToWindow() { - maybeSendPoolingContainerAttach(); - } - - void onDetachedFromWindow() { - for (int i = 0; i < mCachedViews.size(); i++) { - PoolingContainer.callPoolingContainerOnRelease(mCachedViews.get(i).itemView); - } - poolingContainerDetach(mAdapter); - } - - RecycledViewPool getRecycledViewPool() { - if (mRecyclerPool == null) { - mRecyclerPool = new RecycledViewPool(); - maybeSendPoolingContainerAttach(); - } - return mRecyclerPool; - } - - void viewRangeUpdate(int positionStart, int itemCount) { - final int positionEnd = positionStart + itemCount; - final int cachedCount = mCachedViews.size(); - for (int i = cachedCount - 1; i >= 0; i--) { - final ViewHolder holder = mCachedViews.get(i); - if (holder == null) { - continue; - } - - final int pos = holder.mPosition; - if (pos >= positionStart && pos < positionEnd) { - holder.addFlags(ViewHolder.FLAG_UPDATE); - recycleCachedViewAt(i); - // cached views should not be flagged as changed because this will cause them - // to animate when they are returned from cache. - } - } - } - - void markKnownViewsInvalid() { - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - if (holder != null) { - holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); - holder.addChangePayload(null); - } - } - - if (mAdapter == null || !mAdapter.hasStableIds()) { - // we cannot re-use cached views in this case. Recycle them all - recycleAndClearCachedViews(); - } - } - - void clearOldPositions() { - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - holder.clearOldPosition(); - } - final int scrapCount = mAttachedScrap.size(); - for (int i = 0; i < scrapCount; i++) { - mAttachedScrap.get(i).clearOldPosition(); - } - if (mChangedScrap != null) { - final int changedScrapCount = mChangedScrap.size(); - for (int i = 0; i < changedScrapCount; i++) { - mChangedScrap.get(i).clearOldPosition(); - } - } - } - - void markItemDecorInsetsDirty() { - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); - if (layoutParams != null) { - layoutParams.mInsetsDirty = true; - } - } - } - } - - /** - * ViewCacheExtension is a helper class to provide an additional layer of view caching that can - * be controlled by the developer. - *

- * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and - * first level cache to find a matching View. If it cannot find a suitable View, Recycler will - * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking - * {@link RecycledViewPool}. - *

- * Note that, Recycler never sends Views to this method to be cached. It is developers - * responsibility to decide whether they want to keep their Views in this custom cache or let - * the default recycling policy handle it. - */ - public abstract static class ViewCacheExtension { - - /** - * Returns a View that can be binded to the given Adapter position. - *

- * This method should not create a new View. Instead, it is expected to return - * an already created View that can be re-used for the given type and position. - * If the View is marked as ignored, it should first call - * {@link LayoutManager#stopIgnoringView(View)} before returning the View. - *

- * RecyclerView will re-bind the returned View to the position if necessary. - * - * @param recycler The Recycler that can be used to bind the View - * @param position The adapter position - * @param type The type of the View, defined by adapter - * @return A View that is bound to the given position or NULL if there is no View to re-use - * @see LayoutManager#ignoreView(View) - */ - @Nullable - public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, - int type); - } - - /** - * Base class for an Adapter - * - *

Adapters provide a binding from an app-specific data set to views that are displayed - * within a {@link RecyclerView}.

- * - * @param A class that extends ViewHolder that will be used by the adapter. - */ - public abstract static class Adapter { - private final AdapterDataObservable mObservable = new AdapterDataObservable(); - private boolean mHasStableIds = false; - private StateRestorationPolicy mStateRestorationPolicy = StateRestorationPolicy.ALLOW; - - /** - * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent - * an item. - *

- * This new ViewHolder should be constructed with a new View that can represent the items - * of the given type. You can either create a new View manually or inflate it from an XML - * layout file. - *

- * The new ViewHolder will be used to display items of the adapter using - * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display - * different items in the data set, it is a good idea to cache references to sub views of - * the View to avoid unnecessary {@link View#findViewById(int)} calls. - * - * @param parent The ViewGroup into which the new View will be added after it is bound to - * an adapter position. - * @param viewType The view type of the new View. - * @return A new ViewHolder that holds a View of the given view type. - * @see #getItemViewType(int) - * @see #onBindViewHolder(ViewHolder, int) - */ - @NonNull - public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); - - /** - * Called by RecyclerView to display the data at the specified position. This method should - * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given - * position. - *

- * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method - * again if the position of the item changes in the data set unless the item itself is - * invalidated or the new position cannot be determined. For this reason, you should only - * use the position parameter while acquiring the related data item inside - * this method and should not keep a copy of it. If you need the position of an item later - * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which - * will have the updated adapter position. - * - * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can - * handle efficient partial bind. - * - * @param holder The ViewHolder which should be updated to represent the contents of the - * item at the given position in the data set. - * @param position The position of the item within the adapter's data set. - */ - public abstract void onBindViewHolder(@NonNull VH holder, int position); - - /** - * Called by RecyclerView to display the data at the specified position. This method - * should update the contents of the {@link ViewHolder#itemView} to reflect the item at - * the given position. - *

- * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method - * again if the position of the item changes in the data set unless the item itself is - * invalidated or the new position cannot be determined. For this reason, you should only - * use the position parameter while acquiring the related data item inside - * this method and should not keep a copy of it. If you need the position of an item later - * on (e.g. in a click listener), use {@link ViewHolder#getBindingAdapterPosition()} which - * will have the updated adapter position. - *

- * Partial bind vs full bind: - *

- * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or - * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, - * the ViewHolder is currently bound to old data and Adapter may run an efficient partial - * update using the payload info. If the payload is empty, Adapter must run a full bind. - * Adapter should not assume that the payload passed in notify methods will be received by - * onBindViewHolder(). For example when the view is not attached to the screen, the - * payload in notifyItemChange() will be simply dropped. - * - * @param holder The ViewHolder which should be updated to represent the contents of the - * item at the given position in the data set. - * @param position The position of the item within the adapter's data set. - * @param payloads A non-null list of merged payloads. Can be empty list if requires full - * update. - */ - public void onBindViewHolder(@NonNull VH holder, int position, - @NonNull List payloads) { - onBindViewHolder(holder, position); - } - - /** - * Returns the position of the given {@link ViewHolder} in the given {@link Adapter}. - * - * If the given {@link Adapter} is not part of this {@link Adapter}, - * {@link RecyclerView#NO_POSITION} is returned. - * - * @param adapter The adapter which is a sub adapter of this adapter or itself. - * @param viewHolder The ViewHolder whose local position in the given adapter will be - * returned. - * @param localPosition The position of the given {@link ViewHolder} in this - * {@link Adapter}. - * - * @return The local position of the given {@link ViewHolder} in this {@link Adapter} - * or {@link RecyclerView#NO_POSITION} if the {@link ViewHolder} is not bound to an item - * or the given {@link Adapter} is not part of this Adapter (if this Adapter merges other - * adapters). - */ - public int findRelativeAdapterPositionIn( - @NonNull Adapter adapter, - @NonNull ViewHolder viewHolder, - int localPosition - ) { - if (adapter == this) { - return localPosition; - } - return NO_POSITION; - } - - /** - * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new - * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. - * - * @see #onCreateViewHolder(ViewGroup, int) - */ - @NonNull - public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { - try { - TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); - final VH holder = onCreateViewHolder(parent, viewType); - if (holder.itemView.getParent() != null) { - throw new IllegalStateException("ViewHolder views must not be attached when" - + " created. Ensure that you are not passing 'true' to the attachToRoot" - + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); - } - holder.mItemViewType = viewType; - return holder; - } finally { - TraceCompat.endSection(); - } - } - - /** - * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the - * {@link ViewHolder} contents with the item at the given position and also sets up some - * private fields to be used by RecyclerView. - * - * Adapters that merge other adapters should use - * {@link #bindViewHolder(ViewHolder, int)} when calling nested adapters so that - * RecyclerView can track which adapter bound the {@link ViewHolder} to return the correct - * position from {@link ViewHolder#getBindingAdapterPosition()} method. - * They should also override - * the {@link #findRelativeAdapterPositionIn(Adapter, ViewHolder, int)} method. - * - * @param holder The view holder whose contents should be updated - * @param position The position of the holder with respect to this adapter - * @see #onBindViewHolder(ViewHolder, int) - */ - public final void bindViewHolder(@NonNull VH holder, int position) { - boolean rootBind = holder.mBindingAdapter == null; - if (rootBind) { - holder.mPosition = position; - if (hasStableIds()) { - holder.mItemId = getItemId(position); - } - holder.setFlags(ViewHolder.FLAG_BOUND, - ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID - | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); - TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); - } - holder.mBindingAdapter = this; - if (sDebugAssertionsEnabled) { - if (holder.itemView.getParent() == null - && (ViewCompat.isAttachedToWindow(holder.itemView) - != holder.isTmpDetached())) { - throw new IllegalStateException("Temp-detached state out of sync with reality. " - + "holder.isTmpDetached(): " + holder.isTmpDetached() - + ", attached to window: " - + ViewCompat.isAttachedToWindow(holder.itemView) - + ", holder: " + holder); - } - if (holder.itemView.getParent() == null - && ViewCompat.isAttachedToWindow(holder.itemView)) { - throw new IllegalStateException( - "Attempting to bind attached holder with no parent" - + " (AKA temp detached): " + holder); - } - } - onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); - if (rootBind) { - holder.clearPayload(); - final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); - if (layoutParams instanceof RecyclerView.LayoutParams) { - ((LayoutParams) layoutParams).mInsetsDirty = true; - } - TraceCompat.endSection(); - } - } - - /** - * Return the view type of the item at position for the purposes - * of view recycling. - * - *

The default implementation of this method returns 0, making the assumption of - * a single view type for the adapter. Unlike ListView adapters, types need not - * be contiguous. Consider using id resources to uniquely identify item view types. - * - * @param position position to query - * @return integer value identifying the type of the view needed to represent the item at - * position. Type codes need not be contiguous. - */ - public int getItemViewType(int position) { - return 0; - } - - /** - * Indicates whether each item in the data set can be represented with a unique identifier - * of type {@link java.lang.Long}. - * - * @param hasStableIds Whether items in data set have unique identifiers or not. - * @see #hasStableIds() - * @see #getItemId(int) - */ - public void setHasStableIds(boolean hasStableIds) { - if (hasObservers()) { - throw new IllegalStateException("Cannot change whether this adapter has " - + "stable IDs while the adapter has registered observers."); - } - mHasStableIds = hasStableIds; - } - - /** - * Return the stable ID for the item at position. If {@link #hasStableIds()} - * would return false this method should return {@link #NO_ID}. The default implementation - * of this method returns {@link #NO_ID}. - * - * @param position Adapter position to query - * @return the stable ID of the item at position - */ - public long getItemId(int position) { - return NO_ID; - } - - /** - * Returns the total number of items in the data set held by the adapter. - * - * @return The total number of items in this adapter. - */ - public abstract int getItemCount(); - - /** - * Returns true if this adapter publishes a unique long value that can - * act as a key for the item at a given position in the data set. If that item is relocated - * in the data set, the ID returned for that item should be the same. - * - * @return true if this adapter's items have stable IDs - */ - public final boolean hasStableIds() { - return mHasStableIds; - } - - /** - * Called when a view created by this adapter has been recycled. - * - *

A view is recycled when a {@link LayoutManager} decides that it no longer - * needs to be attached to its parent {@link RecyclerView}. This can be because it has - * fallen out of visibility or a set of cached views represented by views still - * attached to the parent RecyclerView. If an item view has large or expensive data - * bound to it such as large bitmaps, this may be a good place to release those - * resources.

- *

- * RecyclerView calls this method right before clearing ViewHolder's internal data and - * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information - * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get - * its adapter position. - * - * @param holder The ViewHolder for the view being recycled - */ - public void onViewRecycled(@NonNull VH holder) { - } - - /** - * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled - * due to its transient state. Upon receiving this callback, Adapter can clear the - * animation(s) that effect the View's transient state and return true so that - * the View can be recycled. Keep in mind that the View in question is already removed from - * the RecyclerView. - *

- * In some cases, it is acceptable to recycle a View although it has transient state. Most - * of the time, this is a case where the transient state will be cleared in - * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. - * For this reason, RecyclerView leaves the decision to the Adapter and uses the return - * value of this method to decide whether the View should be recycled or not. - *

- * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you - * should never receive this callback because RecyclerView keeps those Views as children - * until their animations are complete. This callback is useful when children of the item - * views create animations which may not be easy to implement using an {@link ItemAnimator}. - *

- * You should never fix this issue by calling - * holder.itemView.setHasTransientState(false); unless you've previously called - * holder.itemView.setHasTransientState(true);. Each - * View.setHasTransientState(true) call must be matched by a - * View.setHasTransientState(false) call, otherwise, the state of the View - * may become inconsistent. You should always prefer to end or cancel animations that are - * triggering the transient state instead of handling it manually. - * - * @param holder The ViewHolder containing the View that could not be recycled due to its - * transient state. - * @return True if the View should be recycled, false otherwise. Note that if this method - * returns true, RecyclerView will ignore the transient state of - * the View and recycle it regardless. If this method returns false, - * RecyclerView will check the View's transient state again before giving a final decision. - * Default implementation returns false. - */ - public boolean onFailedToRecycleView(@NonNull VH holder) { - return false; - } - - /** - * Called when a view created by this adapter has been attached to a window. - * - *

This can be used as a reasonable signal that the view is about to be seen - * by the user. If the adapter previously freed any resources in - * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} - * those resources should be restored here.

- * - * @param holder Holder of the view being attached - */ - public void onViewAttachedToWindow(@NonNull VH holder) { - } - - /** - * Called when a view created by this adapter has been detached from its window. - * - *

Becoming detached from the window is not necessarily a permanent condition; - * the consumer of an Adapter's views may choose to cache views offscreen while they - * are not visible, attaching and detaching them as appropriate.

- * - * @param holder Holder of the view being detached - */ - public void onViewDetachedFromWindow(@NonNull VH holder) { - } - - /** - * Returns true if one or more observers are attached to this adapter. - * - * @return true if this adapter has observers - */ - public final boolean hasObservers() { - return mObservable.hasObservers(); - } - - /** - * Register a new observer to listen for data changes. - * - *

The adapter may publish a variety of events describing specific changes. - * Not all adapters may support all change types and some may fall back to a generic - * {@link RecyclerView.AdapterDataObserver#onChanged() - * "something changed"} event if more specific data is not available.

- * - *

Components registering observers with an adapter are responsible for - * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) - * unregistering} those observers when finished.

- * - * @param observer Observer to register - * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) - */ - public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { - mObservable.registerObserver(observer); - } - - /** - * Unregister an observer currently listening for data changes. - * - *

The unregistered observer will no longer receive events about changes - * to the adapter.

- * - * @param observer Observer to unregister - * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) - */ - public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { - mObservable.unregisterObserver(observer); - } - - /** - * Called by RecyclerView when it starts observing this Adapter. - *

- * Keep in mind that same adapter may be observed by multiple RecyclerViews. - * - * @param recyclerView The RecyclerView instance which started observing this adapter. - * @see #onDetachedFromRecyclerView(RecyclerView) - */ - public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { - } - - /** - * Called by RecyclerView when it stops observing this Adapter. - * - * @param recyclerView The RecyclerView instance which stopped observing this adapter. - * @see #onAttachedToRecyclerView(RecyclerView) - */ - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - } - - /** - * Notify any registered observers that the data set has changed. - * - *

There are two different classes of data change events, item changes and structural - * changes. Item changes are when a single item has its data updated but no positional - * changes have occurred. Structural changes are when items are inserted, removed or moved - * within the data set.

- * - *

This event does not specify what about the data set has changed, forcing - * any observers to assume that all existing items and structure may no longer be valid. - * LayoutManagers will be forced to fully rebind and relayout all visible views.

- * - *

RecyclerView will attempt to synthesize visible structural change events - * for adapters that report that they have {@link #hasStableIds() stable IDs} when - * this method is used. This can help for the purposes of animation and visual - * object persistence but individual item views will still need to be rebound - * and relaid out.

- * - *

If you are writing an adapter it will always be more efficient to use the more - * specific change events if you can. Rely on notifyDataSetChanged() - * as a last resort.

- * - * @see #notifyItemChanged(int) - * @see #notifyItemInserted(int) - * @see #notifyItemRemoved(int) - * @see #notifyItemRangeChanged(int, int) - * @see #notifyItemRangeInserted(int, int) - * @see #notifyItemRangeRemoved(int, int) - */ - public final void notifyDataSetChanged() { - mObservable.notifyChanged(); - } - - /** - * Notify any registered observers that the item at position has changed. - * Equivalent to calling notifyItemChanged(position, null);. - * - *

This is an item change event, not a structural change event. It indicates that any - * reflection of the data at position is out of date and should be updated. - * The item at position retains the same identity.

- * - * @param position Position of the item that has changed - * @see #notifyItemRangeChanged(int, int) - */ - public final void notifyItemChanged(int position) { - mObservable.notifyItemRangeChanged(position, 1); - } - - /** - * Notify any registered observers that the item at position has changed with - * an optional payload object. - * - *

This is an item change event, not a structural change event. It indicates that any - * reflection of the data at position is out of date and should be updated. - * The item at position retains the same identity. - *

- * - *

- * Client can optionally pass a payload for partial change. These payloads will be merged - * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the - * item is already represented by a ViewHolder and it will be rebound to the same - * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing - * payloads on that item and prevent future payload until - * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume - * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not - * attached, the payload will be simply dropped. - * - * @param position Position of the item that has changed - * @param payload Optional parameter, use null to identify a "full" update - * @see #notifyItemRangeChanged(int, int) - */ - public final void notifyItemChanged(int position, @Nullable Object payload) { - mObservable.notifyItemRangeChanged(position, 1, payload); - } - - /** - * Notify any registered observers that the itemCount items starting at - * position positionStart have changed. - * Equivalent to calling notifyItemRangeChanged(position, itemCount, null);. - * - *

This is an item change event, not a structural change event. It indicates that - * any reflection of the data in the given position range is out of date and should - * be updated. The items in the given range retain the same identity.

- * - * @param positionStart Position of the first item that has changed - * @param itemCount Number of items that have changed - * @see #notifyItemChanged(int) - */ - public final void notifyItemRangeChanged(int positionStart, int itemCount) { - mObservable.notifyItemRangeChanged(positionStart, itemCount); - } - - /** - * Notify any registered observers that the itemCount items starting at - * position positionStart have changed. An optional payload can be - * passed to each changed item. - * - *

This is an item change event, not a structural change event. It indicates that any - * reflection of the data in the given position range is out of date and should be updated. - * The items in the given range retain the same identity. - *

- * - *

- * Client can optionally pass a payload for partial change. These payloads will be merged - * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the - * item is already represented by a ViewHolder and it will be rebound to the same - * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing - * payloads on that item and prevent future payload until - * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume - * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not - * attached, the payload will be simply dropped. - * - * @param positionStart Position of the first item that has changed - * @param itemCount Number of items that have changed - * @param payload Optional parameter, use null to identify a "full" update - * @see #notifyItemChanged(int) - */ - public final void notifyItemRangeChanged(int positionStart, int itemCount, - @Nullable Object payload) { - mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); - } - - /** - * Notify any registered observers that the item reflected at position - * has been newly inserted. The item previously at position is now at - * position position + 1. - * - *

This is a structural change event. Representations of other existing items in the - * data set are still considered up to date and will not be rebound, though their - * positions may be altered.

- * - * @param position Position of the newly inserted item in the data set - * @see #notifyItemRangeInserted(int, int) - */ - public final void notifyItemInserted(int position) { - mObservable.notifyItemRangeInserted(position, 1); - } - - /** - * Notify any registered observers that the item reflected at fromPosition - * has been moved to toPosition. - * - *

This is a structural change event. Representations of other existing items in the - * data set are still considered up to date and will not be rebound, though their - * positions may be altered.

- * - * @param fromPosition Previous position of the item. - * @param toPosition New position of the item. - */ - public final void notifyItemMoved(int fromPosition, int toPosition) { - mObservable.notifyItemMoved(fromPosition, toPosition); - } - - /** - * Notify any registered observers that the currently reflected itemCount - * items starting at positionStart have been newly inserted. The items - * previously located at positionStart and beyond can now be found starting - * at position positionStart + itemCount. - * - *

This is a structural change event. Representations of other existing items in the - * data set are still considered up to date and will not be rebound, though their positions - * may be altered.

- * - * @param positionStart Position of the first item that was inserted - * @param itemCount Number of items inserted - * @see #notifyItemInserted(int) - */ - public final void notifyItemRangeInserted(int positionStart, int itemCount) { - mObservable.notifyItemRangeInserted(positionStart, itemCount); - } - - /** - * Notify any registered observers that the item previously located at position - * has been removed from the data set. The items previously located at and after - * position may now be found at oldPosition - 1. - * - *

This is a structural change event. Representations of other existing items in the - * data set are still considered up to date and will not be rebound, though their positions - * may be altered.

- * - * @param position Position of the item that has now been removed - * @see #notifyItemRangeRemoved(int, int) - */ - public final void notifyItemRemoved(int position) { - mObservable.notifyItemRangeRemoved(position, 1); - } - - /** - * Notify any registered observers that the itemCount items previously - * located at positionStart have been removed from the data set. The items - * previously located at and after positionStart + itemCount may now be found - * at oldPosition - itemCount. - * - *

This is a structural change event. Representations of other existing items in the data - * set are still considered up to date and will not be rebound, though their positions - * may be altered.

- * - * @param positionStart Previous position of the first item that was removed - * @param itemCount Number of items removed from the data set - */ - public final void notifyItemRangeRemoved(int positionStart, int itemCount) { - mObservable.notifyItemRangeRemoved(positionStart, itemCount); - } - - /** - * Sets the state restoration strategy for the Adapter. - * - * By default, it is set to {@link StateRestorationPolicy#ALLOW} which means RecyclerView - * expects any set Adapter to be immediately capable of restoring the RecyclerView's saved - * scroll position. - *

- * This behaviour might be undesired if the Adapter's data is loaded asynchronously, and - * thus unavailable during initial layout (e.g. after Activity rotation). To avoid losing - * scroll position, you can change this to be either - * {@link StateRestorationPolicy#PREVENT_WHEN_EMPTY} or - * {@link StateRestorationPolicy#PREVENT}. - * Note that the former means your RecyclerView will restore state as soon as Adapter has - * 1 or more items while the latter requires you to call - * {@link #setStateRestorationPolicy(StateRestorationPolicy)} with either - * {@link StateRestorationPolicy#ALLOW} or - * {@link StateRestorationPolicy#PREVENT_WHEN_EMPTY} again when the Adapter is - * ready to restore its state. - *

- * RecyclerView will still layout even when State restoration is disabled. The behavior of - * how State is restored is up to the {@link LayoutManager}. All default LayoutManagers - * will override current state with restored state when state restoration happens (unless - * an explicit call to {@link LayoutManager#scrollToPosition(int)} is made). - *

- * Calling this method after state is restored will not have any effect other than changing - * the return value of {@link #getStateRestorationPolicy()}. - * - * @param strategy The saved state restoration strategy for this Adapter. - * @see #getStateRestorationPolicy() - */ - public void setStateRestorationPolicy(@NonNull StateRestorationPolicy strategy) { - mStateRestorationPolicy = strategy; - mObservable.notifyStateRestorationPolicyChanged(); - } - - /** - * Returns when this Adapter wants to restore the state. - * - * @return The current {@link StateRestorationPolicy} for this Adapter. Defaults to - * {@link StateRestorationPolicy#ALLOW}. - * @see #setStateRestorationPolicy(StateRestorationPolicy) - */ - @NonNull - public final StateRestorationPolicy getStateRestorationPolicy() { - return mStateRestorationPolicy; - } - - /** - * Called by the RecyclerView to decide whether the SavedState should be given to the - * LayoutManager or not. - * - * @return {@code true} if the Adapter is ready to restore its state, {@code false} - * otherwise. - */ - boolean canRestoreState() { - switch (mStateRestorationPolicy) { - case PREVENT: - return false; - case PREVENT_WHEN_EMPTY: - return getItemCount() > 0; - default: - return true; - } - } - - /** - * Defines how this Adapter wants to restore its state after a view reconstruction (e.g. - * configuration change). - */ - public enum StateRestorationPolicy { - /** - * Adapter is ready to restore State immediately, RecyclerView will provide the state - * to the LayoutManager in the next layout pass. - */ - ALLOW, - /** - * Adapter is ready to restore State when it has more than 0 items. RecyclerView will - * provide the state to the LayoutManager as soon as the Adapter has 1 or more items. - */ - PREVENT_WHEN_EMPTY, - /** - * RecyclerView will not restore the state for the Adapter until a call to - * {@link #setStateRestorationPolicy(StateRestorationPolicy)} is made with either - * {@link #ALLOW} or {@link #PREVENT_WHEN_EMPTY}. - */ - PREVENT - } - } - - @SuppressWarnings("unchecked") - void dispatchChildDetached(View child) { - final ViewHolder viewHolder = getChildViewHolderInt(child); - onChildDetachedFromWindow(child); - if (mAdapter != null && viewHolder != null) { - mAdapter.onViewDetachedFromWindow(viewHolder); - } - if (mOnChildAttachStateListeners != null) { - final int cnt = mOnChildAttachStateListeners.size(); - for (int i = cnt - 1; i >= 0; i--) { - mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child); - } - } - } - - @SuppressWarnings("unchecked") - void dispatchChildAttached(View child) { - final ViewHolder viewHolder = getChildViewHolderInt(child); - onChildAttachedToWindow(child); - if (mAdapter != null && viewHolder != null) { - mAdapter.onViewAttachedToWindow(viewHolder); - } - if (mOnChildAttachStateListeners != null) { - final int cnt = mOnChildAttachStateListeners.size(); - for (int i = cnt - 1; i >= 0; i--) { - mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); - } - } - } - - /** - * A LayoutManager is responsible for measuring and positioning item views - * within a RecyclerView as well as determining the policy for when to recycle - * item views that are no longer visible to the user. By changing the LayoutManager - * a RecyclerView can be used to implement a standard vertically scrolling list, - * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock - * layout managers are provided for general use. - *

- * If the LayoutManager specifies a default constructor or one with the signature - * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will - * instantiate and set the LayoutManager when being inflated. Most used properties can - * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case - * a LayoutManager specifies both constructors, the non-default constructor will take - * precedence. - */ - public abstract static class LayoutManager { - ChildHelper mChildHelper; - RecyclerView mRecyclerView; - - /** - * The callback used for retrieving information about a RecyclerView and its children in the - * horizontal direction. - */ - private final ViewBoundsCheck.Callback mHorizontalBoundCheckCallback = - new ViewBoundsCheck.Callback() { - @Override - public View getChildAt(int index) { - return LayoutManager.this.getChildAt(index); - } - - @Override - public int getParentStart() { - return LayoutManager.this.getPaddingLeft(); - } - - @Override - public int getParentEnd() { - return LayoutManager.this.getWidth() - LayoutManager.this.getPaddingRight(); - } - - @Override - public int getChildStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return LayoutManager.this.getDecoratedLeft(view) - params.leftMargin; - } - - @Override - public int getChildEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return LayoutManager.this.getDecoratedRight(view) + params.rightMargin; - } - }; - - /** - * The callback used for retrieving information about a RecyclerView and its children in the - * vertical direction. - */ - private final ViewBoundsCheck.Callback mVerticalBoundCheckCallback = - new ViewBoundsCheck.Callback() { - @Override - public View getChildAt(int index) { - return LayoutManager.this.getChildAt(index); - } - - @Override - public int getParentStart() { - return LayoutManager.this.getPaddingTop(); - } - - @Override - public int getParentEnd() { - return LayoutManager.this.getHeight() - - LayoutManager.this.getPaddingBottom(); - } - - @Override - public int getChildStart(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return LayoutManager.this.getDecoratedTop(view) - params.topMargin; - } - - @Override - public int getChildEnd(View view) { - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) - view.getLayoutParams(); - return LayoutManager.this.getDecoratedBottom(view) + params.bottomMargin; - } - }; - - /** - * Utility objects used to check the boundaries of children against their parent - * RecyclerView. - * - * @see #isViewPartiallyVisible(View, boolean, boolean), - * {@link LinearLayoutManager#findOneVisibleChild(int, int, boolean, boolean)}, - * and {@link LinearLayoutManager#findOnePartiallyOrCompletelyInvisibleChild(int, int)}. - */ - ViewBoundsCheck mHorizontalBoundCheck = new ViewBoundsCheck(mHorizontalBoundCheckCallback); - ViewBoundsCheck mVerticalBoundCheck = new ViewBoundsCheck(mVerticalBoundCheckCallback); - - @Nullable - SmoothScroller mSmoothScroller; - - boolean mRequestedSimpleAnimations = false; - - boolean mIsAttachedToWindow = false; - - /** - * This field is only set via the deprecated {@link #setAutoMeasureEnabled(boolean)} and is - * only accessed via {@link #isAutoMeasureEnabled()} for backwards compatability reasons. - */ - boolean mAutoMeasure = false; - - /** - * LayoutManager has its own more strict measurement cache to avoid re-measuring a child - * if the space that will be given to it is already larger than what it has measured before. - */ - private boolean mMeasurementCacheEnabled = true; - - private boolean mItemPrefetchEnabled = true; - - /** - * Written by {@link GapWorker} when prefetches occur to track largest number of view ever - * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or - * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. - * - * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, - * will be reset upon layout to prevent initial prefetches (often large, since they're - * proportional to expected child count) from expanding cache permanently. - */ - int mPrefetchMaxCountObserved; - - /** - * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. - */ - boolean mPrefetchMaxObservedInInitialPrefetch; - - /** - * These measure specs might be the measure specs that were passed into RecyclerView's - * onMeasure method OR fake measure specs created by the RecyclerView. - * For example, when a layout is run, RecyclerView always sets these specs to be - * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. - *

- * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the - * API level and sets the size to 0 pre-M to avoid any issue that might be caused by - * corrupt values. Older platforms have no responsibility to provide a size if they set - * mode to unspecified. - */ - private int mWidthMode, mHeightMode; - private int mWidth, mHeight; - - - /** - * Interface for LayoutManagers to request items to be prefetched, based on position, with - * specified distance from viewport, which indicates priority. - * - * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) - * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) - */ - public interface LayoutPrefetchRegistry { - /** - * Requests an an item to be prefetched, based on position, with a specified distance, - * indicating priority. - * - * @param layoutPosition Position of the item to prefetch. - * @param pixelDistance Distance from the current viewport to the bounds of the item, - * must be non-negative. - */ - void addPosition(int layoutPosition, int pixelDistance); - } - - void setRecyclerView(RecyclerView recyclerView) { - if (recyclerView == null) { - mRecyclerView = null; - mChildHelper = null; - mWidth = 0; - mHeight = 0; - } else { - mRecyclerView = recyclerView; - mChildHelper = recyclerView.mChildHelper; - mWidth = recyclerView.getWidth(); - mHeight = recyclerView.getHeight(); - } - mWidthMode = MeasureSpec.EXACTLY; - mHeightMode = MeasureSpec.EXACTLY; - } - - void setMeasureSpecs(int wSpec, int hSpec) { - mWidth = MeasureSpec.getSize(wSpec); - mWidthMode = MeasureSpec.getMode(wSpec); - if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { - mWidth = 0; - } - - mHeight = MeasureSpec.getSize(hSpec); - mHeightMode = MeasureSpec.getMode(hSpec); - if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { - mHeight = 0; - } - } - - /** - * Called after a layout is calculated during a measure pass when using auto-measure. - *

- * It simply traverses all children to calculate a bounding box then calls - * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method - * if they need to handle the bounding box differently. - *

- * For example, GridLayoutManager override that method to ensure that even if a column is - * empty, the GridLayoutManager still measures wide enough to include it. - * - * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure - * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure - */ - void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { - final int count = getChildCount(); - if (count == 0) { - mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); - return; - } - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - final Rect bounds = mRecyclerView.mTempRect; - getDecoratedBoundsWithMargins(child, bounds); - if (bounds.left < minX) { - minX = bounds.left; - } - if (bounds.right > maxX) { - maxX = bounds.right; - } - if (bounds.top < minY) { - minY = bounds.top; - } - if (bounds.bottom > maxY) { - maxY = bounds.bottom; - } - } - mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); - setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); - } - - /** - * Sets the measured dimensions from the given bounding box of the children and the - * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is - * only called if a LayoutManager returns true from - * {@link #isAutoMeasureEnabled()} and it is called after the RecyclerView calls - * {@link LayoutManager#onLayoutChildren(Recycler, State)} in the execution of - * {@link RecyclerView#onMeasure(int, int)}. - *

- * This method must call {@link #setMeasuredDimension(int, int)}. - *

- * The default implementation adds the RecyclerView's padding to the given bounding box - * then caps the value to be within the given measurement specs. - * - * @param childrenBounds The bounding box of all children - * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. - * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. - * @see #isAutoMeasureEnabled() - * @see #setMeasuredDimension(int, int) - */ - public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { - int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); - int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); - int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); - int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); - setMeasuredDimension(width, height); - } - - /** - * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView - */ - public void requestLayout() { - if (mRecyclerView != null) { - mRecyclerView.requestLayout(); - } - } - - /** - * Checks if RecyclerView is in the middle of a layout or scroll and throws an - * {@link IllegalStateException} if it is not. - * - * @param message The message for the exception. Can be null. - * @see #assertNotInLayoutOrScroll(String) - */ - public void assertInLayoutOrScroll(String message) { - if (mRecyclerView != null) { - mRecyclerView.assertInLayoutOrScroll(message); - } - } - - /** - * Chooses a size from the given specs and parameters that is closest to the desired size - * and also complies with the spec. - * - * @param spec The measureSpec - * @param desired The preferred measurement - * @param min The minimum value - * @return A size that fits to the given specs - */ - public static int chooseSize(int spec, int desired, int min) { - final int mode = View.MeasureSpec.getMode(spec); - final int size = View.MeasureSpec.getSize(spec); - switch (mode) { - case View.MeasureSpec.EXACTLY: - return size; - case View.MeasureSpec.AT_MOST: - return Math.min(size, Math.max(desired, min)); - case View.MeasureSpec.UNSPECIFIED: - default: - return Math.max(desired, min); - } - } - - /** - * Checks if RecyclerView is in the middle of a layout or scroll and throws an - * {@link IllegalStateException} if it is. - * - * @param message The message for the exception. Can be null. - * @see #assertInLayoutOrScroll(String) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void assertNotInLayoutOrScroll(String message) { - if (mRecyclerView != null) { - mRecyclerView.assertNotInLayoutOrScroll(message); - } - } - - /** - * Defines whether the measuring pass of layout should use the AutoMeasure mechanism of - * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of - * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. - * - * @param enabled True if layout measurement should be done by the - * RecyclerView, false if it should be done by this - * LayoutManager. - * @see #isAutoMeasureEnabled() - * @deprecated Implementors of LayoutManager should define whether or not it uses - * AutoMeasure by overriding {@link #isAutoMeasureEnabled()}. - */ - @Deprecated - public void setAutoMeasureEnabled(boolean enabled) { - mAutoMeasure = enabled; - } - - /** - * Returns whether the measuring pass of layout should use the AutoMeasure mechanism of - * {@link RecyclerView} or if it should be done by the LayoutManager's implementation of - * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. - *

- * This method returns false by default (it actually returns the value passed to the - * deprecated {@link #setAutoMeasureEnabled(boolean)}) and should be overridden to return - * true if a LayoutManager wants to be auto measured by the RecyclerView. - *

- * If this method is overridden to return true, - * {@link LayoutManager#onMeasure(Recycler, State, int, int)} should not be overridden. - *

- * AutoMeasure is a RecyclerView mechanism that handles the measuring pass of layout in a - * simple and contract satisfying way, including the wrapping of children laid out by - * LayoutManager. Simply put, it handles wrapping children by calling - * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a call to - * {@link RecyclerView#onMeasure(int, int)}, and then calculating desired dimensions based - * on children's dimensions and positions. It does this while supporting all existing - * animation capabilities of the RecyclerView. - *

- * More specifically: - *

    - *
  1. When {@link RecyclerView#onMeasure(int, int)} is called, if the provided measure - * specs both have a mode of {@link View.MeasureSpec#EXACTLY}, RecyclerView will set its - * measured dimensions accordingly and return, allowing layout to continue as normal - * (Actually, RecyclerView will call - * {@link LayoutManager#onMeasure(Recycler, State, int, int)} for backwards compatibility - * reasons but it should not be overridden if AutoMeasure is being used).
  2. - *
  3. If one of the layout specs is not {@code EXACT}, the RecyclerView will start the - * layout process. It will first process all pending Adapter updates and - * then decide whether to run a predictive layout. If it decides to do so, it will first - * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to - * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still - * return the width and height of the RecyclerView as of the last layout calculation. - *

    - * After handling the predictive case, RecyclerView will call - * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to - * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can - * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, - * {@link #getWidth()} and {@link #getWidthMode()}.

  4. - *
  5. After the layout calculation, RecyclerView sets the measured width & height by - * calculating the bounding box for the children (+ RecyclerView's padding). The - * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose - * different values. For instance, GridLayoutManager overrides this value to handle the case - * where if it is vertical and has 3 columns but only 2 items, it should still measure its - * width to fit 3 items, not 2.
  6. - *
  7. Any following calls to {@link RecyclerView#onMeasure(int, int)} will run - * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to - * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will - * take care of which views are actually added / removed / moved / changed for animations so - * that the LayoutManager should not worry about them and handle each - * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
  8. - *
  9. When measure is complete and RecyclerView's - * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks - * whether it already did layout calculations during the measure pass and if so, it re-uses - * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} - * if the last measure spec was different from the final dimensions or adapter contents - * have changed between the measure call and the layout call.
  10. - *
  11. Finally, animations are calculated and run as usual.
  12. - *
- * - * @return True if the measuring pass of layout should use the AutoMeasure - * mechanism of {@link RecyclerView} or False if it should be done by the - * LayoutManager's implementation of - * {@link LayoutManager#onMeasure(Recycler, State, int, int)}. - * @see #setMeasuredDimension(Rect, int, int) - * @see #onMeasure(Recycler, State, int, int) - */ - public boolean isAutoMeasureEnabled() { - return mAutoMeasure; - } - - /** - * Returns whether this LayoutManager supports "predictive item animations". - *

- * "Predictive item animations" are automatically created animations that show - * where items came from, and where they are going to, as items are added, removed, - * or moved within a layout. - *

- * A LayoutManager wishing to support predictive item animations must override this - * method to return true (the default implementation returns false) and must obey certain - * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}. - *

- * Whether item animations actually occur in a RecyclerView is actually determined by both - * the return value from this method and the - * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the - * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this - * method returns false, then only "simple item animations" will be enabled in the - * RecyclerView, in which views whose position are changing are simply faded in/out. If the - * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive - * item animations will be enabled in the RecyclerView. - * - * @return true if this LayoutManager supports predictive item animations, false otherwise. - */ - public boolean supportsPredictiveItemAnimations() { - return false; - } - - /** - * Sets whether the LayoutManager should be queried for views outside of - * its viewport while the UI thread is idle between frames. - * - *

If enabled, the LayoutManager will be queried for items to inflate/bind in between - * view system traversals on devices running API 21 or greater. Default value is true.

- * - *

On platforms API level 21 and higher, the UI thread is idle between passing a frame - * to RenderThread and the starting up its next frame at the next VSync pulse. By - * prefetching out of window views in this time period, delays from inflation and view - * binding are much less likely to cause jank and stuttering during scrolls and flings.

- * - *

While prefetch is enabled, it will have the side effect of expanding the effective - * size of the View cache to hold prefetched views.

- * - * @param enabled True if items should be prefetched in between traversals. - * @see #isItemPrefetchEnabled() - */ - public final void setItemPrefetchEnabled(boolean enabled) { - if (enabled != mItemPrefetchEnabled) { - mItemPrefetchEnabled = enabled; - mPrefetchMaxCountObserved = 0; - if (mRecyclerView != null) { - mRecyclerView.mRecycler.updateViewCacheSize(); - } - } - } - - /** - * Sets whether the LayoutManager should be queried for views outside of - * its viewport while the UI thread is idle between frames. - * - * @return true if item prefetch is enabled, false otherwise - * @see #setItemPrefetchEnabled(boolean) - */ - public final boolean isItemPrefetchEnabled() { - return mItemPrefetchEnabled; - } - - /** - * Gather all positions from the LayoutManager to be prefetched, given specified momentum. - * - *

If item prefetch is enabled, this method is called in between traversals to gather - * which positions the LayoutManager will soon need, given upcoming movement in subsequent - * traversals.

- * - *

The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for - * each item to be prepared, and these positions will have their ViewHolders created and - * bound, if there is sufficient time available, in advance of being needed by a - * scroll or layout.

- * - * @param dx X movement component. - * @param dy Y movement component. - * @param state State of RecyclerView - * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. - * @see #isItemPrefetchEnabled() - * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void collectAdjacentPrefetchPositions(int dx, int dy, State state, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - } - - /** - * Gather all positions from the LayoutManager to be prefetched in preperation for its - * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. - * - *

This method is only called when a RecyclerView is nested in another RecyclerView.

- * - *

If item prefetch is enabled for this LayoutManager, as well in another containing - * LayoutManager, this method is called in between draw traversals to gather - * which positions this LayoutManager will first need, once it appears on the screen.

- * - *

For example, if this LayoutManager represents a horizontally scrolling list within a - * vertically scrolling LayoutManager, this method would be called when the horizontal list - * is about to come onscreen.

- * - *

The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for - * each item to be prepared, and these positions will have their ViewHolders created and - * bound, if there is sufficient time available, in advance of being needed by a - * scroll or layout.

- * - * @param adapterItemCount number of items in the associated adapter. - * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. - * @see #isItemPrefetchEnabled() - * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void collectInitialPrefetchPositions(int adapterItemCount, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - } - - void dispatchAttachedToWindow(RecyclerView view) { - mIsAttachedToWindow = true; - onAttachedToWindow(view); - } - - void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { - mIsAttachedToWindow = false; - onDetachedFromWindow(view, recycler); - } - - /** - * Returns whether LayoutManager is currently attached to a RecyclerView which is attached - * to a window. - * - * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView - * is attached to window. - */ - public boolean isAttachedToWindow() { - return mIsAttachedToWindow; - } - - /** - * Causes the Runnable to execute on the next animation time step. - * The runnable will be run on the user interface thread. - *

- * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. - * - * @param action The Runnable that will be executed. - * @see #removeCallbacks - */ - public void postOnAnimation(Runnable action) { - if (mRecyclerView != null) { - ViewCompat.postOnAnimation(mRecyclerView, action); - } - } - - /** - * Removes the specified Runnable from the message queue. - *

- * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. - * - * @param action The Runnable to remove from the message handling queue - * @return true if RecyclerView could ask the Handler to remove the Runnable, - * false otherwise. When the returned value is true, the Runnable - * may or may not have been actually removed from the message queue - * (for instance, if the Runnable was not in the queue already.) - * @see #postOnAnimation - */ - public boolean removeCallbacks(Runnable action) { - if (mRecyclerView != null) { - return mRecyclerView.removeCallbacks(action); - } - return false; - } - - /** - * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView - * is attached to a window. - *

- * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not - * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was - * not requested on the RecyclerView while it was detached. - *

- * Subclass implementations should always call through to the superclass implementation. - * - * @param view The RecyclerView this LayoutManager is bound to - * @see #onDetachedFromWindow(RecyclerView, Recycler) - */ - @CallSuper - public void onAttachedToWindow(RecyclerView view) { - } - - /** - * @deprecated override {@link #onDetachedFromWindow(RecyclerView, Recycler)} - */ - @Deprecated - public void onDetachedFromWindow(RecyclerView view) { - - } - - /** - * Called when this LayoutManager is detached from its parent RecyclerView or when - * its parent RecyclerView is detached from its window. - *

- * LayoutManager should clear all of its View references as another LayoutManager might be - * assigned to the RecyclerView. - *

- * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not - * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was - * not requested on the RecyclerView while it was detached. - *

- * If your LayoutManager has View references that it cleans in on-detach, it should also - * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when - * RecyclerView is re-attached. - *

- * Subclass implementations should always call through to the superclass implementation. - * - * @param view The RecyclerView this LayoutManager is bound to - * @param recycler The recycler to use if you prefer to recycle your children instead of - * keeping them around. - * @see #onAttachedToWindow(RecyclerView) - */ - @CallSuper - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { - onDetachedFromWindow(view); - } - - /** - * Check if the RecyclerView is configured to clip child views to its padding. - * - * @return true if this RecyclerView clips children to its padding, false otherwise - */ - public boolean getClipToPadding() { - return mRecyclerView != null && mRecyclerView.mClipToPadding; - } - - /** - * Lay out all relevant child views from the given adapter. - * - * The LayoutManager is in charge of the behavior of item animations. By default, - * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple - * item animations are enabled. This means that add/remove operations on the - * adapter will result in animations to add new or appearing items, removed or - * disappearing items, and moved items. If a LayoutManager returns false from - * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a - * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the - * RecyclerView will have enough information to run those animations in a simple - * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will - * simply fade views in and out, whether they are actually added/removed or whether - * they are moved on or off the screen due to other add/remove operations. - * - *

A LayoutManager wanting a better item animation experience, where items can be - * animated onto and off of the screen according to where the items exist when they - * are not on screen, then the LayoutManager should return true from - * {@link #supportsPredictiveItemAnimations()} and add additional logic to - * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations - * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; - * once as a "pre" layout step to determine where items would have been prior to - * a real layout, and again to do the "real" layout. In the pre-layout phase, - * items will remember their pre-layout positions to allow them to be laid out - * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will - * be returned from the scrap to help determine correct placement of other items. - * These removed items should not be added to the child list, but should be used - * to help calculate correct positioning of other views, including views that - * were not previously onscreen (referred to as APPEARING views), but whose - * pre-layout offscreen position can be determined given the extra - * information about the pre-layout removed views.

- * - *

The second layout pass is the real layout in which only non-removed views - * will be used. The only additional requirement during this pass is, if - * {@link #supportsPredictiveItemAnimations()} returns true, to note which - * views exist in the child list prior to layout and which are not there after - * layout (referred to as DISAPPEARING views), and to position/layout those views - * appropriately, without regard to the actual bounds of the RecyclerView. This allows - * the animation system to know the location to which to animate these disappearing - * views.

- * - *

The default LayoutManager implementations for RecyclerView handle all of these - * requirements for animations already. Clients of RecyclerView can either use one - * of these layout managers directly or look at their implementations of - * onLayoutChildren() to see how they account for the APPEARING and - * DISAPPEARING views.

- * - * @param recycler Recycler to use for fetching potentially cached views for a - * position - * @param state Transient state of RecyclerView - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onLayoutChildren(Recycler recycler, State state) { - Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); - } - - /** - * Called after a full layout calculation is finished. The layout calculation may include - * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or - * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. - * This method will be called at the end of {@link View#layout(int, int, int, int)} call. - *

- * This is a good place for the LayoutManager to do some cleanup like pending scroll - * position, saved state etc. - * - * @param state Transient state of RecyclerView - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onLayoutCompleted(State state) { - } - - /** - * Create a default LayoutParams object for a child of the RecyclerView. - * - *

LayoutManagers will often want to use a custom LayoutParams type - * to store extra information specific to the layout. Client code should subclass - * {@link RecyclerView.LayoutParams} for this purpose.

- * - *

Important: if you use your own custom LayoutParams type - * you must also override - * {@link #checkLayoutParams(LayoutParams)}, - * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and - * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.

- * - * @return A new LayoutParams for a child view - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract LayoutParams generateDefaultLayoutParams(); - - /** - * Determines the validity of the supplied LayoutParams object. - * - *

This should check to make sure that the object is of the correct type - * and all values are within acceptable ranges. The default implementation - * returns true for non-null params.

- * - * @param lp LayoutParams object to check - * @return true if this LayoutParams object is valid, false otherwise - */ - public boolean checkLayoutParams(LayoutParams lp) { - return lp != null; - } - - /** - * Create a LayoutParams object suitable for this LayoutManager, copying relevant - * values from the supplied LayoutParams object if possible. - * - *

Important: if you use your own custom LayoutParams type - * you must also override - * {@link #checkLayoutParams(LayoutParams)}, - * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and - * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.

- * - * @param lp Source LayoutParams object to copy values from - * @return a new LayoutParams object - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { - if (lp instanceof LayoutParams) { - return new LayoutParams((LayoutParams) lp); - } else if (lp instanceof MarginLayoutParams) { - return new LayoutParams((MarginLayoutParams) lp); - } else { - return new LayoutParams(lp); - } - } - - /** - * Create a LayoutParams object suitable for this LayoutManager from - * an inflated layout resource. - * - *

Important: if you use your own custom LayoutParams type - * you must also override - * {@link #checkLayoutParams(LayoutParams)}, - * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and - * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.

- * - * @param c Context for obtaining styled attributes - * @param attrs AttributeSet describing the supplied arguments - * @return a new LayoutParams object - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { - return new LayoutParams(c, attrs); - } - - /** - * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. - * The default implementation does nothing and returns 0. - * - * @param dx distance to scroll by in pixels. X increases as scroll position - * approaches the right. - * @param recycler Recycler to use for fetching potentially cached views for a - * position - * @param state Transient state of RecyclerView - * @return The actual distance scrolled. The return value will be negative if dx was - * negative and scrolling proceeeded in that direction. - * Math.abs(result) may be less than dx if a boundary was reached. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { - return 0; - } - - /** - * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. - * The default implementation does nothing and returns 0. - * - * @param dy distance to scroll in pixels. Y increases as scroll position - * approaches the bottom. - * @param recycler Recycler to use for fetching potentially cached views for a - * position - * @param state Transient state of RecyclerView - * @return The actual distance scrolled. The return value will be negative if dy was - * negative and scrolling proceeeded in that direction. - * Math.abs(result) may be less than dy if a boundary was reached. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int scrollVerticallyBy(int dy, Recycler recycler, State state) { - return 0; - } - - /** - * Query if horizontal scrolling is currently supported. The default implementation - * returns false. - * - * @return True if this LayoutManager can scroll the current contents horizontally - */ - public boolean canScrollHorizontally() { - return false; - } - - /** - * Query if vertical scrolling is currently supported. The default implementation - * returns false. - * - * @return True if this LayoutManager can scroll the current contents vertically - */ - public boolean canScrollVertically() { - return false; - } - - /** - * Scroll to the specified adapter position. - * - * Actual position of the item on the screen depends on the LayoutManager implementation. - * - * @param position Scroll to this adapter position. - */ - public void scrollToPosition(int position) { - if (sVerboseLoggingEnabled) { - Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); - } - } - - /** - *

Smooth scroll to the specified adapter position.

- *

To support smooth scrolling, override this method, create your {@link SmoothScroller} - * instance and call {@link #startSmoothScroll(SmoothScroller)}. - *

- * - * @param recyclerView The RecyclerView to which this layout manager is attached - * @param state Current State of RecyclerView - * @param position Scroll to this adapter position. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void smoothScrollToPosition(RecyclerView recyclerView, State state, - int position) { - Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); - } - - /** - * Starts a smooth scroll using the provided {@link SmoothScroller}. - * - *

Each instance of SmoothScroller is intended to only be used once. Provide a new - * SmoothScroller instance each time this method is called. - * - *

Calling this method will cancel any previous smooth scroll request. - * - * @param smoothScroller Instance which defines how smooth scroll should be animated - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void startSmoothScroll(SmoothScroller smoothScroller) { - if (mSmoothScroller != null && smoothScroller != mSmoothScroller - && mSmoothScroller.isRunning()) { - mSmoothScroller.stop(); - } - mSmoothScroller = smoothScroller; - mSmoothScroller.start(mRecyclerView, this); - } - - /** - * @return true if RecyclerView is currently in the state of smooth scrolling. - */ - public boolean isSmoothScrolling() { - return mSmoothScroller != null && mSmoothScroller.isRunning(); - } - - /** - * Returns the resolved layout direction for this RecyclerView. - * - * @return {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout - * direction is RTL or returns - * {@link androidx.core.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction - * is not RTL. - */ - public int getLayoutDirection() { - return ViewCompat.getLayoutDirection(mRecyclerView); - } - - /** - * Ends all animations on the view created by the {@link ItemAnimator}. - * - * @param view The View for which the animations should be ended. - * @see RecyclerView.ItemAnimator#endAnimations() - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void endAnimation(View view) { - if (mRecyclerView.mItemAnimator != null) { - mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); - } - } - - /** - * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view - * to the layout that is known to be going away, either because it has been - * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the - * visible portion of the container but is being laid out in order to inform RecyclerView - * in how to animate the item out of view. - *

- * Views added via this method are going to be invisible to LayoutManager after the - * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} - * or won't be included in {@link #getChildCount()} method. - * - * @param child View to add and then remove with animation. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void addDisappearingView(View child) { - addDisappearingView(child, -1); - } - - /** - * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view - * to the layout that is known to be going away, either because it has been - * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the - * visible portion of the container but is being laid out in order to inform RecyclerView - * in how to animate the item out of view. - *

- * Views added via this method are going to be invisible to LayoutManager after the - * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} - * or won't be included in {@link #getChildCount()} method. - * - * @param child View to add and then remove with animation. - * @param index Index of the view. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void addDisappearingView(View child, int index) { - addViewInt(child, index, true); - } - - /** - * Add a view to the currently attached RecyclerView if needed. LayoutManagers should - * use this method to add views obtained from a {@link Recycler} using - * {@link Recycler#getViewForPosition(int)}. - * - * @param child View to add - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void addView(View child) { - addView(child, -1); - } - - /** - * Add a view to the currently attached RecyclerView if needed. LayoutManagers should - * use this method to add views obtained from a {@link Recycler} using - * {@link Recycler#getViewForPosition(int)}. - * - * @param child View to add - * @param index Index to add child at - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void addView(View child, int index) { - addViewInt(child, index, false); - } - - private void addViewInt(View child, int index, boolean disappearing) { - final ViewHolder holder = getChildViewHolderInt(child); - if (disappearing || holder.isRemoved()) { - // these views will be hidden at the end of the layout pass. - mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); - } else { - // This may look like unnecessary but may happen if layout manager supports - // predictive layouts and adapter removed then re-added the same item. - // In this case, added version will be visible in the post layout (because add is - // deferred) but RV will still bind it to the same View. - // So if a View re-appears in post layout pass, remove it from disappearing list. - mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); - } - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (holder.wasReturnedFromScrap() || holder.isScrap()) { - if (holder.isScrap()) { - holder.unScrap(); - } else { - holder.clearReturnedFromScrapFlag(); - } - mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); - if (DISPATCH_TEMP_DETACH) { - ViewCompat.dispatchFinishTemporaryDetach(child); - } - } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child - // ensure in correct position - int currentIndex = mChildHelper.indexOfChild(child); - if (index == -1) { - index = mChildHelper.getChildCount(); - } - if (currentIndex == -1) { - throw new IllegalStateException("Added View has RecyclerView as parent but" - + " view is not a real child. Unfiltered index:" - + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel()); - } - if (currentIndex != index) { - mRecyclerView.mLayout.moveView(currentIndex, index); - } - } else { - mChildHelper.addView(child, index, false); - lp.mInsetsDirty = true; - if (mSmoothScroller != null && mSmoothScroller.isRunning()) { - mSmoothScroller.onChildAttachedToWindow(child); - } - } - if (lp.mPendingInvalidate) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); - } - holder.itemView.invalidate(); - lp.mPendingInvalidate = false; - } - } - - /** - * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should - * use this method to completely remove a child view that is no longer needed. - * LayoutManagers should strongly consider recycling removed views using - * {@link Recycler#recycleView(android.view.View)}. - * - * @param child View to remove - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void removeView(View child) { - mChildHelper.removeView(child); - } - - /** - * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should - * use this method to completely remove a child view that is no longer needed. - * LayoutManagers should strongly consider recycling removed views using - * {@link Recycler#recycleView(android.view.View)}. - * - * @param index Index of the child view to remove - */ - public void removeViewAt(int index) { - final View child = getChildAt(index); - if (child != null) { - mChildHelper.removeViewAt(index); - } - } - - /** - * Remove all views from the currently attached RecyclerView. This will not recycle - * any of the affected views; the LayoutManager is responsible for doing so if desired. - */ - public void removeAllViews() { - // Only remove non-animating views - final int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - mChildHelper.removeViewAt(i); - } - } - - /** - * Returns offset of the RecyclerView's text baseline from the its top boundary. - * - * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if - * there is no baseline. - */ - public int getBaseline() { - return -1; - } - - /** - * Returns the adapter position of the item represented by the given View. This does not - * contain any adapter changes that might have happened after the last layout. - * - * @param view The view to query - * @return The adapter position of the item which is rendered by this View. - */ - public int getPosition(@NonNull View view) { - return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); - } - - /** - * Returns the View type defined by the adapter. - * - * @param view The view to query - * @return The type of the view assigned by the adapter. - */ - public int getItemViewType(@NonNull View view) { - return getChildViewHolderInt(view).getItemViewType(); - } - - /** - * Traverses the ancestors of the given view and returns the item view that contains it - * and also a direct child of the LayoutManager. - *

- * Note that this method may return null if the view is a child of the RecyclerView but - * not a child of the LayoutManager (e.g. running a disappear animation). - * - * @param view The view that is a descendant of the LayoutManager. - * @return The direct child of the LayoutManager which contains the given view or null if - * the provided view is not a descendant of this LayoutManager. - * @see RecyclerView#getChildViewHolder(View) - * @see RecyclerView#findContainingViewHolder(View) - */ - @Nullable - public View findContainingItemView(@NonNull View view) { - if (mRecyclerView == null) { - return null; - } - View found = mRecyclerView.findContainingItemView(view); - if (found == null) { - return null; - } - if (mChildHelper.isHidden(found)) { - return null; - } - return found; - } - - /** - * Finds the view which represents the given adapter position. - *

- * This method traverses each child since it has no information about child order. - * Override this method to improve performance if your LayoutManager keeps data about - * child views. - *

- * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. - * - * @param position Position of the item in adapter - * @return The child view that represents the given position or null if the position is not - * laid out - */ - @Nullable - public View findViewByPosition(int position) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - ViewHolder vh = getChildViewHolderInt(child); - if (vh == null) { - continue; - } - if (vh.getLayoutPosition() == position && !vh.shouldIgnore() - && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { - return child; - } - } - return null; - } - - /** - * Temporarily detach a child view. - * - *

LayoutManagers may want to perform a lightweight detach operation to rearrange - * views currently attached to the RecyclerView. Generally LayoutManager implementations - * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} - * so that the detached view may be rebound and reused.

- * - *

If a LayoutManager uses this method to detach a view, it must - * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} - * or {@link #removeDetachedView(android.view.View) fully remove} the detached view - * before the LayoutManager entry point method called by RecyclerView returns.

- * - * @param child Child to detach - */ - public void detachView(@NonNull View child) { - final int ind = mChildHelper.indexOfChild(child); - if (ind >= 0) { - detachViewInternal(ind, child); - } - } - - /** - * Temporarily detach a child view. - * - *

LayoutManagers may want to perform a lightweight detach operation to rearrange - * views currently attached to the RecyclerView. Generally LayoutManager implementations - * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} - * so that the detached view may be rebound and reused.

- * - *

If a LayoutManager uses this method to detach a view, it must - * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} - * or {@link #removeDetachedView(android.view.View) fully remove} the detached view - * before the LayoutManager entry point method called by RecyclerView returns.

- * - * @param index Index of the child to detach - */ - public void detachViewAt(int index) { - detachViewInternal(index, getChildAt(index)); - } - - private void detachViewInternal(int index, @NonNull View view) { - if (DISPATCH_TEMP_DETACH) { - ViewCompat.dispatchStartTemporaryDetach(view); - } - mChildHelper.detachViewFromParent(index); - } - - /** - * Reattach a previously {@link #detachView(android.view.View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. - * - * @param child Child to reattach - * @param index Intended child index for child - * @param lp LayoutParams for child - */ - public void attachView(@NonNull View child, int index, LayoutParams lp) { - ViewHolder vh = getChildViewHolderInt(child); - if (vh.isRemoved()) { - mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); - } else { - mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); - } - mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); - if (DISPATCH_TEMP_DETACH) { - ViewCompat.dispatchFinishTemporaryDetach(child); - } - } - - /** - * Reattach a previously {@link #detachView(android.view.View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. - * - * @param child Child to reattach - * @param index Intended child index for child - */ - public void attachView(@NonNull View child, int index) { - attachView(child, index, (LayoutParams) child.getLayoutParams()); - } - - /** - * Reattach a previously {@link #detachView(android.view.View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. - * - * @param child Child to reattach - */ - public void attachView(@NonNull View child) { - attachView(child, -1); - } - - /** - * Finish removing a view that was previously temporarily - * {@link #detachView(android.view.View) detached}. - * - * @param child Detached child to remove - */ - public void removeDetachedView(@NonNull View child) { - mRecyclerView.removeDetachedView(child, false); - } - - /** - * Moves a View from one position to another. - * - * @param fromIndex The View's initial index - * @param toIndex The View's target index - */ - public void moveView(int fromIndex, int toIndex) { - View view = getChildAt(fromIndex); - if (view == null) { - throw new IllegalArgumentException("Cannot move a child from non-existing index:" - + fromIndex + mRecyclerView.toString()); - } - detachViewAt(fromIndex); - attachView(view, toIndex); - } - - /** - * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. - * - *

Scrapping a view allows it to be rebound and reused to show updated or - * different data.

- * - * @param child Child to detach and scrap - * @param recycler Recycler to deposit the new scrap view into - */ - public void detachAndScrapView(@NonNull View child, @NonNull Recycler recycler) { - int index = mChildHelper.indexOfChild(child); - scrapOrRecycleView(recycler, index, child); - } - - /** - * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. - * - *

Scrapping a view allows it to be rebound and reused to show updated or - * different data.

- * - * @param index Index of child to detach and scrap - * @param recycler Recycler to deposit the new scrap view into - */ - public void detachAndScrapViewAt(int index, @NonNull Recycler recycler) { - final View child = getChildAt(index); - scrapOrRecycleView(recycler, index, child); - } - - /** - * Remove a child view and recycle it using the given Recycler. - * - * @param child Child to remove and recycle - * @param recycler Recycler to use to recycle child - */ - public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) { - removeView(child); - recycler.recycleView(child); - } - - /** - * Remove a child view and recycle it using the given Recycler. - * - * @param index Index of child to remove and recycle - * @param recycler Recycler to use to recycle child - */ - public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { - final View view = getChildAt(index); - removeViewAt(index); - recycler.recycleView(view); - } - - /** - * Return the current number of child views attached to the parent RecyclerView. - * This does not include child views that were temporarily detached and/or scrapped. - * - * @return Number of attached children - */ - public int getChildCount() { - return mChildHelper != null ? mChildHelper.getChildCount() : 0; - } - - /** - * Return the child view at the given index - * - * @param index Index of child to return - * @return Child view at index - */ - @Nullable - public View getChildAt(int index) { - return mChildHelper != null ? mChildHelper.getChildAt(index) : null; - } - - /** - * Return the width measurement spec mode that is currently relevant to the LayoutManager. - * - *

This value is set only if the LayoutManager opts into the AutoMeasure api via - * {@link #setAutoMeasureEnabled(boolean)}. - * - *

When RecyclerView is running a layout, this value is always set to - * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. - * - * @return Width measure spec mode - * @see View.MeasureSpec#getMode(int) - */ - public int getWidthMode() { - return mWidthMode; - } - - /** - * Return the height measurement spec mode that is currently relevant to the LayoutManager. - * - *

This value is set only if the LayoutManager opts into the AutoMeasure api via - * {@link #setAutoMeasureEnabled(boolean)}. - * - *

When RecyclerView is running a layout, this value is always set to - * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. - * - * @return Height measure spec mode - * @see View.MeasureSpec#getMode(int) - */ - public int getHeightMode() { - return mHeightMode; - } - - /** - * Returns the width that is currently relevant to the LayoutManager. - * - *

This value is usually equal to the laid out width of the {@link RecyclerView} but may - * reflect the current {@link android.view.View.MeasureSpec} width if the - * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of - * measuring. The LayoutManager must always use this method to retrieve the width relevant - * to it at any given time. - * - * @return Width in pixels - */ - @Px - public int getWidth() { - return mWidth; - } - - /** - * Returns the height that is currently relevant to the LayoutManager. - * - *

This value is usually equal to the laid out height of the {@link RecyclerView} but may - * reflect the current {@link android.view.View.MeasureSpec} height if the - * {@link LayoutManager} is using AutoMeasure and the RecyclerView is in the process of - * measuring. The LayoutManager must always use this method to retrieve the height relevant - * to it at any given time. - * - * @return Height in pixels - */ - @Px - public int getHeight() { - return mHeight; - } - - /** - * Return the left padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingLeft() { - return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; - } - - /** - * Return the top padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingTop() { - return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; - } - - /** - * Return the right padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingRight() { - return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; - } - - /** - * Return the bottom padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingBottom() { - return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; - } - - /** - * Return the start padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingStart() { - return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; - } - - /** - * Return the end padding of the parent RecyclerView - * - * @return Padding in pixels - */ - @Px - public int getPaddingEnd() { - return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; - } - - /** - * Returns true if the RecyclerView this LayoutManager is bound to has focus. - * - * @return True if the RecyclerView has focus, false otherwise. - * @see View#isFocused() - */ - public boolean isFocused() { - return mRecyclerView != null && mRecyclerView.isFocused(); - } - - /** - * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. - * - * @return true if the RecyclerView has or contains focus - * @see View#hasFocus() - */ - public boolean hasFocus() { - return mRecyclerView != null && mRecyclerView.hasFocus(); - } - - /** - * Returns the item View which has or contains focus. - * - * @return A direct child of RecyclerView which has focus or contains the focused child. - */ - @Nullable - public View getFocusedChild() { - if (mRecyclerView == null) { - return null; - } - final View focused = mRecyclerView.getFocusedChild(); - if (focused == null || mChildHelper.isHidden(focused)) { - return null; - } - return focused; - } - - /** - * Returns the number of items in the adapter bound to the parent RecyclerView. - *

- * Note that this number is not necessarily equal to - * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is - * available, you should use {@link State#getItemCount() State#getItemCount()} instead. - * For more details, check the documentation for - * {@link State#getItemCount() State#getItemCount()}. - * - * @return The number of items in the bound adapter - * @see State#getItemCount() - */ - public int getItemCount() { - final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; - return a != null ? a.getItemCount() : 0; - } - - /** - * Offset all child views attached to the parent RecyclerView by dx pixels along - * the horizontal axis. - * - * @param dx Pixels to offset by - */ - public void offsetChildrenHorizontal(@Px int dx) { - if (mRecyclerView != null) { - mRecyclerView.offsetChildrenHorizontal(dx); - } - } - - /** - * Offset all child views attached to the parent RecyclerView by dy pixels along - * the vertical axis. - * - * @param dy Pixels to offset by - */ - public void offsetChildrenVertical(@Px int dy) { - if (mRecyclerView != null) { - mRecyclerView.offsetChildrenVertical(dy); - } - } - - /** - * Flags a view so that it will not be scrapped or recycled. - *

- * Scope of ignoring a child is strictly restricted to position tracking, scrapping and - * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child - * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not - * ignore the child. - *

- * Before this child can be recycled again, you have to call - * {@link #stopIgnoringView(View)}. - *

- * You can call this method only if your LayoutManger is in onLayout or onScroll callback. - * - * @param view View to ignore. - * @see #stopIgnoringView(View) - */ - public void ignoreView(@NonNull View view) { - if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { - // checking this because calling this method on a recycled or detached view may - // cause loss of state. - throw new IllegalArgumentException("View should be fully attached to be ignored" - + mRecyclerView.exceptionLabel()); - } - final ViewHolder vh = getChildViewHolderInt(view); - vh.addFlags(ViewHolder.FLAG_IGNORE); - mRecyclerView.mViewInfoStore.removeViewHolder(vh); - } - - /** - * View can be scrapped and recycled again. - *

- * Note that calling this method removes all information in the view holder. - *

- * You can call this method only if your LayoutManger is in onLayout or onScroll callback. - * - * @param view View to ignore. - */ - public void stopIgnoringView(@NonNull View view) { - final ViewHolder vh = getChildViewHolderInt(view); - vh.stopIgnoring(); - vh.resetInternal(); - vh.addFlags(ViewHolder.FLAG_INVALID); - } - - /** - * Temporarily detach and scrap all currently attached child views. Views will be scrapped - * into the given Recycler. The Recycler may prefer to reuse scrap views before - * other views that were previously recycled. - * - * @param recycler Recycler to scrap views into - */ - public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { - final int childCount = getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - final View v = getChildAt(i); - scrapOrRecycleView(recycler, i, v); - } - } - - private void scrapOrRecycleView(Recycler recycler, int index, View view) { - final ViewHolder viewHolder = getChildViewHolderInt(view); - if (viewHolder.shouldIgnore()) { - if (sVerboseLoggingEnabled) { - Log.d(TAG, "ignoring view " + viewHolder); - } - return; - } - if (viewHolder.isInvalid() && !viewHolder.isRemoved() - && !mRecyclerView.mAdapter.hasStableIds()) { - removeViewAt(index); - recycler.recycleViewHolderInternal(viewHolder); - } else { - detachViewAt(index); - recycler.scrapView(view); - mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); - } - } - - /** - * Recycles the scrapped views. - *

- * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is - * the expected behavior if scrapped views are used for animations. Otherwise, we need to - * call remove and invalidate RecyclerView to ensure UI update. - * - * @param recycler Recycler - */ - void removeAndRecycleScrapInt(Recycler recycler) { - final int scrapCount = recycler.getScrapCount(); - // Loop backward, recycler might be changed by removeDetachedView() - for (int i = scrapCount - 1; i >= 0; i--) { - final View scrap = recycler.getScrapViewAt(i); - final ViewHolder vh = getChildViewHolderInt(scrap); - if (vh.shouldIgnore()) { - continue; - } - // If the scrap view is animating, we need to cancel them first. If we cancel it - // here, ItemAnimator callback may recycle it which will cause double recycling. - // To avoid this, we mark it as not recyclable before calling the item animator. - // Since removeDetachedView calls a user API, a common mistake (ending animations on - // the view) may recycle it too, so we guard it before we call user APIs. - vh.setIsRecyclable(false); - if (vh.isTmpDetached()) { - mRecyclerView.removeDetachedView(scrap, false); - } - if (mRecyclerView.mItemAnimator != null) { - mRecyclerView.mItemAnimator.endAnimation(vh); - } - vh.setIsRecyclable(true); - recycler.quickRecycleScrapView(scrap); - } - recycler.clearScrap(); - if (scrapCount > 0) { - mRecyclerView.invalidate(); - } - } - - - /** - * Measure a child view using standard measurement policy, taking the padding - * of the parent RecyclerView and any added item decorations into account. - * - *

If the RecyclerView can be scrolled in either dimension the caller may - * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.

- * - * @param child Child view to measure - * @param widthUsed Width in pixels currently consumed by other views, if relevant - * @param heightUsed Height in pixels currently consumed by other views, if relevant - */ - public void measureChild(@NonNull View child, int widthUsed, int heightUsed) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); - widthUsed += insets.left + insets.right; - heightUsed += insets.top + insets.bottom; - final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), - getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, - canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), - getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, - canScrollVertically()); - if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { - child.measure(widthSpec, heightSpec); - } - } - - /** - * RecyclerView internally does its own View measurement caching which should help with - * WRAP_CONTENT. - *

- * Use this method if the View is already measured once in this layout pass. - */ - boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { - return !mMeasurementCacheEnabled - || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) - || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); - } - - // we may consider making this public - - /** - * RecyclerView internally does its own View measurement caching which should help with - * WRAP_CONTENT. - *

- * Use this method if the View is not yet measured and you need to decide whether to - * measure this View or not. - */ - boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { - return child.isLayoutRequested() - || !mMeasurementCacheEnabled - || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) - || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); - } - - /** - * In addition to the View Framework's measurement cache, RecyclerView uses its own - * additional measurement cache for its children to avoid re-measuring them when not - * necessary. It is on by default but it can be turned off via - * {@link #setMeasurementCacheEnabled(boolean)}. - * - * @return True if measurement cache is enabled, false otherwise. - * @see #setMeasurementCacheEnabled(boolean) - */ - public boolean isMeasurementCacheEnabled() { - return mMeasurementCacheEnabled; - } - - /** - * Sets whether RecyclerView should use its own measurement cache for the children. This is - * a more aggressive cache than the framework uses. - * - * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. - * @see #isMeasurementCacheEnabled() - */ - public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { - mMeasurementCacheEnabled = measurementCacheEnabled; - } - - private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { - final int specMode = MeasureSpec.getMode(spec); - final int specSize = MeasureSpec.getSize(spec); - if (dimension > 0 && childSize != dimension) { - return false; - } - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - return true; - case MeasureSpec.AT_MOST: - return specSize >= childSize; - case MeasureSpec.EXACTLY: - return specSize == childSize; - } - return false; - } - - /** - * Measure a child view using standard measurement policy, taking the padding - * of the parent RecyclerView, any added item decorations and the child margins - * into account. - * - *

If the RecyclerView can be scrolled in either dimension the caller may - * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.

- * - * @param child Child view to measure - * @param widthUsed Width in pixels currently consumed by other views, if relevant - * @param heightUsed Height in pixels currently consumed by other views, if relevant - */ - public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); - widthUsed += insets.left + insets.right; - heightUsed += insets.top + insets.bottom; - - final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), - getPaddingLeft() + getPaddingRight() - + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, - canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), - getPaddingTop() + getPaddingBottom() - + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, - canScrollVertically()); - if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { - child.measure(widthSpec, heightSpec); - } - } - - /** - * Calculate a MeasureSpec value for measuring a child view in one dimension. - * - * @param parentSize Size of the parent view where the child will be placed - * @param padding Total space currently consumed by other elements of the parent - * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. - * Generally obtained from the child view's LayoutParams - * @param canScroll true if the parent RecyclerView can scroll in this dimension - * @return a MeasureSpec value for the child view - * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} - */ - @Deprecated - public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, - boolean canScroll) { - int size = Math.max(0, parentSize - padding); - int resultSize = 0; - int resultMode = 0; - if (canScroll) { - if (childDimension >= 0) { - resultSize = childDimension; - resultMode = MeasureSpec.EXACTLY; - } else { - // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap - // instead using UNSPECIFIED. - resultSize = 0; - resultMode = MeasureSpec.UNSPECIFIED; - } - } else { - if (childDimension >= 0) { - resultSize = childDimension; - resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.MATCH_PARENT) { - resultSize = size; - // TODO this should be my spec. - resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - resultSize = size; - resultMode = MeasureSpec.AT_MOST; - } - } - return MeasureSpec.makeMeasureSpec(resultSize, resultMode); - } - - /** - * Calculate a MeasureSpec value for measuring a child view in one dimension. - * - * @param parentSize Size of the parent view where the child will be placed - * @param parentMode The measurement spec mode of the parent - * @param padding Total space currently consumed by other elements of parent - * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. - * Generally obtained from the child view's LayoutParams - * @param canScroll true if the parent RecyclerView can scroll in this dimension - * @return a MeasureSpec value for the child view - */ - public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, - int childDimension, boolean canScroll) { - int size = Math.max(0, parentSize - padding); - int resultSize = 0; - int resultMode = 0; - if (canScroll) { - if (childDimension >= 0) { - resultSize = childDimension; - resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.MATCH_PARENT) { - switch (parentMode) { - case MeasureSpec.AT_MOST: - case MeasureSpec.EXACTLY: - resultSize = size; - resultMode = parentMode; - break; - case MeasureSpec.UNSPECIFIED: - resultSize = 0; - resultMode = MeasureSpec.UNSPECIFIED; - break; - } - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - resultSize = 0; - resultMode = MeasureSpec.UNSPECIFIED; - } - } else { - if (childDimension >= 0) { - resultSize = childDimension; - resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.MATCH_PARENT) { - resultSize = size; - resultMode = parentMode; - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - resultSize = size; - if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { - resultMode = MeasureSpec.AT_MOST; - } else { - resultMode = MeasureSpec.UNSPECIFIED; - } - - } - } - //noinspection WrongConstant - return MeasureSpec.makeMeasureSpec(resultSize, resultMode); - } - - /** - * Returns the measured width of the given child, plus the additional size of - * any insets applied by {@link ItemDecoration ItemDecorations}. - * - * @param child Child view to query - * @return child's measured width plus ItemDecoration insets - * @see View#getMeasuredWidth() - */ - public int getDecoratedMeasuredWidth(@NonNull View child) { - final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; - return child.getMeasuredWidth() + insets.left + insets.right; - } - - /** - * Returns the measured height of the given child, plus the additional size of - * any insets applied by {@link ItemDecoration ItemDecorations}. - * - * @param child Child view to query - * @return child's measured height plus ItemDecoration insets - * @see View#getMeasuredHeight() - */ - public int getDecoratedMeasuredHeight(@NonNull View child) { - final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; - return child.getMeasuredHeight() + insets.top + insets.bottom; - } - - /** - * Lay out the given child view within the RecyclerView using coordinates that - * include any current {@link ItemDecoration ItemDecorations}. - * - *

LayoutManagers should prefer working in sizes and coordinates that include - * item decoration insets whenever possible. This allows the LayoutManager to effectively - * ignore decoration insets within measurement and layout code. See the following - * methods:

- *
    - *
  • {@link #layoutDecoratedWithMargins(View, int, int, int, int)}
  • - *
  • {@link #getDecoratedBoundsWithMargins(View, Rect)}
  • - *
  • {@link #measureChild(View, int, int)}
  • - *
  • {@link #measureChildWithMargins(View, int, int)}
  • - *
  • {@link #getDecoratedLeft(View)}
  • - *
  • {@link #getDecoratedTop(View)}
  • - *
  • {@link #getDecoratedRight(View)}
  • - *
  • {@link #getDecoratedBottom(View)}
  • - *
  • {@link #getDecoratedMeasuredWidth(View)}
  • - *
  • {@link #getDecoratedMeasuredHeight(View)}
  • - *
- * - * @param child Child to lay out - * @param left Left edge, with item decoration insets included - * @param top Top edge, with item decoration insets included - * @param right Right edge, with item decoration insets included - * @param bottom Bottom edge, with item decoration insets included - * @see View#layout(int, int, int, int) - * @see #layoutDecoratedWithMargins(View, int, int, int, int) - */ - public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) { - final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; - child.layout(left + insets.left, top + insets.top, right - insets.right, - bottom - insets.bottom); - } - - /** - * Lay out the given child view within the RecyclerView using coordinates that - * include any current {@link ItemDecoration ItemDecorations} and margins. - * - *

LayoutManagers should prefer working in sizes and coordinates that include - * item decoration insets whenever possible. This allows the LayoutManager to effectively - * ignore decoration insets within measurement and layout code. See the following - * methods:

- *
    - *
  • {@link #layoutDecorated(View, int, int, int, int)}
  • - *
  • {@link #measureChild(View, int, int)}
  • - *
  • {@link #measureChildWithMargins(View, int, int)}
  • - *
  • {@link #getDecoratedLeft(View)}
  • - *
  • {@link #getDecoratedTop(View)}
  • - *
  • {@link #getDecoratedRight(View)}
  • - *
  • {@link #getDecoratedBottom(View)}
  • - *
  • {@link #getDecoratedMeasuredWidth(View)}
  • - *
  • {@link #getDecoratedMeasuredHeight(View)}
  • - *
- * - * @param child Child to lay out - * @param left Left edge, with item decoration insets and left margin included - * @param top Top edge, with item decoration insets and top margin included - * @param right Right edge, with item decoration insets and right margin included - * @param bottom Bottom edge, with item decoration insets and bottom margin included - * @see View#layout(int, int, int, int) - * @see #layoutDecorated(View, int, int, int, int) - */ - public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right, - int bottom) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final Rect insets = lp.mDecorInsets; - child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, - right - insets.right - lp.rightMargin, - bottom - insets.bottom - lp.bottomMargin); - } - - /** - * Calculates the bounding box of the View while taking into account its matrix changes - * (translation, scale etc) with respect to the RecyclerView. - *

- * If {@code includeDecorInsets} is {@code true}, they are applied first before applying - * the View's matrix so that the decor offsets also go through the same transformation. - * - * @param child The ItemView whose bounding box should be calculated. - * @param includeDecorInsets True if the decor insets should be included in the bounding box - * @param out The rectangle into which the output will be written. - */ - public void getTransformedBoundingBox(@NonNull View child, boolean includeDecorInsets, - @NonNull Rect out) { - if (includeDecorInsets) { - Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; - out.set(-insets.left, -insets.top, - child.getWidth() + insets.right, child.getHeight() + insets.bottom); - } else { - out.set(0, 0, child.getWidth(), child.getHeight()); - } - - if (mRecyclerView != null) { - final Matrix childMatrix = child.getMatrix(); - if (childMatrix != null && !childMatrix.isIdentity()) { - final RectF tempRectF = mRecyclerView.mTempRectF; - tempRectF.set(out); - childMatrix.mapRect(tempRectF); - out.set( - (int) Math.floor(tempRectF.left), - (int) Math.floor(tempRectF.top), - (int) Math.ceil(tempRectF.right), - (int) Math.ceil(tempRectF.bottom) - ); - } - } - out.offset(child.getLeft(), child.getTop()); - } - - /** - * Returns the bounds of the view including its decoration and margins. - * - * @param view The view element to check - * @param outBounds A rect that will receive the bounds of the element including its - * decoration and margins. - */ - public void getDecoratedBoundsWithMargins(@NonNull View view, @NonNull Rect outBounds) { - RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); - } - - /** - * Returns the left edge of the given child view within its parent, offset by any applied - * {@link ItemDecoration ItemDecorations}. - * - * @param child Child to query - * @return Child left edge with offsets applied - * @see #getLeftDecorationWidth(View) - */ - public int getDecoratedLeft(@NonNull View child) { - return child.getLeft() - getLeftDecorationWidth(child); - } - - /** - * Returns the top edge of the given child view within its parent, offset by any applied - * {@link ItemDecoration ItemDecorations}. - * - * @param child Child to query - * @return Child top edge with offsets applied - * @see #getTopDecorationHeight(View) - */ - public int getDecoratedTop(@NonNull View child) { - return child.getTop() - getTopDecorationHeight(child); - } - - /** - * Returns the right edge of the given child view within its parent, offset by any applied - * {@link ItemDecoration ItemDecorations}. - * - * @param child Child to query - * @return Child right edge with offsets applied - * @see #getRightDecorationWidth(View) - */ - public int getDecoratedRight(@NonNull View child) { - return child.getRight() + getRightDecorationWidth(child); - } - - /** - * Returns the bottom edge of the given child view within its parent, offset by any applied - * {@link ItemDecoration ItemDecorations}. - * - * @param child Child to query - * @return Child bottom edge with offsets applied - * @see #getBottomDecorationHeight(View) - */ - public int getDecoratedBottom(@NonNull View child) { - return child.getBottom() + getBottomDecorationHeight(child); - } - - /** - * Calculates the item decor insets applied to the given child and updates the provided - * Rect instance with the inset values. - *

    - *
  • The Rect's left is set to the total width of left decorations.
  • - *
  • The Rect's top is set to the total height of top decorations.
  • - *
  • The Rect's right is set to the total width of right decorations.
  • - *
  • The Rect's bottom is set to total height of bottom decorations.
  • - *
- *

- * Note that item decorations are automatically calculated when one of the LayoutManager's - * measure child methods is called. If you need to measure the child with custom specs via - * {@link View#measure(int, int)}, you can use this method to get decorations. - * - * @param child The child view whose decorations should be calculated - * @param outRect The Rect to hold result values - */ - public void calculateItemDecorationsForChild(@NonNull View child, @NonNull Rect outRect) { - if (mRecyclerView == null) { - outRect.set(0, 0, 0, 0); - return; - } - Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); - outRect.set(insets); - } - - /** - * Returns the total height of item decorations applied to child's top. - *

- * Note that this value is not updated until the View is measured or - * {@link #calculateItemDecorationsForChild(View, Rect)} is called. - * - * @param child Child to query - * @return The total height of item decorations applied to the child's top. - * @see #getDecoratedTop(View) - * @see #calculateItemDecorationsForChild(View, Rect) - */ - public int getTopDecorationHeight(@NonNull View child) { - return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; - } - - /** - * Returns the total height of item decorations applied to child's bottom. - *

- * Note that this value is not updated until the View is measured or - * {@link #calculateItemDecorationsForChild(View, Rect)} is called. - * - * @param child Child to query - * @return The total height of item decorations applied to the child's bottom. - * @see #getDecoratedBottom(View) - * @see #calculateItemDecorationsForChild(View, Rect) - */ - public int getBottomDecorationHeight(@NonNull View child) { - return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; - } - - /** - * Returns the total width of item decorations applied to child's left. - *

- * Note that this value is not updated until the View is measured or - * {@link #calculateItemDecorationsForChild(View, Rect)} is called. - * - * @param child Child to query - * @return The total width of item decorations applied to the child's left. - * @see #getDecoratedLeft(View) - * @see #calculateItemDecorationsForChild(View, Rect) - */ - public int getLeftDecorationWidth(@NonNull View child) { - return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; - } - - /** - * Returns the total width of item decorations applied to child's right. - *

- * Note that this value is not updated until the View is measured or - * {@link #calculateItemDecorationsForChild(View, Rect)} is called. - * - * @param child Child to query - * @return The total width of item decorations applied to the child's right. - * @see #getDecoratedRight(View) - * @see #calculateItemDecorationsForChild(View, Rect) - */ - public int getRightDecorationWidth(@NonNull View child) { - return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; - } - - /** - * Called when searching for a focusable view in the given direction has failed - * for the current content of the RecyclerView. - * - *

This is the LayoutManager's opportunity to populate views in the given direction - * to fulfill the request if it can. The LayoutManager should attach and return - * the view to be focused, if a focusable view in the given direction is found. - * Otherwise, if all the existing (or the newly populated views) are unfocusable, it returns - * the next unfocusable view to become visible on the screen. This unfocusable view is - * typically the first view that's either partially or fully out of RV's padded bounded - * area in the given direction. The default implementation returns null.

- * - * @param focused The currently focused view - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * or 0 for not applicable - * @param recycler The recycler to use for obtaining views for currently offscreen items - * @param state Transient state of RecyclerView - * @return The chosen view to be focused if a focusable view is found, otherwise an - * unfocusable view to become visible onto the screen, else null. - */ - @Nullable - public View onFocusSearchFailed(@NonNull View focused, int direction, - @NonNull Recycler recycler, @NonNull State state) { - return null; - } - - /** - * This method gives a LayoutManager an opportunity to intercept the initial focus search - * before the default behavior of {@link FocusFinder} is used. If this method returns - * null FocusFinder will attempt to find a focusable child view. If it fails - * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} - * will be called to give the LayoutManager an opportunity to add new views for items - * that did not have attached views representing them. The LayoutManager should not add - * or remove views from this method. - * - * @param focused The currently focused view - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * @return A descendant view to focus or null to fall back to default behavior. - * The default implementation returns null. - */ - @Nullable - public View onInterceptFocusSearch(@NonNull View focused, int direction) { - return null; - } - - /** - * Returns the scroll amount that brings the given rect in child's coordinate system within - * the padded area of RecyclerView. - * - * @param child The direct child making the request. - * @param rect The rectangle in the child's coordinates the child - * wishes to be on the screen. - * @return The array containing the scroll amount in x and y directions that brings the - * given rect into RV's padded area. - */ - private int[] getChildRectangleOnScreenScrollAmount(View child, Rect rect) { - int[] out = new int[2]; - final int parentLeft = getPaddingLeft(); - final int parentTop = getPaddingTop(); - final int parentRight = getWidth() - getPaddingRight(); - final int parentBottom = getHeight() - getPaddingBottom(); - final int childLeft = child.getLeft() + rect.left - child.getScrollX(); - final int childTop = child.getTop() + rect.top - child.getScrollY(); - final int childRight = childLeft + rect.width(); - final int childBottom = childTop + rect.height(); - - final int offScreenLeft = Math.min(0, childLeft - parentLeft); - final int offScreenTop = Math.min(0, childTop - parentTop); - final int offScreenRight = Math.max(0, childRight - parentRight); - final int offScreenBottom = Math.max(0, childBottom - parentBottom); - - // Favor the "start" layout direction over the end when bringing one side or the other - // of a large rect into view. If we decide to bring in end because start is already - // visible, limit the scroll such that start won't go out of bounds. - final int dx; - if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) { - dx = offScreenRight != 0 ? offScreenRight - : Math.max(offScreenLeft, childRight - parentRight); - } else { - dx = offScreenLeft != 0 ? offScreenLeft - : Math.min(childLeft - parentLeft, offScreenRight); - } - - // Favor bringing the top into view over the bottom. If top is already visible and - // we should scroll to make bottom visible, make sure top does not go out of bounds. - final int dy = offScreenTop != 0 ? offScreenTop - : Math.min(childTop - parentTop, offScreenBottom); - out[0] = dx; - out[1] = dy; - return out; - } - - /** - * Called when a child of the RecyclerView wants a particular rectangle to be positioned - * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, - * android.graphics.Rect, boolean)} for more details. - * - *

The base implementation will attempt to perform a standard programmatic scroll - * to bring the given rect into view, within the padded area of the RecyclerView.

- * - * @param child The direct child making the request. - * @param rect The rectangle in the child's coordinates the child - * wishes to be on the screen. - * @param immediate True to forbid animated or delayed scrolling, - * false otherwise - * @return Whether the group scrolled to handle the operation - */ - public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, - @NonNull View child, @NonNull Rect rect, boolean immediate) { - return requestChildRectangleOnScreen(parent, child, rect, immediate, false); - } - - /** - * Requests that the given child of the RecyclerView be positioned onto the screen. This - * method can be called for both unfocusable and focusable child views. For unfocusable - * child views, focusedChildVisible is typically true in which case, layout manager - * makes the child view visible only if the currently focused child stays in-bounds of RV. - * - * @param parent The parent RecyclerView. - * @param child The direct child making the request. - * @param rect The rectangle in the child's coordinates the child - * wishes to be on the screen. - * @param immediate True to forbid animated or delayed scrolling, - * false otherwise - * @param focusedChildVisible Whether the currently focused view must stay visible. - * @return Whether the group scrolled to handle the operation - */ - public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent, - @NonNull View child, @NonNull Rect rect, boolean immediate, - boolean focusedChildVisible) { - int[] scrollAmount = getChildRectangleOnScreenScrollAmount(child, rect - ); - int dx = scrollAmount[0]; - int dy = scrollAmount[1]; - if (!focusedChildVisible || isFocusedChildVisibleAfterScrolling(parent, dx, dy)) { - if (dx != 0 || dy != 0) { - if (immediate) { - parent.scrollBy(dx, dy); - } else { - parent.smoothScrollBy(dx, dy); - } - return true; - } - } - return false; - } - - /** - * Returns whether the given child view is partially or fully visible within the padded - * bounded area of RecyclerView, depending on the input parameters. - * A view is partially visible if it has non-zero overlap with RV's padded bounded area. - * If acceptEndPointInclusion flag is set to true, it's also considered partially - * visible if it's located outside RV's bounds and it's hitting either RV's start or end - * bounds. - * - * @param child The child view to be examined. - * @param completelyVisible If true, the method returns true if and only if the - * child is - * completely visible. If false, the method returns true - * if and - * only if the child is only partially visible (that is it - * will - * return false if the child is either completely visible - * or out - * of RV's bounds). - * @param acceptEndPointInclusion If the view's endpoint intersection with RV's start of end - * bounds is enough to consider it partially visible, - * false otherwise. - * @return True if the given child is partially or fully visible, false otherwise. - */ - public boolean isViewPartiallyVisible(@NonNull View child, boolean completelyVisible, - boolean acceptEndPointInclusion) { - int boundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS - | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE); - boolean isViewFullyVisible = mHorizontalBoundCheck.isViewWithinBoundFlags(child, - boundsFlag) - && mVerticalBoundCheck.isViewWithinBoundFlags(child, boundsFlag); - if (completelyVisible) { - return isViewFullyVisible; - } else { - return !isViewFullyVisible; - } - } - - /** - * Returns whether the currently focused child stays within RV's bounds with the given - * amount of scrolling. - * - * @param parent The parent RecyclerView. - * @param dx The scrolling in x-axis direction to be performed. - * @param dy The scrolling in y-axis direction to be performed. - * @return {@code false} if the focused child is not at least partially visible after - * scrolling or no focused child exists, {@code true} otherwise. - */ - private boolean isFocusedChildVisibleAfterScrolling(RecyclerView parent, int dx, int dy) { - final View focusedChild = parent.getFocusedChild(); - if (focusedChild == null) { - return false; - } - final int parentLeft = getPaddingLeft(); - final int parentTop = getPaddingTop(); - final int parentRight = getWidth() - getPaddingRight(); - final int parentBottom = getHeight() - getPaddingBottom(); - final Rect bounds = mRecyclerView.mTempRect; - getDecoratedBoundsWithMargins(focusedChild, bounds); - - if (bounds.left - dx >= parentRight || bounds.right - dx <= parentLeft - || bounds.top - dy >= parentBottom || bounds.bottom - dy <= parentTop) { - return false; - } - return true; - } - - /** - * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} - */ - @Deprecated - public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull View child, - @Nullable View focused) { - // eat the request if we are in the middle of a scroll or layout - return isSmoothScrolling() || parent.isComputingLayout(); - } - - /** - * Called when a descendant view of the RecyclerView requests focus. - * - *

A LayoutManager wishing to keep focused views aligned in a specific - * portion of the view may implement that behavior in an override of this method.

- * - *

If the LayoutManager executes different behavior that should override the default - * behavior of scrolling the focused child on screen instead of running alongside it, - * this method should return true.

- * - * @param parent The RecyclerView hosting this LayoutManager - * @param state Current state of RecyclerView - * @param child Direct child of the RecyclerView containing the newly focused view - * @param focused The newly focused view. This may be the same view as child or it may be - * null - * @return true if the default scroll behavior should be suppressed - */ - public boolean onRequestChildFocus(@NonNull RecyclerView parent, @NonNull State state, - @NonNull View child, @Nullable View focused) { - return onRequestChildFocus(parent, child, focused); - } - - /** - * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via - * {@link RecyclerView#setAdapter(Adapter)} or - * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this - * opportunity to clear caches and configure state such that it can relayout appropriately - * with the new data and potentially new view types. - * - *

The default implementation removes all currently attached views.

- * - * @param oldAdapter The previous adapter instance. Will be null if there was previously no - * adapter. - * @param newAdapter The new adapter instance. Might be null if - * {@link RecyclerView#setAdapter(RecyclerView.Adapter)} is called with - * {@code null}. - */ - public void onAdapterChanged(@Nullable Adapter oldAdapter, @Nullable Adapter newAdapter) { - } - - /** - * Called to populate focusable views within the RecyclerView. - * - *

The LayoutManager implementation should return true if the default - * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be - * suppressed.

- * - *

The default implementation returns false to trigger RecyclerView - * to fall back to the default ViewGroup behavior.

- * - * @param recyclerView The RecyclerView hosting this LayoutManager - * @param views List of output views. This method should add valid focusable views - * to this list. - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * @param focusableMode The type of focusables to be added. - * @return true to suppress the default behavior, false to add default focusables after - * this method returns. - * @see #FOCUSABLES_ALL - * @see #FOCUSABLES_TOUCH_MODE - */ - public boolean onAddFocusables(@NonNull RecyclerView recyclerView, - @NonNull ArrayList views, int direction, int focusableMode) { - return false; - } - - /** - * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or - * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire - * data set has changed. - */ - public void onItemsChanged(@NonNull RecyclerView recyclerView) { - } - - /** - * Called when items have been added to the adapter. The LayoutManager may choose to - * requestLayout if the inserted items would require refreshing the currently visible set - * of child views. (e.g. currently empty space would be filled by appended items, etc.) - */ - public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart, - int itemCount) { - } - - /** - * Called when items have been removed from the adapter. - */ - public void onItemsRemoved(@NonNull RecyclerView recyclerView, int positionStart, - int itemCount) { - } - - /** - * Called when items have been changed in the adapter. - * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)} - * instead, then this callback will not be invoked. - */ - public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, - int itemCount) { - } - - /** - * Called when items have been changed in the adapter and with optional payload. - * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}. - */ - public void onItemsUpdated(@NonNull RecyclerView recyclerView, int positionStart, - int itemCount, @Nullable Object payload) { - onItemsUpdated(recyclerView, positionStart, itemCount); - } - - /** - * Called when an item is moved withing the adapter. - *

- * Note that, an item may also change position in response to another ADD/REMOVE/MOVE - * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} - * is called. - */ - public void onItemsMoved(@NonNull RecyclerView recyclerView, int from, int to, - int itemCount) { - - } - - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current state of RecyclerView - * @return The horizontal extent of the scrollbar's thumb - * @see RecyclerView#computeHorizontalScrollExtent() - */ - public int computeHorizontalScrollExtent(@NonNull State state) { - return 0; - } - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current State of RecyclerView where you can find total item count - * @return The horizontal offset of the scrollbar's thumb - * @see RecyclerView#computeHorizontalScrollOffset() - */ - public int computeHorizontalScrollOffset(@NonNull State state) { - return 0; - } - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeHorizontalScrollRange()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current State of RecyclerView where you can find total item count - * @return The total horizontal range represented by the vertical scrollbar - * @see RecyclerView#computeHorizontalScrollRange() - */ - public int computeHorizontalScrollRange(@NonNull State state) { - return 0; - } - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeVerticalScrollExtent()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current state of RecyclerView - * @return The vertical extent of the scrollbar's thumb - * @see RecyclerView#computeVerticalScrollExtent() - */ - public int computeVerticalScrollExtent(@NonNull State state) { - return 0; - } - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeVerticalScrollOffset()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current State of RecyclerView where you can find total item count - * @return The vertical offset of the scrollbar's thumb - * @see RecyclerView#computeVerticalScrollOffset() - */ - public int computeVerticalScrollOffset(@NonNull State state) { - return 0; - } - - /** - *

Override this method if you want to support scroll bars.

- * - *

Read {@link RecyclerView#computeVerticalScrollRange()} for details.

- * - *

Default implementation returns 0.

- * - * @param state Current State of RecyclerView where you can find total item count - * @return The total vertical range represented by the vertical scrollbar - * @see RecyclerView#computeVerticalScrollRange() - */ - public int computeVerticalScrollRange(@NonNull State state) { - return 0; - } - - /** - * Measure the attached RecyclerView. Implementations must call - * {@link #setMeasuredDimension(int, int)} before returning. - *

- * It is strongly advised to use the AutoMeasure mechanism by overriding - * {@link #isAutoMeasureEnabled()} to return true as AutoMeasure handles all the standard - * measure cases including when the RecyclerView's layout_width or layout_height have been - * set to wrap_content. If {@link #isAutoMeasureEnabled()} is overridden to return true, - * this method should not be overridden. - *

- * The default implementation will handle EXACTLY measurements and respect - * the minimum width and height properties of the host RecyclerView if measured - * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView - * will consume all available space. - * - * @param recycler Recycler - * @param state Transient state of RecyclerView - * @param widthSpec Width {@link android.view.View.MeasureSpec} - * @param heightSpec Height {@link android.view.View.MeasureSpec} - * @see #isAutoMeasureEnabled() - * @see #setMeasuredDimension(int, int) - */ - public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, - int heightSpec) { - mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); - } - - /** - * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the - * host RecyclerView. - * - * @param widthSize Measured width - * @param heightSize Measured height - */ - public void setMeasuredDimension(int widthSize, int heightSize) { - mRecyclerView.setMeasuredDimension(widthSize, heightSize); - } - - /** - * @return The host RecyclerView's {@link View#getMinimumWidth()} - */ - @Px - public int getMinimumWidth() { - return ViewCompat.getMinimumWidth(mRecyclerView); - } - - /** - * @return The host RecyclerView's {@link View#getMinimumHeight()} - */ - @Px - public int getMinimumHeight() { - return ViewCompat.getMinimumHeight(mRecyclerView); - } - - /** - *

Called when the LayoutManager should save its state. This is a good time to save your - * scroll position, configuration and anything else that may be required to restore the same - * layout state if the LayoutManager is recreated.

- *

RecyclerView does NOT verify if the LayoutManager has changed between state save and - * restore. This will let you share information between your LayoutManagers but it is also - * your responsibility to make sure they use the same parcelable class.

- * - * @return Necessary information for LayoutManager to be able to restore its state - */ - @Nullable - public Parcelable onSaveInstanceState() { - return null; - } - - /** - * Called when the RecyclerView is ready to restore the state based on a previous - * RecyclerView. - * - * Notice that this might happen after an actual layout, based on how Adapter prefers to - * restore State. See {@link Adapter#getStateRestorationPolicy()} for more information. - * - * @param state The parcelable that was returned by the previous LayoutManager's - * {@link #onSaveInstanceState()} method. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onRestoreInstanceState(Parcelable state) { - - } - - void stopSmoothScroller() { - if (mSmoothScroller != null) { - mSmoothScroller.stop(); - } - } - - void onSmoothScrollerStopped(SmoothScroller smoothScroller) { - if (mSmoothScroller == smoothScroller) { - mSmoothScroller = null; - } - } - - /** - * RecyclerView calls this method to notify LayoutManager that scroll state has changed. - * - * @param state The new scroll state for RecyclerView - */ - public void onScrollStateChanged(int state) { - } - - /** - * Removes all views and recycles them using the given recycler. - *

- * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. - *

- * If a View is marked as "ignored", it is not removed nor recycled. - * - * @param recycler Recycler to use to recycle children - * @see #removeAndRecycleView(View, Recycler) - * @see #removeAndRecycleViewAt(int, Recycler) - * @see #ignoreView(View) - */ - public void removeAndRecycleAllViews(@NonNull Recycler recycler) { - for (int i = getChildCount() - 1; i >= 0; i--) { - final View view = getChildAt(i); - if (!getChildViewHolderInt(view).shouldIgnore()) { - removeAndRecycleViewAt(i, recycler); - } - } - } - - // called by accessibility delegate - void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { - onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info); - } - - /** - * Called by the AccessibilityDelegate when the information about the current layout should - * be populated. - *

- * Default implementation adds a {@link - * androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat}. - *

- * You should override - * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, - * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, - * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and - * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for - * more accurate accessibility information. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @param info The info that should be filled by the LayoutManager - * @see View#onInitializeAccessibilityNodeInfo( - *android.view.accessibility.AccessibilityNodeInfo) - * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) - * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) - * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) - * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) - */ - public void onInitializeAccessibilityNodeInfo(@NonNull Recycler recycler, - @NonNull State state, @NonNull AccessibilityNodeInfoCompat info) { - if (mRecyclerView.canScrollVertically(-1) || mRecyclerView.canScrollHorizontally(-1)) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); - info.setScrollable(true); - } - if (mRecyclerView.canScrollVertically(1) || mRecyclerView.canScrollHorizontally(1)) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); - info.setScrollable(true); - } - final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo = - AccessibilityNodeInfoCompat.CollectionInfoCompat - .obtain(getRowCountForAccessibility(recycler, state), - getColumnCountForAccessibility(recycler, state), - isLayoutHierarchical(recycler, state), - getSelectionModeForAccessibility(recycler, state)); - info.setCollectionInfo(collectionInfo); - } - - // called by accessibility delegate - public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) { - onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); - } - - /** - * Called by the accessibility delegate to initialize an accessibility event. - *

- * Default implementation adds item count and scroll information to the event. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @param event The event instance to initialize - * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) - */ - public void onInitializeAccessibilityEvent(@NonNull Recycler recycler, @NonNull State state, - @NonNull AccessibilityEvent event) { - if (mRecyclerView == null || event == null) { - return; - } - event.setScrollable(mRecyclerView.canScrollVertically(1) - || mRecyclerView.canScrollVertically(-1) - || mRecyclerView.canScrollHorizontally(-1) - || mRecyclerView.canScrollHorizontally(1)); - - if (mRecyclerView.mAdapter != null) { - event.setItemCount(mRecyclerView.mAdapter.getItemCount()); - } - } - - // called by accessibility delegate - void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) { - final ViewHolder vh = getChildViewHolderInt(host); - // avoid trying to create accessibility node info for removed children - if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) { - onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, - mRecyclerView.mState, host, info); - } - } - - /** - * Called by the AccessibilityDelegate when the accessibility information for a specific - * item should be populated. - *

- * Default implementation adds basic positioning information about the item. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @param host The child for which accessibility node info should be populated - * @param info The info to fill out about the item - * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, - * android.view.accessibility.AccessibilityNodeInfo) - */ - public void onInitializeAccessibilityNodeInfoForItem(@NonNull Recycler recycler, - @NonNull State state, @NonNull View host, - @NonNull AccessibilityNodeInfoCompat info) { - } - - /** - * A LayoutManager can call this method to force RecyclerView to run simple animations in - * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data - * change). - *

- * Note that, calling this method will not guarantee that RecyclerView will run animations - * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will - * not run any animations but will still clear this flag after the layout is complete. - */ - public void requestSimpleAnimationsInNextLayout() { - mRequestedSimpleAnimations = true; - } - - /** - * Returns the selection mode for accessibility. Should be - * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}, - * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_SINGLE} or - * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_MULTIPLE}. - *

- * Default implementation returns - * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @return Selection mode for accessibility. Default implementation returns - * {@link AccessibilityNodeInfoCompat.CollectionInfoCompat#SELECTION_MODE_NONE}. - */ - public int getSelectionModeForAccessibility(@NonNull Recycler recycler, - @NonNull State state) { - return AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE; - } - - /** - * Returns the number of rows for accessibility. - *

- * Default implementation returns the number of items in the adapter if LayoutManager - * supports vertical scrolling or 1 if LayoutManager does not support vertical - * scrolling. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @return The number of rows in LayoutManager for accessibility. - */ - public int getRowCountForAccessibility(@NonNull Recycler recycler, @NonNull State state) { - return -1; - } - - /** - * Returns the number of columns for accessibility. - *

- * Default implementation returns the number of items in the adapter if LayoutManager - * supports horizontal scrolling or 1 if LayoutManager does not support horizontal - * scrolling. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @return The number of rows in LayoutManager for accessibility. - */ - public int getColumnCountForAccessibility(@NonNull Recycler recycler, - @NonNull State state) { - return -1; - } - - /** - * Returns whether layout is hierarchical or not to be used for accessibility. - *

- * Default implementation returns false. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @return True if layout is hierarchical. - */ - public boolean isLayoutHierarchical(@NonNull Recycler recycler, @NonNull State state) { - return false; - } - - // called by accessibility delegate - boolean performAccessibilityAction(int action, @Nullable Bundle args) { - return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, - action, args); - } - - /** - * Called by AccessibilityDelegate when an action is requested from the RecyclerView. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @param action The action to perform - * @param args Optional action arguments - * @see View#performAccessibilityAction(int, android.os.Bundle) - */ - public boolean performAccessibilityAction(@NonNull Recycler recycler, @NonNull State state, - int action, @Nullable Bundle args) { - if (mRecyclerView == null) { - return false; - } - int vScroll = 0, hScroll = 0; - int height = getHeight(); - int width = getWidth(); - Rect rect = new Rect(); - // Gets the visible rect on the screen except for the rotation or scale cases which - // might affect the result. - if (mRecyclerView.getMatrix().isIdentity() && mRecyclerView.getGlobalVisibleRect( - rect)) { - height = rect.height(); - width = rect.width(); - } - switch (action) { - case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: - if (mRecyclerView.canScrollVertically(-1)) { - vScroll = -(height - getPaddingTop() - getPaddingBottom()); - } - if (mRecyclerView.canScrollHorizontally(-1)) { - hScroll = -(width - getPaddingLeft() - getPaddingRight()); - } - break; - case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: - if (mRecyclerView.canScrollVertically(1)) { - vScroll = height - getPaddingTop() - getPaddingBottom(); - } - if (mRecyclerView.canScrollHorizontally(1)) { - hScroll = width - getPaddingLeft() - getPaddingRight(); - } - break; - } - if (vScroll == 0 && hScroll == 0) { - return false; - } - mRecyclerView.smoothScrollBy(hScroll, vScroll, null, UNDEFINED_DURATION, true); - return true; - } - - // called by accessibility delegate - boolean performAccessibilityActionForItem(@NonNull View view, int action, - @Nullable Bundle args) { - return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, - view, action, args); - } - - /** - * Called by AccessibilityDelegate when an accessibility action is requested on one of the - * children of LayoutManager. - *

- * Default implementation does not do anything. - * - * @param recycler The Recycler that can be used to convert view positions into adapter - * positions - * @param state The current state of RecyclerView - * @param view The child view on which the action is performed - * @param action The action to perform - * @param args Optional action arguments - * @return true if action is handled - * @see View#performAccessibilityAction(int, android.os.Bundle) - */ - public boolean performAccessibilityActionForItem(@NonNull Recycler recycler, - @NonNull State state, @NonNull View view, int action, @Nullable Bundle args) { - return false; - } - - /** - * Parse the xml attributes to get the most common properties used by layout managers. - * - * {@link android.R.attr#orientation} - * {@link androidx.recyclerview.R.attr#spanCount} - * {@link androidx.recyclerview.R.attr#reverseLayout} - * {@link androidx.recyclerview.R.attr#stackFromEnd} - * - * @return an object containing the properties as specified in the attrs. - */ - public static Properties getProperties(@NonNull Context context, - @Nullable AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - Properties properties = new Properties(); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, - defStyleAttr, defStyleRes); - properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation, - DEFAULT_ORIENTATION); - properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1); - properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false); - properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false); - a.recycle(); - return properties; - } - - void setExactMeasureSpecsFrom(RecyclerView recyclerView) { - setMeasureSpecs( - MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) - ); - } - - /** - * Internal API to allow LayoutManagers to be measured twice. - *

- * This is not public because LayoutManagers should be able to handle their layouts in one - * pass but it is very convenient to make existing LayoutManagers support wrapping content - * when both orientations are undefined. - *

- * This API will be removed after default LayoutManagers properly implement wrap content in - * non-scroll orientation. - */ - boolean shouldMeasureTwice() { - return false; - } - - boolean hasFlexibleChildInBothOrientations() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final ViewGroup.LayoutParams lp = child.getLayoutParams(); - if (lp.width < 0 && lp.height < 0) { - return true; - } - } - return false; - } - - /** - * Some general properties that a LayoutManager may want to use. - */ - public static class Properties { - /** {@link android.R.attr#orientation} */ - public int orientation; - /** {@link androidx.recyclerview.R.attr#spanCount} */ - public int spanCount; - /** {@link androidx.recyclerview.R.attr#reverseLayout} */ - public boolean reverseLayout; - /** {@link androidx.recyclerview.R.attr#stackFromEnd} */ - public boolean stackFromEnd; - } - } - - /** - * An ItemDecoration allows the application to add a special drawing and layout offset - * to specific item views from the adapter's data set. This can be useful for drawing dividers - * between items, highlights, visual grouping boundaries and more. - * - *

All ItemDecorations are drawn in the order they were added, before the item - * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} - * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, - * RecyclerView.State)}.

- */ - public abstract static class ItemDecoration { - /** - * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. - * Any content drawn by this method will be drawn before the item views are drawn, - * and will thus appear underneath the views. - * - * @param c Canvas to draw into - * @param parent RecyclerView this ItemDecoration is drawing into - * @param state The current state of RecyclerView - */ - public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) { - onDraw(c, parent); - } - - /** - * @deprecated Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} - */ - @Deprecated - public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) { - } - - /** - * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. - * Any content drawn by this method will be drawn after the item views are drawn - * and will thus appear over the views. - * - * @param c Canvas to draw into - * @param parent RecyclerView this ItemDecoration is drawing into - * @param state The current state of RecyclerView. - */ - public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, - @NonNull State state) { - onDrawOver(c, parent); - } - - /** - * @deprecated Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} - */ - @Deprecated - public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) { - } - - - /** - * @deprecated Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} - */ - @Deprecated - public void getItemOffsets(@NonNull Rect outRect, int itemPosition, - @NonNull RecyclerView parent) { - outRect.set(0, 0, 0, 0); - } - - /** - * Retrieve any offsets for the given item. Each field of outRect specifies - * the number of pixels that the item view should be inset by, similar to padding or margin. - * The default implementation sets the bounds of outRect to 0 and returns. - * - *

- * If this ItemDecoration does not affect the positioning of item views, it should set - * all four fields of outRect (left, top, right, bottom) to zero - * before returning. - * - *

- * If you need to access Adapter for additional data, you can call - * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the - * View. - * - * @param outRect Rect to receive the output. - * @param view The child view to decorate - * @param parent RecyclerView this ItemDecoration is decorating - * @param state The current state of RecyclerView. - */ - public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, - @NonNull RecyclerView parent, @NonNull State state) { - getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), - parent); - } - } - - /** - * An OnItemTouchListener allows the application to intercept touch events in progress at the - * view hierarchy level of the RecyclerView before those touch events are considered for - * RecyclerView's own scrolling behavior. - * - *

This can be useful for applications that wish to implement various forms of gestural - * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept - * a touch interaction already in progress even if the RecyclerView is already handling that - * gesture stream itself for the purposes of scrolling.

- * - * @see SimpleOnItemTouchListener - */ - public interface OnItemTouchListener { - /** - * Silently observe and/or take over touch events sent to the RecyclerView - * before they are handled by either the RecyclerView itself or its child views. - * - *

The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run - * in the order in which each listener was added, before any other touch processing - * by the RecyclerView itself or child views occurs.

- * - * @param e MotionEvent describing the touch event. All coordinates are in - * the RecyclerView's coordinate system. - * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false - * to continue with the current behavior and continue observing future events in - * the gesture. - */ - boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); - - /** - * Process a touch event as part of a gesture that was claimed by returning true from - * a previous call to {@link #onInterceptTouchEvent}. - * - * @param e MotionEvent describing the touch event. All coordinates are in - * the RecyclerView's coordinate system. - */ - void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); - - /** - * Called when a child of RecyclerView does not want RecyclerView and its ancestors to - * intercept touch events with - * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. - * - * @param disallowIntercept True if the child does not want the parent to - * intercept touch events. - * @see ViewParent#requestDisallowInterceptTouchEvent(boolean) - */ - void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); - } - - /** - * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies - * and default return values. - *

- * You may prefer to extend this class if you don't need to override all methods. Another - * benefit of using this class is future compatibility. As the interface may change, we'll - * always provide a default implementation on this class so that your code won't break when - * you update to a new version of the support library. - */ - public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener { - @Override - public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { - return false; - } - - @Override - public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { - } - - @Override - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - } - } - - - /** - * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event - * has occurred on that RecyclerView. - *

- * - * @see RecyclerView#addOnScrollListener(OnScrollListener) - * @see RecyclerView#clearOnChildAttachStateChangeListeners() - */ - public abstract static class OnScrollListener { - /** - * Callback method to be invoked when RecyclerView's scroll state changes. - * - * @param recyclerView The RecyclerView whose scroll state has changed. - * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, - * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. - */ - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - } - - /** - * Callback method to be invoked when the RecyclerView has been scrolled. This will be - * called after the scroll has completed. - *

- * This callback will also be called if visible item range changes after a layout - * calculation. In that case, dx and dy will be 0. - * - * @param recyclerView The RecyclerView which scrolled. - * @param dx The amount of horizontal scroll. - * @param dy The amount of vertical scroll. - */ - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - } - } - - /** - * A RecyclerListener can be set on a RecyclerView to receive messages whenever - * a view is recycled. - * - * @see RecyclerView#setRecyclerListener(RecyclerListener) - */ - public interface RecyclerListener { - - /** - * This method is called whenever the view in the ViewHolder is recycled. - * - * RecyclerView calls this method right before clearing ViewHolder's internal data and - * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information - * before being recycled, you can call {@link ViewHolder#getBindingAdapterPosition()} to get - * its adapter position. - * - * @param holder The ViewHolder containing the view that was recycled - */ - void onViewRecycled(@NonNull ViewHolder holder); - } - - /** - * A Listener interface that can be attached to a RecylcerView to get notified - * whenever a ViewHolder is attached to or detached from RecyclerView. - */ - public interface OnChildAttachStateChangeListener { - - /** - * Called when a view is attached to the RecyclerView. - * - * @param view The View which is attached to the RecyclerView - */ - void onChildViewAttachedToWindow(@NonNull View view); - - /** - * Called when a view is detached from RecyclerView. - * - * @param view The View which is being detached from the RecyclerView - */ - void onChildViewDetachedFromWindow(@NonNull View view); - } - - /** - * A ViewHolder describes an item view and metadata about its place within the RecyclerView. - * - *

{@link Adapter} implementations should subclass ViewHolder and add fields for caching - * potentially expensive {@link View#findViewById(int)} results.

- * - *

While {@link LayoutParams} belong to the {@link LayoutManager}, - * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use - * their own custom ViewHolder implementations to store data that makes binding view contents - * easier. Implementations should assume that individual item views will hold strong references - * to ViewHolder objects and that RecyclerView instances may hold - * strong references to extra off-screen item views for caching purposes

- */ - public abstract static class ViewHolder { - @NonNull - public final View itemView; - WeakReference mNestedRecyclerView; - int mPosition = NO_POSITION; - int mOldPosition = NO_POSITION; - long mItemId = NO_ID; - int mItemViewType = INVALID_TYPE; - int mPreLayoutPosition = NO_POSITION; - - // The item that this holder is shadowing during an item change event/animation - ViewHolder mShadowedHolder = null; - // The item that is shadowing this holder during an item change event/animation - ViewHolder mShadowingHolder = null; - - /** - * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType - * are all valid. - */ - static final int FLAG_BOUND = 1 << 0; - - /** - * The data this ViewHolder's view reflects is stale and needs to be rebound - * by the adapter. mPosition and mItemId are consistent. - */ - static final int FLAG_UPDATE = 1 << 1; - - /** - * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId - * are not to be trusted and may no longer match the item view type. - * This ViewHolder must be fully rebound to different data. - */ - static final int FLAG_INVALID = 1 << 2; - - /** - * This ViewHolder points at data that represents an item previously removed from the - * data set. Its view may still be used for things like outgoing animations. - */ - static final int FLAG_REMOVED = 1 << 3; - - /** - * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() - * and is intended to keep views around during animations. - */ - static final int FLAG_NOT_RECYCLABLE = 1 << 4; - - /** - * This ViewHolder is returned from scrap which means we are expecting an addView call - * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until - * the end of the layout pass and then recycled by RecyclerView if it is not added back to - * the RecyclerView. - */ - static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; - - /** - * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove - * it unless LayoutManager is replaced. - * It is still fully visible to the LayoutManager. - */ - static final int FLAG_IGNORE = 1 << 7; - - /** - * When the View is detached form the parent, we set this flag so that we can take correct - * action when we need to remove it or add it back. - */ - static final int FLAG_TMP_DETACHED = 1 << 8; - - /** - * Set when we can no longer determine the adapter position of this ViewHolder until it is - * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is - * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon - * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is - * re-calculated. - */ - static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; - - /** - * Set when a addChangePayload(null) is called - */ - static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; - - /** - * Used by ItemAnimator when a ViewHolder's position changes - */ - static final int FLAG_MOVED = 1 << 11; - - /** - * Used by ItemAnimator when a ViewHolder appears in pre-layout - */ - static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; - - static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; - - /** - * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from - * hidden list (as if it was scrap) without being recycled in between. - * - * When a ViewHolder is hidden, there are 2 paths it can be re-used: - * a) Animation ends, view is recycled and used from the recycle pool. - * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. - * - * This flag is used to represent "case b" where the ViewHolder is reused without being - * recycled (thus "bounced" from the hidden list). This state requires special handling - * because the ViewHolder must be added to pre layout maps for animations as if it was - * already there. - */ - static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; - - int mFlags; - - private static final List FULLUPDATE_PAYLOADS = Collections.emptyList(); - - List mPayloads = null; - List mUnmodifiedPayloads = null; - - private int mIsRecyclableCount = 0; - - // If non-null, view is currently considered scrap and may be reused for other data by the - // scrap container. - Recycler mScrapContainer = null; - // Keeps whether this ViewHolder lives in Change scrap or Attached scrap - boolean mInChangeScrap = false; - - // Saves isImportantForAccessibility value for the view item while it's in hidden state and - // marked as unimportant for accessibility. - private int mWasImportantForAccessibilityBeforeHidden = - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; - // set if we defer the accessibility state change of the view holder - @VisibleForTesting - int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; - - /** - * Is set when VH is bound from the adapter and cleaned right before it is sent to - * {@link RecycledViewPool}. - */ - RecyclerView mOwnerRecyclerView; - - // The last adapter that bound this ViewHolder. It is cleaned before VH is recycled. - Adapter mBindingAdapter; - - public ViewHolder(@NonNull View itemView) { - if (itemView == null) { - throw new IllegalArgumentException("itemView may not be null"); - } - this.itemView = itemView; - } - - void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { - addFlags(ViewHolder.FLAG_REMOVED); - offsetPosition(offset, applyToPreLayout); - mPosition = mNewPosition; - } - - void offsetPosition(int offset, boolean applyToPreLayout) { - if (mOldPosition == NO_POSITION) { - mOldPosition = mPosition; - } - if (mPreLayoutPosition == NO_POSITION) { - mPreLayoutPosition = mPosition; - } - if (applyToPreLayout) { - mPreLayoutPosition += offset; - } - mPosition += offset; - if (itemView.getLayoutParams() != null) { - ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; - } - } - - void clearOldPosition() { - mOldPosition = NO_POSITION; - mPreLayoutPosition = NO_POSITION; - } - - void saveOldPosition() { - if (mOldPosition == NO_POSITION) { - mOldPosition = mPosition; - } - } - - boolean shouldIgnore() { - return (mFlags & FLAG_IGNORE) != 0; - } - - /** - * @see #getLayoutPosition() - * @see #getBindingAdapterPosition() - * @see #getAbsoluteAdapterPosition() - * @deprecated This method is deprecated because its meaning is ambiguous due to the async - * handling of adapter updates. You should use {@link #getLayoutPosition()}, - * {@link #getBindingAdapterPosition()} or {@link #getAbsoluteAdapterPosition()} - * depending on your use case. - */ - @Deprecated - public final int getPosition() { - return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; - } - - /** - * Returns the position of the ViewHolder in terms of the latest layout pass. - *

- * This position is mostly used by RecyclerView components to be consistent while - * RecyclerView lazily processes adapter updates. - *

- * For performance and animation reasons, RecyclerView batches all adapter updates until the - * next layout pass. This may cause mismatches between the Adapter position of the item and - * the position it had in the latest layout calculations. - *

- * LayoutManagers should always call this method while doing calculations based on item - * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, - * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position - * of the item. - *

- * If LayoutManager needs to call an external method that requires the adapter position of - * the item, it can use {@link #getAbsoluteAdapterPosition()} or - * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. - * - * @return Returns the adapter position of the ViewHolder in the latest layout pass. - * @see #getBindingAdapterPosition() - * @see #getAbsoluteAdapterPosition() - */ - public final int getLayoutPosition() { - return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; - } - - - /** - * @return {@link #getBindingAdapterPosition()} - * @deprecated This method is confusing when adapters nest other adapters. - * If you are calling this in the context of an Adapter, you probably want to call - * {@link #getBindingAdapterPosition()} or if you want the position as {@link RecyclerView} - * sees it, you should call {@link #getAbsoluteAdapterPosition()}. - */ - @Deprecated - public final int getAdapterPosition() { - return getBindingAdapterPosition(); - } - - /** - * Returns the Adapter position of the item represented by this ViewHolder with respect to - * the {@link Adapter} that bound it. - *

- * Note that this might be different than the {@link #getLayoutPosition()} if there are - * pending adapter updates but a new layout pass has not happened yet. - *

- * RecyclerView does not handle any adapter updates until the next layout traversal. This - * may create temporary inconsistencies between what user sees on the screen and what - * adapter contents have. This inconsistency is not important since it will be less than - * 16ms but it might be a problem if you want to use ViewHolder position to access the - * adapter. Sometimes, you may need to get the exact adapter position to do - * some actions in response to user events. In that case, you should use this method which - * will calculate the Adapter position of the ViewHolder. - *

- * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the - * next layout pass, the return value of this method will be {@link #NO_POSITION}. - *

- * If the {@link Adapter} that bound this {@link ViewHolder} is inside another - * {@link Adapter} (e.g. {@link ConcatAdapter}), this position might be different than - * {@link #getAbsoluteAdapterPosition()}. If you would like to know the position that - * {@link RecyclerView} considers (e.g. for saved state), you should use - * {@link #getAbsoluteAdapterPosition()}. - * - * @return The adapter position of the item if it still exists in the adapter. - * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, - * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last - * layout pass or the ViewHolder has already been recycled. - * @see #getAbsoluteAdapterPosition() - * @see #getLayoutPosition() - */ - public final int getBindingAdapterPosition() { - if (mBindingAdapter == null) { - return NO_POSITION; - } - if (mOwnerRecyclerView == null) { - return NO_POSITION; - } - @SuppressWarnings("unchecked") - Adapter rvAdapter = mOwnerRecyclerView.getAdapter(); - if (rvAdapter == null) { - return NO_POSITION; - } - int globalPosition = mOwnerRecyclerView.getAdapterPositionInRecyclerView(this); - if (globalPosition == NO_POSITION) { - return NO_POSITION; - } - return rvAdapter.findRelativeAdapterPositionIn(mBindingAdapter, this, globalPosition); - } - - /** - * Returns the Adapter position of the item represented by this ViewHolder with respect to - * the {@link RecyclerView}'s {@link Adapter}. If the {@link Adapter} that bound this - * {@link ViewHolder} is inside another adapter (e.g. {@link ConcatAdapter}), this - * position might be different and will include - * the offsets caused by other adapters in the {@link ConcatAdapter}. - *

- * Note that this might be different than the {@link #getLayoutPosition()} if there are - * pending adapter updates but a new layout pass has not happened yet. - *

- * RecyclerView does not handle any adapter updates until the next layout traversal. This - * may create temporary inconsistencies between what user sees on the screen and what - * adapter contents have. This inconsistency is not important since it will be less than - * 16ms but it might be a problem if you want to use ViewHolder position to access the - * adapter. Sometimes, you may need to get the exact adapter position to do - * some actions in response to user events. In that case, you should use this method which - * will calculate the Adapter position of the ViewHolder. - *

- * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the - * next layout pass, the return value of this method will be {@link #NO_POSITION}. - *

- * Note that if you are querying the position as {@link RecyclerView} sees, you should use - * {@link #getAbsoluteAdapterPosition()} (e.g. you want to use it to save scroll - * state). If you are querying the position to access the {@link Adapter} contents, - * you should use {@link #getBindingAdapterPosition()}. - * - * @return The adapter position of the item from {@link RecyclerView}'s perspective if it - * still exists in the adapter and bound to a valid item. - * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, - * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last - * layout pass or the ViewHolder has already been recycled. - * @see #getBindingAdapterPosition() - * @see #getLayoutPosition() - */ - public final int getAbsoluteAdapterPosition() { - if (mOwnerRecyclerView == null) { - return NO_POSITION; - } - return mOwnerRecyclerView.getAdapterPositionInRecyclerView(this); - } - - /** - * Returns the {@link Adapter} that last bound this {@link ViewHolder}. - * Might return {@code null} if this {@link ViewHolder} is not bound to any adapter. - * - * @return The {@link Adapter} that last bound this {@link ViewHolder} or {@code null} if - * this {@link ViewHolder} is not bound by any adapter (e.g. recycled). - */ - @Nullable - public final Adapter getBindingAdapter() { - return mBindingAdapter; - } - - /** - * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders - * to perform animations. - *

- * If a ViewHolder was laid out in the previous onLayout call, old position will keep its - * adapter index in the previous layout. - * - * @return The previous adapter index of the Item represented by this ViewHolder or - * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is - * complete). - */ - public final int getOldPosition() { - return mOldPosition; - } - - /** - * Returns The itemId represented by this ViewHolder. - * - * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} - * otherwise - */ - public final long getItemId() { - return mItemId; - } - - /** - * @return The view type of this ViewHolder. - */ - public final int getItemViewType() { - return mItemViewType; - } - - boolean isScrap() { - return mScrapContainer != null; - } - - void unScrap() { - mScrapContainer.unscrapView(this); - } - - boolean wasReturnedFromScrap() { - return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; - } - - void clearReturnedFromScrapFlag() { - mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; - } - - void clearTmpDetachFlag() { - mFlags = mFlags & ~FLAG_TMP_DETACHED; - } - - void stopIgnoring() { - mFlags = mFlags & ~FLAG_IGNORE; - } - - void setScrapContainer(Recycler recycler, boolean isChangeScrap) { - mScrapContainer = recycler; - mInChangeScrap = isChangeScrap; - } - - boolean isInvalid() { - return (mFlags & FLAG_INVALID) != 0; - } - - boolean needsUpdate() { - return (mFlags & FLAG_UPDATE) != 0; - } - - boolean isBound() { - return (mFlags & FLAG_BOUND) != 0; - } - - boolean isRemoved() { - return (mFlags & FLAG_REMOVED) != 0; - } - - boolean hasAnyOfTheFlags(int flags) { - return (mFlags & flags) != 0; - } - - boolean isTmpDetached() { - return (mFlags & FLAG_TMP_DETACHED) != 0; - } - - boolean isAttachedToTransitionOverlay() { - return itemView.getParent() != null && itemView.getParent() != mOwnerRecyclerView; - } - - boolean isAdapterPositionUnknown() { - return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); - } - - void setFlags(int flags, int mask) { - mFlags = (mFlags & ~mask) | (flags & mask); - } - - void addFlags(int flags) { - mFlags |= flags; - } - - void addChangePayload(Object payload) { - if (payload == null) { - addFlags(FLAG_ADAPTER_FULLUPDATE); - } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { - createPayloadsIfNeeded(); - mPayloads.add(payload); - } - } - - private void createPayloadsIfNeeded() { - if (mPayloads == null) { - mPayloads = new ArrayList(); - mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); - } - } - - void clearPayload() { - if (mPayloads != null) { - mPayloads.clear(); - } - mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE; - } - - List getUnmodifiedPayloads() { - if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { - if (mPayloads == null || mPayloads.size() == 0) { - // Initial state, no update being called. - return FULLUPDATE_PAYLOADS; - } - // there are none-null payloads - return mUnmodifiedPayloads; - } else { - // a full update has been called. - return FULLUPDATE_PAYLOADS; - } - } - - void resetInternal() { - if (sDebugAssertionsEnabled && isTmpDetached()) { - throw new IllegalStateException("Attempting to reset temp-detached ViewHolder: " - + this + ". ViewHolders should be fully detached before resetting."); - } - - mFlags = 0; - mPosition = NO_POSITION; - mOldPosition = NO_POSITION; - mItemId = NO_ID; - mPreLayoutPosition = NO_POSITION; - mIsRecyclableCount = 0; - mShadowedHolder = null; - mShadowingHolder = null; - clearPayload(); - mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; - mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; - clearNestedRecyclerViewIfNotNested(this); - } - - /** - * Called when the child view enters the hidden state - */ - void onEnteredHiddenState(RecyclerView parent) { - // While the view item is in hidden state, make it invisible for the accessibility. - if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) { - mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState; - } else { - mWasImportantForAccessibilityBeforeHidden = - ViewCompat.getImportantForAccessibility(itemView); - } - parent.setChildImportantForAccessibilityInternal(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } - - /** - * Called when the child view leaves the hidden state - */ - void onLeftHiddenState(RecyclerView parent) { - parent.setChildImportantForAccessibilityInternal(this, - mWasImportantForAccessibilityBeforeHidden); - mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; - } - - @Override - public String toString() { - String className = - getClass().isAnonymousClass() ? "ViewHolder" : getClass().getSimpleName(); - final StringBuilder sb = new StringBuilder(className + "{" - + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId - + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); - if (isScrap()) { - sb.append(" scrap ") - .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); - } - if (isInvalid()) sb.append(" invalid"); - if (!isBound()) sb.append(" unbound"); - if (needsUpdate()) sb.append(" update"); - if (isRemoved()) sb.append(" removed"); - if (shouldIgnore()) sb.append(" ignored"); - if (isTmpDetached()) sb.append(" tmpDetached"); - if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); - if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); - - if (itemView.getParent() == null) sb.append(" no parent"); - sb.append("}"); - return sb.toString(); - } - - /** - * Informs the recycler whether this item can be recycled. Views which are not - * recyclable will not be reused for other items until setIsRecyclable() is - * later set to true. Calls to setIsRecyclable() should always be paired (one - * call to setIsRecyclabe(false) should always be matched with a later call to - * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally - * reference-counted. - * - * @param recyclable Whether this item is available to be recycled. Default value - * is true. - * @see #isRecyclable() - */ - public final void setIsRecyclable(boolean recyclable) { - mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; - if (mIsRecyclableCount < 0) { - mIsRecyclableCount = 0; - if (sDebugAssertionsEnabled) { - throw new RuntimeException("isRecyclable decremented below 0: " - + "unmatched pair of setIsRecyable() calls for " + this); - } - Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " - + "unmatched pair of setIsRecyable() calls for " + this); - } else if (!recyclable && mIsRecyclableCount == 1) { - mFlags |= FLAG_NOT_RECYCLABLE; - } else if (recyclable && mIsRecyclableCount == 0) { - mFlags &= ~FLAG_NOT_RECYCLABLE; - } - if (sVerboseLoggingEnabled) { - Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); - } - } - - /** - * @return true if this item is available to be recycled, false otherwise. - * @see #setIsRecyclable(boolean) - */ - public final boolean isRecyclable() { - return (mFlags & FLAG_NOT_RECYCLABLE) == 0 - && !ViewCompat.hasTransientState(itemView); - } - - /** - * Returns whether we have animations referring to this view holder or not. - * This is similar to isRecyclable flag but does not check transient state. - */ - boolean shouldBeKeptAsChild() { - return (mFlags & FLAG_NOT_RECYCLABLE) != 0; - } - - /** - * @return True if ViewHolder is not referenced by RecyclerView animations but has - * transient state which will prevent it from being recycled. - */ - boolean doesTransientStatePreventRecycling() { - return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView); - } - - boolean isUpdated() { - return (mFlags & FLAG_UPDATE) != 0; - } - } - - /** - * This method is here so that we can control the important for a11y changes and test it. - */ - @VisibleForTesting - boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, - int importantForAccessibility) { - if (isComputingLayout()) { - viewHolder.mPendingAccessibilityState = importantForAccessibility; - mPendingAccessibilityImportanceChange.add(viewHolder); - return false; - } - ViewCompat.setImportantForAccessibility(viewHolder.itemView, importantForAccessibility); - return true; - } - - void dispatchPendingImportantForAccessibilityChanges() { - for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) { - ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); - if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { - continue; - } - int state = viewHolder.mPendingAccessibilityState; - if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { - //noinspection WrongConstant - ViewCompat.setImportantForAccessibility(viewHolder.itemView, state); - viewHolder.mPendingAccessibilityState = - ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; - } - } - mPendingAccessibilityImportanceChange.clear(); - } - - int getAdapterPositionInRecyclerView(ViewHolder viewHolder) { - if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID - | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) - || !viewHolder.isBound()) { - return RecyclerView.NO_POSITION; - } - return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); - } - - @VisibleForTesting - void initFastScroller(StateListDrawable verticalThumbDrawable, - Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, - Drawable horizontalTrackDrawable) { - if (verticalThumbDrawable == null || verticalTrackDrawable == null - || horizontalThumbDrawable == null || horizontalTrackDrawable == null) { - throw new IllegalArgumentException( - "Trying to set fast scroller without both required drawables." - + exceptionLabel()); - } - - Resources resources = getContext().getResources(); - new FastScroller(this, verticalThumbDrawable, verticalTrackDrawable, - horizontalThumbDrawable, horizontalTrackDrawable, - resources.getDimensionPixelSize(R.dimen.fastscroll_default_thickness), - resources.getDimensionPixelSize(R.dimen.fastscroll_minimum_range), - resources.getDimensionPixelOffset(R.dimen.fastscroll_margin)); - } - - // NestedScrollingChild - - @Override - public void setNestedScrollingEnabled(boolean enabled) { - getScrollingChildHelper().setNestedScrollingEnabled(enabled); - } - - @Override - public boolean isNestedScrollingEnabled() { - return getScrollingChildHelper().isNestedScrollingEnabled(); - } - - @Override - public boolean startNestedScroll(int axes) { - return getScrollingChildHelper().startNestedScroll(axes); - } - - @Override - public boolean startNestedScroll(int axes, int type) { - return getScrollingChildHelper().startNestedScroll(axes, type); - } - - @Override - public void stopNestedScroll() { - getScrollingChildHelper().stopNestedScroll(); - } - - @Override - public void stopNestedScroll(int type) { - getScrollingChildHelper().stopNestedScroll(type); - } - - @Override - public boolean hasNestedScrollingParent() { - return getScrollingChildHelper().hasNestedScrollingParent(); - } - - @Override - public boolean hasNestedScrollingParent(int type) { - return getScrollingChildHelper().hasNestedScrollingParent(type); - } - - @Override - public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed, int[] offsetInWindow) { - return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed, int[] offsetInWindow, int type) { - return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed, offsetInWindow, type); - } - - @Override - public final void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed, int[] offsetInWindow, int type, @NonNull int[] consumed) { - getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, - dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed); - } - - @Override - public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { - return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, - int type) { - return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, - type); - } - - @Override - public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { - return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); - } - - @Override - public boolean dispatchNestedPreFling(float velocityX, float velocityY) { - return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); - } - - /** - * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of - * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged - * to create their own subclass of this LayoutParams class - * to store any additional required per-child view metadata about the layout. - */ - public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { - ViewHolder mViewHolder; - final Rect mDecorInsets = new Rect(); - boolean mInsetsDirty = true; - // Flag is set to true if the view is bound while it is detached from RV. - // In this case, we need to manually call invalidate after view is added to guarantee that - // invalidation is populated through the View hierarchy - boolean mPendingInvalidate = false; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(LayoutParams source) { - super((ViewGroup.LayoutParams) source); - } - - /** - * Returns true if the view this LayoutParams is attached to needs to have its content - * updated from the corresponding adapter. - * - * @return true if the view should have its content updated - */ - public boolean viewNeedsUpdate() { - return mViewHolder.needsUpdate(); - } - - /** - * Returns true if the view this LayoutParams is attached to is now representing - * potentially invalid data. A LayoutManager should scrap/recycle it. - * - * @return true if the view is invalid - */ - public boolean isViewInvalid() { - return mViewHolder.isInvalid(); - } - - /** - * Returns true if the adapter data item corresponding to the view this LayoutParams - * is attached to has been removed from the data set. A LayoutManager may choose to - * treat it differently in order to animate its outgoing or disappearing state. - * - * @return true if the item the view corresponds to was removed from the data set - */ - public boolean isItemRemoved() { - return mViewHolder.isRemoved(); - } - - /** - * Returns true if the adapter data item corresponding to the view this LayoutParams - * is attached to has been changed in the data set. A LayoutManager may choose to - * treat it differently in order to animate its changing state. - * - * @return true if the item the view corresponds to was changed in the data set - */ - public boolean isItemChanged() { - return mViewHolder.isUpdated(); - } - - /** - * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} - */ - @Deprecated - public int getViewPosition() { - return mViewHolder.getPosition(); - } - - /** - * Returns the adapter position that the view this LayoutParams is attached to corresponds - * to as of latest layout calculation. - * - * @return the adapter position this view as of latest layout pass - */ - public int getViewLayoutPosition() { - return mViewHolder.getLayoutPosition(); - } - - /** - * @deprecated This method is confusing when nested adapters are used. - * If you are calling from the context of an {@link Adapter}, - * use {@link #getBindingAdapterPosition()}. If you need the position that - * {@link RecyclerView} sees, use {@link #getAbsoluteAdapterPosition()}. - */ - @Deprecated - public int getViewAdapterPosition() { - return mViewHolder.getBindingAdapterPosition(); - } - - /** - * Returns the up-to-date adapter position that the view this LayoutParams is attached to - * corresponds to in the {@link RecyclerView}. If the {@link RecyclerView} has an - * {@link Adapter} that merges other adapters, this position will be with respect to the - * adapter that is assigned to the {@link RecyclerView}. - * - * @return the up-to-date adapter position this view with respect to the RecyclerView. It - * may return {@link RecyclerView#NO_POSITION} if item represented by this View has been - * removed or - * its up-to-date position cannot be calculated. - */ - public int getAbsoluteAdapterPosition() { - return mViewHolder.getAbsoluteAdapterPosition(); - } - - /** - * Returns the up-to-date adapter position that the view this LayoutParams is attached to - * corresponds to with respect to the {@link Adapter} that bound this View. - * - * @return the up-to-date adapter position this view relative to the {@link Adapter} that - * bound this View. It may return {@link RecyclerView#NO_POSITION} if item represented by - * this View has been removed or its up-to-date position cannot be calculated. - */ - public int getBindingAdapterPosition() { - return mViewHolder.getBindingAdapterPosition(); - } - } - - /** - * Observer base class for watching changes to an {@link Adapter}. - * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. - */ - public abstract static class AdapterDataObserver { - public void onChanged() { - // Do nothing - } - - public void onItemRangeChanged(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { - // fallback to onItemRangeChanged(positionStart, itemCount) if app - // does not override this method. - onItemRangeChanged(positionStart, itemCount); - } - - public void onItemRangeInserted(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeRemoved(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - // do nothing - } - - /** - * Called when the {@link Adapter.StateRestorationPolicy} of the {@link Adapter} changed. - * When this method is called, the Adapter might be ready to restore its state if it has - * not already been restored. - * - * @see Adapter#getStateRestorationPolicy() - * @see Adapter#setStateRestorationPolicy(Adapter.StateRestorationPolicy) - */ - public void onStateRestorationPolicyChanged() { - // do nothing - } - } - - /** - * Base class for smooth scrolling. Handles basic tracking of the target view position and - * provides methods to trigger a programmatic scroll. - * - *

An instance of SmoothScroller is only intended to be used once. You should create a new - * instance for each call to {@link LayoutManager#startSmoothScroll(SmoothScroller)}. - * - * @see LinearSmoothScroller - */ - public abstract static class SmoothScroller { - - private int mTargetPosition = RecyclerView.NO_POSITION; - - private RecyclerView mRecyclerView; - - private LayoutManager mLayoutManager; - - private boolean mPendingInitialRun; - - private boolean mRunning; - - private View mTargetView; - - private final Action mRecyclingAction; - - private boolean mStarted; - - public SmoothScroller() { - mRecyclingAction = new Action(0, 0); - } - - /** - * Starts a smooth scroll for the given target position. - *

In each animation step, {@link RecyclerView} will check - * for the target view and call either - * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or - * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until - * SmoothScroller is stopped.

- * - *

Note that if RecyclerView finds the target view, it will automatically stop the - * SmoothScroller. This does not mean that scroll will stop, it only means it will - * stop calling SmoothScroller in each animation step.

- */ - void start(RecyclerView recyclerView, LayoutManager layoutManager) { - - // Stop any previous ViewFlinger animations now because we are about to start a new one. - recyclerView.mViewFlinger.stop(); - - if (mStarted) { - Log.w(TAG, "An instance of " + this.getClass().getSimpleName() + " was started " - + "more than once. Each instance of" + this.getClass().getSimpleName() + " " - + "is intended to only be used once. You should create a new instance for " - + "each use."); - } - - mRecyclerView = recyclerView; - mLayoutManager = layoutManager; - if (mTargetPosition == RecyclerView.NO_POSITION) { - throw new IllegalArgumentException("Invalid target position"); - } - mRecyclerView.mState.mTargetPosition = mTargetPosition; - mRunning = true; - mPendingInitialRun = true; - mTargetView = findViewByPosition(getTargetPosition()); - onStart(); - mRecyclerView.mViewFlinger.postOnAnimation(); - - mStarted = true; - } - - public void setTargetPosition(int targetPosition) { - mTargetPosition = targetPosition; - } - - /** - * Compute the scroll vector for a given target position. - *

- * This method can return null if the layout manager cannot calculate a scroll vector - * for the given position (e.g. it has no current scroll position). - * - * @param targetPosition the position to which the scroller is scrolling - * @return the scroll vector for a given target position - */ - @Nullable - public PointF computeScrollVectorForPosition(int targetPosition) { - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager instanceof ScrollVectorProvider) { - return ((ScrollVectorProvider) layoutManager) - .computeScrollVectorForPosition(targetPosition); - } - Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager" - + " does not implement " + ScrollVectorProvider.class.getCanonicalName()); - return null; - } - - /** - * @return The LayoutManager to which this SmoothScroller is attached. Will return - * null after the SmoothScroller is stopped. - */ - @Nullable - public LayoutManager getLayoutManager() { - return mLayoutManager; - } - - /** - * Stops running the SmoothScroller in each animation callback. Note that this does not - * cancel any existing {@link Action} updated by - * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or - * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. - */ - protected final void stop() { - if (!mRunning) { - return; - } - mRunning = false; - onStop(); - mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; - mTargetView = null; - mTargetPosition = RecyclerView.NO_POSITION; - mPendingInitialRun = false; - // trigger a cleanup - mLayoutManager.onSmoothScrollerStopped(this); - // clear references to avoid any potential leak by a custom smooth scroller - mLayoutManager = null; - mRecyclerView = null; - } - - /** - * Returns true if SmoothScroller has been started but has not received the first - * animation - * callback yet. - * - * @return True if this SmoothScroller is waiting to start - */ - public boolean isPendingInitialRun() { - return mPendingInitialRun; - } - - - /** - * @return True if SmoothScroller is currently active - */ - public boolean isRunning() { - return mRunning; - } - - /** - * Returns the adapter position of the target item - * - * @return Adapter position of the target item or - * {@link RecyclerView#NO_POSITION} if no target view is set. - */ - public int getTargetPosition() { - return mTargetPosition; - } - - void onAnimation(int dx, int dy) { - final RecyclerView recyclerView = mRecyclerView; - if (mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) { - stop(); - } - - // The following if block exists to have the LayoutManager scroll 1 pixel in the correct - // direction in order to cause the LayoutManager to draw two pages worth of views so - // that the target view may be found before scrolling any further. This is done to - // prevent an initial scroll distance from scrolling past the view, which causes a - // jittery looking animation. - if (mPendingInitialRun && mTargetView == null && mLayoutManager != null) { - PointF pointF = computeScrollVectorForPosition(mTargetPosition); - if (pointF != null && (pointF.x != 0 || pointF.y != 0)) { - recyclerView.scrollStep( - (int) Math.signum(pointF.x), - (int) Math.signum(pointF.y), - null); - } - } - - mPendingInitialRun = false; - - if (mTargetView != null) { - // verify target position - if (getChildPosition(mTargetView) == mTargetPosition) { - onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction); - mRecyclingAction.runIfNecessary(recyclerView); - stop(); - } else { - Log.e(TAG, "Passed over target position while smooth scrolling."); - mTargetView = null; - } - } - if (mRunning) { - onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction); - boolean hadJumpTarget = mRecyclingAction.hasJumpTarget(); - mRecyclingAction.runIfNecessary(recyclerView); - if (hadJumpTarget) { - // It is not stopped so needs to be restarted - if (mRunning) { - mPendingInitialRun = true; - recyclerView.mViewFlinger.postOnAnimation(); - } - } - } - } - - /** - * @see RecyclerView#getChildLayoutPosition(android.view.View) - */ - public int getChildPosition(View view) { - return mRecyclerView.getChildLayoutPosition(view); - } - - /** - * @see RecyclerView.LayoutManager#getChildCount() - */ - public int getChildCount() { - return mRecyclerView.mLayout.getChildCount(); - } - - /** - * @see RecyclerView.LayoutManager#findViewByPosition(int) - */ - public View findViewByPosition(int position) { - return mRecyclerView.mLayout.findViewByPosition(position); - } - - /** - * @see RecyclerView#scrollToPosition(int) - * @deprecated Use {@link Action#jumpTo(int)}. - */ - @Deprecated - public void instantScrollToPosition(int position) { - mRecyclerView.scrollToPosition(position); - } - - protected void onChildAttachedToWindow(View child) { - if (getChildPosition(child) == getTargetPosition()) { - mTargetView = child; - if (sVerboseLoggingEnabled) { - Log.d(TAG, "smooth scroll target view has been attached"); - } - } - } - - /** - * Normalizes the vector. - * - * @param scrollVector The vector that points to the target scroll position - */ - protected void normalize(@NonNull PointF scrollVector) { - final float magnitude = (float) Math.sqrt(scrollVector.x * scrollVector.x - + scrollVector.y * scrollVector.y); - scrollVector.x /= magnitude; - scrollVector.y /= magnitude; - } - - /** - * Called when smooth scroll is started. This might be a good time to do setup. - */ - protected abstract void onStart(); - - /** - * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. - * - * @see #stop() - */ - protected abstract void onStop(); - - /** - *

RecyclerView will call this method each time it scrolls until it can find the target - * position in the layout.

- *

SmoothScroller should check dx, dy and if scroll should be changed, update the - * provided {@link Action} to define the next scroll.

- * - * @param dx Last scroll amount horizontally - * @param dy Last scroll amount vertically - * @param state Transient state of RecyclerView - * @param action If you want to trigger a new smooth scroll and cancel the previous one, - * update this object. - */ - protected abstract void onSeekTargetStep(@Px int dx, @Px int dy, @NonNull State state, - @NonNull Action action); - - /** - * Called when the target position is laid out. This is the last callback SmoothScroller - * will receive and it should update the provided {@link Action} to define the scroll - * details towards the target view. - * - * @param targetView The view element which render the target position. - * @param state Transient state of RecyclerView - * @param action Action instance that you should update to define final scroll action - * towards the targetView - */ - protected abstract void onTargetFound(@NonNull View targetView, @NonNull State state, - @NonNull Action action); - - /** - * Holds information about a smooth scroll request by a {@link SmoothScroller}. - */ - public static class Action { - - public static final int UNDEFINED_DURATION = RecyclerView.UNDEFINED_DURATION; - - private int mDx; - - private int mDy; - - private int mDuration; - - private int mJumpToPosition = NO_POSITION; - - private Interpolator mInterpolator; - - private boolean mChanged = false; - - // we track this variable to inform custom implementer if they are updating the action - // in every animation callback - private int mConsecutiveUpdates = 0; - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - */ - public Action(@Px int dx, @Px int dy) { - this(dx, dy, UNDEFINED_DURATION, null); - } - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - */ - public Action(@Px int dx, @Px int dy, int duration) { - this(dx, dy, duration, null); - } - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - * @param interpolator Interpolator to be used when calculating scroll position in each - * animation step - */ - public Action(@Px int dx, @Px int dy, int duration, - @Nullable Interpolator interpolator) { - mDx = dx; - mDy = dy; - mDuration = duration; - mInterpolator = interpolator; - } - - /** - * Instead of specifying pixels to scroll, use the target position to jump using - * {@link RecyclerView#scrollToPosition(int)}. - *

- * You may prefer using this method if scroll target is really far away and you prefer - * to jump to a location and smooth scroll afterwards. - *

- * Note that calling this method takes priority over other update methods such as - * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)}, - * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call - * {@link #jumpTo(int)}, the other changes will not be considered for this animation - * frame. - * - * @param targetPosition The target item position to scroll to using instant scrolling. - */ - public void jumpTo(int targetPosition) { - mJumpToPosition = targetPosition; - } - - boolean hasJumpTarget() { - return mJumpToPosition >= 0; - } - - void runIfNecessary(RecyclerView recyclerView) { - if (mJumpToPosition >= 0) { - final int position = mJumpToPosition; - mJumpToPosition = NO_POSITION; - recyclerView.jumpToPositionForSmoothScroller(position); - mChanged = false; - return; - } - if (mChanged) { - validate(); - recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator); - mConsecutiveUpdates++; - if (mConsecutiveUpdates > 10) { - // A new action is being set in every animation step. This looks like a bad - // implementation. Inform developer. - Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" - + " you are not changing it unless necessary"); - } - mChanged = false; - } else { - mConsecutiveUpdates = 0; - } - } - - private void validate() { - if (mInterpolator != null && mDuration < 1) { - throw new IllegalStateException("If you provide an interpolator, you must" - + " set a positive duration"); - } else if (mDuration < 1) { - throw new IllegalStateException("Scroll duration must be a positive number"); - } - } - - @Px - public int getDx() { - return mDx; - } - - public void setDx(@Px int dx) { - mChanged = true; - mDx = dx; - } - - @Px - public int getDy() { - return mDy; - } - - public void setDy(@Px int dy) { - mChanged = true; - mDy = dy; - } - - public int getDuration() { - return mDuration; - } - - public void setDuration(int duration) { - mChanged = true; - mDuration = duration; - } - - @Nullable - public Interpolator getInterpolator() { - return mInterpolator; - } - - /** - * Sets the interpolator to calculate scroll steps - * - * @param interpolator The interpolator to use. If you specify an interpolator, you must - * also set the duration. - * @see #setDuration(int) - */ - public void setInterpolator(@Nullable Interpolator interpolator) { - mChanged = true; - mInterpolator = interpolator; - } - - /** - * Updates the action with given parameters. - * - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - * @param interpolator Interpolator to be used when calculating scroll position in each - * animation step - */ - public void update(@Px int dx, @Px int dy, int duration, - @Nullable Interpolator interpolator) { - mDx = dx; - mDy = dy; - mDuration = duration; - mInterpolator = interpolator; - mChanged = true; - } - } - - /** - * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} - * to provide a hint to a {@link SmoothScroller} about the location of the target position. - */ - public interface ScrollVectorProvider { - /** - * Should calculate the vector that points to the direction where the target position - * can be found. - *

- * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards - * the target position. - *

- * The magnitude of the vector is not important. It is always normalized before being - * used by the {@link LinearSmoothScroller}. - *

- * LayoutManager should not check whether the position exists in the adapter or not. - * - * @param targetPosition the target position to which the returned vector should point - * @return the scroll vector for a given position. - */ - @Nullable - PointF computeScrollVectorForPosition(int targetPosition); - } - } - - static class AdapterDataObservable extends Observable { - public boolean hasObservers() { - return !mObservers.isEmpty(); - } - - public void notifyChanged() { - // since onChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onChanged(); - } - } - - public void notifyStateRestorationPolicyChanged() { - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onStateRestorationPolicyChanged(); - } - } - - public void notifyItemRangeChanged(int positionStart, int itemCount) { - notifyItemRangeChanged(positionStart, itemCount, null); - } - - public void notifyItemRangeChanged(int positionStart, int itemCount, - @Nullable Object payload) { - // since onItemRangeChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); - } - } - - public void notifyItemRangeInserted(int positionStart, int itemCount) { - // since onItemRangeInserted() is implemented by the app, it could do anything, - // including removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeInserted(positionStart, itemCount); - } - } - - public void notifyItemRangeRemoved(int positionStart, int itemCount) { - // since onItemRangeRemoved() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); - } - } - - public void notifyItemMoved(int fromPosition, int toPosition) { - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); - } - } - } - - /** - * This is public so that the CREATOR can be accessed on cold launch. - * - * @hide - */ - @RestrictTo(LIBRARY) - public static class SavedState extends AbsSavedState { - - Parcelable mLayoutState; - - /** - * called by CREATOR - */ - @SuppressWarnings("deprecation") - SavedState(Parcel in, ClassLoader loader) { - super(in, loader); - mLayoutState = in.readParcelable( - loader != null ? loader : LayoutManager.class.getClassLoader()); - } - - /** - * Called by onSaveInstanceState - */ - SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeParcelable(mLayoutState, 0); - } - - void copyFrom(SavedState other) { - mLayoutState = other.mLayoutState; - } - - public static final Creator CREATOR = new ClassLoaderCreator() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in, null); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - *

Contains useful information about the current RecyclerView state like target scroll - * position or view focus. State object can also keep arbitrary data, identified by resource - * ids.

- *

Often times, RecyclerView components will need to pass information between each other. - * To provide a well defined data bus between components, RecyclerView passes the same State - * object to component callbacks and these components can use it to exchange data.

- *

If you implement custom components, you can use State's put/get/remove methods to pass - * data between your components without needing to manage their lifecycles.

- */ - public static class State { - static final int STEP_START = 1; - static final int STEP_LAYOUT = 1 << 1; - static final int STEP_ANIMATIONS = 1 << 2; - - void assertLayoutStep(int accepted) { - if ((accepted & mLayoutStep) == 0) { - throw new IllegalStateException("Layout state should be one of " - + Integer.toBinaryString(accepted) + " but it is " - + Integer.toBinaryString(mLayoutStep)); - } - } - - - /** Owned by SmoothScroller */ - int mTargetPosition = RecyclerView.NO_POSITION; - - private SparseArray mData; - - //////////////////////////////////////////////////////////////////////////////////////////// - // Fields below are carried from one layout pass to the next - //////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Number of items adapter had in the previous layout. - */ - int mPreviousLayoutItemCount = 0; - - /** - * Number of items that were NOT laid out but has been deleted from the adapter after the - * previous layout. - */ - int mDeletedInvisibleItemCountSincePreviousLayout = 0; - - //////////////////////////////////////////////////////////////////////////////////////////// - // Fields below must be updated or cleared before they are used (generally before a pass) - //////////////////////////////////////////////////////////////////////////////////////////// - - @IntDef(flag = true, value = { - STEP_START, STEP_LAYOUT, STEP_ANIMATIONS - }) - @Retention(RetentionPolicy.SOURCE) - @interface LayoutState { - } - - @LayoutState - int mLayoutStep = STEP_START; - - /** - * Number of items adapter has. - */ - int mItemCount = 0; - - boolean mStructureChanged = false; - - /** - * True if the associated {@link RecyclerView} is in the pre-layout step where it is having - * its {@link LayoutManager} layout items where they will be at the beginning of a set of - * predictive item animations. - */ - boolean mInPreLayout = false; - - boolean mTrackOldChangeHolders = false; - - boolean mIsMeasuring = false; - - //////////////////////////////////////////////////////////////////////////////////////////// - // Fields below are always reset outside of the pass (or passes) that use them - //////////////////////////////////////////////////////////////////////////////////////////// - - boolean mRunSimpleAnimations = false; - - boolean mRunPredictiveAnimations = false; - - /** - * This data is saved before a layout calculation happens. After the layout is finished, - * if the previously focused view has been replaced with another view for the same item, we - * move the focus to the new item automatically. - */ - int mFocusedItemPosition; - long mFocusedItemId; - // when a sub child has focus, record its id and see if we can directly request focus on - // that one instead - int mFocusedSubChildId; - - int mRemainingScrollHorizontal; - int mRemainingScrollVertical; - - //////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially - * prior to any layout passes. - * - *

Don't touch any state stored between layout passes, only reset per-layout state, so - * that Recycler#getViewForPosition() can function safely.

- */ - void prepareForNestedPrefetch(Adapter adapter) { - mLayoutStep = STEP_START; - mItemCount = adapter.getItemCount(); - mInPreLayout = false; - mTrackOldChangeHolders = false; - mIsMeasuring = false; - } - - /** - * Returns true if the RecyclerView is currently measuring the layout. This value is - * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView - * has non-exact measurement specs. - *

- * Note that if the LayoutManager supports predictive animations and it is calculating the - * pre-layout step, this value will be {@code false} even if the RecyclerView is in - * {@code onMeasure} call. This is because pre-layout means the previous state of the - * RecyclerView and measurements made for that state cannot change the RecyclerView's size. - * LayoutManager is always guaranteed to receive another call to - * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. - * - * @return True if the RecyclerView is currently calculating its bounds, false otherwise. - */ - public boolean isMeasuring() { - return mIsMeasuring; - } - - /** - * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its - * {@link LayoutManager} layout items where they will be at the beginning of a set of - * predictive item animations. - */ - public boolean isPreLayout() { - return mInPreLayout; - } - - /** - * Returns whether RecyclerView will run predictive animations in this layout pass - * or not. - * - * @return true if RecyclerView is calculating predictive animations to be run at the end - * of the layout pass. - */ - public boolean willRunPredictiveAnimations() { - return mRunPredictiveAnimations; - } - - /** - * Returns whether RecyclerView will run simple animations in this layout pass - * or not. - * - * @return true if RecyclerView is calculating simple animations to be run at the end of - * the layout pass. - */ - public boolean willRunSimpleAnimations() { - return mRunSimpleAnimations; - } - - /** - * Removes the mapping from the specified id, if there was any. - * - * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to - * preserve cross functionality and avoid conflicts. - */ - public void remove(int resourceId) { - if (mData == null) { - return; - } - mData.remove(resourceId); - } - - /** - * Gets the Object mapped from the specified id, or null - * if no such data exists. - * - * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* - * to - * preserve cross functionality and avoid conflicts. - */ - @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"}) - public T get(int resourceId) { - if (mData == null) { - return null; - } - return (T) mData.get(resourceId); - } - - /** - * Adds a mapping from the specified id to the specified value, replacing the previous - * mapping from the specified key if there was one. - * - * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to - * preserve cross functionality and avoid conflicts. - * @param data The data you want to associate with the resourceId. - */ - public void put(int resourceId, Object data) { - if (mData == null) { - mData = new SparseArray(); - } - mData.put(resourceId, data); - } - - /** - * If scroll is triggered to make a certain item visible, this value will return the - * adapter index of that item. - * - * @return Adapter index of the target item or - * {@link RecyclerView#NO_POSITION} if there is no target - * position. - */ - public int getTargetScrollPosition() { - return mTargetPosition; - } - - /** - * Returns if current scroll has a target position. - * - * @return true if scroll is being triggered to make a certain position visible - * @see #getTargetScrollPosition() - */ - public boolean hasTargetScrollPosition() { - return mTargetPosition != RecyclerView.NO_POSITION; - } - - /** - * @return true if the structure of the data set has changed since the last call to - * onLayoutChildren, false otherwise - */ - public boolean didStructureChange() { - return mStructureChanged; - } - - /** - * Returns the total number of items that can be laid out. Note that this number is not - * necessarily equal to the number of items in the adapter, so you should always use this - * number for your position calculations and never access the adapter directly. - *

- * RecyclerView listens for Adapter's notify events and calculates the effects of adapter - * data changes on existing Views. These calculations are used to decide which animations - * should be run. - *

- * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to - * present the correct state to LayoutManager in pre-layout pass. - *

- * For example, a newly added item is not included in pre-layout item count because - * pre-layout reflects the contents of the adapter before the item is added. Behind the - * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that - * LayoutManager does not know about the new item's existence in pre-layout. The item will - * be available in second layout pass and will be included in the item count. Similar - * adjustments are made for moved and removed items as well. - *

- * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. - * - * @return The number of items currently available - * @see LayoutManager#getItemCount() - */ - public int getItemCount() { - return mInPreLayout - ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) - : mItemCount; - } - - /** - * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/ - * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is - * other than {@link #SCROLL_STATE_SETTLING}. - * - * @return Remaining horizontal scroll distance - */ - public int getRemainingScrollHorizontal() { - return mRemainingScrollHorizontal; - } - - /** - * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/ - * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is - * other than {@link #SCROLL_STATE_SETTLING}. - * - * @return Remaining vertical scroll distance - */ - public int getRemainingScrollVertical() { - return mRemainingScrollVertical; - } - - @Override - public String toString() { - return "State{" - + "mTargetPosition=" + mTargetPosition - + ", mData=" + mData - + ", mItemCount=" + mItemCount - + ", mIsMeasuring=" + mIsMeasuring - + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount - + ", mDeletedInvisibleItemCountSincePreviousLayout=" - + mDeletedInvisibleItemCountSincePreviousLayout - + ", mStructureChanged=" + mStructureChanged - + ", mInPreLayout=" + mInPreLayout - + ", mRunSimpleAnimations=" + mRunSimpleAnimations - + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations - + '}'; - } - } - - /** - * This class defines the behavior of fling if the developer wishes to handle it. - *

- * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. - * - * @see #setOnFlingListener(OnFlingListener) - */ - public abstract static class OnFlingListener { - - /** - * Override this to handle a fling given the velocities in both x and y directions. - * Note that this method will only be called if the associated {@link LayoutManager} - * supports scrolling and the fling is not handled by nested scrolls first. - * - * @param velocityX the fling velocity on the X axis - * @param velocityY the fling velocity on the Y axis - * @return true if the fling was handled, false otherwise. - */ - public abstract boolean onFling(int velocityX, int velocityY); - } - - /** - * Internal listener that manages items after animations finish. This is how items are - * retained (not recycled) during animations, but allowed to be recycled afterwards. - * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() - * method on the animator's listener when it is done animating any item. - */ - private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { - - ItemAnimatorRestoreListener() { - } - - @Override - public void onAnimationFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh - item.mShadowedHolder = null; - } - // always null this because an OldViewHolder can never become NewViewHolder w/o being - // recycled. - item.mShadowingHolder = null; - if (!item.shouldBeKeptAsChild()) { - if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { - removeDetachedView(item.itemView, false); - } - } - } - } - - /** - * This class defines the animations that take place on items as changes are made - * to the adapter. - * - * Subclasses of ItemAnimator can be used to implement custom animations for actions on - * ViewHolder items. The RecyclerView will manage retaining these items while they - * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} - * when a ViewHolder's animation is finished. In other words, there must be a matching - * {@link #dispatchAnimationFinished(ViewHolder)} call for each - * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, - * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()} - * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, - * and - * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateDisappearance()} call. - * - *

By default, RecyclerView uses {@link DefaultItemAnimator}.

- * - * @see #setItemAnimator(ItemAnimator) - */ - @SuppressWarnings("UnusedParameters") - public abstract static class ItemAnimator { - - /** - * The Item represented by this ViewHolder is updated. - *

- * - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - */ - public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; - - /** - * The Item represented by this ViewHolder is removed from the adapter. - *

- * - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - */ - public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; - - /** - * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content - * represented by this ViewHolder is invalid. - *

- * - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - */ - public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; - - /** - * The position of the Item represented by this ViewHolder has been changed. This flag is - * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to - * any adapter change that may have a side effect on this item. (e.g. The item before this - * one has been removed from the Adapter). - *

- * - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - */ - public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; - - /** - * This ViewHolder was not laid out but has been added to the layout in pre-layout state - * by the {@link LayoutManager}. This means that the item was already in the Adapter but - * invisible and it may become visible in the post layout phase. LayoutManagers may prefer - * to add new items in pre-layout to specify their virtual location when they are invisible - * (e.g. to specify the item should animate in from below the visible area). - *

- * - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - */ - public static final int FLAG_APPEARED_IN_PRE_LAYOUT = - ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; - - /** - * The set of flags that might be passed to - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - */ - @IntDef(flag = true, value = { - FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, - FLAG_APPEARED_IN_PRE_LAYOUT - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AdapterChanges { - } - - private ItemAnimatorListener mListener = null; - private ArrayList mFinishedListeners = - new ArrayList(); - - private long mAddDuration = 120; - private long mRemoveDuration = 120; - private long mMoveDuration = 250; - private long mChangeDuration = 250; - - /** - * Gets the current duration for which all move animations will run. - * - * @return The current move duration - */ - public long getMoveDuration() { - return mMoveDuration; - } - - /** - * Sets the duration for which all move animations will run. - * - * @param moveDuration The move duration - */ - public void setMoveDuration(long moveDuration) { - mMoveDuration = moveDuration; - } - - /** - * Gets the current duration for which all add animations will run. - * - * @return The current add duration - */ - public long getAddDuration() { - return mAddDuration; - } - - /** - * Sets the duration for which all add animations will run. - * - * @param addDuration The add duration - */ - public void setAddDuration(long addDuration) { - mAddDuration = addDuration; - } - - /** - * Gets the current duration for which all remove animations will run. - * - * @return The current remove duration - */ - public long getRemoveDuration() { - return mRemoveDuration; - } - - /** - * Sets the duration for which all remove animations will run. - * - * @param removeDuration The remove duration - */ - public void setRemoveDuration(long removeDuration) { - mRemoveDuration = removeDuration; - } - - /** - * Gets the current duration for which all change animations will run. - * - * @return The current change duration - */ - public long getChangeDuration() { - return mChangeDuration; - } - - /** - * Sets the duration for which all change animations will run. - * - * @param changeDuration The change duration - */ - public void setChangeDuration(long changeDuration) { - mChangeDuration = changeDuration; - } - - /** - * Internal only: - * Sets the listener that must be called when the animator is finished - * animating the item (or immediately if no animation happens). This is set - * internally and is not intended to be set by external code. - * - * @param listener The listener that must be called. - */ - void setListener(ItemAnimatorListener listener) { - mListener = listener; - } - - /** - * Called by the RecyclerView before the layout begins. Item animator should record - * necessary information about the View before it is potentially rebound, moved or removed. - *

- * The data returned from this method will be passed to the related animate** - * methods. - *

- * Note that this method may be called after pre-layout phase if LayoutManager adds new - * Views to the layout in pre-layout pass. - *

- * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of - * the View and the adapter change flags. - * - * @param state The current State of RecyclerView which includes some useful data - * about the layout that will be calculated. - * @param viewHolder The ViewHolder whose information should be recorded. - * @param changeFlags Additional information about what changes happened in the Adapter - * about the Item represented by this ViewHolder. For instance, if - * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. - * @param payloads The payload list that was previously passed to - * {@link Adapter#notifyItemChanged(int, Object)} or - * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. - * @return An ItemHolderInfo instance that preserves necessary information about the - * ViewHolder. This object will be passed back to related animate** methods - * after layout is complete. - * @see #recordPostLayoutInformation(State, ViewHolder) - * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) - */ - public @NonNull - ItemHolderInfo recordPreLayoutInformation(@NonNull State state, - @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, - @NonNull List payloads) { - return obtainHolderInfo().setFrom(viewHolder); - } - - /** - * Called by the RecyclerView after the layout is complete. Item animator should record - * necessary information about the View's final state. - *

- * The data returned from this method will be passed to the related animate** - * methods. - *

- * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of - * the View. - * - * @param state The current State of RecyclerView which includes some useful data about - * the layout that will be calculated. - * @param viewHolder The ViewHolder whose information should be recorded. - * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. - * This object will be passed back to related animate** methods when - * RecyclerView decides how items should be animated. - * @see #recordPreLayoutInformation(State, ViewHolder, int, List) - * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) - */ - public @NonNull - ItemHolderInfo recordPostLayoutInformation(@NonNull State state, - @NonNull ViewHolder viewHolder) { - return obtainHolderInfo().setFrom(viewHolder); - } - - /** - * Called by the RecyclerView when a ViewHolder has disappeared from the layout. - *

- * This means that the View was a child of the LayoutManager when layout started but has - * been removed by the LayoutManager. It might have been removed from the adapter or simply - * become invisible due to other factors. You can distinguish these two cases by checking - * the change flags that were passed to - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - *

- * Note that when a ViewHolder both changes and disappears in the same layout pass, the - * animation callback method which will be called by the RecyclerView depends on the - * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the - * LayoutManager's decision whether to layout the changed version of a disappearing - * ViewHolder or not. RecyclerView will call - * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator - * returns {@code false} from - * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the - * LayoutManager lays out a new disappearing view that holds the updated information. - * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. - *

- * If LayoutManager supports predictive animations, it might provide a target disappear - * location for the View by laying it out in that location. When that happens, - * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the - * response of that call will be passed to this method as the postLayoutInfo. - *

- * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation - * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it - * decides not to animate the view). - * - * @param viewHolder The ViewHolder which should be animated - * @param preLayoutInfo The information that was returned from - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @param postLayoutInfo The information that was returned from - * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be - * null if the LayoutManager did not layout the item. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, - @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); - - /** - * Called by the RecyclerView when a ViewHolder is added to the layout. - *

- * In detail, this means that the ViewHolder was not a child when the layout started - * but has been added by the LayoutManager. It might be newly added to the adapter or - * simply become visible due to other factors. - *

- * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation - * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it - * decides not to animate the view). - * - * @param viewHolder The ViewHolder which should be animated - * @param preLayoutInfo The information that was returned from - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * Might be null if Item was just added to the adapter or - * LayoutManager does not support predictive animations or it could - * not predict that this ViewHolder will become visible. - * @param postLayoutInfo The information that was returned from {@link - * #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, - @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); - - /** - * Called by the RecyclerView when a ViewHolder is present in both before and after the - * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call - * for it or a {@link Adapter#notifyDataSetChanged()} call. - *

- * This ViewHolder still represents the same data that it was representing when the layout - * started but its position / size may be changed by the LayoutManager. - *

- * If the Item's layout position didn't change, RecyclerView still calls this method because - * it does not track this information (or does not necessarily know that an animation is - * not required). Your ItemAnimator should handle this case and if there is nothing to - * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return - * false. - *

- * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation - * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it - * decides not to animate the view). - * - * @param viewHolder The ViewHolder which should be animated - * @param preLayoutInfo The information that was returned from - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @param postLayoutInfo The information that was returned from {@link - * #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, - @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); - - /** - * Called by the RecyclerView when an adapter item is present both before and after the - * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call - * for it. This method may also be called when - * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that - * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when - * {@link Adapter#notifyDataSetChanged()} is called, this method will not be called, - * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be - * called for the new ViewHolder and the old one will be recycled. - *

- * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is - * a good possibility that item contents didn't really change but it is rebound from the - * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the - * screen didn't change and your animator should handle this case as well and avoid creating - * unnecessary animations. - *

- * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the - * previous presentation of the item as-is and supply a new ViewHolder for the updated - * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. - * This is useful if you don't know the contents of the Item and would like - * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). - *

- * When you are writing a custom item animator for your layout, it might be more performant - * and elegant to re-use the same ViewHolder and animate the content changes manually. - *

- * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. - * If the Item's view type has changed or ItemAnimator returned false for - * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the - * oldHolder and newHolder will be different ViewHolder instances - * which represent the same Item. In that case, only the new ViewHolder is visible - * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. - *

- * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct - * ViewHolder when their animation is complete - * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to - * animate the view). - *

- * If oldHolder and newHolder are the same instance, you should call - * {@link #dispatchAnimationFinished(ViewHolder)} only once. - *

- * Note that when a ViewHolder both changes and disappears in the same layout pass, the - * animation callback method which will be called by the RecyclerView depends on the - * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the - * LayoutManager's decision whether to layout the changed version of a disappearing - * ViewHolder or not. RecyclerView will call - * {@code animateChange} instead of - * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateDisappearance} if and only if the ItemAnimator returns {@code false} from - * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the - * LayoutManager lays out a new disappearing view that holds the updated information. - * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. - * - * @param oldHolder The ViewHolder before the layout is started, might be the same - * instance with newHolder. - * @param newHolder The ViewHolder after the layout is finished, might be the same - * instance with oldHolder. - * @param preLayoutInfo The information that was returned from - * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @param postLayoutInfo The information that was returned from {@link - * #recordPreLayoutInformation(State, ViewHolder, int, List)}. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - public abstract boolean animateChange(@NonNull ViewHolder oldHolder, - @NonNull ViewHolder newHolder, - @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); - - @AdapterChanges - static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { - int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); - if (viewHolder.isInvalid()) { - return FLAG_INVALIDATED; - } - if ((flags & FLAG_INVALIDATED) == 0) { - final int oldPos = viewHolder.getOldPosition(); - final int pos = viewHolder.getAbsoluteAdapterPosition(); - if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) { - flags |= FLAG_MOVED; - } - } - return flags; - } - - /** - * Called when there are pending animations waiting to be started. This state - * is governed by the return values from - * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateAppearance()}, - * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()} - * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animatePersistence()}, and - * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be - * called later to start the associated animations. runPendingAnimations() will be scheduled - * to be run on the next frame. - */ - public abstract void runPendingAnimations(); - - /** - * Method called when an animation on a view should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on the item - * are canceled and affected properties are set to their end values. - * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished - * animation since the animations are effectively done when this method is called. - * - * @param item The item for which an animation should be stopped. - */ - public abstract void endAnimation(@NonNull ViewHolder item); - - /** - * Method called when all item animations should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on any items - * are canceled and affected properties are set to their end values. - * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished - * animation since the animations are effectively done when this method is called. - */ - public abstract void endAnimations(); - - /** - * Method which returns whether there are any item animations currently running. - * This method can be used to determine whether to delay other actions until - * animations end. - * - * @return true if there are any item animations currently running, false otherwise. - */ - public abstract boolean isRunning(); - - /** - * Method to be called by subclasses when an animation is finished. - *

- * For each call RecyclerView makes to - * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateAppearance()}, - * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animatePersistence()}, or - * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateDisappearance()}, there - * should - * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. - *

- * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()}, subclass should call this method for both the oldHolder - * and newHolder (if they are not the same instance). - * - * @param viewHolder The ViewHolder whose animation is finished. - * @see #onAnimationFinished(ViewHolder) - */ - public final void dispatchAnimationFinished(@NonNull ViewHolder viewHolder) { - onAnimationFinished(viewHolder); - if (mListener != null) { - mListener.onAnimationFinished(viewHolder); - } - } - - /** - * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the - * ItemAnimator. - * - * @param viewHolder The ViewHolder whose animation is finished. There might still be other - * animations running on this ViewHolder. - * @see #dispatchAnimationFinished(ViewHolder) - */ - public void onAnimationFinished(@NonNull ViewHolder viewHolder) { - } - - /** - * Method to be called by subclasses when an animation is started. - *

- * For each call RecyclerView makes to - * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateAppearance()}, - * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animatePersistence()}, or - * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateDisappearance()}, there should be a matching - * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. - *

- * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()}, subclass should call this method for both the oldHolder - * and newHolder (if they are not the same instance). - *

- * If your ItemAnimator decides not to animate a ViewHolder, it should call - * {@link #dispatchAnimationFinished(ViewHolder)} without calling - * {@link #dispatchAnimationStarted(ViewHolder)}. - * - * @param viewHolder The ViewHolder whose animation is starting. - * @see #onAnimationStarted(ViewHolder) - */ - public final void dispatchAnimationStarted(@NonNull ViewHolder viewHolder) { - onAnimationStarted(viewHolder); - } - - /** - * Called when a new animation is started on the given ViewHolder. - * - * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder - * might already be animating and this might be another animation. - * @see #dispatchAnimationStarted(ViewHolder) - */ - public void onAnimationStarted(@NonNull ViewHolder viewHolder) { - - } - - /** - * Like {@link #isRunning()}, this method returns whether there are any item - * animations currently running. Additionally, the listener passed in will be called - * when there are no item animations running, either immediately (before the method - * returns) if no animations are currently running, or when the currently running - * animations are {@link #dispatchAnimationsFinished() finished}. - * - *

Note that the listener is transient - it is either called immediately and not - * stored at all, or stored only until it is called when running animations - * are finished sometime later.

- * - * @param listener A listener to be called immediately if no animations are running - * or later when currently-running animations have finished. A null - * listener is - * equivalent to calling {@link #isRunning()}. - * @return true if there are any item animations currently running, false otherwise. - */ - public final boolean isRunning(@Nullable ItemAnimatorFinishedListener listener) { - boolean running = isRunning(); - if (listener != null) { - if (!running) { - listener.onAnimationsFinished(); - } else { - mFinishedListeners.add(listener); - } - } - return running; - } - - /** - * When an item is changed, ItemAnimator can decide whether it wants to re-use - * the same ViewHolder for animations or RecyclerView should create a copy of the - * item and ItemAnimator will use both to run the animation (e.g. cross-fade). - *

- * Note that this method will only be called if the {@link ViewHolder} still has the same - * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive - * both {@link ViewHolder}s in the - * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. - *

- * If your application is using change payloads, you can override - * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. - * - * @param viewHolder The ViewHolder which represents the changed item's old content. - * @return True if RecyclerView should just rebind to the same ViewHolder or false if - * RecyclerView should create a new ViewHolder and pass this ViewHolder to the - * ItemAnimator to animate. Default implementation returns true. - * @see #canReuseUpdatedViewHolder(ViewHolder, List) - */ - public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { - return true; - } - - /** - * When an item is changed, ItemAnimator can decide whether it wants to re-use - * the same ViewHolder for animations or RecyclerView should create a copy of the - * item and ItemAnimator will use both to run the animation (e.g. cross-fade). - *

- * Note that this method will only be called if the {@link ViewHolder} still has the same - * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive - * both {@link ViewHolder}s in the - * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. - * - * @param viewHolder The ViewHolder which represents the changed item's old content. - * @param payloads A non-null list of merged payloads that were sent with change - * notifications. Can be empty if the adapter is invalidated via - * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of - * payloads will be passed into - * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} - * method if this method returns true. - * @return True if RecyclerView should just rebind to the same ViewHolder or false if - * RecyclerView should create a new ViewHolder and pass this ViewHolder to the - * ItemAnimator to animate. Default implementation calls - * {@link #canReuseUpdatedViewHolder(ViewHolder)}. - * @see #canReuseUpdatedViewHolder(ViewHolder) - */ - public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, - @NonNull List payloads) { - return canReuseUpdatedViewHolder(viewHolder); - } - - /** - * This method should be called by ItemAnimator implementations to notify - * any listeners that all pending and active item animations are finished. - */ - public final void dispatchAnimationsFinished() { - final int count = mFinishedListeners.size(); - for (int i = 0; i < count; ++i) { - mFinishedListeners.get(i).onAnimationsFinished(); - } - mFinishedListeners.clear(); - } - - /** - * Returns a new {@link ItemHolderInfo} which will be used to store information about the - * ViewHolder. This information will later be passed into animate** methods. - *

- * You can override this method if you want to extend {@link ItemHolderInfo} and provide - * your own instances. - * - * @return A new {@link ItemHolderInfo}. - */ - @NonNull - public ItemHolderInfo obtainHolderInfo() { - return new ItemHolderInfo(); - } - - /** - * The interface to be implemented by listeners to animation events from this - * ItemAnimator. This is used internally and is not intended for developers to - * create directly. - */ - interface ItemAnimatorListener { - void onAnimationFinished(@NonNull ViewHolder item); - } - - /** - * This interface is used to inform listeners when all pending or running animations - * in an ItemAnimator are finished. This can be used, for example, to delay an action - * in a data set until currently-running animations are complete. - * - * @see #isRunning(ItemAnimatorFinishedListener) - */ - public interface ItemAnimatorFinishedListener { - /** - * Notifies when all pending or running animations in an ItemAnimator are finished. - */ - void onAnimationsFinished(); - } - - /** - * A simple data structure that holds information about an item's bounds. - * This information is used in calculating item animations. Default implementation of - * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and - * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data - * structure. You can extend this class if you would like to keep more information about - * the Views. - *

- * If you want to provide your own implementation but still use `super` methods to record - * basic information, you can override {@link #obtainHolderInfo()} to provide your own - * instances. - */ - public static class ItemHolderInfo { - - /** - * The left edge of the View (excluding decorations) - */ - public int left; - - /** - * The top edge of the View (excluding decorations) - */ - public int top; - - /** - * The right edge of the View (excluding decorations) - */ - public int right; - - /** - * The bottom edge of the View (excluding decorations) - */ - public int bottom; - - /** - * The change flags that were passed to - * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. - */ - @AdapterChanges - public int changeFlags; - - public ItemHolderInfo() { - } - - /** - * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from - * the given ViewHolder. Clears all {@link #changeFlags}. - * - * @param holder The ViewHolder whose bounds should be copied. - * @return This {@link ItemHolderInfo} - */ - @NonNull - public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder) { - return setFrom(holder, 0); - } - - /** - * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from - * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. - * - * @param holder The ViewHolder whose bounds should be copied. - * @param flags The adapter change flags that were passed into - * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, - * List)}. - * @return This {@link ItemHolderInfo} - */ - @NonNull - public ItemHolderInfo setFrom(@NonNull RecyclerView.ViewHolder holder, - @AdapterChanges int flags) { - final View view = holder.itemView; - this.left = view.getLeft(); - this.top = view.getTop(); - this.right = view.getRight(); - this.bottom = view.getBottom(); - return this; - } - } - } - - @Override - protected int getChildDrawingOrder(int childCount, int i) { - if (mChildDrawingOrderCallback == null) { - return super.getChildDrawingOrder(childCount, i); - } else { - return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i); - } - } - - /** - * A callback interface that can be used to alter the drawing order of RecyclerView children. - *

- * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case - * that applies to that method also applies to this callback. For example, changing the drawing - * order of two views will not have any effect if their elevation values are different since - * elevation overrides the result of this callback. - */ - public interface ChildDrawingOrderCallback { - /** - * Returns the index of the child to draw for this iteration. Override this - * if you want to change the drawing order of children. By default, it - * returns i. - * - * @param i The current iteration. - * @return The index of the child to draw this iteration. - * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) - */ - int onGetChildDrawingOrder(int childCount, int i); - } - - private NestedScrollingChildHelper getScrollingChildHelper() { - if (mScrollingChildHelper == null) { - mScrollingChildHelper = new NestedScrollingChildHelper(this); - } - return mScrollingChildHelper; - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java b/app/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java deleted file mode 100644 index fd9d86c345..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.AccessibilityDelegateCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeProviderCompat; - -import java.util.Map; -import java.util.WeakHashMap; - -/** - * The AccessibilityDelegate used by RecyclerView. - *

- * This class handles basic accessibility actions and delegates them to LayoutManager. - */ -public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat { - final RecyclerView mRecyclerView; - private final ItemDelegate mItemDelegate; - - public RecyclerViewAccessibilityDelegate(@NonNull RecyclerView recyclerView) { - mRecyclerView = recyclerView; - AccessibilityDelegateCompat itemDelegate = getItemDelegate(); - if (itemDelegate != null && itemDelegate instanceof ItemDelegate) { - mItemDelegate = (ItemDelegate) itemDelegate; - } else { - mItemDelegate = new ItemDelegate(this); - } - } - - boolean shouldIgnore() { - return mRecyclerView.hasPendingAdapterUpdates(); - } - - @Override - public boolean performAccessibilityAction( - @SuppressLint("InvalidNullabilityOverride") @NonNull View host, - int action, - @SuppressLint("InvalidNullabilityOverride") @Nullable Bundle args - ) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) { - return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args); - } - - return false; - } - - @Override - public void onInitializeAccessibilityNodeInfo( - @SuppressLint("InvalidNullabilityOverride") @NonNull View host, - @SuppressLint("InvalidNullabilityOverride") @NonNull AccessibilityNodeInfoCompat info - ) { - super.onInitializeAccessibilityNodeInfo(host, info); - if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) { - mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info); - } - } - - @Override - public void onInitializeAccessibilityEvent( - @SuppressLint("InvalidNullabilityOverride") @NonNull View host, - @SuppressLint("InvalidNullabilityOverride") @NonNull AccessibilityEvent event - ) { - super.onInitializeAccessibilityEvent(host, event); - if (host instanceof RecyclerView && !shouldIgnore()) { - RecyclerView rv = (RecyclerView) host; - if (rv.getLayoutManager() != null) { - rv.getLayoutManager().onInitializeAccessibilityEvent(event); - } - } - } - - /** - * Gets the AccessibilityDelegate for an individual item in the RecyclerView. - * A basic item delegate is provided by default, but you can override this - * method to provide a custom per-item delegate. - * For now, returning an {@code AccessibilityDelegateCompat} as opposed to an - * {@code ItemDelegate} will prevent use of the {@code ViewCompat} accessibility API on - * item views. - */ - @NonNull - public AccessibilityDelegateCompat getItemDelegate() { - return mItemDelegate; - } - - /** - * The default implementation of accessibility delegate for the individual items of the - * RecyclerView. - *

- * If you are overriding {@code RecyclerViewAccessibilityDelegate#getItemDelegate()} but still - * want to keep some default behavior, you can create an instance of this class and delegate to - * the parent as necessary. - */ - public static class ItemDelegate extends AccessibilityDelegateCompat { - final RecyclerViewAccessibilityDelegate mRecyclerViewDelegate; - private Map mOriginalItemDelegates = new WeakHashMap<>(); - - /** - * Creates an item delegate for the given {@code RecyclerViewAccessibilityDelegate}. - * - * @param recyclerViewDelegate The parent RecyclerView's accessibility delegate. - */ - public ItemDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate) { - mRecyclerViewDelegate = recyclerViewDelegate; - } - - /** - * Saves a reference to the original delegate of the itemView so that it's behavior can be - * combined with the ItemDelegate's behavior. - */ - void saveOriginalDelegate(View itemView) { - AccessibilityDelegateCompat delegate = ViewCompat.getAccessibilityDelegate(itemView); - if (delegate != null && delegate != this) { - mOriginalItemDelegates.put(itemView, delegate); - } - } - - /** - * @return The delegate associated with itemView before the view was bound. - */ - AccessibilityDelegateCompat getAndRemoveOriginalDelegateForItem(View itemView) { - return mOriginalItemDelegates.remove(itemView); - } - - @Override - public void onInitializeAccessibilityNodeInfo( - @SuppressLint("InvalidNullabilityOverride") @NonNull View host, - @SuppressLint("InvalidNullabilityOverride") @NonNull - AccessibilityNodeInfoCompat info - ) { - if (!mRecyclerViewDelegate.shouldIgnore() - && mRecyclerViewDelegate.mRecyclerView.getLayoutManager() != null) { - mRecyclerViewDelegate.mRecyclerView.getLayoutManager() - .onInitializeAccessibilityNodeInfoForItem(host, info); - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - originalDelegate.onInitializeAccessibilityNodeInfo(host, info); - } else { - super.onInitializeAccessibilityNodeInfo(host, info); - } - } else { - super.onInitializeAccessibilityNodeInfo(host, info); - } - } - - @Override - public boolean performAccessibilityAction( - @SuppressLint("InvalidNullabilityOverride") @NonNull View host, - int action, - @SuppressLint("InvalidNullabilityOverride") @Nullable Bundle args - ) { - if (!mRecyclerViewDelegate.shouldIgnore() - && mRecyclerViewDelegate.mRecyclerView.getLayoutManager() != null) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - if (originalDelegate.performAccessibilityAction(host, action, args)) { - return true; - } - } else if (super.performAccessibilityAction(host, action, args)) { - return true; - } - return mRecyclerViewDelegate.mRecyclerView.getLayoutManager() - .performAccessibilityActionForItem(host, action, args); - } else { - return super.performAccessibilityAction(host, action, args); - } - } - - @Override - public void sendAccessibilityEvent(@NonNull View host, int eventType) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - originalDelegate.sendAccessibilityEvent(host, eventType); - } else { - super.sendAccessibilityEvent(host, eventType); - } - } - - @Override - public void sendAccessibilityEventUnchecked(@NonNull View host, - @NonNull AccessibilityEvent event) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - originalDelegate.sendAccessibilityEventUnchecked(host, event); - } else { - super.sendAccessibilityEventUnchecked(host, event); - } - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(@NonNull View host, - @NonNull AccessibilityEvent event) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - return originalDelegate.dispatchPopulateAccessibilityEvent(host, event); - } else { - return super.dispatchPopulateAccessibilityEvent(host, event); - } - } - - @Override - public void onPopulateAccessibilityEvent(@NonNull View host, - @NonNull AccessibilityEvent event) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - originalDelegate.onPopulateAccessibilityEvent(host, event); - } else { - super.onPopulateAccessibilityEvent(host, event); - } - } - - @Override - public void onInitializeAccessibilityEvent(@NonNull View host, - @NonNull AccessibilityEvent event) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - originalDelegate.onInitializeAccessibilityEvent(host, event); - } else { - super.onInitializeAccessibilityEvent(host, event); - } - } - - @Override - public boolean onRequestSendAccessibilityEvent(@NonNull ViewGroup host, - @NonNull View child, @NonNull AccessibilityEvent event) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - return originalDelegate.onRequestSendAccessibilityEvent(host, child, event); - } else { - return super.onRequestSendAccessibilityEvent(host, child, event); - } - } - - @Override - @Nullable - public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(@NonNull View host) { - AccessibilityDelegateCompat originalDelegate = mOriginalItemDelegates.get(host); - if (originalDelegate != null) { - return originalDelegate.getAccessibilityNodeProvider(host); - } else { - return super.getAccessibilityNodeProvider(host); - } - } - } -} - diff --git a/app/src/main/java/androidx/recyclerview/widget/ScrollbarHelper.java b/app/src/main/java/androidx/recyclerview/widget/ScrollbarHelper.java deleted file mode 100644 index 8cc24aeb1e..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ScrollbarHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import android.view.View; - -/** - * A helper class to do scroll offset calculations. - */ -class ScrollbarHelper { - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled, boolean reverseLayout) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null - || endChild == null) { - return 0; - } - final int minPosition = Math.min(lm.getPosition(startChild), - lm.getPosition(endChild)); - final int maxPosition = Math.max(lm.getPosition(startChild), - lm.getPosition(endChild)); - final int itemsBefore = reverseLayout - ? Math.max(0, state.getItemCount() - maxPosition - 1) - : Math.max(0, minPosition); - if (!smoothScrollbarEnabled) { - return itemsBefore; - } - final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild)); - final int itemRange = Math.abs(lm.getPosition(startChild) - - lm.getPosition(endChild)) + 1; - final float avgSizePerRow = (float) laidOutArea / itemRange; - - return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding() - - orientation.getDecoratedStart(startChild))); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null - || endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1; - } - final int extend = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - return Math.min(orientation.getTotalSpace(), extend); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null - || endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return state.getItemCount(); - } - // smooth scrollbar enabled. try to estimate better. - final int laidOutArea = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - final int laidOutRange = Math.abs(lm.getPosition(startChild) - - lm.getPosition(endChild)) - + 1; - // estimate a size for full list. - return (int) ((float) laidOutArea / laidOutRange * state.getItemCount()); - } - - private ScrollbarHelper() { - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/SimpleItemAnimator.java b/app/src/main/java/androidx/recyclerview/widget/SimpleItemAnimator.java deleted file mode 100644 index 4dc33c6315..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/SimpleItemAnimator.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.util.Log; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * A wrapper class for ItemAnimator that records View bounds and decides whether it should run - * move, change, add or remove animations. This class also replicates the original ItemAnimator - * API. - *

- * It uses {@link RecyclerView.ItemAnimator.ItemHolderInfo} to track the bounds information of - * the Views. If you would like to extend this class, you can override {@link #obtainHolderInfo()} - * method to provide your own info class that extends - * {@link RecyclerView.ItemAnimator.ItemHolderInfo}. - */ -public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator { - - private static final boolean DEBUG = false; - - private static final String TAG = "SimpleItemAnimator"; - - boolean mSupportsChangeAnimations = true; - - /** - * Returns whether this ItemAnimator supports animations of change events. - * - * @return true if change animations are supported, false otherwise - */ - @SuppressWarnings("unused") - public boolean getSupportsChangeAnimations() { - return mSupportsChangeAnimations; - } - - /** - * Sets whether this ItemAnimator supports animations of item change events. - * If you set this property to false, actions on the data set which change the - * contents of items will not be animated. What those animations do is left - * up to the discretion of the ItemAnimator subclass, in its - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} implementation. - * The value of this property is true by default. - * - * @param supportsChangeAnimations true if change animations are supported by - * this ItemAnimator, false otherwise. If the property is false, - * the ItemAnimator - * will not receive a call to - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, - * int)} when changes occur. - * @see RecyclerView.Adapter#notifyItemChanged(int) - * @see RecyclerView.Adapter#notifyItemRangeChanged(int, int) - */ - public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { - mSupportsChangeAnimations = supportsChangeAnimations; - } - - /** - * {@inheritDoc} - * - * @return True if change animations are not supported or the ViewHolder is invalid, - * false otherwise. - * - * @see #setSupportsChangeAnimations(boolean) - */ - @Override - public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { - return !mSupportsChangeAnimations || viewHolder.isInvalid(); - } - - @Override - public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, - @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { - int oldLeft = preLayoutInfo.left; - int oldTop = preLayoutInfo.top; - View disappearingItemView = viewHolder.itemView; - int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left; - int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top; - if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) { - disappearingItemView.layout(newLeft, newTop, - newLeft + disappearingItemView.getWidth(), - newTop + disappearingItemView.getHeight()); - if (DEBUG) { - Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView); - } - return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop); - } else { - if (DEBUG) { - Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView); - } - return animateRemove(viewHolder); - } - } - - @Override - public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, - @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { - if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left - || preLayoutInfo.top != postLayoutInfo.top)) { - // slide items in if before/after locations differ - if (DEBUG) { - Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder); - } - return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top, - postLayoutInfo.left, postLayoutInfo.top); - } else { - if (DEBUG) { - Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder); - } - return animateAdd(viewHolder); - } - } - - @Override - public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, - @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { - if (preLayoutInfo.left != postLayoutInfo.left || preLayoutInfo.top != postLayoutInfo.top) { - if (DEBUG) { - Log.d(TAG, "PERSISTENT: " + viewHolder - + " with view " + viewHolder.itemView); - } - return animateMove(viewHolder, - preLayoutInfo.left, preLayoutInfo.top, postLayoutInfo.left, postLayoutInfo.top); - } - dispatchMoveFinished(viewHolder); - return false; - } - - @Override - public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, - @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, - @NonNull ItemHolderInfo postLayoutInfo) { - if (DEBUG) { - Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); - } - final int fromLeft = preLayoutInfo.left; - final int fromTop = preLayoutInfo.top; - final int toLeft, toTop; - if (newHolder.shouldIgnore()) { - toLeft = preLayoutInfo.left; - toTop = preLayoutInfo.top; - } else { - toLeft = postLayoutInfo.left; - toTop = postLayoutInfo.top; - } - return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop); - } - - /** - * Called when an item is removed from the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchRemoveFinished(RecyclerView.ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()}, - * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - *

This method may also be called for disappearing items which continue to exist in the - * RecyclerView, but for which the system does not have enough information to animate - * them out of view. In that case, the default animation for removing items is run - * on those items as well.

- * - * @param holder The item that is being removed. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract boolean animateRemove(RecyclerView.ViewHolder holder); - - /** - * Called when an item is added to the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchAddFinished(RecyclerView.ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()}, - * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - *

This method may also be called for appearing items which were already in the - * RecyclerView, but for which the system does not have enough information to animate - * them into view. In that case, the default animation for adding items is run - * on those items as well.

- * - * @param holder The item that is being added. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract boolean animateAdd(RecyclerView.ViewHolder holder); - - /** - * Called when an item is moved in the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchMoveFinished(RecyclerView.ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()}, - * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - * @param holder The item that is being moved. - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, - int toX, int toY); - - /** - * Called when an item is changed in the RecyclerView, as indicated by a call to - * {@link RecyclerView.Adapter#notifyItemChanged(int)} or - * {@link RecyclerView.Adapter#notifyItemRangeChanged(int, int)}. - *

- * Implementers can choose whether and how to animate changes, but must always call - * {@link #dispatchChangeFinished(RecyclerView.ViewHolder, boolean)} for each non-null - * distinct ViewHolder, - * either immediately (if no animation will occur) or after the animation actually finishes. - * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call - * {@link #dispatchChangeFinished(RecyclerView.ViewHolder, boolean)} once and only once. In - * that case, the - * second parameter of {@code dispatchChangeFinished} is ignored. - *

- * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()}, - * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - * @param oldHolder The original item that changed. - * @param newHolder The new item that was created with the changed content. Might be null - * @param fromLeft Left of the old view holder - * @param fromTop Top of the old view holder - * @param toLeft Left of the new view holder - * @param toTop Top of the new view holder - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract boolean animateChange(RecyclerView.ViewHolder oldHolder, - RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); - - /** - * Method to be called by subclasses when a remove animation is done. - * - * @param item The item which has been removed - * @see RecyclerView.ItemAnimator#animateDisappearance(RecyclerView.ViewHolder, ItemHolderInfo, - * ItemHolderInfo) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchRemoveFinished(RecyclerView.ViewHolder item) { - onRemoveFinished(item); - dispatchAnimationFinished(item); - } - - /** - * Method to be called by subclasses when a move animation is done. - * - * @param item The item which has been moved - * @see RecyclerView.ItemAnimator#animateDisappearance(RecyclerView.ViewHolder, ItemHolderInfo, - * ItemHolderInfo) - * @see RecyclerView.ItemAnimator#animatePersistence(RecyclerView.ViewHolder, ItemHolderInfo, ItemHolderInfo) - * @see RecyclerView.ItemAnimator#animateAppearance(RecyclerView.ViewHolder, ItemHolderInfo, ItemHolderInfo) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchMoveFinished(RecyclerView.ViewHolder item) { - onMoveFinished(item); - dispatchAnimationFinished(item); - } - - /** - * Method to be called by subclasses when an add animation is done. - * - * @param item The item which has been added - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchAddFinished(RecyclerView.ViewHolder item) { - onAddFinished(item); - dispatchAnimationFinished(item); - } - - /** - * Method to be called by subclasses when a change animation is done. - * - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - * @see #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int) - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { - onChangeFinished(item, oldItem); - dispatchAnimationFinished(item); - } - - /** - * Method to be called by subclasses when a remove animation is being started. - * - * @param item The item being removed - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchRemoveStarting(RecyclerView.ViewHolder item) { - onRemoveStarting(item); - } - - /** - * Method to be called by subclasses when a move animation is being started. - * - * @param item The item being moved - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchMoveStarting(RecyclerView.ViewHolder item) { - onMoveStarting(item); - } - - /** - * Method to be called by subclasses when an add animation is being started. - * - * @param item The item being added - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchAddStarting(RecyclerView.ViewHolder item) { - onAddStarting(item); - } - - /** - * Method to be called by subclasses when a change animation is being started. - * - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public final void dispatchChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) { - onChangeStarting(item, oldItem); - } - - /** - * Called when a remove animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - @SuppressWarnings("UnusedParameters") - public void onRemoveStarting(RecyclerView.ViewHolder item) { - } - - /** - * Called when a remove animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onRemoveFinished(RecyclerView.ViewHolder item) { - } - - /** - * Called when an add animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressWarnings("UnusedParameters") - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onAddStarting(RecyclerView.ViewHolder item) { - } - - /** - * Called when an add animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onAddFinished(RecyclerView.ViewHolder item) { - } - - /** - * Called when a move animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressWarnings("UnusedParameters") - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onMoveStarting(RecyclerView.ViewHolder item) { - } - - /** - * Called when a move animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onMoveFinished(RecyclerView.ViewHolder item) { - } - - /** - * Called when a change animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - @SuppressWarnings("UnusedParameters") - public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) { - } - - /** - * Called when a change animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { - } -} - diff --git a/app/src/main/java/androidx/recyclerview/widget/SnapHelper.java b/app/src/main/java/androidx/recyclerview/widget/SnapHelper.java deleted file mode 100644 index 5ba267b385..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/SnapHelper.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.animation.DecelerateInterpolator; -import android.widget.Scroller; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Class intended to support snapping for a {@link RecyclerView}. - *

- * SnapHelper tries to handle fling as well but for this to work properly, the - * {@link RecyclerView.LayoutManager} must implement the {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface or - * you should override {@link #onFling(int, int)} and handle fling manually. - */ -public abstract class SnapHelper extends RecyclerView.OnFlingListener { - - static final float MILLISECONDS_PER_INCH = 100f; - - RecyclerView mRecyclerView; - private Scroller mGravityScroller; - - // Handles the snap on scroll case. - private final RecyclerView.OnScrollListener mScrollListener = - new RecyclerView.OnScrollListener() { - boolean mScrolled = false; - - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - super.onScrollStateChanged(recyclerView, newState); - if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { - mScrolled = false; - snapToTargetExistingView(); - } - } - - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - if (dx != 0 || dy != 0) { - mScrolled = true; - } - } - }; - - @Override - public boolean onFling(int velocityX, int velocityY) { - RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); - if (layoutManager == null) { - return false; - } - RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); - if (adapter == null) { - return false; - } - int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); - return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) - && snapFromFling(layoutManager, velocityX, velocityY); - } - - /** - * Attaches the {@link SnapHelper} to the provided RecyclerView, by calling - * {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}. - * You can call this method with {@code null} to detach it from the current RecyclerView. - * - * @param recyclerView The RecyclerView instance to which you want to add this helper or - * {@code null} if you want to remove SnapHelper from the current - * RecyclerView. - * - * @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener} - * attached to the provided {@link RecyclerView}. - * - */ - public void attachToRecyclerView(@Nullable RecyclerView recyclerView) - throws IllegalStateException { - if (mRecyclerView == recyclerView) { - return; // nothing to do - } - if (mRecyclerView != null) { - destroyCallbacks(); - } - mRecyclerView = recyclerView; - if (mRecyclerView != null) { - setupCallbacks(); - mGravityScroller = new Scroller(mRecyclerView.getContext(), - new DecelerateInterpolator()); - snapToTargetExistingView(); - } - } - - /** - * Called when an instance of a {@link RecyclerView} is attached. - */ - private void setupCallbacks() throws IllegalStateException { - if (mRecyclerView.getOnFlingListener() != null) { - throw new IllegalStateException("An instance of OnFlingListener already set."); - } - mRecyclerView.addOnScrollListener(mScrollListener); - mRecyclerView.setOnFlingListener(this); - } - - /** - * Called when the instance of a {@link RecyclerView} is detached. - */ - private void destroyCallbacks() { - mRecyclerView.removeOnScrollListener(mScrollListener); - mRecyclerView.setOnFlingListener(null); - } - - /** - * Calculated the estimated scroll distance in each direction given velocities on both axes. - * - * @param velocityX Fling velocity on the horizontal axis. - * @param velocityY Fling velocity on the vertical axis. - * - * @return array holding the calculated distances in x and y directions - * respectively. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public int[] calculateScrollDistance(int velocityX, int velocityY) { - int[] outDist = new int[2]; - mGravityScroller.fling(0, 0, velocityX, velocityY, - Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); - outDist[0] = mGravityScroller.getFinalX(); - outDist[1] = mGravityScroller.getFinalY(); - return outDist; - } - - /** - * Helper method to facilitate for snapping triggered by a fling. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * @param velocityX Fling velocity on the horizontal axis. - * @param velocityY Fling velocity on the vertical axis. - * - * @return true if it is handled, false otherwise. - */ - private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX, - int velocityY) { - if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { - return false; - } - - RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager); - if (smoothScroller == null) { - return false; - } - - int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); - if (targetPosition == RecyclerView.NO_POSITION) { - return false; - } - - smoothScroller.setTargetPosition(targetPosition); - layoutManager.startSmoothScroll(smoothScroller); - return true; - } - - /** - * Snaps to a target view which currently exists in the attached {@link RecyclerView}. This - * method is used to snap the view when the {@link RecyclerView} is first attached; when - * snapping was triggered by a scroll and when the fling is at its final stages. - */ - void snapToTargetExistingView() { - if (mRecyclerView == null) { - return; - } - RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); - if (layoutManager == null) { - return; - } - View snapView = findSnapView(layoutManager); - if (snapView == null) { - return; - } - int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); - if (snapDistance[0] != 0 || snapDistance[1] != 0) { - mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]); - } - } - - /** - * Creates a scroller to be used in the snapping implementation. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * - * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling. - */ - @Nullable - protected RecyclerView.SmoothScroller createScroller( - @NonNull RecyclerView.LayoutManager layoutManager) { - return createSnapScroller(layoutManager); - } - - /** - * Creates a scroller to be used in the snapping implementation. - * - * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView}. - * - * @return a {@link LinearSmoothScroller} which will handle the scrolling. - * @deprecated use {@link #createScroller(RecyclerView.LayoutManager)} instead. - */ - @Nullable - @Deprecated - protected LinearSmoothScroller createSnapScroller( - @NonNull RecyclerView.LayoutManager layoutManager) { - if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { - return null; - } - return new LinearSmoothScroller(mRecyclerView.getContext()) { - @Override - protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { - if (mRecyclerView == null) { - // The associated RecyclerView has been removed so there is no action to take. - return; - } - int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), - targetView); - final int dx = snapDistances[0]; - final int dy = snapDistances[1]; - final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); - if (time > 0) { - action.update(dx, dy, time, mDecelerateInterpolator); - } - } - - @Override - protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; - } - }; - } - - /** - * Override this method to snap to a particular point within the target view or the container - * view on any axis. - *

- * This method is called when the {@link SnapHelper} has intercepted a fling and it needs - * to know the exact distance required to scroll by in order to snap to the target view. - * - * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView} - * @param targetView the target view that is chosen as the view to snap - * - * @return the output coordinates the put the result into. out[0] is the distance - * on horizontal axis and out[1] is the distance on vertical axis. - */ - @SuppressWarnings("WeakerAccess") - @Nullable - public abstract int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, - @NonNull View targetView); - - /** - * Override this method to provide a particular target view for snapping. - *

- * This method is called when the {@link SnapHelper} is ready to start snapping and requires - * a target view to snap to. It will be explicitly called when the scroll state becomes idle - * after a scroll. It will also be called when the {@link SnapHelper} is preparing to snap - * after a fling and requires a reference view from the current set of child views. - *

- * If this method returns {@code null}, SnapHelper will not snap to any view. - * - * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView} - * - * @return the target view to which to snap on fling or end of scroll - */ - @SuppressWarnings("WeakerAccess") - @Nullable - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract View findSnapView(RecyclerView.LayoutManager layoutManager); - - /** - * Override to provide a particular adapter target position for snapping. - * - * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached - * {@link RecyclerView} - * @param velocityX fling velocity on the horizontal axis - * @param velocityY fling velocity on the vertical axis - * - * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION} - * if no snapping should happen - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public abstract int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, - int velocityX, int velocityY); -} diff --git a/app/src/main/java/androidx/recyclerview/widget/SortedList.java b/app/src/main/java/androidx/recyclerview/widget/SortedList.java deleted file mode 100644 index 3ae96b81fc..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/SortedList.java +++ /dev/null @@ -1,1015 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; - -/** - * A Sorted list implementation that can keep items in order and also notify for changes in the - * list - * such that it can be bound to a {@link RecyclerView.Adapter - * RecyclerView.Adapter}. - *

- * It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses - * binary search to retrieve items. If the sorting criteria of your items may change, make sure you - * call appropriate methods while editing them to avoid data inconsistencies. - *

- * You can control the order of items and change notifications via the {@link Callback} parameter. - */ -@SuppressWarnings("unchecked") -public class SortedList { - - /** - * Used by {@link #indexOf(Object)} when the item cannot be found in the list. - */ - public static final int INVALID_POSITION = -1; - - private static final int MIN_CAPACITY = 10; - private static final int CAPACITY_GROWTH = MIN_CAPACITY; - private static final int INSERTION = 1; - private static final int DELETION = 1 << 1; - private static final int LOOKUP = 1 << 2; - T[] mData; - - /** - * A reference to the previous set of data that is kept during a mutation operation (addAll or - * replaceAll). - */ - private T[] mOldData; - - /** - * The current index into mOldData that has not yet been processed during a mutation operation - * (addAll or replaceAll). - */ - private int mOldDataStart; - private int mOldDataSize; - - /** - * The current index into the new data that has not yet been processed during a mutation - * operation (addAll or replaceAll). - */ - private int mNewDataStart; - - /** - * The callback instance that controls the behavior of the SortedList and get notified when - * changes happen. - */ - private Callback mCallback; - - private BatchedCallback mBatchedCallback; - - private int mSize; - private final Class mTClass; - - /** - * Creates a new SortedList of type T. - * - * @param klass The class of the contents of the SortedList. - * @param callback The callback that controls the behavior of SortedList. - */ - public SortedList(@NonNull Class klass, @NonNull Callback callback) { - this(klass, callback, MIN_CAPACITY); - } - - /** - * Creates a new SortedList of type T. - * - * @param klass The class of the contents of the SortedList. - * @param callback The callback that controls the behavior of SortedList. - * @param initialCapacity The initial capacity to hold items. - */ - public SortedList(@NonNull Class klass, @NonNull Callback callback, int initialCapacity) { - mTClass = klass; - mData = (T[]) Array.newInstance(klass, initialCapacity); - mCallback = callback; - mSize = 0; - } - - /** - * The number of items in the list. - * - * @return The number of items in the list. - */ - public int size() { - return mSize; - } - - /** - * Adds the given item to the list. If this is a new item, SortedList calls - * {@link Callback#onInserted(int, int)}. - *

- * If the item already exists in the list and its sorting criteria is not changed, it is - * replaced with the existing Item. SortedList uses - * {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item - * and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should - * call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the - * reference to the old item and puts the new item into the backing array even if - * {@link Callback#areContentsTheSame(Object, Object)} returns false. - *

- * If the sorting criteria of the item is changed, SortedList won't be able to find - * its duplicate in the list which will result in having a duplicate of the Item in the list. - * If you need to update sorting criteria of an item that already exists in the list, - * use {@link #updateItemAt(int, Object)}. You can find the index of the item using - * {@link #indexOf(Object)} before you update the object. - * - * @param item The item to be added into the list. - * - * @return The index of the newly added item. - * @see Callback#compare(Object, Object) - * @see Callback#areItemsTheSame(Object, Object) - * @see Callback#areContentsTheSame(Object, Object)} - */ - public int add(T item) { - throwIfInMutationOperation(); - return add(item, true); - } - - /** - * Adds the given items to the list. Equivalent to calling {@link SortedList#add} in a loop, - * except the callback events may be in a different order/granularity since addAll can batch - * them for better performance. - *

- * If allowed, will reference the input array during, and possibly after, the operation to avoid - * extra memory allocation, in which case you should not continue to reference or modify the - * array yourself. - *

- * @param items Array of items to be added into the list. - * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the - * input array. - * @see SortedList#addAll(T[] items) - */ - public void addAll(@NonNull T[] items, boolean mayModifyInput) { - throwIfInMutationOperation(); - if (items.length == 0) { - return; - } - - if (mayModifyInput) { - addAllInternal(items); - } else { - addAllInternal(copyArray(items)); - } - } - - /** - * Adds the given items to the list. Does not modify or retain the input. - * - * @see SortedList#addAll(T[] items, boolean mayModifyInput) - * - * @param items Array of items to be added into the list. - */ - public void addAll(@NonNull T... items) { - addAll(items, false); - } - - /** - * Adds the given items to the list. Does not modify or retain the input. - * - * @see SortedList#addAll(T[] items, boolean mayModifyInput) - * - * @param items Collection of items to be added into the list. - */ - public void addAll(@NonNull Collection items) { - T[] copy = (T[]) Array.newInstance(mTClass, items.size()); - addAll(items.toArray(copy), true); - } - - /** - * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events - * for each change detected as appropriate. - *

- * If allowed, will reference the input array during, and possibly after, the operation to avoid - * extra memory allocation, in which case you should not continue to reference or modify the - * array yourself. - *

- * Note: this method does not detect moves or dispatch - * {@link ListUpdateCallback#onMoved(int, int)} events. It instead treats moves as a remove - * followed by an add and therefore dispatches {@link ListUpdateCallback#onRemoved(int, int)} - * and {@link ListUpdateCallback#onRemoved(int, int)} events. See {@link DiffUtil} if you want - * your implementation to dispatch move events. - *

- * @param items Array of items to replace current items. - * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the - * input array. - * @see #replaceAll(T[]) - */ - public void replaceAll(@NonNull T[] items, boolean mayModifyInput) { - throwIfInMutationOperation(); - - if (mayModifyInput) { - replaceAllInternal(items); - } else { - replaceAllInternal(copyArray(items)); - } - } - - /** - * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events - * for each change detected as appropriate. Does not modify or retain the input. - * - * @see #replaceAll(T[], boolean) - * - * @param items Array of items to replace current items. - */ - public void replaceAll(@NonNull T... items) { - replaceAll(items, false); - } - - /** - * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events - * for each change detected as appropriate. Does not modify or retain the input. - * - * @see #replaceAll(T[], boolean) - * - * @param items Array of items to replace current items. - */ - public void replaceAll(@NonNull Collection items) { - T[] copy = (T[]) Array.newInstance(mTClass, items.size()); - replaceAll(items.toArray(copy), true); - } - - private void addAllInternal(T[] newItems) { - if (newItems.length < 1) { - return; - } - - final int newSize = sortAndDedup(newItems); - - if (mSize == 0) { - mData = newItems; - mSize = newSize; - mCallback.onInserted(0, newSize); - } else { - merge(newItems, newSize); - } - } - - private void replaceAllInternal(@NonNull T[] newData) { - final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); - if (forceBatchedUpdates) { - beginBatchedUpdates(); - } - - mOldDataStart = 0; - mOldDataSize = mSize; - mOldData = mData; - - mNewDataStart = 0; - int newSize = sortAndDedup(newData); - mData = (T[]) Array.newInstance(mTClass, newSize); - - while (mNewDataStart < newSize || mOldDataStart < mOldDataSize) { - if (mOldDataStart >= mOldDataSize) { - int insertIndex = mNewDataStart; - int itemCount = newSize - mNewDataStart; - System.arraycopy(newData, insertIndex, mData, insertIndex, itemCount); - mNewDataStart += itemCount; - mSize += itemCount; - mCallback.onInserted(insertIndex, itemCount); - break; - } - if (mNewDataStart >= newSize) { - int itemCount = mOldDataSize - mOldDataStart; - mSize -= itemCount; - mCallback.onRemoved(mNewDataStart, itemCount); - break; - } - - T oldItem = mOldData[mOldDataStart]; - T newItem = newData[mNewDataStart]; - - int result = mCallback.compare(oldItem, newItem); - if (result < 0) { - replaceAllRemove(); - } else if (result > 0) { - replaceAllInsert(newItem); - } else { - if (!mCallback.areItemsTheSame(oldItem, newItem)) { - // The items aren't the same even though they were supposed to occupy the same - // place, so both notify to remove and add an item in the current location. - replaceAllRemove(); - replaceAllInsert(newItem); - } else { - mData[mNewDataStart] = newItem; - mOldDataStart++; - mNewDataStart++; - if (!mCallback.areContentsTheSame(oldItem, newItem)) { - // The item is the same but the contents have changed, so notify that an - // onChanged event has occurred. - mCallback.onChanged(mNewDataStart - 1, 1, - mCallback.getChangePayload(oldItem, newItem)); - } - } - } - } - - mOldData = null; - - if (forceBatchedUpdates) { - endBatchedUpdates(); - } - } - - private void replaceAllInsert(T newItem) { - mData[mNewDataStart] = newItem; - mNewDataStart++; - mSize++; - mCallback.onInserted(mNewDataStart - 1, 1); - } - - private void replaceAllRemove() { - mSize--; - mOldDataStart++; - mCallback.onRemoved(mNewDataStart, 1); - } - - /** - * Sorts and removes duplicate items, leaving only the last item from each group of "same" - * items. Move the remaining items to the beginning of the array. - * - * @return Number of deduplicated items at the beginning of the array. - */ - private int sortAndDedup(@NonNull T[] items) { - if (items.length == 0) { - return 0; - } - - // Arrays.sort is stable. - Arrays.sort(items, mCallback); - - // Keep track of the range of equal items at the end of the output. - // Start with the range containing just the first item. - int rangeStart = 0; - int rangeEnd = 1; - - for (int i = 1; i < items.length; ++i) { - T currentItem = items[i]; - - int compare = mCallback.compare(items[rangeStart], currentItem); - - if (compare == 0) { - // The range of equal items continues, update it. - final int sameItemPos = findSameItem(currentItem, items, rangeStart, rangeEnd); - if (sameItemPos != INVALID_POSITION) { - // Replace the duplicate item. - items[sameItemPos] = currentItem; - } else { - // Expand the range. - if (rangeEnd != i) { // Avoid redundant copy. - items[rangeEnd] = currentItem; - } - rangeEnd++; - } - } else { - // The range has ended. Reset it to contain just the current item. - if (rangeEnd != i) { // Avoid redundant copy. - items[rangeEnd] = currentItem; - } - rangeStart = rangeEnd++; - } - } - return rangeEnd; - } - - - private int findSameItem(T item, T[] items, int from, int to) { - for (int pos = from; pos < to; pos++) { - if (mCallback.areItemsTheSame(items[pos], item)) { - return pos; - } - } - return INVALID_POSITION; - } - - /** - * This method assumes that newItems are sorted and deduplicated. - */ - private void merge(T[] newData, int newDataSize) { - final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback); - if (forceBatchedUpdates) { - beginBatchedUpdates(); - } - - mOldData = mData; - mOldDataStart = 0; - mOldDataSize = mSize; - - final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH; - mData = (T[]) Array.newInstance(mTClass, mergedCapacity); - mNewDataStart = 0; - - int newDataStart = 0; - while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) { - if (mOldDataStart == mOldDataSize) { - // No more old items, copy the remaining new items. - int itemCount = newDataSize - newDataStart; - System.arraycopy(newData, newDataStart, mData, mNewDataStart, itemCount); - mNewDataStart += itemCount; - mSize += itemCount; - mCallback.onInserted(mNewDataStart - itemCount, itemCount); - break; - } - - if (newDataStart == newDataSize) { - // No more new items, copy the remaining old items. - int itemCount = mOldDataSize - mOldDataStart; - System.arraycopy(mOldData, mOldDataStart, mData, mNewDataStart, itemCount); - mNewDataStart += itemCount; - break; - } - - T oldItem = mOldData[mOldDataStart]; - T newItem = newData[newDataStart]; - int compare = mCallback.compare(oldItem, newItem); - if (compare > 0) { - // New item is lower, output it. - mData[mNewDataStart++] = newItem; - mSize++; - newDataStart++; - mCallback.onInserted(mNewDataStart - 1, 1); - } else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) { - // Items are the same. Output the new item, but consume both. - mData[mNewDataStart++] = newItem; - newDataStart++; - mOldDataStart++; - if (!mCallback.areContentsTheSame(oldItem, newItem)) { - mCallback.onChanged(mNewDataStart - 1, 1, - mCallback.getChangePayload(oldItem, newItem)); - } - } else { - // Old item is lower than or equal to (but not the same as the new). Output it. - // New item with the same sort order will be inserted later. - mData[mNewDataStart++] = oldItem; - mOldDataStart++; - } - } - - mOldData = null; - - if (forceBatchedUpdates) { - endBatchedUpdates(); - } - } - - /** - * Throws an exception if called while we are in the middle of a mutation operation (addAll or - * replaceAll). - */ - private void throwIfInMutationOperation() { - if (mOldData != null) { - throw new IllegalStateException("Data cannot be mutated in the middle of a batch " - + "update operation such as addAll or replaceAll."); - } - } - - /** - * Batches adapter updates that happen after calling this method and before calling - * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop - * and they are placed into consecutive indices, SortedList calls - * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event - * cannot be merged with the previous event, the previous event is dispatched - * to the callback instantly. - *

- * After running your data updates, you must call {@link #endBatchedUpdates()} - * which will dispatch any deferred data change event to the current callback. - *

- * A sample implementation may look like this: - *

-     *     mSortedList.beginBatchedUpdates();
-     *     try {
-     *         mSortedList.add(item1)
-     *         mSortedList.add(item2)
-     *         mSortedList.remove(item3)
-     *         ...
-     *     } finally {
-     *         mSortedList.endBatchedUpdates();
-     *     }
-     * 
- *

- * Instead of using this method to batch calls, you can use a Callback that extends - * {@link BatchedCallback}. In that case, you must make sure that you are manually calling - * {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes. - * Failing to do so may create data inconsistencies with the Callback. - *

- * If the current Callback is an instance of {@link BatchedCallback}, calling this method - * has no effect. - */ - public void beginBatchedUpdates() { - throwIfInMutationOperation(); - if (mCallback instanceof BatchedCallback) { - return; - } - if (mBatchedCallback == null) { - mBatchedCallback = new BatchedCallback(mCallback); - } - mCallback = mBatchedCallback; - } - - /** - * Ends the update transaction and dispatches any remaining event to the callback. - */ - public void endBatchedUpdates() { - throwIfInMutationOperation(); - if (mCallback instanceof BatchedCallback) { - ((BatchedCallback) mCallback).dispatchLastEvent(); - } - if (mCallback == mBatchedCallback) { - mCallback = mBatchedCallback.mWrappedCallback; - } - } - - private int add(T item, boolean notify) { - int index = findIndexOf(item, mData, 0, mSize, INSERTION); - if (index == INVALID_POSITION) { - index = 0; - } else if (index < mSize) { - T existing = mData[index]; - if (mCallback.areItemsTheSame(existing, item)) { - if (mCallback.areContentsTheSame(existing, item)) { - //no change but still replace the item - mData[index] = item; - return index; - } else { - mData[index] = item; - mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); - return index; - } - } - } - addToData(index, item); - if (notify) { - mCallback.onInserted(index, 1); - } - return index; - } - - /** - * Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}. - * - * @param item The item to be removed from the list. - * - * @return True if item is removed, false if item cannot be found in the list. - */ - public boolean remove(T item) { - throwIfInMutationOperation(); - return remove(item, true); - } - - /** - * Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}. - * - * @param index The index of the item to be removed. - * - * @return The removed item. - */ - public T removeItemAt(int index) { - throwIfInMutationOperation(); - T item = get(index); - removeItemAtIndex(index, true); - return item; - } - - private boolean remove(T item, boolean notify) { - int index = findIndexOf(item, mData, 0, mSize, DELETION); - if (index == INVALID_POSITION) { - return false; - } - removeItemAtIndex(index, notify); - return true; - } - - private void removeItemAtIndex(int index, boolean notify) { - System.arraycopy(mData, index + 1, mData, index, mSize - index - 1); - mSize--; - mData[mSize] = null; - if (notify) { - mCallback.onRemoved(index, 1); - } - } - - /** - * Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or - * {@link Callback#onMoved(int, int)} if necessary. - *

- * You can use this method if you need to change an existing Item such that its position in the - * list may change. - *

- * If the new object is a different object (get(index) != item) and - * {@link Callback#areContentsTheSame(Object, Object)} returns true, SortedList - * avoids calling {@link Callback#onChanged(int, int)} otherwise it calls - * {@link Callback#onChanged(int, int)}. - *

- * If the new position of the item is different than the provided index, - * SortedList - * calls {@link Callback#onMoved(int, int)}. - * - * @param index The index of the item to replace - * @param item The item to replace the item at the given Index. - * @see #add(Object) - */ - public void updateItemAt(int index, T item) { - throwIfInMutationOperation(); - final T existing = get(index); - // assume changed if the same object is given back - boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item); - if (existing != item) { - // different items, we can use comparison and may avoid lookup - final int cmp = mCallback.compare(existing, item); - if (cmp == 0) { - mData[index] = item; - if (contentsChanged) { - mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); - } - return; - } - } - if (contentsChanged) { - mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item)); - } - // TODO this done in 1 pass to avoid shifting twice. - removeItemAtIndex(index, false); - int newIndex = add(item, false); - if (index != newIndex) { - mCallback.onMoved(index, newIndex); - } - } - - /** - * This method can be used to recalculate the position of the item at the given index, without - * triggering an {@link Callback#onChanged(int, int)} callback. - *

- * If you are editing objects in the list such that their position in the list may change but - * you don't want to trigger an onChange animation, you can use this method to re-position it. - * If the item changes position, SortedList will call {@link Callback#onMoved(int, int)} - * without - * calling {@link Callback#onChanged(int, int)}. - *

- * A sample usage may look like: - * - *

-     *     final int position = mSortedList.indexOf(item);
-     *     item.incrementPriority(); // assume items are sorted by priority
-     *     mSortedList.recalculatePositionOfItemAt(position);
-     * 
- * In the example above, because the sorting criteria of the item has been changed, - * mSortedList.indexOf(item) will not be able to find the item. This is why the code above - * first - * gets the position before editing the item, edits it and informs the SortedList that item - * should be repositioned. - * - * @param index The current index of the Item whose position should be re-calculated. - * @see #updateItemAt(int, Object) - * @see #add(Object) - */ - public void recalculatePositionOfItemAt(int index) { - throwIfInMutationOperation(); - // TODO can be improved - final T item = get(index); - removeItemAtIndex(index, false); - int newIndex = add(item, false); - if (index != newIndex) { - mCallback.onMoved(index, newIndex); - } - } - - /** - * Returns the item at the given index. - * - * @param index The index of the item to retrieve. - * - * @return The item at the given index. - * @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the - * size of the list. - */ - public T get(int index) throws IndexOutOfBoundsException { - if (index >= mSize || index < 0) { - throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is " - + mSize); - } - if (mOldData != null) { - // The call is made from a callback during addAll execution. The data is split - // between mData and mOldData. - if (index >= mNewDataStart) { - return mOldData[index - mNewDataStart + mOldDataStart]; - } - } - return mData[index]; - } - - /** - * Returns the position of the provided item. - * - * @param item The item to query for position. - * - * @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the - * list. - */ - public int indexOf(T item) { - if (mOldData != null) { - int index = findIndexOf(item, mData, 0, mNewDataStart, LOOKUP); - if (index != INVALID_POSITION) { - return index; - } - index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP); - if (index != INVALID_POSITION) { - return index - mOldDataStart + mNewDataStart; - } - return INVALID_POSITION; - } - return findIndexOf(item, mData, 0, mSize, LOOKUP); - } - - private int findIndexOf(T item, T[] mData, int left, int right, int reason) { - while (left < right) { - final int middle = (left + right) / 2; - T myItem = mData[middle]; - final int cmp = mCallback.compare(myItem, item); - if (cmp < 0) { - left = middle + 1; - } else if (cmp == 0) { - if (mCallback.areItemsTheSame(myItem, item)) { - return middle; - } else { - int exact = linearEqualitySearch(item, middle, left, right); - if (reason == INSERTION) { - return exact == INVALID_POSITION ? middle : exact; - } else { - return exact; - } - } - } else { - right = middle; - } - } - return reason == INSERTION ? left : INVALID_POSITION; - } - - private int linearEqualitySearch(T item, int middle, int left, int right) { - // go left - for (int next = middle - 1; next >= left; next--) { - T nextItem = mData[next]; - int cmp = mCallback.compare(nextItem, item); - if (cmp != 0) { - break; - } - if (mCallback.areItemsTheSame(nextItem, item)) { - return next; - } - } - for (int next = middle + 1; next < right; next++) { - T nextItem = mData[next]; - int cmp = mCallback.compare(nextItem, item); - if (cmp != 0) { - break; - } - if (mCallback.areItemsTheSame(nextItem, item)) { - return next; - } - } - return INVALID_POSITION; - } - - private void addToData(int index, T item) { - if (index > mSize) { - throw new IndexOutOfBoundsException( - "cannot add item to " + index + " because size is " + mSize); - } - if (mSize == mData.length) { - // we are at the limit enlarge - T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH); - System.arraycopy(mData, 0, newData, 0, index); - newData[index] = item; - System.arraycopy(mData, index, newData, index + 1, mSize - index); - mData = newData; - } else { - // just shift, we fit - System.arraycopy(mData, index, mData, index + 1, mSize - index); - mData[index] = item; - } - mSize++; - } - - private T[] copyArray(T[] items) { - T[] copy = (T[]) Array.newInstance(mTClass, items.length); - System.arraycopy(items, 0, copy, 0, items.length); - return copy; - } - - /** - * Removes all items from the SortedList. - */ - public void clear() { - throwIfInMutationOperation(); - if (mSize == 0) { - return; - } - final int prevSize = mSize; - Arrays.fill(mData, 0, prevSize, null); - mSize = 0; - mCallback.onRemoved(0, prevSize); - } - - /** - * The class that controls the behavior of the {@link SortedList}. - *

- * It defines how items should be sorted and how duplicates should be handled. - *

- * SortedList calls the callback methods on this class to notify changes about the underlying - * data. - */ - public static abstract class Callback implements Comparator, ListUpdateCallback { - - /** - * Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and - * return how they should be ordered. - * - * @param o1 The first object to compare. - * @param o2 The second object to compare. - * - * @return a negative integer, zero, or a positive integer as the - * first argument is less than, equal to, or greater than the - * second. - */ - @Override - abstract public int compare(T2 o1, T2 o2); - - /** - * Called by the SortedList when the item at the given position is updated. - * - * @param position The position of the item which has been updated. - * @param count The number of items which has changed. - */ - abstract public void onChanged(int position, int count); - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChanged(int position, int count, Object payload) { - onChanged(position, count); - } - - /** - * Called by the SortedList when it wants to check whether two items have the same data - * or not. SortedList uses this information to decide whether it should call - * {@link #onChanged(int, int)} or not. - *

- * SortedList uses this method to check equality instead of {@link Object#equals(Object)} - * so - * that you can change its behavior depending on your UI. - *

- * For example, if you are using SortedList with a - * {@link RecyclerView.Adapter RecyclerView.Adapter}, you should - * return whether the items' visual representations are the same or not. - * - * @param oldItem The previous representation of the object. - * @param newItem The new object that replaces the previous one. - * - * @return True if the contents of the items are the same or false if they are different. - */ - abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem); - - /** - * Called by the SortedList to decide whether two objects represent the same Item or not. - *

- * For example, if your items have unique ids, this method should check their equality. - * - * @param item1 The first item to check. - * @param item2 The second item to check. - * - * @return True if the two items represent the same object or false if they are different. - */ - abstract public boolean areItemsTheSame(T2 item1, T2 item2); - - /** - * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and - * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this - * method to get a payload about the change. - *

- * For example, if you are using {@link Callback} with - * {@link RecyclerView}, you can return the particular field that - * changed in the item and your - * {@link RecyclerView.ItemAnimator ItemAnimator} can use that - * information to run the correct animation. - *

- * Default implementation returns {@code null}. - * - * @param item1 The first item to check. - * @param item2 The second item to check. - * @return A payload object that represents the changes between the two items. - */ - @Nullable - public Object getChangePayload(T2 item1, T2 item2) { - return null; - } - } - - /** - * A callback implementation that can batch notify events dispatched by the SortedList. - *

- * This class can be useful if you want to do multiple operations on a SortedList but don't - * want to dispatch each event one by one, which may result in a performance issue. - *

- * For example, if you are going to add multiple items to a SortedList, BatchedCallback call - * convert individual onInserted(index, 1) calls into one - * onInserted(index, N) if items are added into consecutive indices. This change - * can help RecyclerView resolve changes much more easily. - *

- * If consecutive changes in the SortedList are not suitable for batching, BatchingCallback - * dispatches them as soon as such case is detected. After your edits on the SortedList is - * complete, you must always call {@link BatchedCallback#dispatchLastEvent()} to flush - * all changes to the Callback. - */ - public static class BatchedCallback extends Callback { - - final Callback mWrappedCallback; - private final BatchingListUpdateCallback mBatchingListUpdateCallback; - /** - * Creates a new BatchedCallback that wraps the provided Callback. - * - * @param wrappedCallback The Callback which should received the data change callbacks. - * Other method calls (e.g. {@link #compare(Object, Object)} from - * the SortedList are directly forwarded to this Callback. - */ - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public BatchedCallback(Callback wrappedCallback) { - mWrappedCallback = wrappedCallback; - mBatchingListUpdateCallback = new BatchingListUpdateCallback(mWrappedCallback); - } - - @Override - public int compare(T2 o1, T2 o2) { - return mWrappedCallback.compare(o1, o2); - } - - @Override - public void onInserted(int position, int count) { - mBatchingListUpdateCallback.onInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - mBatchingListUpdateCallback.onRemoved(position, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - mBatchingListUpdateCallback.onMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count) { - mBatchingListUpdateCallback.onChanged(position, count, null); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChanged(int position, int count, Object payload) { - mBatchingListUpdateCallback.onChanged(position, count, payload); - } - - @Override - public boolean areContentsTheSame(T2 oldItem, T2 newItem) { - return mWrappedCallback.areContentsTheSame(oldItem, newItem); - } - - @Override - public boolean areItemsTheSame(T2 item1, T2 item2) { - return mWrappedCallback.areItemsTheSame(item1, item2); - } - - @Nullable - @Override - public Object getChangePayload(T2 item1, T2 item2) { - return mWrappedCallback.getChangePayload(item1, item2); - } - - /** - * This method dispatches any pending event notifications to the wrapped Callback. - * You must always call this method after you are done with editing the SortedList. - */ - public void dispatchLastEvent() { - mBatchingListUpdateCallback.dispatchLastEvent(); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/SortedListAdapterCallback.java b/app/src/main/java/androidx/recyclerview/widget/SortedListAdapterCallback.java deleted file mode 100644 index 639a26a690..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/SortedListAdapterCallback.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; - -/** - * A {@link SortedList.Callback} implementation that can bind a {@link SortedList} to a - * {@link RecyclerView.Adapter}. - */ -public abstract class SortedListAdapterCallback extends SortedList.Callback { - - final RecyclerView.Adapter mAdapter; - - /** - * Creates a {@link SortedList.Callback} that will forward data change events to the provided - * Adapter. - * - * @param adapter The Adapter instance which should receive events from the SortedList. - */ - public SortedListAdapterCallback( - // b/240775049: Cannot annotate properly - @SuppressLint({"UnknownNullness", "MissingNullability"}) - RecyclerView.Adapter adapter) { - mAdapter = adapter; - } - - @Override - public void onInserted(int position, int count) { - mAdapter.notifyItemRangeInserted(position, count); - } - - @Override - public void onRemoved(int position, int count) { - mAdapter.notifyItemRangeRemoved(position, count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - mAdapter.notifyItemMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count) { - mAdapter.notifyItemRangeChanged(position, count); - } - - @Override - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - public void onChanged(int position, int count, Object payload) { - mAdapter.notifyItemRangeChanged(position, count, payload); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/StableIdStorage.java b/app/src/main/java/androidx/recyclerview/widget/StableIdStorage.java deleted file mode 100644 index 8c64384402..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/StableIdStorage.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import androidx.annotation.NonNull; -import androidx.collection.LongSparseArray; - -/** - * Used by {@link ConcatAdapter} to isolate item ids between nested adapters, if necessary. - */ -interface StableIdStorage { - @NonNull - StableIdLookup createStableIdLookup(); - - /** - * Interface that provides {@link NestedAdapterWrapper}s a way to map their local stable ids - * into global stable ids, based on the configuration of the {@link ConcatAdapter}. - */ - interface StableIdLookup { - long localToGlobal(long localId); - } - - /** - * Returns {@link RecyclerView#NO_ID} for all positions. In other words, stable ids are not - * supported. - */ - class NoStableIdStorage implements StableIdStorage { - private final StableIdLookup mNoIdLookup = new StableIdLookup() { - @Override - public long localToGlobal(long localId) { - return RecyclerView.NO_ID; - } - }; - - @NonNull - @Override - public StableIdLookup createStableIdLookup() { - return mNoIdLookup; - } - } - - /** - * A pass-through implementation that reports the stable id in sub adapters as is. - */ - class SharedPoolStableIdStorage implements StableIdStorage { - private final StableIdLookup mSameIdLookup = new StableIdLookup() { - @Override - public long localToGlobal(long localId) { - return localId; - } - }; - - @NonNull - @Override - public StableIdLookup createStableIdLookup() { - return mSameIdLookup; - } - } - - /** - * An isolating implementation that ensures the stable ids among adapters do not conflict with - * each-other. It keeps a mapping for each adapter from its local stable ids to a global domain - * and always replaces the local id w/ a globally available ID to be consistent. - */ - class IsolatedStableIdStorage implements StableIdStorage { - long mNextStableId = 0; - - long obtainId() { - return mNextStableId++; - } - - @NonNull - @Override - public StableIdLookup createStableIdLookup() { - return new WrapperStableIdLookup(); - } - - class WrapperStableIdLookup implements StableIdLookup { - private final LongSparseArray mLocalToGlobalLookup = new LongSparseArray<>(); - - @Override - public long localToGlobal(long localId) { - Long globalId = mLocalToGlobalLookup.get(localId); - if (globalId == null) { - globalId = obtainId(); - mLocalToGlobalLookup.put(localId, globalId); - } - return globalId; - } - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/StaggeredGridLayoutManager.java b/app/src/main/java/androidx/recyclerview/widget/StaggeredGridLayoutManager.java deleted file mode 100644 index 0387e9605e..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/StaggeredGridLayoutManager.java +++ /dev/null @@ -1,3282 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.core.view.ViewCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.List; - -/** - * A LayoutManager that lays out children in a staggered grid formation. - * It supports horizontal & vertical layout as well as an ability to layout children in reverse. - *

- * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps, - * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can - * control this behavior via {@link #setGapStrategy(int)}. - */ -public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements - RecyclerView.SmoothScroller.ScrollVectorProvider { - - private static final String TAG = "StaggeredGridLManager"; - - static final boolean DEBUG = false; - - public static final int HORIZONTAL = RecyclerView.HORIZONTAL; - - public static final int VERTICAL = RecyclerView.VERTICAL; - - /** - * Does not do anything to hide gaps. - */ - public static final int GAP_HANDLING_NONE = 0; - - /** - * @deprecated No longer supported. - */ - @SuppressWarnings("unused") - @Deprecated - public static final int GAP_HANDLING_LAZY = 1; - - /** - * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will - * check if there are gaps in the because of full span items. If it finds, it will re-layout - * and move items to correct positions with animations. - *

- * For example, if LayoutManager ends up with the following layout due to adapter changes: - *

-     * AAA
-     * _BC
-     * DDD
-     * 
- *

- * It will animate to the following state: - *

-     * AAA
-     * BC_
-     * DDD
-     * 
- */ - public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; - - static final int INVALID_OFFSET = Integer.MIN_VALUE; - /** - * While trying to find next view to focus, LayoutManager will not try to scroll more - * than this factor times the total space of the list. If layout is vertical, total space is the - * height minus padding, if layout is horizontal, total space is the width minus padding. - */ - private static final float MAX_SCROLL_FACTOR = 1 / 3f; - - /** - * Number of spans - */ - private int mSpanCount = -1; - - Span[] mSpans; - - /** - * Primary orientation is the layout's orientation, secondary orientation is the orientation - * for spans. Having both makes code much cleaner for calculations. - */ - @NonNull - OrientationHelper mPrimaryOrientation; - @NonNull - OrientationHelper mSecondaryOrientation; - - private int mOrientation; - - /** - * The width or height per span, depending on the orientation. - */ - private int mSizePerSpan; - - @NonNull - private final LayoutState mLayoutState; - - boolean mReverseLayout = false; - - /** - * Aggregated reverse layout value that takes RTL into account. - */ - boolean mShouldReverseLayout = false; - - /** - * Temporary variable used during fill method to check which spans needs to be filled. - */ - private BitSet mRemainingSpans; - - /** - * When LayoutManager needs to scroll to a position, it sets this variable and requests a - * layout which will check this variable and re-layout accordingly. - */ - int mPendingScrollPosition = RecyclerView.NO_POSITION; - - /** - * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is - * called. - */ - int mPendingScrollPositionOffset = INVALID_OFFSET; - - /** - * Keeps the mapping between the adapter positions and spans. This is necessary to provide - * a consistent experience when user scrolls the list. - */ - LazySpanLookup mLazySpanLookup = new LazySpanLookup(); - - /** - * how we handle gaps in UI. - */ - private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS; - - /** - * Saved state needs this information to properly layout on restore. - */ - private boolean mLastLayoutFromEnd; - - /** - * Saved state and onLayout needs this information to re-layout properly - */ - private boolean mLastLayoutRTL; - - /** - * SavedState is not handled until a layout happens. This is where we keep it until next - * layout. - */ - private SavedState mPendingSavedState; - - /** - * Re-used measurement specs. updated by onLayout. - */ - private int mFullSizeSpec; - - /** - * Re-used rectangle to get child decor offsets. - */ - private final Rect mTmpRect = new Rect(); - - /** - * Re-used anchor info. - */ - private final AnchorInfo mAnchorInfo = new AnchorInfo(); - - /** - * If a full span item is invalid / or created in reverse direction; it may create gaps in - * the UI. While laying out, if such case is detected, we set this flag. - *

- * After scrolling stops, we check this flag and if it is set, re-layout. - */ - private boolean mLaidOutInvalidFullSpan = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. - * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} - */ - private boolean mSmoothScrollbarEnabled = true; - - /** - * Temporary array used (solely in {@link #collectAdjacentPrefetchPositions}) for stashing and - * sorting distances to views being prefetched. - */ - private int[] mPrefetchDistances; - - private final Runnable mCheckForGapsRunnable = new Runnable() { - @Override - public void run() { - checkForGaps(); - } - }; - - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". Defaults to single column and vertical. - */ - @SuppressWarnings("unused") - public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setOrientation(properties.orientation); - setSpanCount(properties.spanCount); - setReverseLayout(properties.reverseLayout); - mLayoutState = new LayoutState(); - createOrientationHelpers(); - } - - /** - * Creates a StaggeredGridLayoutManager with given parameters. - * - * @param spanCount If orientation is vertical, spanCount is number of columns. If - * orientation is horizontal, spanCount is number of rows. - * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL} - */ - public StaggeredGridLayoutManager(int spanCount, int orientation) { - mOrientation = orientation; - setSpanCount(spanCount); - mLayoutState = new LayoutState(); - createOrientationHelpers(); - } - - @Override - public boolean isAutoMeasureEnabled() { - return mGapStrategy != GAP_HANDLING_NONE; - } - - private void createOrientationHelpers() { - mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation); - mSecondaryOrientation = OrientationHelper - .createOrientationHelper(this, 1 - mOrientation); - } - - /** - * Checks for gaps in the UI that may be caused by adapter changes. - *

- * When a full span item is laid out in reverse direction, it sets a flag which we check when - * scroll is stopped (or re-layout happens) and re-layout after first valid item. - */ - boolean checkForGaps() { - if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) { - return false; - } - final int minPos, maxPos; - if (mShouldReverseLayout) { - minPos = getLastChildPosition(); - maxPos = getFirstChildPosition(); - } else { - minPos = getFirstChildPosition(); - maxPos = getLastChildPosition(); - } - if (minPos == 0) { - View gapView = hasGapsToFix(); - if (gapView != null) { - mLazySpanLookup.clear(); - requestSimpleAnimationsInNextLayout(); - requestLayout(); - return true; - } - } - if (!mLaidOutInvalidFullSpan) { - return false; - } - int invalidGapDir = mShouldReverseLayout ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; - final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup - .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true); - if (invalidFsi == null) { - mLaidOutInvalidFullSpan = false; - mLazySpanLookup.forceInvalidateAfter(maxPos + 1); - return false; - } - final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup - .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition, - invalidGapDir * -1, true); - if (validFsi == null) { - mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition); - } else { - mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1); - } - requestSimpleAnimationsInNextLayout(); - requestLayout(); - return true; - } - - @Override - public void onScrollStateChanged(int state) { - if (state == RecyclerView.SCROLL_STATE_IDLE) { - checkForGaps(); - } - } - - @Override - public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { - super.onDetachedFromWindow(view, recycler); - - removeCallbacks(mCheckForGapsRunnable); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].clear(); - } - // SGLM will require fresh layout call to recover state after detach - view.requestLayout(); - } - - /** - * Checks for gaps if we've reached to the top of the list. - *

- * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field. - */ - View hasGapsToFix() { - int startChildIndex = 0; - int endChildIndex = getChildCount() - 1; - BitSet mSpansToCheck = new BitSet(mSpanCount); - mSpansToCheck.set(0, mSpanCount, true); - - final int firstChildIndex, childLimit; - final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1; - - if (mShouldReverseLayout) { - firstChildIndex = endChildIndex; - childLimit = startChildIndex - 1; - } else { - firstChildIndex = startChildIndex; - childLimit = endChildIndex + 1; - } - final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1; - for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) { - View child = getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (mSpansToCheck.get(lp.mSpan.mIndex)) { - if (checkSpanForGap(lp.mSpan)) { - return child; - } - mSpansToCheck.clear(lp.mSpan.mIndex); - } - if (lp.mFullSpan) { - continue; // quick reject - } - - if (i + nextChildDiff != childLimit) { - View nextChild = getChildAt(i + nextChildDiff); - boolean compareSpans = false; - if (mShouldReverseLayout) { - // ensure child's end is below nextChild's end - int myEnd = mPrimaryOrientation.getDecoratedEnd(child); - int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild); - if (myEnd < nextEnd) { - return child; //i should have a better position - } else if (myEnd == nextEnd) { - compareSpans = true; - } - } else { - int myStart = mPrimaryOrientation.getDecoratedStart(child); - int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild); - if (myStart > nextStart) { - return child; //i should have a better position - } else if (myStart == nextStart) { - compareSpans = true; - } - } - if (compareSpans) { - // equal, check span indices. - LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams(); - if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) { - return child; - } - } - } - } - // everything looks good - return null; - } - - private boolean checkSpanForGap(Span span) { - if (mShouldReverseLayout) { - if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) { - // if it is full span, it is OK - final View endView = span.mViews.get(span.mViews.size() - 1); - final LayoutParams lp = span.getLayoutParams(endView); - return !lp.mFullSpan; - } - } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) { - // if it is full span, it is OK - final View startView = span.mViews.get(0); - final LayoutParams lp = span.getLayoutParams(startView); - return !lp.mFullSpan; - } - return false; - } - - /** - * Sets the number of spans for the layout. This will invalidate all of the span assignments - * for Views. - *

- * Calling this method will automatically result in a new layout request unless the spanCount - * parameter is equal to current span count. - * - * @param spanCount Number of spans to layout - */ - public void setSpanCount(int spanCount) { - assertNotInLayoutOrScroll(null); - if (spanCount != mSpanCount) { - invalidateSpanAssignments(); - mSpanCount = spanCount; - mRemainingSpans = new BitSet(mSpanCount); - mSpans = new Span[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - mSpans[i] = new Span(i); - } - requestLayout(); - } - } - - /** - * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep - * scroll position if this method is called after views are laid out. - * - * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} - */ - public void setOrientation(int orientation) { - if (orientation != HORIZONTAL && orientation != VERTICAL) { - throw new IllegalArgumentException("invalid orientation."); - } - assertNotInLayoutOrScroll(null); - if (orientation == mOrientation) { - return; - } - mOrientation = orientation; - OrientationHelper tmp = mPrimaryOrientation; - mPrimaryOrientation = mSecondaryOrientation; - mSecondaryOrientation = tmp; - requestLayout(); - } - - /** - * Sets whether LayoutManager should start laying out items from the end of the UI. The order - * items are traversed is not affected by this call. - *

- * For vertical layout, if it is set to true, first item will be at the bottom of - * the list. - *

- * For horizontal layouts, it depends on the layout direction. - * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if - * {@link RecyclerView}} is RTL, it will layout from LTR. - * - * @param reverseLayout Whether layout should be in reverse or not - */ - public void setReverseLayout(boolean reverseLayout) { - assertNotInLayoutOrScroll(null); - if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) { - mPendingSavedState.mReverseLayout = reverseLayout; - } - mReverseLayout = reverseLayout; - requestLayout(); - } - - /** - * Returns the current gap handling strategy for StaggeredGridLayoutManager. - *

- * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps, - * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and - * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details. - *

- * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}. - * - * @return Current gap handling strategy. - * @see #setGapStrategy(int) - * @see #GAP_HANDLING_NONE - * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS - */ - public int getGapStrategy() { - return mGapStrategy; - } - - /** - * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter - * is different than the current strategy, calling this method will trigger a layout request. - * - * @param gapStrategy The new gap handling strategy. Should be - * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link - * #GAP_HANDLING_NONE}. - * @see #getGapStrategy() - */ - public void setGapStrategy(int gapStrategy) { - assertNotInLayoutOrScroll(null); - if (gapStrategy == mGapStrategy) { - return; - } - if (gapStrategy != GAP_HANDLING_NONE - && gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) { - throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE " - + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS"); - } - mGapStrategy = gapStrategy; - requestLayout(); - } - - @Override - public void assertNotInLayoutOrScroll(String message) { - if (mPendingSavedState == null) { - super.assertNotInLayoutOrScroll(message); - } - } - - /** - * Returns the number of spans laid out by StaggeredGridLayoutManager. - * - * @return Number of spans in the layout - */ - public int getSpanCount() { - return mSpanCount; - } - - /** - * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items. - *

- * If you need to cancel current assignments, you can call this method which will clear all - * assignments and request a new layout. - */ - public void invalidateSpanAssignments() { - mLazySpanLookup.clear(); - requestLayout(); - } - - /** - * Calculates the views' layout order. (e.g. from end to start or start to end) - * RTL layout support is applied automatically. So if layout is RTL and - * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left. - */ - private void resolveShouldLayoutReverse() { - // A == B is the same result, but we rather keep it readable - if (mOrientation == VERTICAL || !isLayoutRTL()) { - mShouldReverseLayout = mReverseLayout; - } else { - mShouldReverseLayout = !mReverseLayout; - } - } - - boolean isLayoutRTL() { - return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - /** - * Returns whether views are laid out in reverse order or not. - *

- * Not that this value is not affected by RecyclerView's layout direction. - * - * @return True if layout is reversed, false otherwise - * @see #setReverseLayout(boolean) - */ - public boolean getReverseLayout() { - return mReverseLayout; - } - - @Override - public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { - // we don't like it to wrap content in our non-scroll direction. - final int width, height; - final int horizontalPadding = getPaddingLeft() + getPaddingRight(); - final int verticalPadding = getPaddingTop() + getPaddingBottom(); - if (mOrientation == VERTICAL) { - final int usedHeight = childrenBounds.height() + verticalPadding; - height = chooseSize(hSpec, usedHeight, getMinimumHeight()); - width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding, - getMinimumWidth()); - } else { - final int usedWidth = childrenBounds.width() + horizontalPadding; - width = chooseSize(wSpec, usedWidth, getMinimumWidth()); - height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding, - getMinimumHeight()); - } - setMeasuredDimension(width, height); - } - - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - onLayoutChildren(recycler, state, true); - } - - @Override - public void onAdapterChanged(@Nullable RecyclerView.Adapter oldAdapter, - @Nullable RecyclerView.Adapter newAdapter) { - // RV will remove all views so we should clear all spans and assignments of views into spans - mLazySpanLookup.clear(); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].clear(); - } - } - - private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean shouldCheckForGaps) { - final AnchorInfo anchorInfo = mAnchorInfo; - if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) { - if (state.getItemCount() == 0) { - removeAndRecycleAllViews(recycler); - anchorInfo.reset(); - return; - } - } - - boolean recalculateAnchor = !anchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION - || mPendingSavedState != null; - if (recalculateAnchor) { - anchorInfo.reset(); - if (mPendingSavedState != null) { - applyPendingSavedState(anchorInfo); - } else { - resolveShouldLayoutReverse(); - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - } - updateAnchorInfoForLayout(state, anchorInfo); - anchorInfo.mValid = true; - } - if (mPendingSavedState == null && mPendingScrollPosition == RecyclerView.NO_POSITION) { - if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd - || isLayoutRTL() != mLastLayoutRTL) { - mLazySpanLookup.clear(); - anchorInfo.mInvalidateOffsets = true; - } - } - - if (getChildCount() > 0 && (mPendingSavedState == null - || mPendingSavedState.mSpanOffsetsSize < 1)) { - if (anchorInfo.mInvalidateOffsets) { - for (int i = 0; i < mSpanCount; i++) { - // Scroll to position is set, clear. - mSpans[i].clear(); - if (anchorInfo.mOffset != INVALID_OFFSET) { - mSpans[i].setLine(anchorInfo.mOffset); - } - } - } else { - if (recalculateAnchor || mAnchorInfo.mSpanReferenceLines == null) { - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, - anchorInfo.mOffset); - } - mAnchorInfo.saveSpanReferenceLines(mSpans); - } else { - for (int i = 0; i < mSpanCount; i++) { - final Span span = mSpans[i]; - span.clear(); - span.setLine(mAnchorInfo.mSpanReferenceLines[i]); - } - } - } - } - detachAndScrapAttachedViews(recycler); - mLayoutState.mRecycle = false; - mLaidOutInvalidFullSpan = false; - updateMeasureSpecs(mSecondaryOrientation.getTotalSpace()); - updateLayoutState(anchorInfo.mPosition, state); - if (anchorInfo.mLayoutFromEnd) { - // Layout start. - setLayoutStateDirection(LayoutState.LAYOUT_START); - fill(recycler, mLayoutState, state); - // Layout end. - setLayoutStateDirection(LayoutState.LAYOUT_END); - mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state); - } else { - // Layout end. - setLayoutStateDirection(LayoutState.LAYOUT_END); - fill(recycler, mLayoutState, state); - // Layout start. - setLayoutStateDirection(LayoutState.LAYOUT_START); - mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state); - } - - repositionToWrapContentIfNecessary(); - - if (getChildCount() > 0) { - if (mShouldReverseLayout) { - fixEndGap(recycler, state, true); - fixStartGap(recycler, state, false); - } else { - fixStartGap(recycler, state, true); - fixEndGap(recycler, state, false); - } - } - boolean hasGaps = false; - if (shouldCheckForGaps && !state.isPreLayout()) { - final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE - && getChildCount() > 0 - && (mLaidOutInvalidFullSpan || hasGapsToFix() != null); - if (needToCheckForGaps) { - removeCallbacks(mCheckForGapsRunnable); - if (checkForGaps()) { - hasGaps = true; - } - } - } - if (state.isPreLayout()) { - mAnchorInfo.reset(); - } - mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd; - mLastLayoutRTL = isLayoutRTL(); - if (hasGaps) { - mAnchorInfo.reset(); - onLayoutChildren(recycler, state, false); - } - } - - @Override - public void onLayoutCompleted(RecyclerView.State state) { - super.onLayoutCompleted(state); - mPendingScrollPosition = RecyclerView.NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - mPendingSavedState = null; // we don't need this anymore - mAnchorInfo.reset(); - } - - private void repositionToWrapContentIfNecessary() { - if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) { - return; // nothing to do - } - float maxSize = 0; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - float size = mSecondaryOrientation.getDecoratedMeasurement(child); - if (size < maxSize) { - continue; - } - LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); - if (layoutParams.isFullSpan()) { - size = 1f * size / mSpanCount; - } - maxSize = Math.max(maxSize, size); - } - int before = mSizePerSpan; - int desired = Math.round(maxSize * mSpanCount); - if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) { - desired = Math.min(desired, mSecondaryOrientation.getTotalSpace()); - } - updateMeasureSpecs(desired); - if (mSizePerSpan == before) { - return; // nothing has changed - } - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mFullSpan) { - continue; - } - if (isLayoutRTL() && mOrientation == VERTICAL) { - int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan; - int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before; - child.offsetLeftAndRight(newOffset - prevOffset); - } else { - int newOffset = lp.mSpan.mIndex * mSizePerSpan; - int prevOffset = lp.mSpan.mIndex * before; - if (mOrientation == VERTICAL) { - child.offsetLeftAndRight(newOffset - prevOffset); - } else { - child.offsetTopAndBottom(newOffset - prevOffset); - } - } - } - } - - private void applyPendingSavedState(AnchorInfo anchorInfo) { - if (DEBUG) { - Log.d(TAG, "found saved state: " + mPendingSavedState); - } - if (mPendingSavedState.mSpanOffsetsSize > 0) { - if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) { - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].clear(); - int line = mPendingSavedState.mSpanOffsets[i]; - if (line != Span.INVALID_LINE) { - if (mPendingSavedState.mAnchorLayoutFromEnd) { - line += mPrimaryOrientation.getEndAfterPadding(); - } else { - line += mPrimaryOrientation.getStartAfterPadding(); - } - } - mSpans[i].setLine(line); - } - } else { - mPendingSavedState.invalidateSpanInfo(); - mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition; - } - } - mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL; - setReverseLayout(mPendingSavedState.mReverseLayout); - resolveShouldLayoutReverse(); - - if (mPendingSavedState.mAnchorPosition != RecyclerView.NO_POSITION) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; - } else { - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - } - if (mPendingSavedState.mSpanLookupSize > 1) { - mLazySpanLookup.mData = mPendingSavedState.mSpanLookup; - mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems; - } - } - - void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) { - if (updateAnchorFromPendingData(state, anchorInfo)) { - return; - } - if (updateAnchorFromChildren(state, anchorInfo)) { - return; - } - if (DEBUG) { - Log.d(TAG, "Deciding anchor info from fresh state"); - } - anchorInfo.assignCoordinateFromPadding(); - anchorInfo.mPosition = 0; - } - - private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) { - // We don't recycle views out of adapter order. This way, we can rely on the first or - // last child as the anchor position. - // Layout direction may change but we should select the child depending on the latest - // layout direction. Otherwise, we'll choose the wrong child. - anchorInfo.mPosition = mLastLayoutFromEnd - ? findLastReferenceChildPosition(state.getItemCount()) - : findFirstReferenceChildPosition(state.getItemCount()); - anchorInfo.mOffset = INVALID_OFFSET; - return true; - } - - boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { - // Validate scroll position if exists. - if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) { - return false; - } - // Validate it. - if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { - mPendingScrollPosition = RecyclerView.NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - return false; - } - - if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == RecyclerView.NO_POSITION - || mPendingSavedState.mSpanOffsetsSize < 1) { - // If item is visible, make it fully visible. - final View child = findViewByPosition(mPendingScrollPosition); - if (child != null) { - // Use regular anchor position, offset according to pending offset and target - // child - anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition() - : getFirstChildPosition(); - if (mPendingScrollPositionOffset != INVALID_OFFSET) { - if (anchorInfo.mLayoutFromEnd) { - final int target = mPrimaryOrientation.getEndAfterPadding() - - mPendingScrollPositionOffset; - anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child); - } else { - final int target = mPrimaryOrientation.getStartAfterPadding() - + mPendingScrollPositionOffset; - anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child); - } - return true; - } - - // no offset provided. Decide according to the child location - final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child); - if (childSize > mPrimaryOrientation.getTotalSpace()) { - // Item does not fit. Fix depending on layout direction. - anchorInfo.mOffset = anchorInfo.mLayoutFromEnd - ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - return true; - } - - final int startGap = mPrimaryOrientation.getDecoratedStart(child) - - mPrimaryOrientation.getStartAfterPadding(); - if (startGap < 0) { - anchorInfo.mOffset = -startGap; - return true; - } - final int endGap = mPrimaryOrientation.getEndAfterPadding() - - mPrimaryOrientation.getDecoratedEnd(child); - if (endGap < 0) { - anchorInfo.mOffset = endGap; - return true; - } - // child already visible. just layout as usual - anchorInfo.mOffset = INVALID_OFFSET; - } else { - // Child is not visible. Set anchor coordinate depending on in which direction - // child will be visible. - anchorInfo.mPosition = mPendingScrollPosition; - if (mPendingScrollPositionOffset == INVALID_OFFSET) { - final int position = calculateScrollDirectionForPosition( - anchorInfo.mPosition); - anchorInfo.mLayoutFromEnd = position == LayoutState.LAYOUT_END; - anchorInfo.assignCoordinateFromPadding(); - } else { - anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset); - } - anchorInfo.mInvalidateOffsets = true; - } - } else { - anchorInfo.mOffset = INVALID_OFFSET; - anchorInfo.mPosition = mPendingScrollPosition; - } - return true; - } - - void updateMeasureSpecs(int totalSpace) { - mSizePerSpan = totalSpace / mSpanCount; - //noinspection ResourceType - mFullSizeSpec = View.MeasureSpec.makeMeasureSpec( - totalSpace, mSecondaryOrientation.getMode()); - } - - @Override - public boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null; - } - - /** - * Returns the adapter position of the first visible view for each span. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those - * views are ignored in this method. - * - * @param into An array to put the results into. If you don't provide any, LayoutManager will - * create a new one. - * @return The adapter position of the first visible item in each span. If a span does not have - * any items, {@link RecyclerView#NO_POSITION} is returned for that span. - * @see #findFirstCompletelyVisibleItemPositions(int[]) - * @see #findLastVisibleItemPositions(int[]) - */ - public int[] findFirstVisibleItemPositions(int[] into) { - if (into == null) { - into = new int[mSpanCount]; - } else if (into.length < mSpanCount) { - throw new IllegalArgumentException("Provided int[]'s size must be more than or equal" - + " to span count. Expected:" + mSpanCount + ", array size:" + into.length); - } - for (int i = 0; i < mSpanCount; i++) { - into[i] = mSpans[i].findFirstVisibleItemPosition(); - } - return into; - } - - /** - * Returns the adapter position of the first completely visible view for each span. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those - * views are ignored in this method. - * - * @param into An array to put the results into. If you don't provide any, LayoutManager will - * create a new one. - * @return The adapter position of the first fully visible item in each span. If a span does - * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span. - * @see #findFirstVisibleItemPositions(int[]) - * @see #findLastCompletelyVisibleItemPositions(int[]) - */ - public int[] findFirstCompletelyVisibleItemPositions(int[] into) { - if (into == null) { - into = new int[mSpanCount]; - } else if (into.length < mSpanCount) { - throw new IllegalArgumentException("Provided int[]'s size must be more than or equal" - + " to span count. Expected:" + mSpanCount + ", array size:" + into.length); - } - for (int i = 0; i < mSpanCount; i++) { - into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition(); - } - return into; - } - - /** - * Returns the adapter position of the last visible view for each span. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those - * views are ignored in this method. - * - * @param into An array to put the results into. If you don't provide any, LayoutManager will - * create a new one. - * @return The adapter position of the last visible item in each span. If a span does not have - * any items, {@link RecyclerView#NO_POSITION} is returned for that span. - * @see #findLastCompletelyVisibleItemPositions(int[]) - * @see #findFirstVisibleItemPositions(int[]) - */ - public int[] findLastVisibleItemPositions(int[] into) { - if (into == null) { - into = new int[mSpanCount]; - } else if (into.length < mSpanCount) { - throw new IllegalArgumentException("Provided int[]'s size must be more than or equal" - + " to span count. Expected:" + mSpanCount + ", array size:" + into.length); - } - for (int i = 0; i < mSpanCount; i++) { - into[i] = mSpans[i].findLastVisibleItemPosition(); - } - return into; - } - - /** - * Returns the adapter position of the last completely visible view for each span. - *

- * Note that, this value is not affected by layout orientation or item order traversal. - * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, - * not in the layout. - *

- * If RecyclerView has item decorators, they will be considered in calculations as well. - *

- * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those - * views are ignored in this method. - * - * @param into An array to put the results into. If you don't provide any, LayoutManager will - * create a new one. - * @return The adapter position of the last fully visible item in each span. If a span does not - * have any items, {@link RecyclerView#NO_POSITION} is returned for that span. - * @see #findFirstCompletelyVisibleItemPositions(int[]) - * @see #findLastVisibleItemPositions(int[]) - */ - public int[] findLastCompletelyVisibleItemPositions(int[] into) { - if (into == null) { - into = new int[mSpanCount]; - } else if (into.length < mSpanCount) { - throw new IllegalArgumentException("Provided int[]'s size must be more than or equal" - + " to span count. Expected:" + mSpanCount + ", array size:" + into.length); - } - for (int i = 0; i < mSpanCount; i++) { - into[i] = mSpans[i].findLastCompletelyVisibleItemPosition(); - } - return into; - } - - @Override - public int computeHorizontalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - private int computeScrollOffset(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), - findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled, mShouldReverseLayout); - } - - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeHorizontalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - private int computeScrollExtent(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), - findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled); - } - - @Override - public int computeVerticalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeHorizontalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private int computeScrollRange(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), - findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled); - } - - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp, - boolean alreadyMeasured) { - if (lp.mFullSpan) { - if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(child, mFullSizeSpec, - getChildMeasureSpec( - getHeight(), - getHeightMode(), - getPaddingTop() + getPaddingBottom(), - lp.height, - true), - alreadyMeasured); - } else { - measureChildWithDecorationsAndMargin( - child, - getChildMeasureSpec( - getWidth(), - getWidthMode(), - getPaddingLeft() + getPaddingRight(), - lp.width, - true), - mFullSizeSpec, - alreadyMeasured); - } - } else { - if (mOrientation == VERTICAL) { - // Padding for width measure spec is 0 because left and right padding were already - // factored into mSizePerSpan. - measureChildWithDecorationsAndMargin( - child, - getChildMeasureSpec( - mSizePerSpan, - getWidthMode(), - 0, - lp.width, - false), - getChildMeasureSpec( - getHeight(), - getHeightMode(), - getPaddingTop() + getPaddingBottom(), - lp.height, - true), - alreadyMeasured); - } else { - // Padding for height measure spec is 0 because top and bottom padding were already - // factored into mSizePerSpan. - measureChildWithDecorationsAndMargin( - child, - getChildMeasureSpec( - getWidth(), - getWidthMode(), - getPaddingLeft() + getPaddingRight(), - lp.width, - true), - getChildMeasureSpec( - mSizePerSpan, - getHeightMode(), - 0, - lp.height, - false), - alreadyMeasured); - } - } - } - - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, - int heightSpec, boolean alreadyMeasured) { - calculateItemDecorationsForChild(child, mTmpRect); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left, - lp.rightMargin + mTmpRect.right); - heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top, - lp.bottomMargin + mTmpRect.bottom); - final boolean measure = alreadyMeasured - ? shouldReMeasureChild(child, widthSpec, heightSpec, lp) - : shouldMeasureChild(child, widthSpec, heightSpec, lp); - if (measure) { - child.measure(widthSpec, heightSpec); - } - - } - - private int updateSpecWithExtra(int spec, int startInset, int endInset) { - if (startInset == 0 && endInset == 0) { - return spec; - } - final int mode = View.MeasureSpec.getMode(spec); - if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { - return View.MeasureSpec.makeMeasureSpec( - Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode); - } - return spec; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mPendingSavedState = (SavedState) state; - if (mPendingScrollPosition != RecyclerView.NO_POSITION) { - mPendingSavedState.invalidateAnchorPositionInfo(); - mPendingSavedState.invalidateSpanInfo(); - } - requestLayout(); - } else if (DEBUG) { - Log.d(TAG, "invalid saved state class"); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (mPendingSavedState != null) { - return new SavedState(mPendingSavedState); - } - SavedState state = new SavedState(); - state.mReverseLayout = mReverseLayout; - state.mAnchorLayoutFromEnd = mLastLayoutFromEnd; - state.mLastLayoutRTL = mLastLayoutRTL; - - if (mLazySpanLookup != null && mLazySpanLookup.mData != null) { - state.mSpanLookup = mLazySpanLookup.mData; - state.mSpanLookupSize = state.mSpanLookup.length; - state.mFullSpanItems = mLazySpanLookup.mFullSpanItems; - } else { - state.mSpanLookupSize = 0; - } - - if (getChildCount() > 0) { - state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition() - : getFirstChildPosition(); - state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt(); - state.mSpanOffsetsSize = mSpanCount; - state.mSpanOffsets = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - int line; - if (mLastLayoutFromEnd) { - line = mSpans[i].getEndLine(Span.INVALID_LINE); - if (line != Span.INVALID_LINE) { - line -= mPrimaryOrientation.getEndAfterPadding(); - } - } else { - line = mSpans[i].getStartLine(Span.INVALID_LINE); - if (line != Span.INVALID_LINE) { - line -= mPrimaryOrientation.getStartAfterPadding(); - } - } - state.mSpanOffsets[i] = line; - } - } else { - state.mAnchorPosition = RecyclerView.NO_POSITION; - state.mVisibleAnchorPosition = RecyclerView.NO_POSITION; - state.mSpanOffsetsSize = 0; - } - if (DEBUG) { - Log.d(TAG, "saved state:\n" + state); - } - return state; - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (getChildCount() > 0) { - final View start = findFirstVisibleItemClosestToStart(false); - final View end = findFirstVisibleItemClosestToEnd(false); - if (start == null || end == null) { - return; - } - final int startPos = getPosition(start); - final int endPos = getPosition(end); - if (startPos < endPos) { - event.setFromIndex(startPos); - event.setToIndex(endPos); - } else { - event.setFromIndex(endPos); - event.setToIndex(startPos); - } - } - } - - /** - * Finds the first fully visible child to be used as an anchor child if span count changes when - * state is restored. If no children is fully visible, returns a partially visible child instead - * of returning null. - */ - int findFirstVisibleItemPositionInt() { - final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) : - findFirstVisibleItemClosestToStart(true); - return first == null ? RecyclerView.NO_POSITION : getPosition(first); - } - - /** - * This is for internal use. Not necessarily the child closest to start but the first child - * we find that matches the criteria. - * This method does not do any sorting based on child's start coordinate, instead, it uses - * children order. - */ - View findFirstVisibleItemClosestToStart(boolean fullyVisible) { - final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); - final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); - final int limit = getChildCount(); - View partiallyVisible = null; - for (int i = 0; i < limit; i++) { - final View child = getChildAt(i); - final int childStart = mPrimaryOrientation.getDecoratedStart(child); - final int childEnd = mPrimaryOrientation.getDecoratedEnd(child); - if (childEnd <= boundsStart || childStart >= boundsEnd) { - continue; // not visible at all - } - if (childStart >= boundsStart || !fullyVisible) { - // when checking for start, it is enough even if part of the child's top is visible - // as long as fully visible is not requested. - return child; - } - if (partiallyVisible == null) { - partiallyVisible = child; - } - } - return partiallyVisible; - } - - /** - * This is for internal use. Not necessarily the child closest to bottom but the first child - * we find that matches the criteria. - * This method does not do any sorting based on child's end coordinate, instead, it uses - * children order. - */ - View findFirstVisibleItemClosestToEnd(boolean fullyVisible) { - final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); - final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); - View partiallyVisible = null; - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - final int childStart = mPrimaryOrientation.getDecoratedStart(child); - final int childEnd = mPrimaryOrientation.getDecoratedEnd(child); - if (childEnd <= boundsStart || childStart >= boundsEnd) { - continue; // not visible at all - } - if (childEnd <= boundsEnd || !fullyVisible) { - // when checking for end, it is enough even if part of the child's bottom is visible - // as long as fully visible is not requested. - return child; - } - if (partiallyVisible == null) { - partiallyVisible = child; - } - } - return partiallyVisible; - } - - private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean canOffsetChildren) { - final int maxEndLine = getMaxEnd(Integer.MIN_VALUE); - if (maxEndLine == Integer.MIN_VALUE) { - return; - } - int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine; - int fixOffset; - if (gap > 0) { - fixOffset = -scrollBy(-gap, recycler, state); - } else { - return; // nothing to fix - } - gap -= fixOffset; - if (canOffsetChildren && gap > 0) { - mPrimaryOrientation.offsetChildren(gap); - } - } - - private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean canOffsetChildren) { - final int minStartLine = getMinStart(Integer.MAX_VALUE); - if (minStartLine == Integer.MAX_VALUE) { - return; - } - int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding(); - int fixOffset; - if (gap > 0) { - fixOffset = scrollBy(gap, recycler, state); - } else { - return; // nothing to fix - } - gap -= fixOffset; - if (canOffsetChildren && gap > 0) { - mPrimaryOrientation.offsetChildren(-gap); - } - } - - private void updateLayoutState(int anchorPosition, RecyclerView.State state) { - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition = anchorPosition; - int startExtra = 0; - int endExtra = 0; - if (isSmoothScrolling()) { - final int targetPos = state.getTargetScrollPosition(); - if (targetPos != RecyclerView.NO_POSITION) { - if (mShouldReverseLayout == targetPos < anchorPosition) { - endExtra = mPrimaryOrientation.getTotalSpace(); - } else { - startExtra = mPrimaryOrientation.getTotalSpace(); - } - } - } - - // Line of the furthest row. - final boolean clipToPadding = getClipToPadding(); - if (clipToPadding) { - mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra; - mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra; - } else { - mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra; - mLayoutState.mStartLine = -startExtra; - } - mLayoutState.mStopInFocusable = false; - mLayoutState.mRecycle = true; - mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED - && mPrimaryOrientation.getEnd() == 0; - } - - private void setLayoutStateDirection(int direction) { - mLayoutState.mLayoutDirection = direction; - mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LayoutState.LAYOUT_START)) - ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD; - } - - @Override - public void offsetChildrenHorizontal(int dx) { - super.offsetChildrenHorizontal(dx); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].onOffset(dx); - } - } - - @Override - public void offsetChildrenVertical(int dy) { - super.offsetChildrenVertical(dy); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].onOffset(dy); - } - } - - @Override - public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE); - } - - @Override - public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD); - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - mLazySpanLookup.clear(); - requestLayout(); - } - - @Override - public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { - handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE); - } - - @Override - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, - Object payload) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE); - } - - /** - * Checks whether it should invalidate span assignments in response to an adapter change. - */ - private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) { - int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition(); - final int affectedRangeEnd; // exclusive - final int affectedRangeStart; // inclusive - - if (cmd == AdapterHelper.UpdateOp.MOVE) { - if (positionStart < itemCountOrToPosition) { - affectedRangeEnd = itemCountOrToPosition + 1; - affectedRangeStart = positionStart; - } else { - affectedRangeEnd = positionStart + 1; - affectedRangeStart = itemCountOrToPosition; - } - } else { - affectedRangeStart = positionStart; - affectedRangeEnd = positionStart + itemCountOrToPosition; - } - - mLazySpanLookup.invalidateAfter(affectedRangeStart); - switch (cmd) { - case AdapterHelper.UpdateOp.ADD: - mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition); - break; - case AdapterHelper.UpdateOp.REMOVE: - mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition); - break; - case AdapterHelper.UpdateOp.MOVE: - // TODO optimize - mLazySpanLookup.offsetForRemoval(positionStart, 1); - mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1); - break; - } - - if (affectedRangeEnd <= minPosition) { - return; - } - - int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition(); - if (affectedRangeStart <= maxPosition) { - requestLayout(); - } - } - - private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, - RecyclerView.State state) { - mRemainingSpans.set(0, mSpanCount, true); - // The target position we are trying to reach. - final int targetLine; - - // Line of the furthest row. - if (mLayoutState.mInfinite) { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - targetLine = Integer.MAX_VALUE; - } else { // LAYOUT_START - targetLine = Integer.MIN_VALUE; - } - } else { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - targetLine = layoutState.mEndLine + layoutState.mAvailable; - } else { // LAYOUT_START - targetLine = layoutState.mStartLine - layoutState.mAvailable; - } - } - - updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine); - if (DEBUG) { - Log.d(TAG, "FILLING targetLine: " + targetLine + "," - + "remaining spans:" + mRemainingSpans + ", state: " + layoutState); - } - - // the default coordinate to add new view. - final int defaultNewViewLine = mShouldReverseLayout - ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - boolean added = false; - while (layoutState.hasMore(state) - && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) { - View view = layoutState.next(recycler); - LayoutParams lp = ((LayoutParams) view.getLayoutParams()); - final int position = lp.getViewLayoutPosition(); - final int spanIndex = mLazySpanLookup.getSpan(position); - Span currentSpan; - final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID; - if (assignSpan) { - currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState); - mLazySpanLookup.setSpan(position, currentSpan); - if (DEBUG) { - Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position); - } - } else { - if (DEBUG) { - Log.d(TAG, "using " + spanIndex + " for pos " + position); - } - currentSpan = mSpans[spanIndex]; - } - // assign span before measuring so that item decorators can get updated span index - lp.mSpan = currentSpan; - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - addView(view); - } else { - addView(view, 0); - } - measureChildWithDecorationsAndMargin(view, lp, false); - - final int start; - final int end; - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine) - : currentSpan.getEndLine(defaultNewViewLine); - end = start + mPrimaryOrientation.getDecoratedMeasurement(view); - if (assignSpan && lp.mFullSpan) { - LazySpanLookup.FullSpanItem fullSpanItem; - fullSpanItem = createFullSpanItemFromEnd(start); - fullSpanItem.mGapDir = LayoutState.LAYOUT_START; - fullSpanItem.mPosition = position; - mLazySpanLookup.addFullSpanItem(fullSpanItem); - } - } else { - end = lp.mFullSpan ? getMinStart(defaultNewViewLine) - : currentSpan.getStartLine(defaultNewViewLine); - start = end - mPrimaryOrientation.getDecoratedMeasurement(view); - if (assignSpan && lp.mFullSpan) { - LazySpanLookup.FullSpanItem fullSpanItem; - fullSpanItem = createFullSpanItemFromStart(end); - fullSpanItem.mGapDir = LayoutState.LAYOUT_END; - fullSpanItem.mPosition = position; - mLazySpanLookup.addFullSpanItem(fullSpanItem); - } - } - - // check if this item may create gaps in the future - if (lp.mFullSpan && layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_HEAD) { - if (assignSpan) { - mLaidOutInvalidFullSpan = true; - } else { - final boolean hasInvalidGap; - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - hasInvalidGap = !areAllEndsEqual(); - } else { // layoutState.mLayoutDirection == LAYOUT_START - hasInvalidGap = !areAllStartsEqual(); - } - if (hasInvalidGap) { - final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup - .getFullSpanItem(position); - if (fullSpanItem != null) { - fullSpanItem.mHasUnwantedGapAfter = true; - } - mLaidOutInvalidFullSpan = true; - } - } - } - attachViewToSpans(view, lp, layoutState); - final int otherStart; - final int otherEnd; - if (isLayoutRTL() && mOrientation == VERTICAL) { - otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() : - mSecondaryOrientation.getEndAfterPadding() - - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan; - otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view); - } else { - otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding() - : currentSpan.mIndex * mSizePerSpan - + mSecondaryOrientation.getStartAfterPadding(); - otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view); - } - - if (mOrientation == VERTICAL) { - layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end); - } else { - layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd); - } - - if (lp.mFullSpan) { - updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine); - } else { - updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine); - } - recycle(recycler, mLayoutState); - if (mLayoutState.mStopInFocusable && view.hasFocusable()) { - if (lp.mFullSpan) { - mRemainingSpans.clear(); - } else { - mRemainingSpans.set(currentSpan.mIndex, false); - } - } - added = true; - } - if (!added) { - recycle(recycler, mLayoutState); - } - final int diff; - if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding()); - diff = mPrimaryOrientation.getStartAfterPadding() - minStart; - } else { - final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding()); - diff = maxEnd - mPrimaryOrientation.getEndAfterPadding(); - } - return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0; - } - - private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) { - LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem(); - fsi.mGapPerSpan = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop); - } - return fsi; - } - - private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) { - LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem(); - fsi.mGapPerSpan = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom; - } - return fsi; - } - - private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - if (lp.mFullSpan) { - appendViewToAllSpans(view); - } else { - lp.mSpan.appendToSpan(view); - } - } else { - if (lp.mFullSpan) { - prependViewToAllSpans(view); - } else { - lp.mSpan.prependToSpan(view); - } - } - } - - private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) { - if (!layoutState.mRecycle || layoutState.mInfinite) { - return; - } - if (layoutState.mAvailable == 0) { - // easy, recycle line is still valid - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - recycleFromEnd(recycler, layoutState.mEndLine); - } else { - recycleFromStart(recycler, layoutState.mStartLine); - } - } else { - // scrolling case, recycle line can be shifted by how much space we could cover - // by adding new views - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - // calculate recycle line - int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine); - final int line; - if (scrolled < 0) { - line = layoutState.mEndLine; - } else { - line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable); - } - recycleFromEnd(recycler, line); - } else { - // calculate recycle line - int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine; - final int line; - if (scrolled < 0) { - line = layoutState.mStartLine; - } else { - line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable); - } - recycleFromStart(recycler, line); - } - } - - } - - private void appendViewToAllSpans(View view) { - // traverse in reverse so that we end up assigning full span items to 0 - for (int i = mSpanCount - 1; i >= 0; i--) { - mSpans[i].appendToSpan(view); - } - } - - private void prependViewToAllSpans(View view) { - // traverse in reverse so that we end up assigning full span items to 0 - for (int i = mSpanCount - 1; i >= 0; i--) { - mSpans[i].prependToSpan(view); - } - } - - private void updateAllRemainingSpans(int layoutDir, int targetLine) { - for (int i = 0; i < mSpanCount; i++) { - if (mSpans[i].mViews.isEmpty()) { - continue; - } - updateRemainingSpans(mSpans[i], layoutDir, targetLine); - } - } - - private void updateRemainingSpans(Span span, int layoutDir, int targetLine) { - final int deletedSize = span.getDeletedSize(); - if (layoutDir == LayoutState.LAYOUT_START) { - final int line = span.getStartLine(); - if (line + deletedSize <= targetLine) { - mRemainingSpans.set(span.mIndex, false); - } - } else { - final int line = span.getEndLine(); - if (line - deletedSize >= targetLine) { - mRemainingSpans.set(span.mIndex, false); - } - } - } - - private int getMaxStart(int def) { - int maxStart = mSpans[0].getStartLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanStart = mSpans[i].getStartLine(def); - if (spanStart > maxStart) { - maxStart = spanStart; - } - } - return maxStart; - } - - private int getMinStart(int def) { - int minStart = mSpans[0].getStartLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanStart = mSpans[i].getStartLine(def); - if (spanStart < minStart) { - minStart = spanStart; - } - } - return minStart; - } - - boolean areAllEndsEqual() { - int end = mSpans[0].getEndLine(Span.INVALID_LINE); - for (int i = 1; i < mSpanCount; i++) { - if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) { - return false; - } - } - return true; - } - - boolean areAllStartsEqual() { - int start = mSpans[0].getStartLine(Span.INVALID_LINE); - for (int i = 1; i < mSpanCount; i++) { - if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) { - return false; - } - } - return true; - } - - private int getMaxEnd(int def) { - int maxEnd = mSpans[0].getEndLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanEnd = mSpans[i].getEndLine(def); - if (spanEnd > maxEnd) { - maxEnd = spanEnd; - } - } - return maxEnd; - } - - private int getMinEnd(int def) { - int minEnd = mSpans[0].getEndLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanEnd = mSpans[i].getEndLine(def); - if (spanEnd < minEnd) { - minEnd = spanEnd; - } - } - return minEnd; - } - - private void recycleFromStart(RecyclerView.Recycler recycler, int line) { - while (getChildCount() > 0) { - View child = getChildAt(0); - if (mPrimaryOrientation.getDecoratedEnd(child) <= line - && mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - // Don't recycle the last View in a span not to lose span's start/end lines - if (lp.mFullSpan) { - for (int j = 0; j < mSpanCount; j++) { - if (mSpans[j].mViews.size() == 1) { - return; - } - } - for (int j = 0; j < mSpanCount; j++) { - mSpans[j].popStart(); - } - } else { - if (lp.mSpan.mViews.size() == 1) { - return; - } - lp.mSpan.popStart(); - } - removeAndRecycleView(child, recycler); - } else { - return; // done - } - } - } - - private void recycleFromEnd(RecyclerView.Recycler recycler, int line) { - final int childCount = getChildCount(); - int i; - for (i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mPrimaryOrientation.getDecoratedStart(child) >= line - && mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - // Don't recycle the last View in a span not to lose span's start/end lines - if (lp.mFullSpan) { - for (int j = 0; j < mSpanCount; j++) { - if (mSpans[j].mViews.size() == 1) { - return; - } - } - for (int j = 0; j < mSpanCount; j++) { - mSpans[j].popEnd(); - } - } else { - if (lp.mSpan.mViews.size() == 1) { - return; - } - lp.mSpan.popEnd(); - } - removeAndRecycleView(child, recycler); - } else { - return; // done - } - } - } - - /** - * @return True if last span is the first one we want to fill - */ - private boolean preferLastSpan(int layoutDir) { - if (mOrientation == HORIZONTAL) { - return (layoutDir == LayoutState.LAYOUT_START) != mShouldReverseLayout; - } - return ((layoutDir == LayoutState.LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL(); - } - - /** - * Finds the span for the next view. - */ - private Span getNextSpan(LayoutState layoutState) { - final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection); - final int startIndex, endIndex, diff; - if (preferLastSpan) { - startIndex = mSpanCount - 1; - endIndex = -1; - diff = -1; - } else { - startIndex = 0; - endIndex = mSpanCount; - diff = 1; - } - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - Span min = null; - int minLine = Integer.MAX_VALUE; - final int defaultLine = mPrimaryOrientation.getStartAfterPadding(); - for (int i = startIndex; i != endIndex; i += diff) { - final Span other = mSpans[i]; - int otherLine = other.getEndLine(defaultLine); - if (otherLine < minLine) { - min = other; - minLine = otherLine; - } - } - return min; - } else { - Span max = null; - int maxLine = Integer.MIN_VALUE; - final int defaultLine = mPrimaryOrientation.getEndAfterPadding(); - for (int i = startIndex; i != endIndex; i += diff) { - final Span other = mSpans[i]; - int otherLine = other.getStartLine(defaultLine); - if (otherLine > maxLine) { - max = other; - maxLine = otherLine; - } - } - return max; - } - } - - @Override - public boolean canScrollVertically() { - return mOrientation == VERTICAL; - } - - @Override - public boolean canScrollHorizontally() { - return mOrientation == HORIZONTAL; - } - - @Override - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - return scrollBy(dx, recycler, state); - } - - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - return scrollBy(dy, recycler, state); - } - - private int calculateScrollDirectionForPosition(int position) { - if (getChildCount() == 0) { - return mShouldReverseLayout ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - } - final int firstChildPos = getFirstChildPosition(); - return position < firstChildPos != mShouldReverseLayout ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END; - } - - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - final int direction = calculateScrollDirectionForPosition(targetPosition); - PointF outVector = new PointF(); - if (direction == 0) { - return null; - } - if (mOrientation == HORIZONTAL) { - outVector.x = direction; - outVector.y = 0; - } else { - outVector.x = 0; - outVector.y = direction; - } - return outVector; - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()); - scroller.setTargetPosition(position); - startSmoothScroll(scroller); - } - - @Override - public void scrollToPosition(int position) { - if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) { - mPendingSavedState.invalidateAnchorPositionInfo(); - } - mPendingScrollPosition = position; - mPendingScrollPositionOffset = INVALID_OFFSET; - requestLayout(); - } - - /** - * Scroll to the specified adapter position with the given offset from layout start. - *

- * Note that scroll position change will not be reflected until the next layout call. - *

- * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. - * - * @param position Index (starting at 0) of the reference item. - * @param offset The distance (in pixels) between the start edge of the item view and - * start edge of the RecyclerView. - * @see #setReverseLayout(boolean) - * @see #scrollToPosition(int) - */ - public void scrollToPositionWithOffset(int position, int offset) { - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchorPositionInfo(); - } - mPendingScrollPosition = position; - mPendingScrollPositionOffset = offset; - requestLayout(); - } - - /** @hide */ - @Override - @RestrictTo(LIBRARY) - public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, - LayoutPrefetchRegistry layoutPrefetchRegistry) { - /* This method uses the simplifying assumption that the next N items (where N = span count) - * will be assigned, one-to-one, to spans, where ordering is based on which span extends - * least beyond the viewport. - * - * While this simplified model will be incorrect in some cases, it's difficult to know - * item heights, or whether individual items will be full span prior to construction. - * - * While this greedy estimation approach may underestimate the distance to prefetch items, - * it's very unlikely to overestimate them, so distances can be conservatively used to know - * the soonest (in terms of scroll distance) a prefetched view may come on screen. - */ - int delta = (mOrientation == HORIZONTAL) ? dx : dy; - if (getChildCount() == 0 || delta == 0) { - // can't support this scroll, so don't bother prefetching - return; - } - prepareLayoutStateForDelta(delta, state); - - // build sorted list of distances to end of each span (though we don't care which is which) - if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) { - mPrefetchDistances = new int[mSpanCount]; - } - - int itemPrefetchCount = 0; - for (int i = 0; i < mSpanCount; i++) { - // compute number of pixels past the edge of the viewport that the current span extends - int distance = mLayoutState.mItemDirection == LayoutState.LAYOUT_START - ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine) - : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine; - if (distance >= 0) { - // span extends to the edge, so prefetch next item - mPrefetchDistances[itemPrefetchCount] = distance; - itemPrefetchCount++; - } - } - Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount); - - // then assign them in order to the next N views (where N = span count) - for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) { - layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, - mPrefetchDistances[i]); - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - } - } - - void prepareLayoutStateForDelta(int delta, RecyclerView.State state) { - final int referenceChildPosition; - final int layoutDir; - if (delta > 0) { // layout towards end - layoutDir = LayoutState.LAYOUT_END; - referenceChildPosition = getLastChildPosition(); - } else { - layoutDir = LayoutState.LAYOUT_START; - referenceChildPosition = getFirstChildPosition(); - } - mLayoutState.mRecycle = true; - updateLayoutState(referenceChildPosition, state); - setLayoutStateDirection(layoutDir); - mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; - mLayoutState.mAvailable = Math.abs(delta); - } - - int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getChildCount() == 0 || dt == 0) { - return 0; - } - - prepareLayoutStateForDelta(dt, state); - int consumed = fill(recycler, mLayoutState, state); - final int available = mLayoutState.mAvailable; - final int totalScroll; - if (available < consumed) { - totalScroll = dt; - } else if (dt < 0) { - totalScroll = -consumed; - } else { // dt > 0 - totalScroll = consumed; - } - if (DEBUG) { - Log.d(TAG, "asked " + dt + " scrolled" + totalScroll); - } - - mPrimaryOrientation.offsetChildren(-totalScroll); - // always reset this if we scroll for a proper save instance state - mLastLayoutFromEnd = mShouldReverseLayout; - mLayoutState.mAvailable = 0; - recycle(recycler, mLayoutState); - return totalScroll; - } - - int getLastChildPosition() { - final int childCount = getChildCount(); - return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1)); - } - - int getFirstChildPosition() { - final int childCount = getChildCount(); - return childCount == 0 ? 0 : getPosition(getChildAt(0)); - } - - /** - * Finds the first View that can be used as an anchor View. - * - * @return Position of the View or 0 if it cannot find any such View. - */ - private int findFirstReferenceChildPosition(int itemCount) { - final int limit = getChildCount(); - for (int i = 0; i < limit; i++) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - return position; - } - } - return 0; - } - - /** - * Finds the last View that can be used as an anchor View. - * - * @return Position of the View or 0 if it cannot find any such View. - */ - private int findLastReferenceChildPosition(int itemCount) { - for (int i = getChildCount() - 1; i >= 0; i--) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - return position; - } - } - return 0; - } - - @SuppressWarnings("deprecation") - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - if (mOrientation == HORIZONTAL) { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT); - } else { - return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { - return new LayoutParams(c, attrs); - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { - if (lp instanceof ViewGroup.MarginLayoutParams) { - return new LayoutParams((ViewGroup.MarginLayoutParams) lp); - } else { - return new LayoutParams(lp); - } - } - - @Override - public boolean checkLayoutParams(RecyclerView.LayoutParams lp) { - return lp instanceof LayoutParams; - } - - public int getOrientation() { - return mOrientation; - } - - @Nullable - @Override - public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (getChildCount() == 0) { - return null; - } - - final View directChild = findContainingItemView(focused); - if (directChild == null) { - return null; - } - - resolveShouldLayoutReverse(); - final int layoutDir = convertFocusDirectionToLayoutDirection(direction); - if (layoutDir == LayoutState.INVALID_LAYOUT) { - return null; - } - LayoutParams prevFocusLayoutParams = (LayoutParams) directChild.getLayoutParams(); - boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan; - final Span prevFocusSpan = prevFocusLayoutParams.mSpan; - final int referenceChildPosition; - if (layoutDir == LayoutState.LAYOUT_END) { // layout towards end - referenceChildPosition = getLastChildPosition(); - } else { - referenceChildPosition = getFirstChildPosition(); - } - updateLayoutState(referenceChildPosition, state); - setLayoutStateDirection(layoutDir); - - mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; - mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace()); - mLayoutState.mStopInFocusable = true; - mLayoutState.mRecycle = false; - fill(recycler, mLayoutState, state); - mLastLayoutFromEnd = mShouldReverseLayout; - if (!prevFocusFullSpan) { - View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir); - if (view != null && view != directChild) { - return view; - } - } - - // either could not find from the desired span or prev view is full span. - // traverse all spans - if (preferLastSpan(layoutDir)) { - for (int i = mSpanCount - 1; i >= 0; i--) { - View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir); - if (view != null && view != directChild) { - return view; - } - } - } else { - for (int i = 0; i < mSpanCount; i++) { - View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir); - if (view != null && view != directChild) { - return view; - } - } - } - - // Could not find any focusable views from any of the existing spans. Now start the search - // to find the best unfocusable candidate to become visible on the screen next. The search - // is done in the same fashion: first, check the views in the desired span and if no - // candidate is found, traverse the views in all the remaining spans. - boolean shouldSearchFromStart = !mReverseLayout == (layoutDir == LayoutState.LAYOUT_START); - View unfocusableCandidate = null; - if (!prevFocusFullSpan) { - unfocusableCandidate = findViewByPosition(shouldSearchFromStart - ? prevFocusSpan.findFirstPartiallyVisibleItemPosition() : - prevFocusSpan.findLastPartiallyVisibleItemPosition()); - if (unfocusableCandidate != null && unfocusableCandidate != directChild) { - return unfocusableCandidate; - } - } - - if (preferLastSpan(layoutDir)) { - for (int i = mSpanCount - 1; i >= 0; i--) { - if (i == prevFocusSpan.mIndex) { - continue; - } - unfocusableCandidate = findViewByPosition(shouldSearchFromStart - ? mSpans[i].findFirstPartiallyVisibleItemPosition() : - mSpans[i].findLastPartiallyVisibleItemPosition()); - if (unfocusableCandidate != null && unfocusableCandidate != directChild) { - return unfocusableCandidate; - } - } - } else { - for (int i = 0; i < mSpanCount; i++) { - unfocusableCandidate = findViewByPosition(shouldSearchFromStart - ? mSpans[i].findFirstPartiallyVisibleItemPosition() : - mSpans[i].findLastPartiallyVisibleItemPosition()); - if (unfocusableCandidate != null && unfocusableCandidate != directChild) { - return unfocusableCandidate; - } - } - } - return null; - } - - /** - * Converts a focusDirection to orientation. - * - * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * or 0 for not applicable - * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction - * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. - */ - private int convertFocusDirectionToLayoutDirection(int focusDirection) { - switch (focusDirection) { - case View.FOCUS_BACKWARD: - if (mOrientation == VERTICAL) { - return LayoutState.LAYOUT_START; - } else if (isLayoutRTL()) { - return LayoutState.LAYOUT_END; - } else { - return LayoutState.LAYOUT_START; - } - case View.FOCUS_FORWARD: - if (mOrientation == VERTICAL) { - return LayoutState.LAYOUT_END; - } else if (isLayoutRTL()) { - return LayoutState.LAYOUT_START; - } else { - return LayoutState.LAYOUT_END; - } - case View.FOCUS_UP: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_DOWN: - return mOrientation == VERTICAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_LEFT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START - : LayoutState.INVALID_LAYOUT; - case View.FOCUS_RIGHT: - return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END - : LayoutState.INVALID_LAYOUT; - default: - if (DEBUG) { - Log.d(TAG, "Unknown focus request:" + focusDirection); - } - return LayoutState.INVALID_LAYOUT; - } - - } - - /** - * LayoutParams used by StaggeredGridLayoutManager. - *

- * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the - * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is - * expected to fill all of the space given to it. - */ - public static class LayoutParams extends RecyclerView.LayoutParams { - - /** - * Span Id for Views that are not laid out yet. - */ - public static final int INVALID_SPAN_ID = -1; - - // Package scope to be able to access from tests. - Span mSpan; - - boolean mFullSpan; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(RecyclerView.LayoutParams source) { - super(source); - } - - /** - * When set to true, the item will layout using all span area. That means, if orientation - * is vertical, the view will have full width; if orientation is horizontal, the view will - * have full height. - * - * @param fullSpan True if this item should traverse all spans. - * @see #isFullSpan() - */ - public void setFullSpan(boolean fullSpan) { - mFullSpan = fullSpan; - } - - /** - * Returns whether this View occupies all available spans or just one. - * - * @return True if the View occupies all spans or false otherwise. - * @see #setFullSpan(boolean) - */ - public boolean isFullSpan() { - return mFullSpan; - } - - /** - * Returns the Span index to which this View is assigned. - * - * @return The Span index of the View. If View is not yet assigned to any span, returns - * {@link #INVALID_SPAN_ID}. - */ - public final int getSpanIndex() { - if (mSpan == null) { - return INVALID_SPAN_ID; - } - return mSpan.mIndex; - } - } - - // Package scoped to access from tests. - class Span { - - static final int INVALID_LINE = Integer.MIN_VALUE; - ArrayList mViews = new ArrayList<>(); - int mCachedStart = INVALID_LINE; - int mCachedEnd = INVALID_LINE; - int mDeletedSize = 0; - final int mIndex; - - Span(int index) { - mIndex = index; - } - - int getStartLine(int def) { - if (mCachedStart != INVALID_LINE) { - return mCachedStart; - } - if (mViews.size() == 0) { - return def; - } - calculateCachedStart(); - return mCachedStart; - } - - void calculateCachedStart() { - final View startView = mViews.get(0); - final LayoutParams lp = getLayoutParams(startView); - mCachedStart = mPrimaryOrientation.getDecoratedStart(startView); - if (lp.mFullSpan) { - LazySpanLookup.FullSpanItem fsi = mLazySpanLookup - .getFullSpanItem(lp.getViewLayoutPosition()); - if (fsi != null && fsi.mGapDir == LayoutState.LAYOUT_START) { - mCachedStart -= fsi.getGapForSpan(mIndex); - } - } - } - - // Use this one when default value does not make sense and not having a value means a bug. - int getStartLine() { - if (mCachedStart != INVALID_LINE) { - return mCachedStart; - } - calculateCachedStart(); - return mCachedStart; - } - - int getEndLine(int def) { - if (mCachedEnd != INVALID_LINE) { - return mCachedEnd; - } - final int size = mViews.size(); - if (size == 0) { - return def; - } - calculateCachedEnd(); - return mCachedEnd; - } - - void calculateCachedEnd() { - final View endView = mViews.get(mViews.size() - 1); - final LayoutParams lp = getLayoutParams(endView); - mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView); - if (lp.mFullSpan) { - LazySpanLookup.FullSpanItem fsi = mLazySpanLookup - .getFullSpanItem(lp.getViewLayoutPosition()); - if (fsi != null && fsi.mGapDir == LayoutState.LAYOUT_END) { - mCachedEnd += fsi.getGapForSpan(mIndex); - } - } - } - - // Use this one when default value does not make sense and not having a value means a bug. - int getEndLine() { - if (mCachedEnd != INVALID_LINE) { - return mCachedEnd; - } - calculateCachedEnd(); - return mCachedEnd; - } - - void prependToSpan(View view) { - LayoutParams lp = getLayoutParams(view); - lp.mSpan = this; - mViews.add(0, view); - mCachedStart = INVALID_LINE; - if (mViews.size() == 1) { - mCachedEnd = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view); - } - } - - void appendToSpan(View view) { - LayoutParams lp = getLayoutParams(view); - lp.mSpan = this; - mViews.add(view); - mCachedEnd = INVALID_LINE; - if (mViews.size() == 1) { - mCachedStart = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view); - } - } - - // Useful method to preserve positions on a re-layout. - void cacheReferenceLineAndClear(boolean reverseLayout, int offset) { - int reference; - if (reverseLayout) { - reference = getEndLine(INVALID_LINE); - } else { - reference = getStartLine(INVALID_LINE); - } - clear(); - if (reference == INVALID_LINE) { - return; - } - if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) - || (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) { - return; - } - if (offset != INVALID_OFFSET) { - reference += offset; - } - mCachedStart = mCachedEnd = reference; - } - - void clear() { - mViews.clear(); - invalidateCache(); - mDeletedSize = 0; - } - - void invalidateCache() { - mCachedStart = INVALID_LINE; - mCachedEnd = INVALID_LINE; - } - - void setLine(int line) { - mCachedEnd = mCachedStart = line; - } - - void popEnd() { - final int size = mViews.size(); - View end = mViews.remove(size - 1); - final LayoutParams lp = getLayoutParams(end); - lp.mSpan = null; - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end); - } - if (size == 1) { - mCachedStart = INVALID_LINE; - } - mCachedEnd = INVALID_LINE; - } - - void popStart() { - View start = mViews.remove(0); - final LayoutParams lp = getLayoutParams(start); - lp.mSpan = null; - if (mViews.size() == 0) { - mCachedEnd = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start); - } - mCachedStart = INVALID_LINE; - } - - public int getDeletedSize() { - return mDeletedSize; - } - - LayoutParams getLayoutParams(View view) { - return (LayoutParams) view.getLayoutParams(); - } - - void onOffset(int dt) { - if (mCachedStart != INVALID_LINE) { - mCachedStart += dt; - } - if (mCachedEnd != INVALID_LINE) { - mCachedEnd += dt; - } - } - - public int findFirstVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(mViews.size() - 1, -1, false) - : findOneVisibleChild(0, mViews.size(), false); - } - - public int findFirstPartiallyVisibleItemPosition() { - return mReverseLayout - ? findOnePartiallyVisibleChild(mViews.size() - 1, -1, true) - : findOnePartiallyVisibleChild(0, mViews.size(), true); - } - - public int findFirstCompletelyVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(mViews.size() - 1, -1, true) - : findOneVisibleChild(0, mViews.size(), true); - } - - public int findLastVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(0, mViews.size(), false) - : findOneVisibleChild(mViews.size() - 1, -1, false); - } - - public int findLastPartiallyVisibleItemPosition() { - return mReverseLayout - ? findOnePartiallyVisibleChild(0, mViews.size(), true) - : findOnePartiallyVisibleChild(mViews.size() - 1, -1, true); - } - - public int findLastCompletelyVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(0, mViews.size(), true) - : findOneVisibleChild(mViews.size() - 1, -1, true); - } - - /** - * Returns the first view within this span that is partially or fully visible. Partially - * visible refers to a view that overlaps but is not fully contained within RV's padded - * bounded area. This view returned can be defined to have an area of overlap strictly - * greater than zero if acceptEndPointInclusion is false. If true, the view's endpoint - * inclusion is enough to consider it partially visible. The latter case can then refer to - * an out-of-bounds view positioned right at the top (or bottom) boundaries of RV's padded - * area. This is used e.g. inside - * {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} for - * calculating the next unfocusable child to become visible on the screen. - * @param fromIndex The child position index to start the search from. - * @param toIndex The child position index to end the search at. - * @param completelyVisible True if we have to only consider completely visible views, - * false otherwise. - * @param acceptCompletelyVisible True if we can consider both partially or fully visible - * views, false, if only a partially visible child should be - * returned. - * @param acceptEndPointInclusion If the view's endpoint intersection with RV's padded - * bounded area is enough to consider it partially visible, - * false otherwise - * @return The adapter position of the first view that's either partially or fully visible. - * {@link RecyclerView#NO_POSITION} if no such view is found. - */ - int findOnePartiallyOrCompletelyVisibleChild(int fromIndex, int toIndex, - boolean completelyVisible, - boolean acceptCompletelyVisible, - boolean acceptEndPointInclusion) { - final int start = mPrimaryOrientation.getStartAfterPadding(); - final int end = mPrimaryOrientation.getEndAfterPadding(); - final int next = toIndex > fromIndex ? 1 : -1; - for (int i = fromIndex; i != toIndex; i += next) { - final View child = mViews.get(i); - final int childStart = mPrimaryOrientation.getDecoratedStart(child); - final int childEnd = mPrimaryOrientation.getDecoratedEnd(child); - boolean childStartInclusion = acceptEndPointInclusion ? (childStart <= end) - : (childStart < end); - boolean childEndInclusion = acceptEndPointInclusion ? (childEnd >= start) - : (childEnd > start); - if (childStartInclusion && childEndInclusion) { - if (completelyVisible && acceptCompletelyVisible) { - // the child has to be completely visible to be returned. - if (childStart >= start && childEnd <= end) { - return getPosition(child); - } - } else if (acceptCompletelyVisible) { - // can return either a partially or completely visible child. - return getPosition(child); - } else if (childStart < start || childEnd > end) { - // should return a partially visible child if exists and a completely - // visible child is not acceptable in this case. - return getPosition(child); - } - } - } - return RecyclerView.NO_POSITION; - } - - int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { - return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, completelyVisible, - true, false); - } - - int findOnePartiallyVisibleChild(int fromIndex, int toIndex, - boolean acceptEndPointInclusion) { - return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, false, false, - acceptEndPointInclusion); - } - - /** - * Depending on the layout direction, returns the View that is after the given position. - */ - public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) { - View candidate = null; - if (layoutDir == LayoutState.LAYOUT_START) { - final int limit = mViews.size(); - for (int i = 0; i < limit; i++) { - final View view = mViews.get(i); - if ((mReverseLayout && getPosition(view) <= referenceChildPosition) - || (!mReverseLayout && getPosition(view) >= referenceChildPosition)) { - break; - } - if (view.hasFocusable()) { - candidate = view; - } else { - break; - } - } - } else { - for (int i = mViews.size() - 1; i >= 0; i--) { - final View view = mViews.get(i); - if ((mReverseLayout && getPosition(view) >= referenceChildPosition) - || (!mReverseLayout && getPosition(view) <= referenceChildPosition)) { - break; - } - if (view.hasFocusable()) { - candidate = view; - } else { - break; - } - } - } - return candidate; - } - } - - /** - * An array of mappings from adapter position to span. - * This only grows when a write happens and it grows up to the size of the adapter. - */ - static class LazySpanLookup { - - private static final int MIN_SIZE = 10; - int[] mData; - List mFullSpanItems; - - - /** - * Invalidates everything after this position, including full span information - */ - int forceInvalidateAfter(int position) { - if (mFullSpanItems != null) { - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= position) { - mFullSpanItems.remove(i); - } - } - } - return invalidateAfter(position); - } - - /** - * returns end position for invalidation. - */ - int invalidateAfter(int position) { - if (mData == null) { - return RecyclerView.NO_POSITION; - } - if (position >= mData.length) { - return RecyclerView.NO_POSITION; - } - int endPosition = invalidateFullSpansAfter(position); - if (endPosition == RecyclerView.NO_POSITION) { - Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID); - return mData.length; - } else { - // Just invalidate items in between `position` and the next full span item, or the - // end of the tracked spans in mData if it's not been lengthened yet. - final int invalidateToIndex = Math.min(endPosition + 1, mData.length); - Arrays.fill(mData, position, invalidateToIndex, LayoutParams.INVALID_SPAN_ID); - return invalidateToIndex; - } - } - - int getSpan(int position) { - if (mData == null || position >= mData.length) { - return LayoutParams.INVALID_SPAN_ID; - } else { - return mData[position]; - } - } - - void setSpan(int position, Span span) { - ensureSize(position); - mData[position] = span.mIndex; - } - - int sizeForPosition(int position) { - int len = mData.length; - while (len <= position) { - len *= 2; - } - return len; - } - - void ensureSize(int position) { - if (mData == null) { - mData = new int[Math.max(position, MIN_SIZE) + 1]; - Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID); - } else if (position >= mData.length) { - int[] old = mData; - mData = new int[sizeForPosition(position)]; - System.arraycopy(old, 0, mData, 0, old.length); - Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID); - } - } - - void clear() { - if (mData != null) { - Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID); - } - mFullSpanItems = null; - } - - void offsetForRemoval(int positionStart, int itemCount) { - if (mData == null || positionStart >= mData.length) { - return; - } - ensureSize(positionStart + itemCount); - System.arraycopy(mData, positionStart + itemCount, mData, positionStart, - mData.length - positionStart - itemCount); - Arrays.fill(mData, mData.length - itemCount, mData.length, - LayoutParams.INVALID_SPAN_ID); - offsetFullSpansForRemoval(positionStart, itemCount); - } - - private void offsetFullSpansForRemoval(int positionStart, int itemCount) { - if (mFullSpanItems == null) { - return; - } - final int end = positionStart + itemCount; - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition < positionStart) { - continue; - } - if (fsi.mPosition < end) { - mFullSpanItems.remove(i); - } else { - fsi.mPosition -= itemCount; - } - } - } - - void offsetForAddition(int positionStart, int itemCount) { - if (mData == null || positionStart >= mData.length) { - return; - } - ensureSize(positionStart + itemCount); - System.arraycopy(mData, positionStart, mData, positionStart + itemCount, - mData.length - positionStart - itemCount); - Arrays.fill(mData, positionStart, positionStart + itemCount, - LayoutParams.INVALID_SPAN_ID); - offsetFullSpansForAddition(positionStart, itemCount); - } - - private void offsetFullSpansForAddition(int positionStart, int itemCount) { - if (mFullSpanItems == null) { - return; - } - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition < positionStart) { - continue; - } - fsi.mPosition += itemCount; - } - } - - /** - * Returns when invalidation should end. e.g. hitting a full span position. - * Returned position SHOULD BE invalidated. - */ - private int invalidateFullSpansAfter(int position) { - if (mFullSpanItems == null) { - return RecyclerView.NO_POSITION; - } - final FullSpanItem item = getFullSpanItem(position); - // if there is an fsi at this position, get rid of it. - if (item != null) { - mFullSpanItems.remove(item); - } - int nextFsiIndex = -1; - final int count = mFullSpanItems.size(); - for (int i = 0; i < count; i++) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= position) { - nextFsiIndex = i; - break; - } - } - if (nextFsiIndex != -1) { - FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex); - mFullSpanItems.remove(nextFsiIndex); - return fsi.mPosition; - } - return RecyclerView.NO_POSITION; - } - - public void addFullSpanItem(FullSpanItem fullSpanItem) { - if (mFullSpanItems == null) { - mFullSpanItems = new ArrayList<>(); - } - final int size = mFullSpanItems.size(); - for (int i = 0; i < size; i++) { - FullSpanItem other = mFullSpanItems.get(i); - if (other.mPosition == fullSpanItem.mPosition) { - if (DEBUG) { - throw new IllegalStateException("two fsis for same position"); - } else { - mFullSpanItems.remove(i); - } - } - if (other.mPosition >= fullSpanItem.mPosition) { - mFullSpanItems.add(i, fullSpanItem); - return; - } - } - // if it is not added to a position. - mFullSpanItems.add(fullSpanItem); - } - - public FullSpanItem getFullSpanItem(int position) { - if (mFullSpanItems == null) { - return null; - } - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - final FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition == position) { - return fsi; - } - } - return null; - } - - /** - * @param minPos inclusive - * @param maxPos exclusive - * @param gapDir if not 0, returns FSIs on in that direction - * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be - * returned even if its gap direction does not match. - */ - public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir, - boolean hasUnwantedGapAfter) { - if (mFullSpanItems == null) { - return null; - } - final int limit = mFullSpanItems.size(); - for (int i = 0; i < limit; i++) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= maxPos) { - return null; - } - if (fsi.mPosition >= minPos - && (gapDir == 0 || fsi.mGapDir == gapDir - || (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) { - return fsi; - } - } - return null; - } - - /** - * We keep information about full span items because they may create gaps in the UI. - */ - @SuppressLint("BanParcelableUsage") - static class FullSpanItem implements Parcelable { - - int mPosition; - int mGapDir; - int[] mGapPerSpan; - // A full span may be laid out in primary direction but may have gaps due to - // invalidation of views after it. This is recorded during a reverse scroll and if - // view is still on the screen after scroll stops, we have to recalculate layout - boolean mHasUnwantedGapAfter; - - FullSpanItem(Parcel in) { - mPosition = in.readInt(); - mGapDir = in.readInt(); - mHasUnwantedGapAfter = in.readInt() == 1; - int spanCount = in.readInt(); - if (spanCount > 0) { - mGapPerSpan = new int[spanCount]; - in.readIntArray(mGapPerSpan); - } - } - - FullSpanItem() { - } - - int getGapForSpan(int spanIndex) { - return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex]; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mPosition); - dest.writeInt(mGapDir); - dest.writeInt(mHasUnwantedGapAfter ? 1 : 0); - if (mGapPerSpan != null && mGapPerSpan.length > 0) { - dest.writeInt(mGapPerSpan.length); - dest.writeIntArray(mGapPerSpan); - } else { - dest.writeInt(0); - } - } - - @Override - public String toString() { - return "FullSpanItem{" - + "mPosition=" + mPosition - + ", mGapDir=" + mGapDir - + ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter - + ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) - + '}'; - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public FullSpanItem createFromParcel(Parcel in) { - return new FullSpanItem(in); - } - - @Override - public FullSpanItem[] newArray(int size) { - return new FullSpanItem[size]; - } - }; - } - } - - /** - * @hide - */ - @RestrictTo(LIBRARY) - @SuppressLint("BanParcelableUsage") - public static class SavedState implements Parcelable { - - int mAnchorPosition; - int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated - int mSpanOffsetsSize; - int[] mSpanOffsets; - int mSpanLookupSize; - int[] mSpanLookup; - List mFullSpanItems; - boolean mReverseLayout; - boolean mAnchorLayoutFromEnd; - boolean mLastLayoutRTL; - - public SavedState() { - } - - SavedState(Parcel in) { - mAnchorPosition = in.readInt(); - mVisibleAnchorPosition = in.readInt(); - mSpanOffsetsSize = in.readInt(); - if (mSpanOffsetsSize > 0) { - mSpanOffsets = new int[mSpanOffsetsSize]; - in.readIntArray(mSpanOffsets); - } - - mSpanLookupSize = in.readInt(); - if (mSpanLookupSize > 0) { - mSpanLookup = new int[mSpanLookupSize]; - in.readIntArray(mSpanLookup); - } - mReverseLayout = in.readInt() == 1; - mAnchorLayoutFromEnd = in.readInt() == 1; - mLastLayoutRTL = in.readInt() == 1; - @SuppressWarnings("unchecked") - List fullSpanItems = - in.readArrayList(LazySpanLookup.FullSpanItem.class.getClassLoader()); - mFullSpanItems = fullSpanItems; - } - - public SavedState(SavedState other) { - mSpanOffsetsSize = other.mSpanOffsetsSize; - mAnchorPosition = other.mAnchorPosition; - mVisibleAnchorPosition = other.mVisibleAnchorPosition; - mSpanOffsets = other.mSpanOffsets; - mSpanLookupSize = other.mSpanLookupSize; - mSpanLookup = other.mSpanLookup; - mReverseLayout = other.mReverseLayout; - mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; - mLastLayoutRTL = other.mLastLayoutRTL; - mFullSpanItems = other.mFullSpanItems; - } - - void invalidateSpanInfo() { - mSpanOffsets = null; - mSpanOffsetsSize = 0; - mSpanLookupSize = 0; - mSpanLookup = null; - mFullSpanItems = null; - } - - void invalidateAnchorPositionInfo() { - mSpanOffsets = null; - mSpanOffsetsSize = 0; - mAnchorPosition = RecyclerView.NO_POSITION; - mVisibleAnchorPosition = RecyclerView.NO_POSITION; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAnchorPosition); - dest.writeInt(mVisibleAnchorPosition); - dest.writeInt(mSpanOffsetsSize); - if (mSpanOffsetsSize > 0) { - dest.writeIntArray(mSpanOffsets); - } - dest.writeInt(mSpanLookupSize); - if (mSpanLookupSize > 0) { - dest.writeIntArray(mSpanLookup); - } - dest.writeInt(mReverseLayout ? 1 : 0); - dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); - dest.writeInt(mLastLayoutRTL ? 1 : 0); - dest.writeList(mFullSpanItems); - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - * Data class to hold the information about an anchor position which is used in onLayout call. - */ - class AnchorInfo { - - int mPosition; - int mOffset; - boolean mLayoutFromEnd; - boolean mInvalidateOffsets; - boolean mValid; - // this is where we save span reference lines in case we need to re-use them for multi-pass - // measure steps - int[] mSpanReferenceLines; - - AnchorInfo() { - reset(); - } - - void reset() { - mPosition = RecyclerView.NO_POSITION; - mOffset = INVALID_OFFSET; - mLayoutFromEnd = false; - mInvalidateOffsets = false; - mValid = false; - if (mSpanReferenceLines != null) { - Arrays.fill(mSpanReferenceLines, -1); - } - } - - void saveSpanReferenceLines(Span[] spans) { - int spanCount = spans.length; - if (mSpanReferenceLines == null || mSpanReferenceLines.length < spanCount) { - mSpanReferenceLines = new int[mSpans.length]; - } - for (int i = 0; i < spanCount; i++) { - // does not matter start or end since this is only recorded when span is reset - mSpanReferenceLines[i] = spans[i].getStartLine(Span.INVALID_LINE); - } - } - - void assignCoordinateFromPadding() { - mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - } - - void assignCoordinateFromPadding(int addedDistance) { - if (mLayoutFromEnd) { - mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance; - } else { - mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance; - } - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ThreadUtil.java b/app/src/main/java/androidx/recyclerview/widget/ThreadUtil.java deleted file mode 100644 index 541f388749..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ThreadUtil.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.annotation.SuppressLint; - -interface ThreadUtil { - - interface MainThreadCallback { - - void updateItemCount(int generation, int itemCount); - - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void addTile(int generation, TileList.Tile tile); - - void removeTile(int generation, int position); - } - - interface BackgroundCallback { - - void refresh(int generation); - - void updateRange(int rangeStart, int rangeEnd, int extRangeStart, int extRangeEnd, - int scrollHint); - - void loadTile(int position, int scrollHint); - - @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly - void recycleTile(TileList.Tile tile); - } - - MainThreadCallback getMainThreadProxy(MainThreadCallback callback); - - BackgroundCallback getBackgroundProxy(BackgroundCallback callback); -} diff --git a/app/src/main/java/androidx/recyclerview/widget/TileList.java b/app/src/main/java/androidx/recyclerview/widget/TileList.java deleted file mode 100644 index 284b1b7092..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/TileList.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.util.SparseArray; - -import androidx.annotation.NonNull; - -import java.lang.reflect.Array; - -/** - * A sparse collection of tiles sorted for efficient access. - */ -class TileList { - - final int mTileSize; - - // Keyed by start position. - private final SparseArray> mTiles = new SparseArray>(10); - - Tile mLastAccessedTile; - - public TileList(int tileSize) { - mTileSize = tileSize; - } - - public T getItemAt(int pos) { - if (mLastAccessedTile == null || !mLastAccessedTile.containsPosition(pos)) { - final int startPosition = pos - (pos % mTileSize); - final int index = mTiles.indexOfKey(startPosition); - if (index < 0) { - return null; - } - mLastAccessedTile = mTiles.valueAt(index); - } - return mLastAccessedTile.getByPosition(pos); - } - - public int size() { - return mTiles.size(); - } - - public void clear() { - mTiles.clear(); - } - - /** - * Returns the {@link Tile} at the provided {@param index}, or {@code null} if the index - * provided is out of bounds. - */ - public Tile getAtIndex(int index) { - if (index < 0 || index >= mTiles.size()) { - return null; - } - return mTiles.valueAt(index); - } - - public Tile addOrReplace(Tile newTile) { - final int index = mTiles.indexOfKey(newTile.mStartPosition); - if (index < 0) { - mTiles.put(newTile.mStartPosition, newTile); - return null; - } - Tile oldTile = mTiles.valueAt(index); - mTiles.setValueAt(index, newTile); - if (mLastAccessedTile == oldTile) { - mLastAccessedTile = newTile; - } - return oldTile; - } - - public Tile removeAtPos(int startPosition) { - Tile tile = mTiles.get(startPosition); - if (mLastAccessedTile == tile) { - mLastAccessedTile = null; - } - mTiles.delete(startPosition); - return tile; - } - - public static class Tile { - public final T[] mItems; - public int mStartPosition; - public int mItemCount; - Tile mNext; // Used only for pooling recycled tiles. - - Tile(@NonNull Class klass, int size) { - @SuppressWarnings("unchecked") - T[] items = (T[]) Array.newInstance(klass, size); - mItems = items; - } - - boolean containsPosition(int pos) { - return mStartPosition <= pos && pos < mStartPosition + mItemCount; - } - - T getByPosition(int pos) { - return mItems[pos - mStartPosition]; - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ViewBoundsCheck.java b/app/src/main/java/androidx/recyclerview/widget/ViewBoundsCheck.java deleted file mode 100644 index 8ea8f75333..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ViewBoundsCheck.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.view.View; - -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * A utility class used to check the boundaries of a given view within its parent view based on - * a set of boundary flags. - */ -class ViewBoundsCheck { - - static final int GT = 1 << 0; - static final int EQ = 1 << 1; - static final int LT = 1 << 2; - - - static final int CVS_PVS_POS = 0; - /** - * The child view's start should be strictly greater than parent view's start. - */ - static final int FLAG_CVS_GT_PVS = GT << CVS_PVS_POS; - - /** - * The child view's start can be equal to its parent view's start. This flag follows with GT - * or LT indicating greater (less) than or equal relation. - */ - static final int FLAG_CVS_EQ_PVS = EQ << CVS_PVS_POS; - - /** - * The child view's start should be strictly less than parent view's start. - */ - static final int FLAG_CVS_LT_PVS = LT << CVS_PVS_POS; - - - static final int CVS_PVE_POS = 4; - /** - * The child view's start should be strictly greater than parent view's end. - */ - static final int FLAG_CVS_GT_PVE = GT << CVS_PVE_POS; - - /** - * The child view's start can be equal to its parent view's end. This flag follows with GT - * or LT indicating greater (less) than or equal relation. - */ - static final int FLAG_CVS_EQ_PVE = EQ << CVS_PVE_POS; - - /** - * The child view's start should be strictly less than parent view's end. - */ - static final int FLAG_CVS_LT_PVE = LT << CVS_PVE_POS; - - - static final int CVE_PVS_POS = 8; - /** - * The child view's end should be strictly greater than parent view's start. - */ - static final int FLAG_CVE_GT_PVS = GT << CVE_PVS_POS; - - /** - * The child view's end can be equal to its parent view's start. This flag follows with GT - * or LT indicating greater (less) than or equal relation. - */ - static final int FLAG_CVE_EQ_PVS = EQ << CVE_PVS_POS; - - /** - * The child view's end should be strictly less than parent view's start. - */ - static final int FLAG_CVE_LT_PVS = LT << CVE_PVS_POS; - - - static final int CVE_PVE_POS = 12; - /** - * The child view's end should be strictly greater than parent view's end. - */ - static final int FLAG_CVE_GT_PVE = GT << CVE_PVE_POS; - - /** - * The child view's end can be equal to its parent view's end. This flag follows with GT - * or LT indicating greater (less) than or equal relation. - */ - static final int FLAG_CVE_EQ_PVE = EQ << CVE_PVE_POS; - - /** - * The child view's end should be strictly less than parent view's end. - */ - static final int FLAG_CVE_LT_PVE = LT << CVE_PVE_POS; - - static final int MASK = GT | EQ | LT; - - final Callback mCallback; - BoundFlags mBoundFlags; - /** - * The set of flags that can be passed for checking the view boundary conditions. - * CVS in the flag name indicates the child view, and PV indicates the parent view.\ - * The following S, E indicate a view's start and end points, respectively. - * GT and LT indicate a strictly greater and less than relationship. - * Greater than or equal (or less than or equal) can be specified by setting both GT and EQ (or - * LT and EQ) flags. - * For instance, setting both {@link #FLAG_CVS_GT_PVS} and {@link #FLAG_CVS_EQ_PVS} indicate the - * child view's start should be greater than or equal to its parent start. - */ - @IntDef(flag = true, value = { - FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS, - FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE, - FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS, - FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ViewBounds {} - - ViewBoundsCheck(Callback callback) { - mCallback = callback; - mBoundFlags = new BoundFlags(); - } - - static class BoundFlags { - int mBoundFlags = 0; - int mRvStart, mRvEnd, mChildStart, mChildEnd; - - void setBounds(int rvStart, int rvEnd, int childStart, int childEnd) { - mRvStart = rvStart; - mRvEnd = rvEnd; - mChildStart = childStart; - mChildEnd = childEnd; - } - - void addFlags(@ViewBounds int flags) { - mBoundFlags |= flags; - } - - void resetFlags() { - mBoundFlags = 0; - } - - int compare(int x, int y) { - if (x > y) { - return GT; - } - if (x == y) { - return EQ; - } - return LT; - } - - boolean boundsMatch() { - if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) { - if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) { - return false; - } - } - - if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) { - if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) { - return false; - } - } - - if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) { - if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) { - return false; - } - } - - if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) { - if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) { - return false; - } - } - return true; - } - }; - - /** - * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within - * its parent bounds based on the provided preferredBoundFlags. If no match is found based on - * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose - * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such - * view is found based on either of these two flags, null is returned. - * @param fromIndex The view position index to start the search from. - * @param toIndex The view position index to end the search at. - * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found - * based on this flag, that view is returned instantly. - * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match - * is found. If so, and if acceptableBoundFlags is non-zero, the - * last matching acceptable view is returned. Otherwise, null is - * returned. - * @return The first view that satisfies acceptableBoundFlags or the last view satisfying - * acceptableBoundFlags boundary conditions. - */ - View findOneViewWithinBoundFlags(int fromIndex, int toIndex, - @ViewBounds int preferredBoundFlags, - @ViewBounds int acceptableBoundFlags) { - final int start = mCallback.getParentStart(); - final int end = mCallback.getParentEnd(); - final int next = toIndex > fromIndex ? 1 : -1; - View acceptableMatch = null; - for (int i = fromIndex; i != toIndex; i += next) { - final View child = mCallback.getChildAt(i); - final int childStart = mCallback.getChildStart(child); - final int childEnd = mCallback.getChildEnd(child); - mBoundFlags.setBounds(start, end, childStart, childEnd); - if (preferredBoundFlags != 0) { - mBoundFlags.resetFlags(); - mBoundFlags.addFlags(preferredBoundFlags); - if (mBoundFlags.boundsMatch()) { - // found a perfect match - return child; - } - } - if (acceptableBoundFlags != 0) { - mBoundFlags.resetFlags(); - mBoundFlags.addFlags(acceptableBoundFlags); - if (mBoundFlags.boundsMatch()) { - acceptableMatch = child; - } - } - } - return acceptableMatch; - } - - /** - * Returns whether the specified view lies within the boundary condition of its parent view. - * @param child The child view to be checked. - * @param boundsFlags The flag against which the child view and parent view are matched. - * @return True if the view meets the boundsFlag, false otherwise. - */ - boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) { - mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(), - mCallback.getChildStart(child), mCallback.getChildEnd(child)); - if (boundsFlags != 0) { - mBoundFlags.resetFlags(); - mBoundFlags.addFlags(boundsFlags); - return mBoundFlags.boundsMatch(); - } - return false; - } - - /** - * Callback provided by the user of this class in order to retrieve information about child and - * parent boundaries. - */ - interface Callback { - View getChildAt(int index); - int getParentStart(); - int getParentEnd(); - int getChildStart(View view); - int getChildEnd(View view); - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ViewInfoStore.java b/app/src/main/java/androidx/recyclerview/widget/ViewInfoStore.java deleted file mode 100644 index 312edad353..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ViewInfoStore.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.recyclerview.widget; - -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_POST; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_PRE; -import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.collection.LongSparseArray; -import androidx.collection.SimpleArrayMap; -import androidx.core.util.Pools; - -/** - * This class abstracts all tracking for Views to run animations. - */ -class ViewInfoStore { - - private static final boolean DEBUG = false; - - /** - * View data records for pre-layout - */ - @VisibleForTesting - final SimpleArrayMap mLayoutHolderMap = - new SimpleArrayMap<>(); - - @VisibleForTesting - final LongSparseArray mOldChangedHolders = new LongSparseArray<>(); - - /** - * Clears the state and all existing tracking data - */ - void clear() { - mLayoutHolderMap.clear(); - mOldChangedHolders.clear(); - } - - /** - * Adds the item information to the prelayout tracking - * @param holder The ViewHolder whose information is being saved - * @param info The information to save - */ - void addToPreLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { - InfoRecord record = mLayoutHolderMap.get(holder); - if (record == null) { - record = InfoRecord.obtain(); - mLayoutHolderMap.put(holder, record); - } - record.preInfo = info; - record.flags |= FLAG_PRE; - } - - boolean isDisappearing(RecyclerView.ViewHolder holder) { - final InfoRecord record = mLayoutHolderMap.get(holder); - return record != null && ((record.flags & FLAG_DISAPPEARED) != 0); - } - - /** - * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it. - * - * @param vh The ViewHolder whose information is being queried - * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist - */ - @Nullable - RecyclerView.ItemAnimator.ItemHolderInfo popFromPreLayout(RecyclerView.ViewHolder vh) { - return popFromLayoutStep(vh, FLAG_PRE); - } - - /** - * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it. - * - * @param vh The ViewHolder whose information is being queried - * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist - */ - @Nullable - RecyclerView.ItemAnimator.ItemHolderInfo popFromPostLayout(RecyclerView.ViewHolder vh) { - return popFromLayoutStep(vh, FLAG_POST); - } - - private RecyclerView.ItemAnimator.ItemHolderInfo popFromLayoutStep(RecyclerView.ViewHolder vh, int flag) { - int index = mLayoutHolderMap.indexOfKey(vh); - if (index < 0) { - return null; - } - final InfoRecord record = mLayoutHolderMap.valueAt(index); - if (record != null && (record.flags & flag) != 0) { - record.flags &= ~flag; - final RecyclerView.ItemAnimator.ItemHolderInfo info; - if (flag == FLAG_PRE) { - info = record.preInfo; - } else if (flag == FLAG_POST) { - info = record.postInfo; - } else { - throw new IllegalArgumentException("Must provide flag PRE or POST"); - } - // if not pre-post flag is left, clear. - if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) { - mLayoutHolderMap.removeAt(index); - InfoRecord.recycle(record); - } - return info; - } - return null; - } - - /** - * Adds the given ViewHolder to the oldChangeHolders list - * @param key The key to identify the ViewHolder. - * @param holder The ViewHolder to store - */ - void addToOldChangeHolders(long key, RecyclerView.ViewHolder holder) { - mOldChangedHolders.put(key, holder); - } - - /** - * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the - * LayoutManager during a pre-layout pass. We distinguish them from other views that were - * already in the pre-layout so that ItemAnimator can choose to run a different animation for - * them. - * - * @param holder The ViewHolder to store - * @param info The information to save - */ - void addToAppearedInPreLayoutHolders(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { - InfoRecord record = mLayoutHolderMap.get(holder); - if (record == null) { - record = InfoRecord.obtain(); - mLayoutHolderMap.put(holder, record); - } - record.flags |= FLAG_APPEAR; - record.preInfo = info; - } - - /** - * Checks whether the given ViewHolder is in preLayout list - * @param viewHolder The ViewHolder to query - * - * @return True if the ViewHolder is present in preLayout, false otherwise - */ - boolean isInPreLayout(RecyclerView.ViewHolder viewHolder) { - final InfoRecord record = mLayoutHolderMap.get(viewHolder); - return record != null && (record.flags & FLAG_PRE) != 0; - } - - /** - * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns - * null. - * @param key The key to be used to find the ViewHolder. - * - * @return A ViewHolder if exists or null if it does not exist. - */ - RecyclerView.ViewHolder getFromOldChangeHolders(long key) { - return mOldChangedHolders.get(key); - } - - /** - * Adds the item information to the post layout list - * @param holder The ViewHolder whose information is being saved - * @param info The information to save - */ - void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) { - InfoRecord record = mLayoutHolderMap.get(holder); - if (record == null) { - record = InfoRecord.obtain(); - mLayoutHolderMap.put(holder, record); - } - record.postInfo = info; - record.flags |= FLAG_POST; - } - - /** - * A ViewHolder might be added by the LayoutManager just to animate its disappearance. - * This list holds such items so that we can animate / recycle these ViewHolders properly. - * - * @param holder The ViewHolder which disappeared during a layout. - */ - void addToDisappearedInLayout(RecyclerView.ViewHolder holder) { - InfoRecord record = mLayoutHolderMap.get(holder); - if (record == null) { - record = InfoRecord.obtain(); - mLayoutHolderMap.put(holder, record); - } - record.flags |= FLAG_DISAPPEARED; - } - - /** - * Removes a ViewHolder from disappearing list. - * @param holder The ViewHolder to be removed from the disappearing list. - */ - void removeFromDisappearedInLayout(RecyclerView.ViewHolder holder) { - InfoRecord record = mLayoutHolderMap.get(holder); - if (record == null) { - return; - } - record.flags &= ~FLAG_DISAPPEARED; - } - - void process(ProcessCallback callback) { - for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) { - final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); - final InfoRecord record = mLayoutHolderMap.removeAt(index); - if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) { - // Appeared then disappeared. Not useful for animations. - callback.unused(viewHolder); - } else if ((record.flags & FLAG_DISAPPEARED) != 0) { - // Set as "disappeared" by the LayoutManager (addDisappearingView) - if (record.preInfo == null) { - // similar to appear disappear but happened between different layout passes. - // this can happen when the layout manager is using auto-measure - callback.unused(viewHolder); - } else { - callback.processDisappeared(viewHolder, record.preInfo, record.postInfo); - } - } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) { - // Appeared in the layout but not in the adapter (e.g. entered the viewport) - callback.processAppeared(viewHolder, record.preInfo, record.postInfo); - } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) { - // Persistent in both passes. Animate persistence - callback.processPersistent(viewHolder, record.preInfo, record.postInfo); - } else if ((record.flags & FLAG_PRE) != 0) { - // Was in pre-layout, never been added to post layout - callback.processDisappeared(viewHolder, record.preInfo, null); - } else if ((record.flags & FLAG_POST) != 0) { - // Was not in pre-layout, been added to post layout - callback.processAppeared(viewHolder, record.preInfo, record.postInfo); - } else if ((record.flags & FLAG_APPEAR) != 0) { - // Scrap view. RecyclerView will handle removing/recycling this. - } else if (DEBUG) { - throw new IllegalStateException("record without any reasonable flag combination:/"); - } - InfoRecord.recycle(record); - } - } - - /** - * Removes the ViewHolder from all list - * @param holder The ViewHolder which we should stop tracking - */ - void removeViewHolder(RecyclerView.ViewHolder holder) { - for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) { - if (holder == mOldChangedHolders.valueAt(i)) { - mOldChangedHolders.removeAt(i); - break; - } - } - final InfoRecord info = mLayoutHolderMap.remove(holder); - if (info != null) { - InfoRecord.recycle(info); - } - } - - void onDetach() { - InfoRecord.drainCache(); - } - - public void onViewDetached(RecyclerView.ViewHolder viewHolder) { - removeFromDisappearedInLayout(viewHolder); - } - - interface ProcessCallback { - void processDisappeared(RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo preInfo, - @Nullable RecyclerView.ItemAnimator.ItemHolderInfo postInfo); - void processAppeared(RecyclerView.ViewHolder viewHolder, @Nullable RecyclerView.ItemAnimator.ItemHolderInfo preInfo, - RecyclerView.ItemAnimator.ItemHolderInfo postInfo); - void processPersistent(RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo preInfo, - @NonNull RecyclerView.ItemAnimator.ItemHolderInfo postInfo); - void unused(RecyclerView.ViewHolder holder); - } - - static class InfoRecord { - // disappearing list - static final int FLAG_DISAPPEARED = 1; - // appear in pre layout list - static final int FLAG_APPEAR = 1 << 1; - // pre layout, this is necessary to distinguish null item info - static final int FLAG_PRE = 1 << 2; - // post layout, this is necessary to distinguish null item info - static final int FLAG_POST = 1 << 3; - static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED; - static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST; - static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST; - int flags; - @Nullable - RecyclerView.ItemAnimator.ItemHolderInfo preInfo; - @Nullable - RecyclerView.ItemAnimator.ItemHolderInfo postInfo; - static Pools.Pool sPool = new Pools.SimplePool<>(20); - - private InfoRecord() { - } - - static InfoRecord obtain() { - InfoRecord record = sPool.acquire(); - return record == null ? new InfoRecord() : record; - } - - static void recycle(InfoRecord record) { - record.flags = 0; - record.preInfo = null; - record.postInfo = null; - sPool.release(record); - } - - static void drainCache() { - //noinspection StatementWithEmptyBody - while (sPool.acquire() != null); - } - } -} diff --git a/app/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java b/app/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java deleted file mode 100644 index adf9d01d77..0000000000 --- a/app/src/main/java/androidx/recyclerview/widget/ViewTypeStorage.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.widget; - -import android.util.SparseArray; -import android.util.SparseIntArray; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.List; - -/** - * Used by {@link ConcatAdapter} to isolate view types between nested adapters, if necessary. - */ -interface ViewTypeStorage { - @NonNull - NestedAdapterWrapper getWrapperForGlobalType(int globalViewType); - - @NonNull - ViewTypeLookup createViewTypeWrapper( - @NonNull NestedAdapterWrapper wrapper - ); - - /** - * Api given to {@link NestedAdapterWrapper}s. - */ - interface ViewTypeLookup { - int localToGlobal(int localType); - - int globalToLocal(int globalType); - - void dispose(); - } - - class SharedIdRangeViewTypeStorage implements ViewTypeStorage { - // we keep a list of nested wrappers here even though we only need 1 to create because - // they might be removed. - SparseArray> mGlobalTypeToWrapper = new SparseArray<>(); - - @NonNull - @Override - public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) { - List nestedAdapterWrappers = mGlobalTypeToWrapper.get( - globalViewType); - if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) { - throw new IllegalArgumentException("Cannot find the wrapper for global view" - + " type " + globalViewType); - } - // just return the first one since they are shared - return nestedAdapterWrappers.get(0); - } - - @NonNull - @Override - public ViewTypeLookup createViewTypeWrapper( - @NonNull NestedAdapterWrapper wrapper) { - return new WrapperViewTypeLookup(wrapper); - } - - void removeWrapper(@NonNull NestedAdapterWrapper wrapper) { - for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) { - List wrappers = mGlobalTypeToWrapper.valueAt(i); - if (wrappers.remove(wrapper)) { - if (wrappers.isEmpty()) { - mGlobalTypeToWrapper.removeAt(i); - } - } - } - } - - class WrapperViewTypeLookup implements ViewTypeLookup { - final NestedAdapterWrapper mWrapper; - - WrapperViewTypeLookup(NestedAdapterWrapper wrapper) { - mWrapper = wrapper; - } - - @Override - public int localToGlobal(int localType) { - // register it first - List wrappers = mGlobalTypeToWrapper.get( - localType); - if (wrappers == null) { - wrappers = new ArrayList<>(); - mGlobalTypeToWrapper.put(localType, wrappers); - } - if (!wrappers.contains(mWrapper)) { - wrappers.add(mWrapper); - } - return localType; - } - - @Override - public int globalToLocal(int globalType) { - return globalType; - } - - @Override - public void dispose() { - removeWrapper(mWrapper); - } - } - } - - class IsolatedViewTypeStorage implements ViewTypeStorage { - SparseArray mGlobalTypeToWrapper = new SparseArray<>(); - - int mNextViewType = 0; - - int obtainViewType(NestedAdapterWrapper wrapper) { - int nextId = mNextViewType++; - mGlobalTypeToWrapper.put(nextId, wrapper); - return nextId; - } - - @NonNull - @Override - public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) { - NestedAdapterWrapper wrapper = mGlobalTypeToWrapper.get( - globalViewType); - if (wrapper == null) { - throw new IllegalArgumentException("Cannot find the wrapper for global" - + " view type " + globalViewType); - } - return wrapper; - } - - @Override - @NonNull - public ViewTypeLookup createViewTypeWrapper( - @NonNull NestedAdapterWrapper wrapper) { - return new WrapperViewTypeLookup(wrapper); - } - - void removeWrapper(@NonNull NestedAdapterWrapper wrapper) { - for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) { - NestedAdapterWrapper existingWrapper = mGlobalTypeToWrapper.valueAt(i); - if (existingWrapper == wrapper) { - mGlobalTypeToWrapper.removeAt(i); - } - } - } - - class WrapperViewTypeLookup implements ViewTypeLookup { - private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1); - private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1); - final NestedAdapterWrapper mWrapper; - - WrapperViewTypeLookup(NestedAdapterWrapper wrapper) { - mWrapper = wrapper; - } - - @Override - public int localToGlobal(int localType) { - int index = mLocalToGlobalMapping.indexOfKey(localType); - if (index > -1) { - return mLocalToGlobalMapping.valueAt(index); - } - // get a new key. - int globalType = obtainViewType(mWrapper); - mLocalToGlobalMapping.put(localType, globalType); - mGlobalToLocalMapping.put(globalType, localType); - return globalType; - } - - @Override - public int globalToLocal(int globalType) { - int index = mGlobalToLocalMapping.indexOfKey(globalType); - if (index < 0) { - throw new IllegalStateException("requested global type " + globalType + " does" - + " not belong to the adapter:" + mWrapper.adapter); - } - return mGlobalToLocalMapping.valueAt(index); - } - - @Override - public void dispose() { - removeWrapper(mWrapper); - } - } - } -}