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 extends ViewHolder>... 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 extends ViewHolder>... 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 extends Adapter extends ViewHolder>> 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 extends Adapter extends ViewHolder>> adapters) {
- mController = new ConcatAdapterController(this, config);
- for (Adapter extends ViewHolder> 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 extends ViewHolder> 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 extends ViewHolder> 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 extends ViewHolder> 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 extends Adapter extends ViewHolder>> 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 extends ViewHolder> 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 extends ViewHolder> 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 extends ViewHolder> 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:
- *
- * An in bounds child.
- * An out of bounds child.
- * 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 extends LayoutManager> layoutManagerClass =
- Class.forName(className, false, classLoader)
- .asSubclass(LayoutManager.class);
- Constructor extends LayoutManager> 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 extends ViewHolder> 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:
- *
- * 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).
- * 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()}.
- * 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.
- * 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.
- * 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.
- * Finally, animations are calculated and run as usual.
- *
- *
- * @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 extends ViewHolder> 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 extends ViewHolder> 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 extends ViewHolder> 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