diff --git a/app/build.gradle b/app/build.gradle index f19c7b4804..29a0b7b28a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,13 +97,11 @@ android { } dependencies { - testCompile 'junit:junit:4.12' - - compile fileTree(dir: 'libs', include: '*.jar') - + compile fileTree(include: '*.jar', dir: 'libs') + compile 'com.android.support:recyclerview-v7:23.2.1' compile 'com.android.support:appcompat-v7:23.2.1' -// compile 'com.android.support:cardview-v7:21.0.0' + // compile 'com.android.support:cardview-v7:21.0.0' // fresco图片框架 compile 'com.facebook.fresco:fresco:0.12.0' compile 'com.facebook.fresco:animated-gif:0.12.0' @@ -132,14 +130,12 @@ dependencies { compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' // compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0' // compile 'com.jakewharton.rxbinding:rxbinding-design:0.3.0' - // zxing 二维码扫描以及生成 compile 'com.google.zxing:core:3.2.1' compile 'com.google.zxing:android-core:3.2.1' //tinker -// compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } -// compile "com.android.support:multidex:1.0.1" - + // compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } + // compile "com.android.support:multidex:1.0.1" compile project(':libraries:EventBus') compile project(':libraries:MiPush') compile project(':libraries:MTA') diff --git a/app/src/main/java/android/support/v7/widget/AdapterHelper.java b/app/src/main/java/android/support/v7/widget/AdapterHelper.java deleted file mode 100644 index ecf0c294d4..0000000000 --- a/app/src/main/java/android/support/v7/widget/AdapterHelper.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.support.v4.util.Pools; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static android.support.v7.widget.RecyclerView.ViewHolder; - -/** - * 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. - */ -class AdapterHelper implements OpReorderer.Callback { - - final static int POSITION_TYPE_INVISIBLE = 0; - - final static 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; - - 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); - } - - 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); - } - - 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; - 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); - 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); - 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); - } - 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++) { - ViewHolder vh = mCallback.findViewHolder(position); - if (vh != null || canFindInPreLayout(position)) { // deferred - if (type == POSITION_TYPE_INVISIBLE) { - UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount); - 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); - postponeAndUpdateViewHolders(newOp); - tmpCount = 0; - tmpStart = position; - } - type = POSITION_TYPE_INVISIBLE; - } - tmpCount++; - } - if (tmpCount != op.itemCount) { // all 1 effect - recycleUpdateOp(op); - op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount); - } - 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); - 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; - } - } - recycleUpdateOp(op); - if (tmpCnt > 0) { - UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt); - 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); - 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); - } -// Utils.log("add UpdateOp to PostponedList"); - mPostponedList.add(op); -// Utils.log("op" + op.positionStart + "=" + op.itemCount); - 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); - break; - default: - throw new IllegalArgumentException("Unknown update op type for " + op); - } - } - - boolean hasPendingUpdates() { - return mPendingUpdates.size() > 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) { - mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount)); - return mPendingUpdates.size() == 1; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeInserted(int positionStart, int itemCount) { - mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount)); - return mPendingUpdates.size() == 1; - } - - /** - * @return True if updates should be processed. - */ - boolean onItemRangeRemoved(int positionStart, int itemCount) { - mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount)); - 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)); - 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); - break; - case UpdateOp.MOVE: - mCallback.onDispatchSecondPass(op); - mCallback.offsetPositionsForMove(op.positionStart, op.itemCount); - break; - } - if (mOnItemProcessedCallback != null) { - mOnItemProcessedCallback.run(); - } - } - recycleUpdateOpsAndClearList(mPendingUpdates); - } - - /** - * Queued operation to happen when child views are updated. - */ - static class UpdateOp { - - static final int ADD = 0; - - static final int REMOVE = 1; - - static final int UPDATE = 2; - - static final int MOVE = 3; - - static final int POOL_SIZE = 30; - - int cmd; - - int positionStart; - - // holds the target position if this is a MOVE - int itemCount; - - UpdateOp(int cmd, int positionStart, int itemCount) { - this.cmd = cmd; - this.positionStart = positionStart; - this.itemCount = itemCount; - } - - 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 "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - 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; - } - - 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) { - UpdateOp op = mUpdateOpPool.acquire(); - if (op == null) { - op = new UpdateOp(cmd, positionStart, itemCount); - } else { - op.cmd = cmd; - op.positionStart = positionStart; - op.itemCount = itemCount; - } - return op; - } - - @Override - public void recycleUpdateOp(UpdateOp op) { - if (!mDisableRecycler) { - 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. - */ - static interface Callback { - - ViewHolder findViewHolder(int position); - - void offsetPositionsForRemovingInvisible(int positionStart, int itemCount); - - void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount); - - void markViewHoldersUpdated(int positionStart, int itemCount); - - 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/android/support/v7/widget/ChildHelper.java b/app/src/main/java/android/support/v7/widget/ChildHelper.java deleted file mode 100644 index e5556796f1..0000000000 --- a/app/src/main/java/android/support/v7/widget/ChildHelper.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.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"; - - final Callback mCallback; - - final Bucket mBucket; - - final List mHiddenViews; - - ChildHelper(Callback callback) { - mCallback = callback; - mBucket = new Bucket(); - mHiddenViews = new ArrayList(); - } - - /** - * 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); - } - mCallback.addView(child, offset); - mBucket.insert(offset, hidden); - if (hidden) { - mHiddenViews.add(child); - } - 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) { - int index = mCallback.indexOfChild(view); - if (index < 0) { - return; - } - mCallback.removeViewAt(index); - if (mBucket.remove(index)) { - mHiddenViews.remove(view); - } - if (DEBUG) { - Log.d(TAG, "remove View off:" + index + "," + this); - } - } - - /** - * 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) { - final int offset = getOffset(index); - final View view = mCallback.getChildAt(offset); - if (view == null) { - return; - } - mCallback.removeViewAt(offset); - if (mBucket.remove(offset)) { - mHiddenViews.remove(view); - } - if (DEBUG) { - Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this); - } - } - - /** - * 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() { - mCallback.removeAllViews(); - mBucket.reset(); - mHiddenViews.clear(); - 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. - * @param type View type, can be {@link RecyclerView#INVALID_TYPE}. - * @return A hidden view with a valid ViewHolder that matches the position and type. - */ - View findHiddenNonRemovedView(int position, int type) { - 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.getPosition() == position && !holder.isInvalid() && - (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) { - 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); - } - mCallback.attachViewToParent(child, offset, layoutParams); - mBucket.insert(offset, hidden); - 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); - mCallback.detachViewFromParent(offset); - mBucket.remove(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); - mHiddenViews.add(view); - if (DEBUG) { - Log.d(TAG, "hiding child " + view + " at offset " + offset+ ", " + this); - } - } - - @Override - public String toString() { - return mBucket.toString(); - } - - /** - * 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) { - final int index = mCallback.indexOfChild(view); - if (index == -1) { - if (mHiddenViews.remove(view) && DEBUG) { - throw new IllegalStateException("view is in hidden list but not in view group"); - } - return true; - } - if (mBucket.get(index)) { - mBucket.remove(index); - mCallback.removeViewAt(index); - if (!mHiddenViews.remove(view) && DEBUG) { - throw new IllegalStateException( - "removed a hidden view but it is not in hidden views list"); - } - return true; - } - return false; - } - - /** - * Bitset implementation that provides methods to offset indices. - */ - static class Bucket { - - final static int BITS_PER_WORD = Long.SIZE; - - final static long LAST_BIT = 1L << (Long.SIZE - 1); - - long mData = 0; - - Bucket next; - - void set(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - next.set(index - BITS_PER_WORD); - } else { - mData |= 1L << index; - } - } - - private void ensureNext() { - if (next == null) { - next = new Bucket(); - } - } - - void clear(int index) { - if (index >= BITS_PER_WORD) { - if (next != null) { - next.clear(index - BITS_PER_WORD); - } - } else { - mData &= ~(1L << index); - } - - } - - boolean get(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - return next.get(index - BITS_PER_WORD); - } else { - return (mData & (1L << index)) != 0; - } - } - - void reset() { - mData = 0; - if (next != null) { - next.reset(); - } - } - - void insert(int index, boolean value) { - if (index >= BITS_PER_WORD) { - ensureNext(); - next.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 || next != null) { - ensureNext(); - next.insert(0, lastBit); - } - } - } - - boolean remove(int index) { - if (index >= BITS_PER_WORD) { - ensureNext(); - return next.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 (next != null) { - if (next.get(0)) { - set(BITS_PER_WORD - 1); - } - next.remove(0); - } - return value; - } - } - - int countOnesBefore(int index) { - if (next == 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 next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData); - } - } - - @Override - public String toString() { - return next == null ? Long.toBinaryString(mData) - : next.toString() + "xx" + Long.toBinaryString(mData); - } - } - - static 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); - } -} diff --git a/app/src/main/java/android/support/v7/widget/DefaultItemAnimator.java b/app/src/main/java/android/support/v7/widget/DefaultItemAnimator.java deleted file mode 100644 index 2a27d65ab8..0000000000 --- a/app/src/main/java/android/support/v7/widget/DefaultItemAnimator.java +++ /dev/null @@ -1,628 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.support.v4.view.ViewPropertyAnimatorListener; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.view.View; - -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 RecyclerView.ItemAnimator { - private static final boolean DEBUG = false; - - private ArrayList mPendingRemovals = new ArrayList(); - private ArrayList mPendingAdditions = new ArrayList(); - private ArrayList mPendingMoves = new ArrayList(); - private ArrayList mPendingChanges = new ArrayList(); - - private ArrayList> mAdditionsList = - new ArrayList>(); - private ArrayList> mMovesList = new ArrayList>(); - private ArrayList> mChangesList = new ArrayList>(); - - private ArrayList mAddAnimations = new ArrayList(); - private ArrayList mMoveAnimations = new ArrayList(); - private ArrayList mRemoveAnimations = new ArrayList(); - private ArrayList mChangeAnimations = new ArrayList(); - - private static class MoveInfo { - public ViewHolder holder; - public int fromX, fromY, toX, toY; - - private MoveInfo(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 ViewHolder oldHolder, newHolder; - public int fromX, fromY, toX, toY; - private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { - this.oldHolder = oldHolder; - this.newHolder = newHolder; - } - - private ChangeInfo(ViewHolder oldHolder, 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 - 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 (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) { - 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() { - public void run() { - for (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 - public boolean animateRemove(final ViewHolder holder) { - endAnimation(holder); - mPendingRemovals.add(holder); - return true; - } - - private void animateRemoveImpl(final ViewHolder holder) { - final View view = holder.itemView; - final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); - animation.setDuration(getRemoveDuration()) - .alpha(0).setListener(new VpaListenerAdapter() { - @Override - public void onAnimationStart(View view) { - dispatchRemoveStarting(holder); - } - @Override - public void onAnimationEnd(View view) { - animation.setListener(null); - ViewCompat.setAlpha(view, 1); - dispatchRemoveFinished(holder); - mRemoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - mRemoveAnimations.add(holder); - } - - @Override - public boolean animateAdd(final ViewHolder holder) { - endAnimation(holder); - ViewCompat.setAlpha(holder.itemView, 0); - mPendingAdditions.add(holder); - return true; - } - - private void animateAddImpl(final ViewHolder holder) { - final View view = holder.itemView; - mAddAnimations.add(holder); - final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); - animation.alpha(1).setDuration(getAddDuration()). - setListener(new VpaListenerAdapter() { - @Override - public void onAnimationStart(View view) { - dispatchAddStarting(holder); - } - @Override - public void onAnimationCancel(View view) { - ViewCompat.setAlpha(view, 1); - } - - @Override - public void onAnimationEnd(View view) { - animation.setListener(null); - dispatchAddFinished(holder); - mAddAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - public boolean animateMove(final ViewHolder holder, int fromX, int fromY, - int toX, int toY) { - final View view = holder.itemView; - fromX += ViewCompat.getTranslationX(holder.itemView); - fromY += ViewCompat.getTranslationY(holder.itemView); - endAnimation(holder); - int deltaX = toX - fromX; - int deltaY = toY - fromY; - if (deltaX == 0 && deltaY == 0) { - dispatchMoveFinished(holder); - return false; - } - if (deltaX != 0) { - ViewCompat.setTranslationX(view, -deltaX); - } - if (deltaY != 0) { - ViewCompat.setTranslationY(view, -deltaY); - } - mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); - return true; - } - - private void animateMoveImpl(final 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) { - ViewCompat.animate(view).translationX(0); - } - if (deltaY != 0) { - ViewCompat.animate(view).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. - mMoveAnimations.add(holder); - final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); - animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { - @Override - public void onAnimationStart(View view) { - dispatchMoveStarting(holder); - } - @Override - public void onAnimationCancel(View view) { - if (deltaX != 0) { - ViewCompat.setTranslationX(view, 0); - } - if (deltaY != 0) { - ViewCompat.setTranslationY(view, 0); - } - } - @Override - public void onAnimationEnd(View view) { - animation.setListener(null); - dispatchMoveFinished(holder); - mMoveAnimations.remove(holder); - dispatchFinishedWhenDone(); - } - }).start(); - } - - @Override - public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, - int fromX, int fromY, int toX, int toY) { - final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); - final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); - final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); - endAnimation(oldHolder); - int deltaX = (int) (toX - fromX - prevTranslationX); - int deltaY = (int) (toY - fromY - prevTranslationY); - // recover prev translation state after ending animation - ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); - ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); - ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); - if (newHolder != null && newHolder.itemView != null) { - // carry over translation values - endAnimation(newHolder); - ViewCompat.setTranslationX(newHolder.itemView, -deltaX); - ViewCompat.setTranslationY(newHolder.itemView, -deltaY); - ViewCompat.setAlpha(newHolder.itemView, 0); - } - mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); - return true; - } - - private void animateChangeImpl(final ChangeInfo changeInfo) { - final ViewHolder holder = changeInfo.oldHolder; - final View view = holder.itemView; - final ViewHolder newHolder = changeInfo.newHolder; - final View newView = newHolder != null ? newHolder.itemView : null; - mChangeAnimations.add(changeInfo.oldHolder); - - final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration( - getChangeDuration()); - oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); - oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); - oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { - @Override - public void onAnimationStart(View view) { - dispatchChangeStarting(changeInfo.oldHolder, true); - } - @Override - public void onAnimationEnd(View view) { - oldViewAnim.setListener(null); - ViewCompat.setAlpha(view, 1); - ViewCompat.setTranslationX(view, 0); - ViewCompat.setTranslationY(view, 0); - dispatchChangeFinished(changeInfo.oldHolder, true); - mChangeAnimations.remove(changeInfo.oldHolder); - dispatchFinishedWhenDone(); - } - }).start(); - if (newView != null) { - mChangeAnimations.add(changeInfo.newHolder); - final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); - newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). - alpha(1).setListener(new VpaListenerAdapter() { - @Override - public void onAnimationStart(View view) { - dispatchChangeStarting(changeInfo.newHolder, false); - } - @Override - public void onAnimationEnd(View view) { - newViewAnimation.setListener(null); - ViewCompat.setAlpha(newView, 1); - ViewCompat.setTranslationX(newView, 0); - ViewCompat.setTranslationY(newView, 0); - dispatchChangeFinished(changeInfo.newHolder, false); - mChangeAnimations.remove(changeInfo.newHolder); - dispatchFinishedWhenDone(); - } - }).start(); - } - } - - private void endChangeAnimation(List infoList, 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, 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; - } - ViewCompat.setAlpha(item.itemView, 1); - ViewCompat.setTranslationX(item.itemView, 0); - ViewCompat.setTranslationY(item.itemView, 0); - dispatchChangeFinished(item, oldItem); - return true; - } - - @Override - public void endAnimation(ViewHolder item) { - final View view = item.itemView; - // this will trigger end callback which should set properties to their target values. - ViewCompat.animate(view).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) { - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); - dispatchMoveFinished(item); - mPendingMoves.remove(item); - } - } - endChangeAnimation(mPendingChanges, item); - if (mPendingRemovals.remove(item)) { - ViewCompat.setAlpha(view, 1); - dispatchRemoveFinished(item); - } - if (mPendingAdditions.remove(item)) { - ViewCompat.setAlpha(view, 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(changes); - } - } - 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) { - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); - dispatchMoveFinished(item); - moves.remove(j); - if (moves.isEmpty()) { - mMovesList.remove(moves); - } - break; - } - } - } - for (int i = mAdditionsList.size() - 1; i >= 0; i--) { - ArrayList additions = mAdditionsList.get(i); - if (additions.remove(item)) { - ViewCompat.setAlpha(view, 1); - dispatchAddFinished(item); - if (additions.isEmpty()) { - mAdditionsList.remove(additions); - } - } - } - - // animations should be ended by the cancel above. - if (mRemoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mRemoveAnimations list"); - } - - if (mAddAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mAddAnimations list"); - } - - if (mChangeAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mChangeAnimations list"); - } - - if (mMoveAnimations.remove(item) && DEBUG) { - throw new IllegalStateException("after animation is cancelled, item should not be in " - + "mMoveAnimations list"); - } - dispatchFinishedWhenDone(); - } - - @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. - */ - private 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; - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); - dispatchMoveFinished(item.holder); - mPendingMoves.remove(i); - } - count = mPendingRemovals.size(); - for (int i = count - 1; i >= 0; i--) { - ViewHolder item = mPendingRemovals.get(i); - dispatchRemoveFinished(item); - mPendingRemovals.remove(i); - } - count = mPendingAdditions.size(); - for (int i = count - 1; i >= 0; i--) { - ViewHolder item = mPendingAdditions.get(i); - View view = item.itemView; - ViewCompat.setAlpha(view, 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); - ViewHolder item = moveInfo.holder; - View view = item.itemView; - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 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--) { - ViewHolder item = additions.get(j); - View view = item.itemView; - ViewCompat.setAlpha(view, 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--) { - ViewCompat.animate(viewHolders.get(i).itemView).cancel(); - } - } - - private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { - @Override - public void onAnimationStart(View view) {} - - @Override - public void onAnimationEnd(View view) {} - - @Override - public void onAnimationCancel(View view) {} - }; -} diff --git a/app/src/main/java/android/support/v7/widget/GridLayoutManager.java b/app/src/main/java/android/support/v7/widget/GridLayoutManager.java deleted file mode 100644 index a05ac473e4..0000000000 --- a/app/src/main/java/android/support/v7/widget/GridLayoutManager.java +++ /dev/null @@ -1,816 +0,0 @@ -/* - * Copyright (C) 2014 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 languag`e governing permissions and - * limitations under the License. - */ -package android.support.v7.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.View; -import android.view.ViewGroup; - -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; - /** - * The measure spec for the scroll direction. - */ - static final int MAIN_DIR_SPEC = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - - int mSpanCount = DEFAULT_SPAN_COUNT; - /** - * The size of each span - */ - int mSizePerSpan; - /** - * 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(); - - /** - * 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, 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; - } - return getSpanGroupIndex(recycler, state, state.getItemCount() - 1); - } - - @Override - public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return mSpanCount; - } - if (state.getItemCount() < 1) { - return 0; - } - return getSpanGroupIndex(recycler, state, state.getItemCount() - 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.getViewPosition()); - if (mOrientation == HORIZONTAL) { - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - glp.getSpanIndex(), glp.getSpanSize(), - spanGroupIndex, 1, - mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false)); - } else { // VERTICAL - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - spanGroupIndex , 1, - glp.getSpanIndex(), glp.getSpanSize(), - mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false)); - } - } - - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - if (state.isPreLayout()) { - cachePreLayoutSpanMapping(); - } - super.onLayoutChildren(recycler, state); - if (DEBUG) { - validateChildOrder(); - } - clearPreLayoutSpanMappingCache(); - } - - 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.getViewPosition(); - mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize()); - mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex()); - } - } - - @Override - public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - @Override - public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - @Override - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - @Override - public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - 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(); - } - mSizePerSpan = totalSpace / mSpanCount; - } - - @Override - void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) { - super.onAnchorReady(state, anchorInfo); - updateMeasurements(); - if (state.getItemCount() > 0 && !state.isPreLayout()) { - ensureAnchorIsInFirstSpan(anchorInfo); - } - if (mSet == null || mSet.length != mSpanCount) { - mSet = new View[mSpanCount]; - } - } - - private void ensureAnchorIsInFirstSpan(AnchorInfo anchorInfo) { - int span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount); - while (span > 0 && anchorInfo.mPosition > 0) { - anchorInfo.mPosition--; - span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount); - } - } - - private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state, - int viewPosition) { - if (!state.isPreLayout()) { - return mSpanSizeLookup.getSpanGroupIndex(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.getSpanGroupIndex(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 layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, - LayoutState layoutState, LayoutChunkResult result) { - final boolean layingOutInPrimaryDirection = - layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL; - int count = 0; - int consumedSpanCount = 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; - } - consumedSpanCount += spanSize; - mSet[count] = view; - count++; - } - - if (count == 0) { - result.mFinished = true; - return; - } - - int maxSize = 0; - - // we should assign spans before item decor offsets are calculated - assignSpans(recycler, state, count, consumedSpanCount, 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); - } - } - - int spanSize = getSpanSize(recycler, state, getPosition(view)); - final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize, - View.MeasureSpec.EXACTLY); - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height)); - } else { - measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec); - } - final int size = mOrientationHelper.getDecoratedMeasurement(view); - if (size > maxSize) { - maxSize = size; - } - } - - // views that did not measure the maxSize has to be re-measured - final int maxMeasureSpec = getMainDirSpec(maxSize); - for (int i = 0; i < count; i ++) { - final View view = mSet[i]; - if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) { - int spanSize = getSpanSize(recycler, state, getPosition(view)); - final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize, - View.MeasureSpec.EXACTLY); - if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec); - } else { - measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec); - } - } - } - - 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) { - left = getPaddingLeft() + mSizePerSpan * params.mSpanIndex; - right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); - } else { - top = getPaddingTop() + mSizePerSpan * 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. - layoutDecorated(view, left + params.leftMargin, top + params.topMargin, - right - params.rightMargin, bottom - params.bottomMargin); - 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.isFocusable(); - } - Arrays.fill(mSet, null); - } - - private int getMainDirSpec(int dim) { - if (dim < 0) { - return MAIN_DIR_SPEC; - } else { - return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY); - } - } - - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) { - calculateItemDecorationsForChild(child, mDecorInsets); - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left, - lp.rightMargin + mDecorInsets.right); - heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top, - lp.bottomMargin + mDecorInsets.bottom); - child.measure(widthSpec, heightSpec); - } - - private int updateSpecWithExtra(int spec, int startInset, int endInset) { - if (startInset == 0 && endInset == 0) { - return spec; - } - final int mode = View.MeasureSpec.getMode(spec); - if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { - return View.MeasureSpec.makeMeasureSpec( - View.MeasureSpec.getSize(spec) - startInset - endInset, mode); - } - return spec; - } - - private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, - int consumedSpanCount, boolean layingOutInPrimaryDirection) { - int span, spanDiff, 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; - } - if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span - span = consumedSpanCount - 1; - spanDiff = -1; - } else { - span = 0; - spanDiff = 1; - } - for (int i = start; i != end; i += diff) { - View view = mSet[i]; - LayoutParams params = (LayoutParams) view.getLayoutParams(); - params.mSpanSize = getSpanSize(recycler, state, getPosition(view)); - if (spanDiff == -1 && params.mSpanSize > 1) { - params.mSpanIndex = span - (params.mSpanSize - 1); - } else { - params.mSpanIndex = span; - } - span += spanDiff * 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; - } - if (spanCount < 1) { - throw new IllegalArgumentException("Span count should be at least 1. Provided " - + spanCount); - } - mSpanCount = spanCount; - mSpanSizeLookup.invalidateSpanIndexCache(); - } - - /** - * 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 static abstract class SpanSizeLookup { - - final SparseIntArray mSpanIndexCache = new SparseIntArray(); - - private boolean mCacheSpanIndices = 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 - */ - abstract public 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)}, 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) { - mCacheSpanIndices = cacheSpanIndices; - } - - /** - * Clears the span index cache. GridLayoutManager automatically calls this method when - * adapter changes occur. - */ - public void invalidateSpanIndexCache() { - mSpanIndexCache.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; - } - - 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; - } - - /** - * 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 && mSpanIndexCache.size() > 0) { - int prevKey = findReferenceIndexFromCache(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; - } - - int findReferenceIndexFromCache(int position) { - int lo = 0; - int hi = mSpanIndexCache.size() - 1; - - while (lo <= hi) { - final int mid = (lo + hi) >>> 1; - final int midVal = mSpanIndexCache.keyAt(mid); - if (midVal < position) { - lo = mid + 1; - } else { - hi = mid - 1; - } - } - int index = lo - 1; - if (index >= 0 && index < mSpanIndexCache.size()) { - return mSpanIndexCache.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 positionSpanSize = getSpanSize(adapterPosition); - for (int i = 0; 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 boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null; - } - - /** - * 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. - */ - 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; - - private int mSpanIndex = INVALID_SPAN_ID; - - private 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. - *

- * Note that span index may change by whether the RecyclerView is RTL or not. For - * example, if the number of spans is 3 and layout is RTL, the rightmost item will have - * span index of 2. If the layout changes back to LTR, span index for this view will be 0. - * If the item was occupying 2 spans, span indices would be 1 and 0 respectively. - *

- * 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/android/support/v7/widget/LayoutState.java b/app/src/main/java/android/support/v7/widget/LayoutState.java deleted file mode 100644 index e62a80a289..0000000000 --- a/app/src/main/java/android/support/v7/widget/LayoutState.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 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 languag`e governing permissions and - * limitations under the License. - */ - -package android.support.v7.widget; -import android.view.View; - -/** - * Helper class that keeps temporary state while {LayoutManager} is filling out the empty - * space. - */ -class LayoutState { - - final static String TAG = "LayoutState"; - - final static int LAYOUT_START = -1; - - final static int LAYOUT_END = 1; - - final static int INVALID_LAYOUT = Integer.MIN_VALUE; - - final static int ITEM_DIRECTION_HEAD = -1; - - final static int ITEM_DIRECTION_TAIL = 1; - - final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE; - - /** - * 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 if you want to pre-layout items that are not yet visible. - * The difference with {@link #mAvailable} is that, when recycling, distance rendered for - * {@link #mExtra} is not considered not to recycle visible children. - */ - int mExtra = 0; - - /** - * @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; - } -} diff --git a/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java b/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java deleted file mode 100644 index 230db34523..0000000000 --- a/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java +++ /dev/null @@ -1,1981 +0,0 @@ -/* - * Copyright (C) 2014 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 languag`e governing permissions and - * limitations under the License. - */ - -package android.support.v7.widget; - -import android.content.Context; -import android.graphics.PointF; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import java.util.List; - -import static android.support.v7.widget.RecyclerView.NO_POSITION; - -/** - * A {@link RecyclerView.LayoutManager} implementation which provides - * similar functionality to {@link android.widget.ListView}. - */ -public class LinearLayoutManager extends RecyclerView.LayoutManager { - - private static final String TAG = "LinearLayoutManager"; - - private static final boolean DEBUG = false; - - public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; - - public static final int VERTICAL = OrientationHelper.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 = 0.33f; - - - /** - * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL} - */ - int mOrientation; - - /** - * 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. - * Based on {@link #mOrientation}, an implementation is lazily created in - * {@link #ensureLayoutState} method. - */ - 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 = 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; - - /** - * Creates a vertical LinearLayoutManager - * - * @param context Current context, will be used to access resources. - */ - public LinearLayoutManager(Context context) { - this(context, VERTICAL, 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(Context context, int orientation, boolean reverseLayout) { - mAnchorInfo = new AnchorInfo(); - setOrientation(orientation); - setReverseLayout(reverseLayout); - } - - /** - * {@inheritDoc} - */ - @Override - 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 avilable 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 - public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { - super.onDetachedFromWindow(view, recycler); - if (mRecycleChildrenOnDetach) { - removeAndRecycleAllViews(recycler); - recycler.clear(); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (getChildCount() > 0) { - final AccessibilityRecordCompat record = AccessibilityEventCompat - .asRecord(event); - record.setFromIndex(findFirstVisibleItemPosition()); - record.setToIndex(findLastVisibleItemPosition()); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (mPendingSavedState != null) { - return new SavedState(mPendingSavedState); - } - SavedState state = new SavedState(); - if (getChildCount() > 0) { - 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 - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mPendingSavedState = (SavedState) state; - 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 orientaion of the layout. - * - * @return Current orientation. - * @see #mOrientation - * @see #setOrientation(int) - */ - 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(int orientation) { - if (orientation != HORIZONTAL && orientation != VERTICAL) { - throw new IllegalArgumentException("invalid orientation:" + orientation); - } - assertNotInLayoutOrScroll(null); - if (orientation == mOrientation) { - return; - } - mOrientation = orientation; - mOrientationHelper = null; - 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 {@link #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 - 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) { - return getChildAt(viewPosition); - } - return null; - } - - /** - *

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.

- *

Laying out invisible elements will eventually come with performance cost. On the other - * hand, in places like smooth scrolling to an unknown location, this extra content helps - * LayoutManager to calculate a much smoother scrolling; which improves user experience.

- *

You can also use this if you are trying to pre-layout your upcoming views.

- * - * @return The extra space that should be laid out (in pixels). - */ - protected int getExtraLayoutSpace(RecyclerView.State state) { - if (state.hasTargetScrollPosition()) { - return mOrientationHelper.getTotalSpace(); - } else { - return 0; - } - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - return LinearLayoutManager.this - .computeScrollVectorForPosition(targetPosition); - } - }; - linearSmoothScroller.setTargetPosition(position); - startSmoothScroll(linearSmoothScroller); - } - - 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 - 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 && mPendingSavedState.hasValidAnchor()) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - } - - ensureLayoutState(); - mLayoutState.mRecycle = false; - // resolve layout direction - resolveShouldLayoutReverse(); - - mAnchorInfo.reset(); - mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; - // calculate anchor position and coordinate - updateAnchorInfoForLayout(state, mAnchorInfo); - 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. - int extraForStart; - int extraForEnd; - final int extra = getExtraLayoutSpace(state); - boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition; - if (before == mShouldReverseLayout) { - extraForEnd = extra; - extraForStart = 0; - } else { - extraForStart = extra; - extraForEnd = 0; - } - extraForStart += mOrientationHelper.getStartAfterPadding(); - extraForEnd += mOrientationHelper.getEndPadding(); - if (state.isPreLayout() && mPendingScrollPosition != 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; - onAnchorReady(state, mAnchorInfo); - detachAndScrapAttachedViews(recycler); - mLayoutState.mIsPreLayout = state.isPreLayout(); - if (mAnchorInfo.mLayoutFromEnd) { - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForEnd += mLayoutState.mAvailable; - } - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - } else { - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForStart += mLayoutState.mAvailable; - } - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - startOffset = 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()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - mOrientationHelper.onLayoutComplete(); - } - mLastStackFromEnd = mStackFromEnd; - mPendingSavedState = null; // we don't need this anymore - if (DEBUG) { - validateChildOrder(); - } - } - - /** - * Method called when Anchor position is decided. Extending class can setup accordingly or - * even update anchor info if necessary. - * - * @param state - * @param anchorInfo Simple data structure to keep anchor point information for the next layout - */ - void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) { - } - - /** - * 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); - final int position = scrap.getPosition(); - 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.mExtra = scrapExtraStart; - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition += mShouldReverseLayout ? 1 : -1; - fill(recycler, mLayoutState, state, false); - } - - if (scrapExtraEnd > 0) { - View anchor = getChildClosestToEnd(); - updateLayoutStateToFillEnd(getPosition(anchor), endOffset); - mLayoutState.mExtra = scrapExtraEnd; - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition += mShouldReverseLayout ? -1 : 1; - fill(recycler, mLayoutState, state, false); - } - mLayoutState.mScrapList = null; - } - - private void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) { - if (updateAnchorFromPendingData(state, anchorInfo)) { - if (DEBUG) { - Log.d(TAG, "updated anchor info from pending information"); - } - return; - } - - if (updateAnchorFromChildren(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.State state, AnchorInfo anchorInfo) { - if (getChildCount() == 0) { - return false; - } - View focused = getFocusedChild(); - if (focused != null && anchorInfo.assignFromViewIfValid(focused, state)) { - if (DEBUG) { - Log.d(TAG, "decided anchor child from focused view"); - } - return true; - } - - if (mLastStackFromEnd != mStackFromEnd) { - return false; - } - - View referenceChild = anchorInfo.mLayoutFromEnd ? findReferenceChildClosestToEnd(state) - : findReferenceChildClosestToStart(state); - if (referenceChild != null) { - anchorInfo.assignFromView(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 boolean notVisible = - mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper - .getEndAfterPadding() - || mOrientationHelper.getDecoratedEnd(referenceChild) - < mOrientationHelper.getStartAfterPadding(); - if (notVisible) { - anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd - ? mOrientationHelper.getEndAfterPadding() - : mOrientationHelper.getStartAfterPadding(); - } - } - 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 == NO_POSITION) { - return false; - } - // validate scroll position - if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { - mPendingScrollPosition = 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 (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.SCOLLING_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.SCOLLING_OFFSET_NaN; - - } - - protected boolean isLayoutRTL() { - return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - void ensureLayoutState() { - if (mLayoutState == null) { - mLayoutState = new LayoutState(); - } - if (mOrientationHelper == null) { - mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation); - } - } - - /** - *

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(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 - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return 0; - } - return scrollBy(dx, recycler, state); - } - - /** - * {@inheritDoc} - */ - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == HORIZONTAL) { - return 0; - } - return scrollBy(dy, recycler, state); - } - - @Override - public int computeHorizontalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeHorizontalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeVerticalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeHorizontalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private int computeScrollOffset(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), this, - mSmoothScrollbarEnabled, mShouldReverseLayout); - } - - private int computeScrollExtent(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), this, - mSmoothScrollbarEnabled); - } - - private int computeScrollRange(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollRange(state, mOrientationHelper, - getChildClosestToStart(), getChildClosestToEnd(), 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) { - mLayoutState.mExtra = getExtraLayoutSpace(state); - mLayoutState.mLayoutDirection = layoutDirection; - int fastScrollSpace; - if (layoutDirection == LayoutState.LAYOUT_END) { - mLayoutState.mExtra += 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) - fastScrollSpace = mOrientationHelper.getDecoratedEnd(child) - - mOrientationHelper.getEndAfterPadding(); - - } else { - final View child = getChildClosestToStart(); - mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding(); - mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL - : LayoutState.ITEM_DIRECTION_HEAD; - mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection; - mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child); - fastScrollSpace = -mOrientationHelper.getDecoratedStart(child) - + mOrientationHelper.getStartAfterPadding(); - } - mLayoutState.mAvailable = requiredSpace; - if (canUseExistingSpace) { - mLayoutState.mAvailable -= fastScrollSpace; - } - mLayoutState.mScrollingOffset = fastScrollSpace; - } - - int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { - if (getChildCount() == 0 || dy == 0) { - return 0; - } - mLayoutState.mRecycle = true; - ensureLayoutState(); - final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; - final int absDy = Math.abs(dy); - updateLayoutState(layoutDirection, absDy, true, state); - final int freeScroll = mLayoutState.mScrollingOffset; - final int consumed = freeScroll + 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 = absDy > consumed ? layoutDirection * consumed : dy; - mOrientationHelper.offsetChildren(-scrolled); - if (DEBUG) { - Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled); - } - return scrolled; - } - - @Override - 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. - * - * @param recycler Recycler instance of {@link RecyclerView} - * @param dt 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. - */ - private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { - if (dt < 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 = dt; - final int childCount = getChildCount(); - if (mShouldReverseLayout) { - for (int i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(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) {// stop here - recycleChildren(recycler, 0, i); - return; - } - } - } - } - - - /** - * Recycles views that went out of bounds after scrolling towards the start of the layout. - * - * @param recycler Recycler instance of {@link RecyclerView} - * @param dt 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. - */ - private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { - final int childCount = getChildCount(); - if (dt < 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() - dt; - if (mShouldReverseLayout) { - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(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) {// 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) - * @see #recycleViewsFromEnd(RecyclerView.Recycler, int) - * @see LayoutState#mLayoutDirection - */ - private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { - if (!layoutState.mRecycle) { - return; - } - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { - recycleViewsFromEnd(recycler, layoutState.mScrollingOffset); - } else { - recycleViewsFromStart(recycler, layoutState.mScrollingOffset); - } - } - - /** - * 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 scoll 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.SCOLLING_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.mExtra; - LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); - while (remainingSpace > 0 && layoutState.hasMore(state)) { - layoutChunkResult.resetInternal(); - layoutChunk(recycler, state, layoutState, layoutChunkResult); - 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 || mLayoutState.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.SCOLLING_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. - layoutDecorated(view, left + params.leftMargin, top + params.topMargin, - right - params.rightMargin, bottom - params.bottomMargin); - 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.isFocusable(); - } - - /** - * Converts a focusDirection to orientation. - * - * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, - * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} - * or 0 for not applicable - * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction - * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise. - */ - private int convertFocusDirectionToLayoutDirection(int focusDirection) { - switch (focusDirection) { - case View.FOCUS_BACKWARD: - return LayoutState.LAYOUT_START; - case View.FOCUS_FORWARD: - 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); - } - - - /** - * Among the children that are suitable to be considered as an anchor child, returns the one - * closest to the end of the layout. - *

- * 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 that are within the visible bounds. - * @return A View that can be used an an anchor View. - */ - private View findReferenceChildClosestToEnd(RecyclerView.State state) { - return mShouldReverseLayout ? findFirstReferenceChild(state.getItemCount()) : - findLastReferenceChild(state.getItemCount()); - } - - /** - * Among the children that are suitable to be considered as an anchor child, returns the one - * closest to the start of the layout. - *

- * 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 that are within the visible bounds. - * - * @return A View that can be used an an anchor View. - */ - private View findReferenceChildClosestToStart(RecyclerView.State state) { - return mShouldReverseLayout ? findLastReferenceChild(state.getItemCount()) : - findFirstReferenceChild(state.getItemCount()); - } - - private View findFirstReferenceChild(int itemCount) { - return findReferenceChild(0, getChildCount(), itemCount); - } - - private View findLastReferenceChild(int itemCount) { - return findReferenceChild(getChildCount() - 1, -1, itemCount); - } - - private View findReferenceChild(int start, int end, int itemCount) { - View invalidMatch = null; - View outOfBoundsMatch = null; - final int boundsStart = mOrientationHelper.getStartAfterPadding(); - final int boundsEnd = mOrientationHelper.getEndAfterPadding(); - final int diff = end > start ? 1 : -1; - for (int i = start; i != end; i += diff) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - 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; - } - - /** - * Returns the adapter position of the first visible view. - *

- * 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); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the first fully visible view. - *

- * 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); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last visible view. - *

- * 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); - return child == null ? NO_POSITION : getPosition(child); - } - - /** - * Returns the adapter position of the last fully visible view. - *

- * 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); - return child == null ? NO_POSITION : getPosition(child); - } - - View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { - final int start = mOrientationHelper.getStartAfterPadding(); - final int end = mOrientationHelper.getEndAfterPadding(); - final int next = toIndex > fromIndex ? 1 : -1; - for (int i = fromIndex; i != toIndex; i+=next) { - final View child = getChildAt(i); - final int childStart = mOrientationHelper.getDecoratedStart(child); - final int childEnd = mOrientationHelper.getDecoratedEnd(child); - if (childStart < end && childEnd > start) { - if (completelyVisible) { - if (childStart >= start && childEnd <= end) { - return child; - } - } else { - return child; - } - } - } - return null; - } - - @Override - public View onFocusSearchFailed(View focused, int focusDirection, - RecyclerView.Recycler recycler, RecyclerView.State state) { - resolveShouldLayoutReverse(); - if (getChildCount() == 0) { - return null; - } - - final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection); - if (layoutDir == LayoutState.INVALID_LAYOUT) { - return null; - } - final View referenceChild; - if (layoutDir == LayoutState.LAYOUT_START) { - referenceChild = findReferenceChildClosestToStart(state); - } else { - referenceChild = findReferenceChildClosestToEnd(state); - } - if (referenceChild == null) { - if (DEBUG) { - Log.d(TAG, - "Cannot find a child with a valid position to be used for focus search."); - } - return null; - } - ensureLayoutState(); - final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); - updateLayoutState(layoutDir, maxScroll, false, state); - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; - mLayoutState.mRecycle = false; - fill(recycler, mLayoutState, state, true); - final View nextFocus; - if (layoutDir == LayoutState.LAYOUT_START) { - nextFocus = getChildClosestToStart(); - } else { - nextFocus = getChildClosestToEnd(); - } - if (nextFocus == referenceChild || !nextFocus.isFocusable()) { - return null; - } - return nextFocus; - } - - /** - * 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; - } - - /** - * Helper class that keeps temporary state while {LayoutManager} is filling out the empty - * space. - */ - static class LayoutState { - - final static String TAG = "LinearLayoutManager#LayoutState"; - - final static int LAYOUT_START = -1; - - final static int LAYOUT_END = 1; - - final static int INVALID_LAYOUT = Integer.MIN_VALUE; - - final static int ITEM_DIRECTION_HEAD = -1; - - final static int ITEM_DIRECTION_TAIL = 1; - - final static int SCOLLING_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 #mExtra} is not considered to avoid recycling visible children. - */ - int mExtra = 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; - - /** - * 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; - - /** - * @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 nextFromLimitedList(); - } - final View view = recycler.getViewForPosition(mCurrentPosition); - mCurrentPosition += mItemDirection; - return view; - } - - /** - * Returns next item from limited 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 nextFromLimitedList() { - int size = mScrapList.size(); - RecyclerView.ViewHolder closest = null; - int closestDistance = Integer.MAX_VALUE; - for (int i = 0; i < size; i++) { - RecyclerView.ViewHolder viewHolder = mScrapList.get(i); - if (!mIsPreLayout && viewHolder.isRemoved()) { - continue; - } - final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection; - if (distance < 0) { - continue; // item is not in current direction - } - if (distance < closestDistance) { - closest = viewHolder; - closestDistance = distance; - if (distance == 0) { - break; - } - } - } - if (DEBUG) { - Log.d(TAG, "layout from scrap. found view:?" + (closest != null)); - } - if (closest != null) { - mCurrentPosition = closest.getPosition() + mItemDirection; - return closest.itemView; - } - return null; - } - - void log() { - Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" + - mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection); - } - } - - 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; - } - - public SavedState(SavedState other) { - mAnchorPosition = other.mAnchorPosition; - mAnchorOffset = other.mAnchorOffset; - mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; - } - - boolean hasValidAnchor() { - return mAnchorPosition >= 0; - } - - void invalidateAnchor() { - mAnchorPosition = 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 Creator CREATOR - = new 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 - */ - class AnchorInfo { - int mPosition; - int mCoordinate; - boolean mLayoutFromEnd; - void reset() { - mPosition = NO_POSITION; - mCoordinate = INVALID_OFFSET; - mLayoutFromEnd = 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 + - '}'; - } - - /** - * Assign anchor position information from the provided view if it is valid as a reference - * child. - */ - public boolean assignFromViewIfValid(View child, RecyclerView.State state) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - if (!lp.isItemRemoved() && lp.getViewPosition() >= 0 - && lp.getViewPosition() < state.getItemCount()) { - assignFromView(child); - return true; - } - return false; - } - - public void assignFromView(View child) { - if (mLayoutFromEnd) { - mCoordinate = mOrientationHelper.getDecoratedEnd(child) + - mOrientationHelper.getTotalSpaceChange(); - } else { - mCoordinate = mOrientationHelper.getDecoratedStart(child); - } - - mPosition = getPosition(child); - } - } - - 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/android/support/v7/widget/LinearSmoothScroller.java b/app/src/main/java/android/support/v7/widget/LinearSmoothScroller.java deleted file mode 100644 index bdf2803d73..0000000000 --- a/app/src/main/java/android/support/v7/widget/LinearSmoothScroller.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.content.Context; -import android.graphics.PointF; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.View; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -/** - * {@link RecyclerView.SmoothScroller} implementation which uses - * {@link LinearInterpolator} until the target position becames a child of - * the RecyclerView and then uses - * {@link DecelerateInterpolator} to slowly approach to target position. - */ -abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller { - - private static final String TAG = "LinearSmoothScroller"; - - 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(View, int) - * @see #calculateDyToMakeVisible(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(View, int) - * @see #calculateDyToMakeVisible(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(View, int) - * @see #calculateDyToMakeVisible(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(); - - protected PointF mTargetVector; - - private final float MILLISECONDS_PER_PX; - - // 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; - - public LinearSmoothScroller(Context context) { - MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics()); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onStart() { - - } - - /** - * {@inheritDoc} - */ - @Override - 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 - protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) { - if (getChildCount() == 0) { - stop(); - return; - } - 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. - * - * @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. - */ - protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { - return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; - } - - /** - *

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(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) * MILLISECONDS_PER_PX); - } - - /** - * 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) - */ - protected void updateActionForInterimTarget(Action action) { - // find an interim target position - PointF scrollVector = computeScrollVectorForPosition(getTargetPosition()); - if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) { - Log.e(TAG, "To support smooth scrolling, you should override \n" - + "LayoutManager#computeScrollVectorForPosition.\n" - + "Falling back to instant scroll"); - final int target = getTargetPosition(); - stop(); - instantScrollToPosition(target); - 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(View, int)} and - * {@link #calculateDyToMakeVisible(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_END}. - * @return The vertical scroll amount necessary to make the view visible with the given - * snap preference. - */ - public int calculateDyToMakeVisible(View view, int snapPreference) { - final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (!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. - */ - public int calculateDxToMakeVisible(View view, int snapPreference) { - final RecyclerView.LayoutManager layoutManager = getLayoutManager(); - if (!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); - } - - abstract public PointF computeScrollVectorForPosition(int targetPosition); -} diff --git a/app/src/main/java/android/support/v7/widget/OpReorderer.java b/app/src/main/java/android/support/v7/widget/OpReorderer.java deleted file mode 100644 index 1a34cba3e2..0000000000 --- a/app/src/main/java/android/support/v7/widget/OpReorderer.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.support.v7.widget.AdapterHelper.UpdateOp; - -import java.util.List; - -import static android.support.v7.widget.AdapterHelper.UpdateOp.ADD; -import static android.support.v7.widget.AdapterHelper.UpdateOp.MOVE; -import static android.support.v7.widget.AdapterHelper.UpdateOp.REMOVE; -import static android.support.v7.widget.AdapterHelper.UpdateOp.UPDATE; - -class OpReorderer { - - final Callback mCallback; - - public 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 UpdateOp moveOp = list.get(badMove); - final UpdateOp nextOp = list.get(next); - switch (nextOp.cmd) { - case REMOVE: - swapMoveRemove(list, badMove, moveOp, next, nextOp); - break; - case ADD: - swapMoveAdd(list, badMove, moveOp, next, nextOp); - break; - case UPDATE: - swapMoveUpdate(list, badMove, moveOp, next, nextOp); - break; - } - } - - void swapMoveRemove(List list, int movePos, UpdateOp moveOp, - int removePos, UpdateOp removeOp) { - 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 = 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(REMOVE, moveOp.positionStart + 1, remaining); - 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, UpdateOp moveOp, int add, - 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, UpdateOp moveOp, int update, - UpdateOp updateOp) { - UpdateOp extraUp1 = null; - 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(UPDATE, moveOp.positionStart, 1); - } - // 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(UPDATE, moveOp.positionStart + 1, remaining); - 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 UpdateOp op1 = list.get(i); - if (op1.cmd == MOVE) { - if (foundNonMove) { - return i; - } - } else { - foundNonMove = true; - } - } - return -1; - } - - static interface Callback { - - UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount); - - void recycleUpdateOp(UpdateOp op); - } -} diff --git a/app/src/main/java/android/support/v7/widget/OrientationHelper.java b/app/src/main/java/android/support/v7/widget/OrientationHelper.java deleted file mode 100644 index a678d86de0..0000000000 --- a/app/src/main/java/android/support/v7/widget/OrientationHelper.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.view.View; -import android.widget.LinearLayout; - -/** - * 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 = LinearLayout.HORIZONTAL; - - public static final int VERTICAL = LinearLayout.VERTICAL; - - private int mLastTotalSpace = INVALID_SIZE; - - private OrientationHelper(RecyclerView.LayoutManager layoutManager) { - mLayoutManager = layoutManager; - } - - /** - * 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(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(View) - */ - public abstract int getDecoratedEnd(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(); - - /** - * 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, 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 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(); - } - }; - } - - /** - * 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 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(); - } - }; - } -} \ No newline at end of file diff --git a/app/src/main/java/android/support/v7/widget/PositionMap.java b/app/src/main/java/android/support/v7/widget/PositionMap.java deleted file mode 100644 index 3777937b72..0000000000 --- a/app/src/main/java/android/support/v7/widget/PositionMap.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright (C) 2013 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 android.support.v7.widget; - -import java.util.ArrayList; - -/** - * Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions. - */ -class PositionMap implements Cloneable { - private static final Object DELETED = new Object(); - private boolean mGarbage = false; - - private int[] mKeys; - private Object[] mValues; - private int mSize; - - /** - * Creates a new SparseArray containing no mappings. - */ - public PositionMap() { - this(10); - } - - /** - * Creates a new PositionMap containing no mappings that will not - * require any additional memory allocation to store the specified - * number of mappings. If you supply an initial capacity of 0, the - * sparse array will be initialized with a light-weight representation - * not requiring any additional array allocations. - */ - public PositionMap(int initialCapacity) { - if (initialCapacity == 0) { - mKeys = ContainerHelpers.EMPTY_INTS; - mValues = ContainerHelpers.EMPTY_OBJECTS; - } else { - initialCapacity = idealIntArraySize(initialCapacity); - mKeys = new int[initialCapacity]; - mValues = new Object[initialCapacity]; - } - mSize = 0; - } - - @Override - @SuppressWarnings("unchecked") - public PositionMap clone() { - PositionMap clone = null; - try { - clone = (PositionMap) super.clone(); - clone.mKeys = mKeys.clone(); - clone.mValues = mValues.clone(); - } catch (CloneNotSupportedException cnse) { - /* ignore */ - } - return clone; - } - - /** - * Gets the Object mapped from the specified key, or null - * if no such mapping has been made. - */ - public E get(int key) { - return get(key, null); - } - - /** - * Gets the Object mapped from the specified key, or the specified Object - * if no such mapping has been made. - */ - @SuppressWarnings("unchecked") - public E get(int key, E valueIfKeyNotFound) { - int i = ContainerHelpers.binarySearch(mKeys, mSize, key); - - if (i < 0 || mValues[i] == DELETED) { - return valueIfKeyNotFound; - } else { - return (E) mValues[i]; - } - } - - /** - * Removes the mapping from the specified key, if there was any. - */ - public void delete(int key) { - int i = ContainerHelpers.binarySearch(mKeys, mSize, key); - - if (i >= 0) { - if (mValues[i] != DELETED) { - mValues[i] = DELETED; - mGarbage = true; - } - } - } - - /** - * Alias for {@link #delete(int)}. - */ - public void remove(int key) { - delete(key); - } - - /** - * Removes the mapping at the specified index. - */ - public void removeAt(int index) { - if (mValues[index] != DELETED) { - mValues[index] = DELETED; - mGarbage = true; - } - } - - /** - * Remove a range of mappings as a batch. - * - * @param index Index to begin at - * @param size Number of mappings to remove - */ - public void removeAtRange(int index, int size) { - final int end = Math.min(mSize, index + size); - for (int i = index; i < end; i++) { - removeAt(i); - } - } - - public void insertKeyRange(int keyStart, int count) { - - } - - public void removeKeyRange(ArrayList removedItems, int keyStart, int count) { - - } - - private void gc() { - // Log.e("SparseArray", "gc start with " + mSize); - - int n = mSize; - int o = 0; - int[] keys = mKeys; - Object[] values = mValues; - - for (int i = 0; i < n; i++) { - Object val = values[i]; - - if (val != DELETED) { - if (i != o) { - keys[o] = keys[i]; - values[o] = val; - values[i] = null; - } - - o++; - } - } - - mGarbage = false; - mSize = o; - - // Log.e("SparseArray", "gc end with " + mSize); - } - - /** - * Adds a mapping from the specified key to the specified value, - * replacing the previous mapping from the specified key if there - * was one. - */ - public void put(int key, E value) { - int i = ContainerHelpers.binarySearch(mKeys, mSize, key); - - if (i >= 0) { - mValues[i] = value; - } else { - i = ~i; - - if (i < mSize && mValues[i] == DELETED) { - mKeys[i] = key; - mValues[i] = value; - return; - } - - if (mGarbage && mSize >= mKeys.length) { - gc(); - - // Search again because indices may have changed. - i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); - } - - if (mSize >= mKeys.length) { - int n = idealIntArraySize(mSize + 1); - - int[] nkeys = new int[n]; - Object[] nvalues = new Object[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - if (mSize - i != 0) { - // Log.e("SparseArray", "move " + (mSize - i)); - System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); - System.arraycopy(mValues, i, mValues, i + 1, mSize - i); - } - - mKeys[i] = key; - mValues[i] = value; - mSize++; - } - } - - /** - * Returns the number of key-value mappings that this SparseArray - * currently stores. - */ - public int size() { - if (mGarbage) { - gc(); - } - - return mSize; - } - - /** - * Given an index in the range 0...size()-1, returns - * the key from the indexth key-value mapping that this - * SparseArray stores. - */ - public int keyAt(int index) { - if (mGarbage) { - gc(); - } - - return mKeys[index]; - } - - /** - * Given an index in the range 0...size()-1, returns - * the value from the indexth key-value mapping that this - * SparseArray stores. - */ - @SuppressWarnings("unchecked") - public E valueAt(int index) { - if (mGarbage) { - gc(); - } - - return (E) mValues[index]; - } - - /** - * Given an index in the range 0...size()-1, sets a new - * value for the indexth key-value mapping that this - * SparseArray stores. - */ - public void setValueAt(int index, E value) { - if (mGarbage) { - gc(); - } - - mValues[index] = value; - } - - /** - * Returns the index for which {@link #keyAt} would return the - * specified key, or a negative number if the specified - * key is not mapped. - */ - public int indexOfKey(int key) { - if (mGarbage) { - gc(); - } - - return ContainerHelpers.binarySearch(mKeys, mSize, key); - } - - /** - * Returns an index for which {@link #valueAt} would return the - * specified key, or a negative number if no keys map to the - * specified value. - *

Beware that this is a linear search, unlike lookups by key, - * and that multiple keys can map to the same value and this will - * find only one of them. - *

Note also that unlike most collections' {@code indexOf} methods, - * this method compares values using {@code ==} rather than {@code equals}. - */ - public int indexOfValue(E value) { - if (mGarbage) { - gc(); - } - - for (int i = 0; i < mSize; i++) - if (mValues[i] == value) - return i; - - return -1; - } - - /** - * Removes all key-value mappings from this SparseArray. - */ - public void clear() { - int n = mSize; - Object[] values = mValues; - - for (int i = 0; i < n; i++) { - values[i] = null; - } - - mSize = 0; - mGarbage = false; - } - - /** - * Puts a key/value pair into the array, optimizing for the case where - * the key is greater than all existing keys in the array. - */ - public void append(int key, E value) { - if (mSize != 0 && key <= mKeys[mSize - 1]) { - put(key, value); - return; - } - - if (mGarbage && mSize >= mKeys.length) { - gc(); - } - - int pos = mSize; - if (pos >= mKeys.length) { - int n = idealIntArraySize(pos + 1); - - int[] nkeys = new int[n]; - Object[] nvalues = new Object[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - mKeys[pos] = key; - mValues[pos] = value; - mSize = pos + 1; - } - - /** - * {@inheritDoc} - * - *

This implementation composes a string by iterating over its mappings. If - * this map contains itself as a value, the string "(this Map)" - * will appear in its place. - */ - @Override - public String toString() { - if (size() <= 0) { - return "{}"; - } - - StringBuilder buffer = new StringBuilder(mSize * 28); - buffer.append('{'); - for (int i=0; i 0) { - buffer.append(", "); - } - int key = keyAt(i); - buffer.append(key); - buffer.append('='); - Object value = valueAt(i); - if (value != this) { - buffer.append(value); - } else { - buffer.append("(this Map)"); - } - } - buffer.append('}'); - return buffer.toString(); - } - - static int idealByteArraySize(int need) { - for (int i = 4; i < 32; i++) - if (need <= (1 << i) - 12) - return (1 << i) - 12; - - return need; - } - - static int idealBooleanArraySize(int need) { - return idealByteArraySize(need); - } - - static int idealShortArraySize(int need) { - return idealByteArraySize(need * 2) / 2; - } - - static int idealCharArraySize(int need) { - return idealByteArraySize(need * 2) / 2; - } - - static int idealIntArraySize(int need) { - return idealByteArraySize(need * 4) / 4; - } - - static int idealFloatArraySize(int need) { - return idealByteArraySize(need * 4) / 4; - } - - static int idealObjectArraySize(int need) { - return idealByteArraySize(need * 4) / 4; - } - - static int idealLongArraySize(int need) { - return idealByteArraySize(need * 8) / 8; - } - - static class ContainerHelpers { - static final boolean[] EMPTY_BOOLEANS = new boolean[0]; - static final int[] EMPTY_INTS = new int[0]; - static final long[] EMPTY_LONGS = new long[0]; - static final Object[] EMPTY_OBJECTS = new Object[0]; - - // This is Arrays.binarySearch(), but doesn't do any argument validation. - static int binarySearch(int[] array, int size, int value) { - int lo = 0; - int hi = size - 1; - - while (lo <= hi) { - final int mid = (lo + hi) >>> 1; - final int midVal = array[mid]; - - if (midVal < value) { - lo = mid + 1; - } else if (midVal > value) { - hi = mid - 1; - } else { - return mid; // value found - } - } - return ~lo; // value not present - } - } - -} diff --git a/app/src/main/java/android/support/v7/widget/RecyclerView.java b/app/src/main/java/android/support/v7/widget/RecyclerView.java deleted file mode 100644 index 2bfa3cee6d..0000000000 --- a/app/src/main/java/android/support/v7/widget/RecyclerView.java +++ /dev/null @@ -1,8303 +0,0 @@ -/* - * Copyright (C) 2013 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 android.support.v7.widget; - -import android.content.Context; -import android.database.Observable; -import android.graphics.Canvas; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.Nullable; -import android.support.v4.util.ArrayMap; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.support.v4.widget.EdgeEffectCompat; -import android.support.v4.widget.ScrollerCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseIntArray; -import android.view.FocusFinder; -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 java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static android.support.v7.widget.AdapterHelper.Callback; -import static android.support.v7.widget.AdapterHelper.UpdateOp; - -/** - * 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.
  • - *
- */ -public class RecyclerView extends ViewGroup { - private static final String TAG = "RecyclerView"; - - private static final boolean DEBUG = false; - - /** - * On Kitkat, 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, Recycler recursively - * traverses itemView and invalidates display list for each ViewGroup that matches this - * criteria. - */ - private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 19 || - Build.VERSION.SDK_INT == 20; - - private static final boolean DISPATCH_TEMP_DETACH = false; - public static final int HORIZONTAL = 0; - public static final int VERTICAL = 1; - - public static final int NO_POSITION = -1; - public static final long NO_ID = -1; - public static final int INVALID_TYPE = -1; - - private static final int MAX_SCROLL_DURATION = 2000; - - private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); - - final Recycler mRecycler = new Recycler(); - - private SavedState mPendingSavedState; - - AdapterHelper mAdapterHelper; - - ChildHelper mChildHelper; - - // we use this like a set - final List mDisappearingViewsInLayoutPass = new ArrayList(); - - /** - * Prior to L, there is no way to query this variable which is why we override the setter and - * track it here. - */ - private 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 - */ - private final Runnable mUpdateChildViewsRunnable = new Runnable() { - public void run() { - if (!mAdapterHelper.hasPendingUpdates()) { - return; - } - if (!mFirstLayoutComplete) { - // a layout request will happen, we should not do layout here. - return; - } - if (mDataSetHasChangedAfterLayout) { - dispatchLayout(); - } else { - eatRequestLayout(); - mAdapterHelper.preProcess(); - if (!mLayoutRequestEaten) { - // We run this after pre-processing is complete so that ViewHolders have their - // final adapter positions. No need to run it if a layout is already requested. - rebindUpdatedViewHolders(); - } - resumeRequestLayout(true); - } - } - }; - - private final Rect mTempRect = new Rect(); - private Adapter mAdapter; - private LayoutManager mLayout; - private RecyclerListener mRecyclerListener; - private final ArrayList mItemDecorations = new ArrayList(); - private final ArrayList mOnItemTouchListeners = - new ArrayList(); - private OnItemTouchListener mActiveOnItemTouchListener; - private boolean mIsAttached; - private boolean mHasFixedSize; - private boolean mFirstLayoutComplete; - private boolean mEatRequestLayout; - private boolean mLayoutRequestEaten; - private boolean mAdapterUpdateDuringMeasure; - private final boolean mPostUpdatesOnAnimation; - private final AccessibilityManager mAccessibilityManager; - - /** - * Set to true when an adapter data set changed notification is received. - * In that case, we cannot run any animations since we don't know what happened. - */ - private boolean mDataSetHasChangedAfterLayout = false; - - /** - * This variable is set to true 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 boolean mRunningLayoutOrScroll = false; - - private EdgeEffectCompat 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; - - // 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 final int mTouchSlop; - private final int mMinFlingVelocity; - private final int mMaxFlingVelocity; - - private final ViewFlinger mViewFlinger = new ViewFlinger(); - - final State mState = new State(); - - private OnScrollListener mScrollListener; - - // For use in item animations - boolean mItemsAddedOrRemoved = false; - boolean mItemsChanged = false; - private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = - new ItemAnimatorRestoreListener(); - private boolean mPostedAnimatorRunner = false; - private RecyclerViewAccessibilityDelegate mAccessibilityDelegate; - private Runnable mItemAnimatorRunner = new Runnable() { - @Override - public void run() { - if (mItemAnimator != null) { - mItemAnimator.runPendingAnimations(); - } - mPostedAnimatorRunner = false; - } - }; - - private static final Interpolator sQuinticInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - public RecyclerView(Context context) { - this(context, null); - } - - public RecyclerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecyclerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setFocusableInTouchMode(true); - final int version = Build.VERSION.SDK_INT; - mPostUpdatesOnAnimation = version >= 16; - - final ViewConfiguration vc = ViewConfiguration.get(context); - mTouchSlop = vc.getScaledTouchSlop(); - mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); - mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); - setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); - - mItemAnimator.setListener(mItemAnimatorListener); - initAdapterManager(); - initChildrenHelper(); - // 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)); - } - - /** - * Returns the accessibility delegate compatibility implementation used by the RecyclerView. - * @return An instance of AccessibilityDelegateCompat used by RecyclerView - */ - 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( - RecyclerViewAccessibilityDelegate accessibilityDelegate) { - mAccessibilityDelegate = accessibilityDelegate; - ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate); - } - - 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) { - RecyclerView.this.addView(child, index); - 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); - } - RecyclerView.this.removeViewAt(index); - } - - @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 ++) { - dispatchChildDetached(getChildAt(i)); - } - RecyclerView.this.removeAllViews(); - } - - @Override - public ViewHolder getChildViewHolder(View view) { - return getChildViewHolderInt(view); - } - - @Override - public void attachViewToParent(View child, int index, - ViewGroup.LayoutParams layoutParams) { - RecyclerView.this.attachViewToParent(child, index, layoutParams); - } - - @Override - public void detachViewFromParent(int offset) { - RecyclerView.this.detachViewFromParent(offset); - } - }); - } - - void initAdapterManager() { - mAdapterHelper = new AdapterHelper(new Callback() { - @Override - public ViewHolder findViewHolder(int position) { - return findViewHolderForPosition(position, true); - } - - @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) { - viewRangeUpdate(positionStart, itemCount); - mItemsChanged = true; - } - - @Override - public void onDispatchFirstPass(UpdateOp op) { - dispatchUpdate(op); - } - - void dispatchUpdate(UpdateOp op) { - switch (op.cmd) { - case UpdateOp.ADD: - mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); - break; - case UpdateOp.REMOVE: - mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); - break; - case UpdateOp.UPDATE: - mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount); - break; - case UpdateOp.MOVE: - mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); - break; - } - } - - @Override - public void onDispatchSecondPass(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 changes in - * adapter content cannot change the size of the RecyclerView itself. - * If your use of RecyclerView falls into this category, set this to true. - * - * @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(); - } - } - - /** - * 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(Adapter adapter, boolean removeAndRecycleExistingViews) { - setAdapterInternal(adapter, true, removeAndRecycleExistingViews); - mDataSetHasChangedAfterLayout = 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(Adapter adapter) { - setAdapterInternal(adapter, false, true); - requestLayout(); - } - - /** - * 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(Adapter adapter, boolean compatibleWithPrevious, - boolean removeAndRecycleViews) { - if (mAdapter != null) { - mAdapter.unregisterAdapterDataObserver(mObserver); - } - if (!compatibleWithPrevious || 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, true); - } - } - mAdapterHelper.reset(); - final Adapter oldAdapter = mAdapter; - mAdapter = adapter; - if (adapter != null) { - adapter.registerAdapterDataObserver(mObserver); - } - if (mLayout != null) { - mLayout.onAdapterChanged(oldAdapter, mAdapter); - } - mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); - mState.mStructureChanged = true; - markKnownViewsInvalid(); - } - - /** - * Retrieves the previously set adapter or null if no adapter is set. - * - * @return The previously set adapter - * @see #setAdapter(Adapter) - */ - 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 - */ - public void setRecyclerListener(RecyclerListener listener) { - mRecyclerListener = listener; - } - - /** - * 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(LayoutManager layout) { - if (layout == mLayout) { - return; - } - // TODO We should do this switch a dispachLayout pass and animate children. There is a good - // chance that LayoutManagers will re-use views. - if (mLayout != null) { - if (mIsAttached) { - mLayout.onDetachedFromWindow(this, mRecycler); - } - mLayout.setRecyclerView(null); - } - mRecycler.clear(); - mChildHelper.removeAllViewsUnfiltered(); - mLayout = layout; - if (layout != null) { - if (layout.mRecyclerView != null) { - throw new IllegalArgumentException("LayoutManager " + layout + - " is already attached to a RecyclerView: " + layout.mRecyclerView); - } - mLayout.setRecyclerView(this); - if (mIsAttached) { - mLayout.onAttachedToWindow(this); - } - } - requestLayout(); - } - - @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) { - mPendingSavedState = (SavedState) state; - super.onRestoreInstanceState(mPendingSavedState.getSuperState()); - if (mLayout != null && mPendingSavedState.mLayoutState != null) { - mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); - } - } - - /** - * 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 view The view to be removed - */ - private void addAnimatingView(View view) { - final boolean alreadyParented = view.getParent() == this; - mRecycler.unscrapView(getChildViewHolder(view)); - if (!alreadyParented) { - mChildHelper.addView(view, true); - } else { - mChildHelper.hide(view); - } - } - - /** - * Removes a view from the animatingViews list. - * @param view The view to be removed - * @see #addAnimatingView(View) - */ - private void removeAnimatingView(View view) { - eatRequestLayout(); - if (mChildHelper.removeViewIfHidden(view)) { - final ViewHolder viewHolder = getChildViewHolderInt(view); - mRecycler.unscrapView(viewHolder); - mRecycler.recycleViewHolderInternal(viewHolder); - if (DEBUG) { - Log.d(TAG, "after removing animated view: " + view + ", " + this); - } - } - resumeRequestLayout(false); - } - - /** - * Return the {@link LayoutManager} currently responsible for - * layout policy for this RecyclerView. - * - * @return The currently bound LayoutManager - */ - 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) - */ - 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 android.support.v4.view.ViewPager ViewPager}. - * - * @param pool Pool to set. If this parameter is null a new pool will be created and used. - */ - public void setRecycledViewPool(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 {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)} - */ - public void setViewCacheExtension(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; - } - - private void setScrollState(int state) { - if (state == mScrollState) { - return; - } - if (DEBUG) { - Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, new Exception()); - } - mScrollState = state; - if (state != SCROLL_STATE_SETTLING) { - stopScrollersInternal(); - } - if (mScrollListener != null) { - mScrollListener.onScrollStateChanged(this, state); - } - mLayout.onScrollStateChanged(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(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(ItemDecoration decor) { - addItemDecoration(decor, -1); - } - - /** - * 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(ItemDecoration decor) { - if (mLayout != null) { - mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" - + " layout"); - } - mItemDecorations.remove(decor); - if (mItemDecorations.isEmpty()) { - setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); - } - markItemDecorInsetsDirty(); - requestLayout(); - } - - /** - * Set a listener that will be notified of any changes in scroll state or position. - * - * @param listener Listener to set or null to clear - */ - public void setOnScrollListener(OnScrollListener listener) { - mScrollListener = listener; - } - - /** - * Convenience method to scroll to a certain position. - * - * RecyclerView does not implement scrolling logic, rather forwards the call to - * {@link LayoutManager#scrollToPosition(int)} - * @param position Scroll to this adapter position - * @see LayoutManager#scrollToPosition(int) - */ - public void scrollToPosition(int position) { - stopScroll(); - 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) { - mLayout.smoothScrollToPosition(this, mState, position); - } - - @Override - public void scrollTo(int x, int y) { - throw new UnsupportedOperationException( - "RecyclerView does not support scrolling to an absolute position."); - } - - @Override - public void scrollBy(int x, int y) { - if (mLayout == null) { - throw new IllegalStateException("Cannot scroll without a LayoutManager set. " + - "Call setLayoutManager with a non-null argument."); - } - final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); - final boolean canScrollVertical = mLayout.canScrollVertically(); - if (canScrollHorizontal || canScrollVertical) { - scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0); - } - } - - /** - * 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. - */ - private void consumePendingUpdateOperations() { - if (mAdapterHelper.hasPendingUpdates()) { - mUpdateChildViewsRunnable.run(); - } - } - - /** - * Does not perform bounds checking. Used by internal methods that have already validated input. - */ - void scrollByInternal(int x, int y) { - int overscrollX = 0, overscrollY = 0; - int hresult = 0, vresult = 0; - consumePendingUpdateOperations(); - if (mAdapter != null) { - eatRequestLayout(); - mRunningLayoutOrScroll = true; - if (x != 0) { - hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); - overscrollX = x - hresult; - } - if (y != 0) { - vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); - overscrollY = y - vresult; - } - if (supportsChangeAnimations()) { - // Fix up shadow views used by changing 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) { - ViewHolder shadowingHolder = holder.mShadowingHolder; - View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null; - if (shadowingView != null) { - 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()); - } - } - } - } - } - mRunningLayoutOrScroll = false; - resumeRequestLayout(false); - } - if (!mItemDecorations.isEmpty()) { - invalidate(); - } - if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { - considerReleasingGlowsOnScroll(x, y); - pullGlows(overscrollX, overscrollY); - } - if (hresult != 0 || vresult != 0) { - onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. - if (mScrollListener != null) { - mScrollListener.onScrolled(this, hresult, vresult); - } - } - if (!awakenScrollBars()) { - invalidate(); - } - } - - /** - *

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 LayoutManager#computeHorizontalScrollOffset(State)} in your - * LayoutManager.

- * - * @return The horizontal offset of the scrollbar's thumb - * @see LayoutManager#computeHorizontalScrollOffset - * (RecyclerView.Adapter) - */ - @Override - protected int computeHorizontalScrollOffset() { - 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 LayoutManager#computeHorizontalScrollExtent(State)} in your - * LayoutManager.

- * - * @return The horizontal extent of the scrollbar's thumb - * @see LayoutManager#computeHorizontalScrollExtent(State) - */ - @Override - protected int computeHorizontalScrollExtent() { - 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 LayoutManager#computeHorizontalScrollRange(State)} in your - * LayoutManager.

- * - * @return The total horizontal range represented by the vertical scrollbar - * @see LayoutManager#computeHorizontalScrollRange(State) - */ - @Override - protected int computeHorizontalScrollRange() { - 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 LayoutManager#computeVerticalScrollOffset(State)} in your - * LayoutManager.

- * - * @return The vertical offset of the scrollbar's thumb - * @see LayoutManager#computeVerticalScrollOffset - * (RecyclerView.Adapter) - */ - @Override - protected int computeVerticalScrollOffset() { - 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 LayoutManager#computeVerticalScrollExtent(State)} in your - * LayoutManager.

- * - * @return The vertical extent of the scrollbar's thumb - * @see LayoutManager#computeVerticalScrollExtent(State) - */ - @Override - protected int computeVerticalScrollExtent() { - 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 LayoutManager#computeVerticalScrollRange(State)} in your - * LayoutManager.

- * - * @return The total vertical range represented by the vertical scrollbar - * @see LayoutManager#computeVerticalScrollRange(State) - */ - @Override - protected int computeVerticalScrollRange() { - return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; - } - - - void eatRequestLayout() { - if (!mEatRequestLayout) { - mEatRequestLayout = true; - mLayoutRequestEaten = false; - } - } - - void resumeRequestLayout(boolean performLayoutChildren) { - if (mEatRequestLayout) { - if (performLayoutChildren && mLayoutRequestEaten && - mLayout != null && mAdapter != null) { - dispatchLayout(); - } - mEatRequestLayout = false; - mLayoutRequestEaten = false; - } - } - - /** - * 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(int dx, int dy) { - if (dx != 0 || dy != 0) { - mViewFlinger.smoothScrollBy(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 - */ - public boolean fling(int velocityX, int velocityY) { - if (Math.abs(velocityX) < mMinFlingVelocity) { - velocityX = 0; - } - if (Math.abs(velocityY) < mMinFlingVelocity) { - velocityY = 0; - } - velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); - velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); - if (velocityX != 0 || velocityY != 0) { - mViewFlinger.fling(velocityX, velocityY); - return true; - } - return false; - } - - /** - * 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(); - mLayout.stopSmoothScroller(); - } - - /** - * Apply a pull to relevant overscroll glow effects - */ - private void pullGlows(int overscrollX, int overscrollY) { - if (overscrollX < 0) { - ensureLeftGlow(); - mLeftGlow.onPull(-overscrollX / (float) getWidth()); - } else if (overscrollX > 0) { - ensureRightGlow(); - mRightGlow.onPull(overscrollX / (float) getWidth()); - } - - if (overscrollY < 0) { - ensureTopGlow(); - mTopGlow.onPull(-overscrollY / (float) getHeight()); - } else if (overscrollY > 0) { - ensureBottomGlow(); - mBottomGlow.onPull(overscrollY / (float) getHeight()); - } - - if (overscrollX != 0 || overscrollY != 0) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private void releaseGlows() { - boolean needsInvalidate = false; - if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease(); - if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease(); - if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease(); - if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease(); - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private void considerReleasingGlowsOnScroll(int dx, int dy) { - boolean needsInvalidate = false; - if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { - needsInvalidate = mLeftGlow.onRelease(); - } - if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { - needsInvalidate |= mRightGlow.onRelease(); - } - if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { - needsInvalidate |= mTopGlow.onRelease(); - } - if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { - needsInvalidate |= mBottomGlow.onRelease(); - } - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void absorbGlows(int velocityX, int velocityY) { - if (velocityX < 0) { - ensureLeftGlow(); - mLeftGlow.onAbsorb(-velocityX); - } else if (velocityX > 0) { - ensureRightGlow(); - mRightGlow.onAbsorb(velocityX); - } - - if (velocityY < 0) { - ensureTopGlow(); - mTopGlow.onAbsorb(-velocityY); - } else if (velocityY > 0) { - ensureBottomGlow(); - mBottomGlow.onAbsorb(velocityY); - } - - if (velocityX != 0 || velocityY != 0) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void ensureLeftGlow() { - if (mLeftGlow != null) { - return; - } - mLeftGlow = new EdgeEffectCompat(getContext()); - if (mClipToPadding) { - mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), - getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); - } else { - mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); - } - } - - void ensureRightGlow() { - if (mRightGlow != null) { - return; - } - mRightGlow = new EdgeEffectCompat(getContext()); - if (mClipToPadding) { - mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), - getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); - } else { - mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); - } - } - - void ensureTopGlow() { - if (mTopGlow != null) { - return; - } - mTopGlow = new EdgeEffectCompat(getContext()); - if (mClipToPadding) { - mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); - } else { - mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); - } - - } - - void ensureBottomGlow() { - if (mBottomGlow != null) { - return; - } - mBottomGlow = new EdgeEffectCompat(getContext()); - if (mClipToPadding) { - mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); - } else { - mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); - } - } - - void invalidateGlows() { - mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; - } - - // Focus handling - - @Override - public View focusSearch(View focused, int direction) { - View result = mLayout.onInterceptFocusSearch(focused, direction); - if (result != null) { - return result; - } - final FocusFinder ff = FocusFinder.getInstance(); - result = ff.findNextFocus(this, focused, direction); - if (result == null && mAdapter != null) { - eatRequestLayout(); - result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); - resumeRequestLayout(false); - } - return result != null ? result : super.focusSearch(focused, direction); - } - - @Override - public void requestChildFocus(View child, View focused) { - if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { - mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); - offsetDescendantRectToMyCoords(focused, mTempRect); - offsetRectIntoDescendantCoords(child, mTempRect); - requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete); - } - super.requestChildFocus(child, focused); - } - - @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.onAddFocusables(this, views, direction, focusableMode)) { - super.addFocusables(views, direction, focusableMode); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mIsAttached = true; - mFirstLayoutComplete = false; - if (mLayout != null) { - mLayout.onAttachedToWindow(this); - } - mPostedAnimatorRunner = false; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - } - mFirstLayoutComplete = false; - - stopScroll(); - mIsAttached = false; - if (mLayout != null) { - mLayout.onDetachedFromWindow(this, mRecycler); - } - removeCallbacks(mItemAnimatorRunner); - } - - /** - * 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 (!mRunningLayoutOrScroll) { - if (message == null) { - throw new IllegalStateException("Cannot call this method unless RecyclerView is " - + "computing a layout or scrolling"); - } - throw new IllegalStateException(message); - - } - } - - /** - * 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 (mRunningLayoutOrScroll) { - if (message == null) { - throw new IllegalStateException("Cannot call this method while RecyclerView is " - + "computing a layout or scrolling"); - } - throw new IllegalStateException(message); - } - } - - /** - * 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 - */ - public void addOnItemTouchListener(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(OnItemTouchListener listener) { - mOnItemTouchListeners.remove(listener); - if (mActiveOnItemTouchListener == listener) { - mActiveOnItemTouchListener = null; - } - } - - private boolean dispatchOnItemTouchIntercept(MotionEvent e) { - final int action = e.getAction(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { - mActiveOnItemTouchListener = null; - } - - 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) { - mActiveOnItemTouchListener = listener; - return true; - } - } - return false; - } - - private boolean dispatchOnItemTouch(MotionEvent e) { - final int action = e.getAction(); - if (mActiveOnItemTouchListener != null) { - if (action == MotionEvent.ACTION_DOWN) { - // Stale state from a previous gesture, we're starting a new one. Clear it. - mActiveOnItemTouchListener = null; - } else { - mActiveOnItemTouchListener.onTouchEvent(this, e); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - // Clean up for the next gesture. - mActiveOnItemTouchListener = null; - } - return true; - } - } - - // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept - // as called from onInterceptTouchEvent; skip it. - if (action != MotionEvent.ACTION_DOWN) { - final int listenerCount = mOnItemTouchListeners.size(); - for (int i = 0; i < listenerCount; i++) { - final OnItemTouchListener listener = mOnItemTouchListeners.get(i); - if (listener.onInterceptTouchEvent(this, e)) { - mActiveOnItemTouchListener = listener; - return true; - } - } - } - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - if (dispatchOnItemTouchIntercept(e)) { - cancelTouch(); - return true; - } - - final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); - final boolean canScrollVertically = mLayout.canScrollVertically(); - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(e); - - final int action = MotionEventCompat.getActionMasked(e); - final int actionIndex = MotionEventCompat.getActionIndex(e); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mScrollPointerId = MotionEventCompat.getPointerId(e, 0); - mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); - - if (mScrollState == SCROLL_STATE_SETTLING) { - getParent().requestDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - } - break; - - case MotionEventCompat.ACTION_POINTER_DOWN: - mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); - break; - - case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(e, 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) (MotionEventCompat.getX(e, index) + 0.5f); - final int y = (int) (MotionEventCompat.getY(e, 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 = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); - startScroll = true; - } - if (canScrollVertically && Math.abs(dy) > mTouchSlop) { - mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); - startScroll = true; - } - if (startScroll) { - getParent().requestDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - } - } - } break; - - case MotionEventCompat.ACTION_POINTER_UP: { - onPointerUp(e); - } break; - - case MotionEvent.ACTION_UP: { - mVelocityTracker.clear(); - } break; - - case MotionEvent.ACTION_CANCEL: { - cancelTouch(); - } - } - return mScrollState == SCROLL_STATE_DRAGGING; - } - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (dispatchOnItemTouch(e)) { - cancelTouch(); - return true; - } - - final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); - final boolean canScrollVertically = mLayout.canScrollVertically(); - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(e); - - final int action = MotionEventCompat.getActionMasked(e); - final int actionIndex = MotionEventCompat.getActionIndex(e); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - mScrollPointerId = MotionEventCompat.getPointerId(e, 0); - mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); - mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); - } break; - - case MotionEventCompat.ACTION_POINTER_DOWN: { - mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); - } break; - - case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(e, 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) (MotionEventCompat.getX(e, index) + 0.5f); - final int y = (int) (MotionEventCompat.getY(e, 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 = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); - startScroll = true; - } - if (canScrollVertically && Math.abs(dy) > mTouchSlop) { - mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); - startScroll = true; - } - if (startScroll) { - getParent().requestDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - } - } - if (mScrollState == SCROLL_STATE_DRAGGING) { - final int dx = x - mLastTouchX; - final int dy = y - mLastTouchY; - scrollByInternal(canScrollHorizontally ? -dx : 0, - canScrollVertically ? -dy : 0); - } - mLastTouchX = x; - mLastTouchY = y; - } break; - - case MotionEventCompat.ACTION_POINTER_UP: { - onPointerUp(e); - } break; - - case MotionEvent.ACTION_UP: { - mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); - final float xvel = canScrollHorizontally ? - -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0; - final float yvel = canScrollVertically ? - -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0; - if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { - setScrollState(SCROLL_STATE_IDLE); - } - mVelocityTracker.clear(); - releaseGlows(); - } break; - - case MotionEvent.ACTION_CANCEL: { - cancelTouch(); - } break; - } - - return true; - } - - private void cancelTouch() { - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - releaseGlows(); - setScrollState(SCROLL_STATE_IDLE); - } - - private void onPointerUp(MotionEvent e) { - final int actionIndex = MotionEventCompat.getActionIndex(e); - if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) { - // Pick a new pointer to pick up the slack. - final int newIndex = actionIndex == 0 ? 1 : 0; - mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f); - } - } - - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - if (mAdapterUpdateDuringMeasure) { - eatRequestLayout(); - processAdapterUpdatesAndSetAnimationFlags(); - - if (mState.mRunPredictiveAnimations) { - // TODO: try to provide a better approach. - // When RV decides to run predictive animations, we need to measure in pre-layout - // state so that pre-layout pass results in correct layout. - // On the other hand, this will prevent the layout manager from resizing properly. - mState.mInPreLayout = true; - } else { - // consume remaining updates to provide a consistent state with the layout pass. - mAdapterHelper.consumeUpdatesInOnePass(); - mState.mInPreLayout = false; - } - mAdapterUpdateDuringMeasure = false; - resumeRequestLayout(false); - } - - if (mAdapter != null) { - mState.mItemCount = mAdapter.getItemCount(); - } else { - mState.mItemCount = 0; - } - - mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); - mState.mInPreLayout = false; // clear - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (w != oldw || h != oldh) { - invalidateGlows(); - } - } - - /** - * 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(ItemAnimator animator) { - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - mItemAnimator.setListener(null); - } - mItemAnimator = animator; - if (mItemAnimator != null) { - mItemAnimator.setListener(mItemAnimatorListener); - } - } - - /** - * 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. - */ - public ItemAnimator getItemAnimator() { - return mItemAnimator; - } - - private boolean supportsChangeAnimations() { - return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations(); - } - - /** - * 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. - */ - private 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(); - markKnownViewsInvalid(); - 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 (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) { - mAdapterHelper.preProcess(); - } else { - mAdapterHelper.consumeUpdatesInOnePass(); - } - boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) || - (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations())); - 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 moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)}) - * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)}) - * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)}) - * DISAPPEARING views are moved off screen - * APPEARING views are moved on screen - */ - void dispatchLayout() { - if (mAdapter == null) { - Log.e(TAG, "No adapter attached; skipping layout"); - return; - } - mDisappearingViewsInLayoutPass.clear(); - eatRequestLayout(); - mRunningLayoutOrScroll = true; - - processAdapterUpdatesAndSetAnimationFlags(); - - mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged - && supportsChangeAnimations() ? new ArrayMap() : null; - mItemsAddedOrRemoved = mItemsChanged = false; - ArrayMap appearingViewInitialBounds = null; - mState.mInPreLayout = mState.mRunPredictiveAnimations; - mState.mItemCount = mAdapter.getItemCount(); - - if (mState.mRunSimpleAnimations) { - // Step 0: Find out where all non-removed items are, pre-layout - mState.mPreLayoutHolderMap.clear(); - mState.mPostLayoutHolderMap.clear(); - 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 View view = holder.itemView; - mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder, - view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); - } - } - 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(); - // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations. - if (mState.mOldChangedHolders != null) { - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { - long key = getChangedHolderKey(holder); - mState.mOldChangedHolders.put(key, holder); - mState.mPreLayoutHolderMap.remove(holder); - } - } - } - - 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; - - appearingViewInitialBounds = new ArrayMap(); - for (int i = 0; i < mChildHelper.getChildCount(); ++i) { - boolean found = false; - View child = mChildHelper.getChildAt(i); - if (getChildViewHolderInt(child).shouldIgnore()) { - continue; - } - for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) { - ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j); - if (holder.itemView == child) { - found = true; - break; - } - } - if (!found) { - appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(), - child.getRight(), child.getBottom())); - } - } - // we don't process disappearing list because they may re-appear in post layout pass. - clearOldPositions(); - mAdapterHelper.consumePostponedUpdates(); - } else { - clearOldPositions(); - // in case pre layout did run but we decided not to run predictive animations. - mAdapterHelper.consumeUpdatesInOnePass(); - if (mState.mOldChangedHolders != null) { - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) { - long key = getChangedHolderKey(holder); - mState.mOldChangedHolders.put(key, holder); - mState.mPreLayoutHolderMap.remove(holder); - } - } - } - } - mState.mItemCount = mAdapter.getItemCount(); - mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; - - // Step 2: Run layout - mState.mInPreLayout = false; - mLayout.onLayoutChildren(mRecycler, mState); - - mState.mStructureChanged = false; - mPendingSavedState = null; - - // onLayoutChildren may have caused client code to disable item animations; re-check - mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; - - if (mState.mRunSimpleAnimations) { - // Step 3: Find out where things are now, post-layout - ArrayMap newChangedHolders = mState.mOldChangedHolders != null ? - new ArrayMap() : null; - int count = mChildHelper.getChildCount(); - for (int i = 0; i < count; ++i) { - ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - if (holder.shouldIgnore()) { - continue; - } - final View view = holder.itemView; - long key = getChangedHolderKey(holder); - if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) { - newChangedHolders.put(key, holder); - } else { - mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder, - view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); - } - } - processDisappearingList(appearingViewInitialBounds); - // Step 4: Animate DISAPPEARING and REMOVED items - int preLayoutCount = mState.mPreLayoutHolderMap.size(); - for (int i = preLayoutCount - 1; i >= 0; i--) { - ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i); - if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) { - ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i); - mState.mPreLayoutHolderMap.removeAt(i); - - View disappearingItemView = disappearingItem.holder.itemView; - removeDetachedView(disappearingItemView, false); - mRecycler.unscrapView(disappearingItem.holder); - - animateDisappearance(disappearingItem); - } - } - // Step 5: Animate APPEARING and ADDED items - int postLayoutCount = mState.mPostLayoutHolderMap.size(); - if (postLayoutCount > 0) { - for (int i = postLayoutCount - 1; i >= 0; i--) { - ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i); - ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i); - if ((mState.mPreLayoutHolderMap.isEmpty() || - !mState.mPreLayoutHolderMap.containsKey(itemHolder))) { - mState.mPostLayoutHolderMap.removeAt(i); - Rect initialBounds = (appearingViewInitialBounds != null) ? - appearingViewInitialBounds.get(itemHolder.itemView) : null; - animateAppearance(itemHolder, initialBounds, - info.left, info.top); - } - } - } - // Step 6: Animate PERSISTENT items - count = mState.mPostLayoutHolderMap.size(); - for (int i = 0; i < count; ++i) { - ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i); - ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i); - ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder); - if (preInfo != null && postInfo != null) { - if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) { - postHolder.setIsRecyclable(false); - if (DEBUG) { - Log.d(TAG, "PERSISTENT: " + postHolder + - " with view " + postHolder.itemView); - } - if (mItemAnimator.animateMove(postHolder, - preInfo.left, preInfo.top, postInfo.left, postInfo.top)) { - postAnimationRunner(); - } - } - } - } - // Step 7: Animate CHANGING items - count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0; - // traverse reverse in case view gets recycled while we are traversing the list. - for (int i = count - 1; i >= 0; i--) { - long key = mState.mOldChangedHolders.keyAt(i); - ViewHolder oldHolder = mState.mOldChangedHolders.get(key); - View oldView = oldHolder.itemView; - if (oldHolder.shouldIgnore()) { - continue; - } - // We probably don't need this check anymore since these views are removed from - // the list if they are recycled. - if (mRecycler.mChangedScrap != null && - mRecycler.mChangedScrap.contains(oldHolder)) { - animateChange(oldHolder, newChangedHolders.get(key)); - } else if (DEBUG) { - Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder); - } - } - } - resumeRequestLayout(false); - mLayout.removeAndRecycleScrapInt(mRecycler, !mState.mRunPredictiveAnimations); - mState.mPreviousLayoutItemCount = mState.mItemCount; - mDataSetHasChangedAfterLayout = false; - mState.mRunSimpleAnimations = false; - mState.mRunPredictiveAnimations = false; - mRunningLayoutOrScroll = false; - mLayout.mRequestedSimpleAnimations = false; - if (mRecycler.mChangedScrap != null) { - mRecycler.mChangedScrap.clear(); - } - mState.mOldChangedHolders = null; - } - - /** - * 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; - } - - /** - * A LayoutManager may want to layout a view just to animate disappearance. - * This method handles those views and triggers remove animation on them. - */ - private void processDisappearingList(ArrayMap appearingViews) { - final int count = mDisappearingViewsInLayoutPass.size(); - for (int i = 0; i < count; i ++) { - View view = mDisappearingViewsInLayoutPass.get(i); - ViewHolder vh = getChildViewHolderInt(view); - final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh); - if (!mState.isPreLayout()) { - mState.mPostLayoutHolderMap.remove(vh); - } - if (appearingViews.remove(view) != null) { - mLayout.removeAndRecycleView(view, mRecycler); - continue; - } - if (info != null) { - animateDisappearance(info); - } else { - // let it disappear from the position it becomes visible - animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(), - view.getRight(), view.getBottom())); - } - } - mDisappearingViewsInLayoutPass.clear(); - } - - private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft, - int afterTop) { - View newItemView = itemHolder.itemView; - if (beforeBounds != null && - (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) { - // slide items in if before/after locations differ - itemHolder.setIsRecyclable(false); - if (DEBUG) { - Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView); - } - if (mItemAnimator.animateMove(itemHolder, - beforeBounds.left, beforeBounds.top, - afterLeft, afterTop)) { - postAnimationRunner(); - } - } else { - if (DEBUG) { - Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView); - } - itemHolder.setIsRecyclable(false); - if (mItemAnimator.animateAdd(itemHolder)) { - postAnimationRunner(); - } - } - } - - private void animateDisappearance(ItemHolderInfo disappearingItem) { - View disappearingItemView = disappearingItem.holder.itemView; - addAnimatingView(disappearingItemView); - int oldLeft = disappearingItem.left; - int oldTop = disappearingItem.top; - int newLeft = disappearingItemView.getLeft(); - int newTop = disappearingItemView.getTop(); - if (oldLeft != newLeft || oldTop != newTop) { - disappearingItem.holder.setIsRecyclable(false); - disappearingItemView.layout(newLeft, newTop, - newLeft + disappearingItemView.getWidth(), - newTop + disappearingItemView.getHeight()); - if (DEBUG) { - Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder + - " with view " + disappearingItemView); - } - if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop, - newLeft, newTop)) { - postAnimationRunner(); - } - } else { - if (DEBUG) { - Log.d(TAG, "REMOVED: " + disappearingItem.holder + - " with view " + disappearingItemView); - } - disappearingItem.holder.setIsRecyclable(false); - if (mItemAnimator.animateRemove(disappearingItem.holder)) { - postAnimationRunner(); - } - } - } - - private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) { - oldHolder.setIsRecyclable(false); - removeDetachedView(oldHolder.itemView, false); - addAnimatingView(oldHolder.itemView); - oldHolder.mShadowedHolder = newHolder; - mRecycler.unscrapView(oldHolder); - if (DEBUG) { - Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView); - } - final int fromLeft = oldHolder.itemView.getLeft(); - final int fromTop = oldHolder.itemView.getTop(); - final int toLeft, toTop; - if (newHolder == null || newHolder.shouldIgnore()) { - toLeft = fromLeft; - toTop = fromTop; - } else { - toLeft = newHolder.itemView.getLeft(); - toTop = newHolder.itemView.getTop(); - newHolder.setIsRecyclable(false); - newHolder.mShadowingHolder = oldHolder; - } - if(mItemAnimator.animateChange(oldHolder, newHolder, - fromLeft, fromTop, toLeft, toTop)) { - postAnimationRunner(); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - eatRequestLayout(); - dispatchLayout(); - resumeRequestLayout(false); - mFirstLayoutComplete = true; - } - - @Override - public void requestLayout() { - if (!mEatRequestLayout) { - super.requestLayout(); - } else { - mLayoutRequestEaten = 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 chilChildrenToPadding 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"); - } - return mLayout.generateDefaultLayoutParams(); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - if (mLayout == null) { - throw new IllegalStateException("RecyclerView has no LayoutManager"); - } - return mLayout.generateLayoutParams(getContext(), attrs); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - if (mLayout == null) { - throw new IllegalStateException("RecyclerView has no LayoutManager"); - } - return mLayout.generateLayoutParams(p); - } - - void saveOldPositions() { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { - throw new IllegalStateException("view holder cannot have position -1 unless it" - + " is removed"); - } - 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 (DEBUG) { - 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 (DEBUG) { - 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 (DEBUG) { - 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 (DEBUG) { - 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) { - 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); - if (supportsChangeAnimations()) { - holder.addFlags(ViewHolder.FLAG_CHANGED); - } - // lp cannot be null since we get ViewHolder from it. - ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; - } - } - mRecycler.viewRangeUpdate(positionStart, itemCount); - } - - void rebindUpdatedViewHolders() { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); - // validate type is correct - if (holder == null || holder.shouldIgnore()) { - continue; - } - if (holder.isRemoved() || holder.isInvalid()) { - requestLayout(); - } else if (holder.needsUpdate()) { - final int type = mAdapter.getItemViewType(holder.mPosition); - if (holder.getItemViewType() == type) { - // Binding an attached view will request a layout if needed. - if (!holder.isChanged() || !supportsChangeAnimations()) { - mAdapter.bindViewHolder(holder, holder.mPosition); - } else { - // Don't rebind changed holders if change animations are enabled. - // We want the old contents for the animation and will get a new - // holder for the new contents. - requestLayout(); - } - } else { - // binding to a new view will need re-layout anyways. We can as well trigger - // it here so that it happens during layout - holder.addFlags(ViewHolder.FLAG_INVALID); - requestLayout(); - } - } - } - } - - /** - * 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(); - } - - /** - * 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(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); - } - - static ViewHolder getChildViewHolderInt(View child) { - if (child == null) { - return null; - } - return ((LayoutParams) child.getLayoutParams()).mViewHolder; - } - - /** - * 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 getChildPosition(View child) { - final ViewHolder holder = getChildViewHolderInt(child); - return holder != null ? holder.getPosition() : 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(View child) { - if (mAdapter == null || !mAdapter.hasStableIds()) { - return NO_ID; - } - final ViewHolder holder = getChildViewHolderInt(child); - return holder != null ? holder.getItemId() : NO_ID; - } - - /** - * Return the ViewHolder for the item in the given position of the data set. - * - * @param position The position of the item in the data set of the adapter - * @return The ViewHolder at position - */ - public ViewHolder findViewHolderForPosition(int position) { - return findViewHolderForPosition(position, false); - } - - ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - 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) { - return holder; - } - } else if (holder.getPosition() == position) { - 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 null; - } - - /** - * 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. - * - * @param id The id for the requested item - * @return The ViewHolder with the given id, of null if there - * is no such item. - */ - public ViewHolder findViewHolderForItemId(long id) { - final int childCount = mChildHelper.getUnfilteredChildCount(); - for (int i = 0; i < childCount; i++) { - final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && holder.getItemId() == id) { - return holder; - } - } - // this method should not query cached views. They are not children so they - // should not be returned in this public method - return null; - } - - /** - * 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 - */ - 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 = ViewCompat.getTranslationX(child); - final float translationY = ViewCompat.getTranslationY(child); - if (x >= child.getLeft() + translationX && - x <= child.getRight() + translationX && - y >= child.getTop() + translationY && - y <= child.getBottom() + translationY) { - return child; - } - } - return null; - } - - /** - * 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(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(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(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(int dx) { - final int childCount = mChildHelper.getChildCount(); - for (int i = 0; i < childCount; i++) { - mChildHelper.getChildAt(i).offsetLeftAndRight(dx); - } - } - - Rect getItemDecorInsetsForChild(View child) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.mInsetsDirty) { - 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; - } - - private class ViewFlinger implements Runnable { - private int mLastFlingX; - private int mLastFlingY; - private ScrollerCompat mScroller; - private 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; - - public ViewFlinger() { - mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator); - } - - @Override - public void run() { - disableRunOnAnimationRequests(); - consumePendingUpdateOperations(); - // keep a local reference so that if it is changed during onAnimation method, it won't - // cause unexpected behaviors - final ScrollerCompat scroller = mScroller; - final SmoothScroller smoothScroller = mLayout.mSmoothScroller; - if (scroller.computeScrollOffset()) { - final int x = scroller.getCurrX(); - final int y = scroller.getCurrY(); - final int dx = x - mLastFlingX; - final int dy = y - mLastFlingY; - int hresult = 0; - int vresult = 0; - mLastFlingX = x; - mLastFlingY = y; - int overscrollX = 0, overscrollY = 0; - if (mAdapter != null) { - eatRequestLayout(); - mRunningLayoutOrScroll = true; - if (dx != 0) { - hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); - overscrollX = dx - hresult; - } - if (dy != 0) { - vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); - overscrollY = dy - vresult; - } - if (supportsChangeAnimations()) { - // Fix up shadow views used by changing 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 != null ? - holder.mShadowingHolder.itemView : null; - if (shadowingView != null) { - 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()); - } - } - } - } - } - - 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(dx - overscrollX, dy - overscrollY); - } else { - smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); - } - } - mRunningLayoutOrScroll = false; - resumeRequestLayout(false); - } - final boolean fullyConsumedScroll = dx == hresult && dy == vresult; - if (!mItemDecorations.isEmpty()) { - invalidate(); - } - if (ViewCompat.getOverScrollMode(RecyclerView.this) != - ViewCompat.OVER_SCROLL_NEVER) { - considerReleasingGlowsOnScroll(dx, dy); - } - if (overscrollX != 0 || overscrollY != 0) { - final int vel = (int) scroller.getCurrVelocity(); - - int velX = 0; - if (overscrollX != x) { - velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; - } - - int velY = 0; - if (overscrollY != y) { - velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; - } - - if (ViewCompat.getOverScrollMode(RecyclerView.this) != - ViewCompat.OVER_SCROLL_NEVER) { - absorbGlows(velX, velY); - } - if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) && - (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { - scroller.abortAnimation(); - } - } - if (hresult != 0 || vresult != 0) { - // dummy values, View's implementation does not use these. - onScrollChanged(0, 0, 0, 0); - if (mScrollListener != null) { - mScrollListener.onScrolled(RecyclerView.this, hresult, vresult); - } - } - - if (!awakenScrollBars()) { - invalidate(); - } - - if (scroller.isFinished() || !fullyConsumedScroll) { - setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. - } else { - postOnAnimation(); - } - } - // call this after the onAnimation is complete not to have inconsistent callbacks etc. - if (smoothScroller != null && smoothScroller.isPendingInitialRun()) { - smoothScroller.onAnimation(0, 0); - } - enableRunOnAnimationRequests(); - } - - private void disableRunOnAnimationRequests() { - mReSchedulePostAnimationCallback = false; - mEatRunOnAnimationRequest = true; - } - - private void enableRunOnAnimationRequests() { - mEatRunOnAnimationRequest = false; - if (mReSchedulePostAnimationCallback) { - postOnAnimation(); - } - } - - void postOnAnimation() { - if (mEatRunOnAnimationRequest) { - mReSchedulePostAnimationCallback = true; - } else { - ViewCompat.postOnAnimation(RecyclerView.this, this); - } - } - - public void fling(int velocityX, int velocityY) { - setScrollState(SCROLL_STATE_SETTLING); - mLastFlingX = mLastFlingY = 0; - mScroller.fling(0, 0, velocityX, velocityY, - Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); - postOnAnimation(); - } - - public void smoothScrollBy(int dx, int dy) { - smoothScrollBy(dx, dy, 0, 0); - } - - public void smoothScrollBy(int dx, int dy, int vx, int vy) { - smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); - } - - private float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - private int computeScrollDuration(int dx, int dy, int vx, int vy) { - final int absDx = Math.abs(dx); - final int absDy = Math.abs(dy); - final boolean horizontal = absDx > absDy; - final int velocity = (int) Math.sqrt(vx * vx + vy * vy); - final int delta = (int) Math.sqrt(dx * dx + dy * dy); - final int containerSize = horizontal ? getWidth() : getHeight(); - final int halfContainerSize = containerSize / 2; - final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); - final float distance = halfContainerSize + halfContainerSize * - distanceInfluenceForSnapDuration(distanceRatio); - - final int duration; - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - float absDelta = (float) (horizontal ? absDx : absDy); - duration = (int) (((absDelta / containerSize) + 1) * 300); - } - return Math.min(duration, MAX_SCROLL_DURATION); - } - - public void smoothScrollBy(int dx, int dy, int duration) { - smoothScrollBy(dx, dy, duration, sQuinticInterpolator); - } - - public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { - if (mInterpolator != interpolator) { - mInterpolator = interpolator; - mScroller = ScrollerCompat.create(getContext(), interpolator); - } - setScrollState(SCROLL_STATE_SETTLING); - mLastFlingX = mLastFlingY = 0; - mScroller.startScroll(0, 0, dx, dy, duration); - postOnAnimation(); - } - - public void stop() { - removeCallbacks(this); - mScroller.abortAnimation(); - } - - } - - private class RecyclerViewDataObserver extends AdapterDataObserver { - @Override - public void onChanged() { - assertNotInLayoutOrScroll(null); - if (mAdapter.hasStableIds()) { - // TODO Determine what actually changed. - // This is more important to implement now since this callback will disable all - // animations because we cannot rely on positions. - mState.mStructureChanged = true; - mDataSetHasChangedAfterLayout = true; - } else { - mState.mStructureChanged = true; - mDataSetHasChangedAfterLayout = true; - } - if (!mAdapterHelper.hasPendingUpdates()) { - requestLayout(); - } - } - - @Override - public void onItemRangeChanged(int positionStart, int itemCount) { - assertNotInLayoutOrScroll(null); - if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) { - 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 (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { - ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); - } else { - mAdapterUpdateDuringMeasure = true; - requestLayout(); - } - } - } - - /** - * 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 SparseArray> mScrap = - new SparseArray>(); - private SparseIntArray mMaxScrap = new SparseIntArray(); - private int mAttachCount = 0; - - private static final int DEFAULT_MAX_SCRAP = 5; - - public void clear() { - mScrap.clear(); - } - - public void setMaxRecycledViews(int viewType, int max) { - mMaxScrap.put(viewType, max); - final ArrayList scrapHeap = mScrap.get(viewType); - if (scrapHeap != null) { - while (scrapHeap.size() > max) { - scrapHeap.remove(scrapHeap.size() - 1); - } - } - } - - public ViewHolder getRecycledView(int viewType) { - final ArrayList scrapHeap = mScrap.get(viewType); - if (scrapHeap != null && !scrapHeap.isEmpty()) { - final int index = scrapHeap.size() - 1; - final ViewHolder scrap = scrapHeap.get(index); - scrapHeap.remove(index); - return scrap; - } - return null; - } - - int size() { - int count = 0; - for (int i = 0; i < mScrap.size(); i ++) { - ArrayList viewHolders = mScrap.valueAt(i); - if (viewHolders != null) { - count += viewHolders.size(); - } - } - return count; - } - - public void putRecycledView(ViewHolder scrap) { - final int viewType = scrap.getItemViewType(); - final ArrayList scrapHeap = getScrapHeapForType(viewType); - if (mMaxScrap.get(viewType) <= scrapHeap.size()) { - return; - } - scrap.resetInternal(); - scrapHeap.add(scrap); - } - - void attach(Adapter adapter) { - mAttachCount++; - } - - void detach() { - mAttachCount--; - } - - - /** - * 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 && mAttachCount == 0) { - clear(); - } - if (newAdapter != null) { - attach(newAdapter); - } - } - - private ArrayList getScrapHeapForType(int viewType) { - ArrayList scrap = mScrap.get(viewType); - if (scrap == null) { - scrap = new ArrayList(); - mScrap.put(viewType, scrap); - if (mMaxScrap.indexOfKey(viewType) < 0) { - mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); - } - } - return scrap; - } - } - - /** - * 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 View#isLayoutRequested() requested layout} - * may be repositioned by a LayoutManager without remeasurement.

- */ - public final class Recycler { - final ArrayList mAttachedScrap = new ArrayList(); - private ArrayList mChangedScrap = null; - - final ArrayList mCachedViews = new ArrayList(); - - private final List - mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); - - private int mViewCacheMax = DEFAULT_CACHE_SIZE; - - private RecycledViewPool mRecyclerPool; - - private ViewCacheExtension mViewCacheExtension; - - private 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) { - mViewCacheMax = viewCount; - // first, try the views that can be recycled - for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) { - tryToRecycleCachedViewAt(i); - } - // if we could not recycle enough of them, remove some. - while (mCachedViews.size() > viewCount) { - mCachedViews.remove(mCachedViews.size() - 1); - } - } - - /** - * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. - * - * @return List of ViewHolders in the scrap list. - */ - 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()) { - return true; - } - if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { - throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " - + "adapter position" + holder); - } - 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; - } - - /** - * 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(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"); - } - 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()); - } - mAdapter.bindViewHolder(holder, offsetPosition); - attachAccessibilityDelegate(view); - if (mState.isPreLayout()) { - holder.mPreLayoutPosition = position; - } - - 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()); - } - 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 - */ - public View getViewForPosition(int position) { - return getViewForPosition(position, false); - } - - View getViewForPosition(int position, boolean dryRun) { - if (position < 0 || position >= mState.getItemCount()) { - throw new IndexOutOfBoundsException("Invalid item position " + position - + "(" + position + "). Item count:" + mState.getItemCount()); - } - boolean fromScrap = false; - ViewHolder holder = null; - // 0) If there is a changed scrap, try to find from there - if (mState.isPreLayout()) { - holder = getChangedScrapViewForPosition(position); - fromScrap = holder != null; - } - // 1) Find from scrap by position - if (holder == null) { - holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); - if (holder != null) { - if (!validateViewHolderForOffsetPosition(holder)) { - // recycle this scrap - 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 { - fromScrap = true; - } - } - } - if (holder == null) { - final int offsetPosition = mAdapterHelper.findPositionOffset(position); -// final int offsetPosition = position; -// Utils.log("offsetPosition position = " + position); -// Utils.log("offsetPosition = " + offsetPosition); -// Utils.log("offsetPosition count = " + mAdapter.getItemCount()); -// Utils.log("offsetPosition count = " + mState.getItemCount()); - if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { - throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " - + "position " + position + "(offset:" + offsetPosition + ")." - + "state:" + mState.getItemCount() - + "adpter:" + mAdapter.getClass().getName()); - } - - final int type = mAdapter.getItemViewType(offsetPosition); - // 2) Find from scrap via stable ids, if exists - if (mAdapter.hasStableIds()) { - holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); - if (holder != null) { - // update position - holder.mPosition = offsetPosition; - fromScrap = 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"); - } else if (holder.shouldIgnore()) { - throw new IllegalArgumentException("getViewForPositionAndType returned" - + " a view that is ignored. You must call stopIgnoring before" - + " returning this view."); - } - } - } - if (holder == null) { // fallback to recycler - // try recycler. - // Head to the shared pool. - if (DEBUG) { - Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " - + "pool"); - } - holder = getRecycledViewPool() - .getRecycledView(mAdapter.getItemViewType(offsetPosition)); - if (holder != null) { - holder.resetInternal(); - if (FORCE_INVALIDATE_DISPLAY_LIST) { - invalidateDisplayListInt(holder); - } - } - } - if (holder == null) { - holder = mAdapter.createViewHolder(RecyclerView.this, - mAdapter.getItemViewType(offsetPosition)); - if (DEBUG) { - Log.d(TAG, "getViewForPosition created new ViewHolder"); - } - } - } - 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 (DEBUG && holder.isRemoved()) { - throw new IllegalStateException("Removed holder should be bound and it should" - + " come here only in pre-layout. Holder: " + holder); - } - final int offsetPosition = mAdapterHelper.findPositionOffset(position); - mAdapter.bindViewHolder(holder, offsetPosition); - attachAccessibilityDelegate(holder.itemView); - bound = true; - if (mState.isPreLayout()) { - holder.mPreLayoutPosition = position; - } - } - - 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 = fromScrap && bound; - return holder.itemView; - } - - private void attachAccessibilityDelegate(View itemView) { - if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()) { - if (ViewCompat.getImportantForAccessibility(itemView) == - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - ViewCompat.setImportantForAccessibility(itemView, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - if (!ViewCompat.hasAccessibilityDelegate(itemView)) { - ViewCompat.setAccessibilityDelegate(itemView, - mAccessibilityDelegate.getItemDelegate()); - } - } - } - - 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 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(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.isScrap()) { - holder.unScrap(); - } else if (holder.wasReturnedFromScrap()){ - holder.clearReturnedFromScrapFlag(); - } - recycleViewHolderInternal(holder); - } - - /** - * Internally, use this method instead of {@link #recycleView(View)} to - * catch potential bugs. - * @param view - */ - void recycleViewInternal(View view) { - recycleViewHolderInternal(getChildViewHolderInt(view)); - } - - void recycleAndClearCachedViews() { - final int count = mCachedViews.size(); - for (int i = count - 1; i >= 0; i--) { - tryToRecycleCachedViewAt(i); - } - mCachedViews.clear(); - } - - /** - * Tries to recyle a cached view and removes the view from the list if and only if it - * is recycled. - * - * @param cachedViewIndex The index of the view in cached views list - * @return True if item is recycled - */ - boolean tryToRecycleCachedViewAt(int cachedViewIndex) { - if (DEBUG) { - Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); - } - ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); - if (DEBUG) { - Log.d(TAG, "CachedViewHolder to be recycled(if recycleable): " + viewHolder); - } - if (viewHolder.isRecyclable()) { - getRecycledViewPool().putRecycledView(viewHolder); - dispatchViewRecycled(viewHolder); - mCachedViews.remove(cachedViewIndex); - return true; - } - return false; - } - - /** - * 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)); - } - - if (holder.shouldIgnore()) { - throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" - + " should first call stopIgnoringView(view) before calling recycle."); - } - if (holder.isRecyclable()) { - boolean cached = false; - if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) && - !holder.isChanged()) { - // Retire oldest cached views first - if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) { - for (int i = 0; i < mCachedViews.size(); i++) { - if (tryToRecycleCachedViewAt(i)) { - break; - } - } - } - if (mCachedViews.size() < mViewCacheMax) { - mCachedViews.add(holder); - cached = true; - } - } - if (!cached) { - getRecycledViewPool().putRecycledView(holder); - dispatchViewRecycled(holder); - } - } else if (DEBUG) { - Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " - + "re-visit here. We are stil removing it from animation lists"); - } - // even if the holder is not removed, we still call this method so that it is removed - // from view holder lists. - mState.onViewRecycled(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.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); - holder.setScrapContainer(this); - if (!holder.isChanged() || !supportsChangeAnimations()) { - 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."); - } - mAttachedScrap.add(holder); - } else { - if (mChangedScrap == null) { - mChangedScrap = new ArrayList(); - } - 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.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) { - mAttachedScrap.remove(holder); - } else { - mChangedScrap.remove(holder); - } - holder.mScrapContainer = null; - holder.clearReturnedFromScrapFlag(); - } - - int getScrapCount() { - return mAttachedScrap.size(); - } - - View getScrapViewAt(int index) { - return mAttachedScrap.get(index).itemView; - } - - void clearScrap() { - mAttachedScrap.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.getPosition() == 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 scrap view for the position. If type is not INVALID_TYPE, it also checks if - * ViewHolder's type matches the provided type. - * - * @param position Item position - * @param type View type - * @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 getScrapViewForPosition(int position, int type, 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.getPosition() == position - && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { - if (type != INVALID_TYPE && holder.getItemViewType() != type) { - Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + - " wrong view type! (found " + holder.getItemViewType() + - " but expected " + type + ")"); - break; - } - holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); - return holder; - } - } - - if (!dryRun) { - View view = mChildHelper.findHiddenNonRemovedView(position, type); - if (view != null) { - // ending the animation should cause it to get recycled before we reuse it - mItemAnimator.endAnimation(getChildViewHolder(view)); - } - } - - // 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 getScrapViewForId - if (!holder.isInvalid() && holder.getPosition() == position) { - if (!dryRun) { - mCachedViews.remove(i); - } - if (DEBUG) { - Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + - ") found match in cache: " + holder); - } - return holder; - } - } - return null; - } - - ViewHolder getScrapViewForId(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) { - // 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) { - if (type == holder.getItemViewType()) { - if (!dryRun) { - mCachedViews.remove(i); - } - return holder; - } else if (!dryRun) { - tryToRecycleCachedViewAt(i); - } - } - } - return null; - } - - void dispatchViewRecycled(ViewHolder holder) { - if (mRecyclerListener != null) { - mRecyclerListener.onViewRecycled(holder); - } - if (mAdapter != null) { - mAdapter.onViewRecycled(holder); - } - if (mState != null) { - mState.onViewRecycled(holder); - } - if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); - } - - void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, - boolean compatibleWithPrevious) { - clear(); - getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); - } - - 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 (DEBUG) { - 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.getPosition() >= insertedAt) { - if (DEBUG) { - Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + - holder + " now at position " + (holder.mPosition + count)); - } - holder.offsetPosition(count, true); - } - } - } - - /** - * @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.getPosition() >= removedEnd) { - if (DEBUG) { - Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + - " holder " + holder + " now at position " + - (holder.mPosition - count)); - } - holder.offsetPosition(-count, applyToPreLayout); - } else if (holder.getPosition() >= removedFrom) { - // Item for this view was removed. Dump it from the cache. - if (!tryToRecycleCachedViewAt(i)) { - // if we cannot recycle it, at least invalidate so that we won't return - // it by position. - holder.addFlags(ViewHolder.FLAG_INVALID); - if (DEBUG) { - Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + - " holder " + holder + " now flagged as invalid because it " - + "could not be recycled"); - } - } else if (DEBUG) { - Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + - " holder " + holder + " now placed in pool"); - } - } - } - } - } - - void setViewCacheExtension(ViewCacheExtension extension) { - mViewCacheExtension = extension; - } - - void setRecycledViewPool(RecycledViewPool pool) { - if (mRecyclerPool != null) { - mRecyclerPool.detach(); - } - mRecyclerPool = pool; - if (pool != null) { - mRecyclerPool.attach(getAdapter()); - } - } - - RecycledViewPool getRecycledViewPool() { - if (mRecyclerPool == null) { - mRecyclerPool = new RecycledViewPool(); - } - return mRecyclerPool; - } - - void viewRangeUpdate(int positionStart, int itemCount) { - final int positionEnd = positionStart + itemCount; - final int cachedCount = mCachedViews.size(); - for (int i = 0; i < cachedCount; i++) { - final ViewHolder holder = mCachedViews.get(i); - if (holder == null) { - continue; - } - - final int pos = holder.getPosition(); - if (pos >= positionStart && pos < positionEnd) { - holder.addFlags(ViewHolder.FLAG_UPDATE); - // cached views should not be flagged as changed because this will cause them - // to animate when they are returned from cache. - } - } - } - - void markKnownViewsInvalid() { - if (mAdapter != null && mAdapter.hasStableIds()) { - 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); - } - } - } else { - // we cannot re-use cached views in this case. Recycle the ones we can and flag - // the remaining as invalid so that they can be recycled later on (when their - // animations end.) - for (int i = mCachedViews.size() - 1; i >= 0; i--) { - if (!tryToRecycleCachedViewAt(i)) { - final ViewHolder holder = mCachedViews.get(i); - holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); - } - } - } - - } - - 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 - * ben 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) - */ - abstract public View getViewForPositionAndType(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}.

- */ - public static abstract class Adapter { - private final AdapterDataObservable mObservable = new AdapterDataObservable(); - private boolean mHasStableIds = false; - - /** - * 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)}. 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) - */ - public abstract VH onCreateViewHolder(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#getPosition()} which will have the - * updated position. - * - * @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(VH holder, int 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) - */ - public final VH createViewHolder(ViewGroup parent, int viewType) { - final VH holder = onCreateViewHolder(parent, viewType); - holder.mItemViewType = viewType; - return holder; - } - - /** - * 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. - * - * @see #onBindViewHolder(ViewHolder, int) - */ - public final void bindViewHolder(VH holder, int position) { - holder.mPosition = position; - if (hasStableIds()) { - holder.mItemId = getItemId(position); - } - onBindViewHolder(holder, position); - holder.setFlags(ViewHolder.FLAG_BOUND, - ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); - } - - /** - * 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 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 hold 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.

- * - * @param holder The ViewHolder for the view being recycled - */ - public void onViewRecycled(VH holder) { - } - - /** - * 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(ViewHolder) onViewDetachedFromWindow} - * those resources should be restored here.

- * - * @param holder Holder of the view being attached - */ - public void onViewAttachedToWindow(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 an detaching them as appropriate.

- * - * @param holder Holder of the view being detached - */ - public void onViewDetachedFromWindow(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 AdapterDataObserver#onChanged() - * "something changed"} event if more specific data is not available.

- * - *

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

- * - * @param observer Observer to register - * - * @see #unregisterAdapterDataObserver(AdapterDataObserver) - */ - public void registerAdapterDataObserver(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(AdapterDataObserver) - */ - public void unregisterAdapterDataObserver(AdapterDataObserver observer) { - mObservable.unregisterObserver(observer); - } - - /** - * 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. - * - *

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 itemCount items starting at - * position positionStart have changed. - * - *

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 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); - } - } - - private void dispatchChildDetached(View child) { - if (mAdapter != null) { - mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); - } - onChildDetachedFromWindow(child); - } - - private void dispatchChildAttached(View child) { - if (mAdapter != null) { - mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child)); - } - onChildAttachedToWindow(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. - */ - public static abstract class LayoutManager { - ChildHelper mChildHelper; - RecyclerView mRecyclerView; - - @Nullable - SmoothScroller mSmoothScroller; - - private boolean mRequestedSimpleAnimations = false; - - void setRecyclerView(RecyclerView recyclerView) { - if (recyclerView == null) { - mRecyclerView = null; - mChildHelper = null; - } else { - mRecyclerView = recyclerView; - mChildHelper = recyclerView.mChildHelper; - } - - } - - /** - * 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); - } - } - - /** - * 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) - */ - public void assertNotInLayoutOrScroll(String message) { - if (mRecyclerView != null) { - mRecyclerView.assertNotInLayoutOrScroll(message); - } - } - - /** - * Returns whether this LayoutManager supports automatic item animations. - * A LayoutManager wishing to support item animations should obey certain - * rules as outlined in {@link #onLayoutChildren(Recycler, State)}. - * The default return value is false, so subclasses of LayoutManager - * will not get predictive item animations by default. - * - *

Whether item animations are enabled in a RecyclerView is determined both - * by 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 simple item animations will be enabled, in which - * views that are moving onto or off of the screen are simply faded in/out. If - * the RecyclerView has a non-null ItemAnimator and this method returns true, - * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to - * setup up the information needed to more intelligently predict where appearing - * and disappearing views should be animated from/to.

- * - * @return true if predictive item animations should be enabled, false otherwise - */ - public boolean supportsPredictiveItemAnimations() { - return false; - } - - /** - * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView - * is attached to a window. - * - *

Subclass implementations should always call through to the superclass implementation. - *

- * - * @param view The RecyclerView this LayoutManager is bound to - */ - 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. - * - *

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. - */ - 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 - * simple fade views in and out, whether they are actuall 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 - */ - public void onLayoutChildren(Recycler recycler, State state) { - Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, 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 LayoutParams} for this purpose.

- * - *

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

- * - * @return A new LayoutParams for a child view - */ - 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(ViewGroup.LayoutParams)} and - * {@link #generateLayoutParams(Context, AttributeSet)}.

- * - * @param lp Source LayoutParams object to copy values from - * @return a new LayoutParams object - */ - 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(ViewGroup.LayoutParams)} and - * {@link #generateLayoutParams(Context, AttributeSet)}.

- * - * @param c Context for obtaining styled attributes - * @param attrs AttributeSet describing the supplied arguments - * @return a new LayoutParams object - */ - 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. - */ - 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. - */ - 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 (DEBUG) { - 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. - */ - 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 SmoothScroller.

- *

Calling this method will cancel any previous smooth scroll request.

- * @param smoothScroller Unstance which defines how smooth scroll should be animated - */ - public void startSmoothScroll(SmoothScroller smoothScroller) { - if (mSmoothScroller != null && smoothScroller != mSmoothScroller - && mSmoothScroller.isRunning()) { - mSmoothScroller.stop(); - } - mSmoothScroller = smoothScroller; - mSmoothScroller.start(mRecyclerView, this); - } - - /** - * @return true if RecycylerView 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 ViewCompat#LAYOUT_DIRECTION_RTL} if the layout - * direction is RTL or returns - * {@link 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 ItemAnimator#endAnimations() - */ - 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. - */ - 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. - */ - 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 - */ - 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 - */ - 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.addToDisappearingList(child); - } 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.removeFromDisappearingList(child); - } - 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)); - } - 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 (DEBUG) { - 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(View)}. - * - * @param child View to remove - */ - 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(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--) { - final View child = getChildAt(i); - mChildHelper.removeViewAt(i); - } - } - - /** - * Returns the adapter position of the item represented by the given View. - * - * @param view The view to query - * @return The adapter position of the item which is rendered by this View. - */ - public int getPosition(View view) { - return ((LayoutParams) view.getLayoutParams()).getViewPosition(); - } - - /** - * 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(View view) { - return getChildViewHolderInt(view).getItemViewType(); - } - - /** - *

- * 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 - * visible - */ - 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.getPosition() == 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(View, 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(View, int, LayoutParams) reattach} - * or {@link #removeDetachedView(View) fully remove} the detached view - * before the LayoutManager entry point method called by RecyclerView returns.

- * - * @param child Child to detach - */ - public void detachView(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(View, 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(View, int, LayoutParams) reattach} - * or {@link #removeDetachedView(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, View view) { - if (DISPATCH_TEMP_DETACH) { - ViewCompat.dispatchStartTemporaryDetach(view); - } - mChildHelper.detachViewFromParent(index); - } - - /** - * Reattach a previously {@link #detachView(View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(View, Recycler)} scrapped}. - * - * @param child Child to reattach - * @param index Intended child index for child - * @param lp LayoutParams for child - */ - public void attachView(View child, int index, LayoutParams lp) { - ViewHolder vh = getChildViewHolderInt(child); - if (vh.isRemoved()) { - mRecyclerView.addToDisappearingList(child); - } else { - mRecyclerView.removeFromDisappearingList(child); - } - mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); - if (DISPATCH_TEMP_DETACH) { - ViewCompat.dispatchFinishTemporaryDetach(child); - } - } - - /** - * Reattach a previously {@link #detachView(View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(View, Recycler)} scrapped}. - * - * @param child Child to reattach - * @param index Intended child index for child - */ - public void attachView(View child, int index) { - attachView(child, index, (LayoutParams) child.getLayoutParams()); - } - - /** - * Reattach a previously {@link #detachView(View) detached} view. - * This method should not be used to reattach views that were previously - * {@link #detachAndScrapView(View, Recycler)} scrapped}. - * - * @param child Child to reattach - */ - public void attachView(View child) { - attachView(child, -1); - } - - /** - * Finish removing a view that was previously temporarily - * {@link #detachView(View) detached}. - * - * @param child Detached child to remove - */ - public void removeDetachedView(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); - } - 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(View child, 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, 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(View child, 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, 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 - */ - public View getChildAt(int index) { - return mChildHelper != null ? mChildHelper.getChildAt(index) : null; - } - - /** - * Return the width of the parent RecyclerView - * - * @return Width in pixels - */ - public int getWidth() { - return mRecyclerView != null ? mRecyclerView.getWidth() : 0; - } - - /** - * Return the height of the parent RecyclerView - * - * @return Height in pixels - */ - public int getHeight() { - return mRecyclerView != null ? mRecyclerView.getHeight() : 0; - } - - /** - * Return the left padding of the parent RecyclerView - * - * @return Padding in pixels - */ - public int getPaddingLeft() { - return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; - } - - /** - * Return the top padding of the parent RecyclerView - * - * @return Padding in pixels - */ - public int getPaddingTop() { - return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; - } - - /** - * Return the right padding of the parent RecyclerView - * - * @return Padding in pixels - */ - public int getPaddingRight() { - return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; - } - - /** - * Return the bottom padding of the parent RecyclerView - * - * @return Padding in pixels - */ - public int getPaddingBottom() { - return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; - } - - /** - * Return the start padding of the parent RecyclerView - * - * @return Padding in pixels - */ - public int getPaddingStart() { - return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; - } - - /** - * Return the end padding of the parent RecyclerView - * - * @return Padding in pixels - */ - 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. - */ - 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()}. In - * methods where State is available, you should use {@link State#getItemCount()} instead. - * For more details, check the documentation for {@link 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(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(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(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"); - } - final ViewHolder vh = getChildViewHolderInt(view); - vh.addFlags(ViewHolder.FLAG_IGNORE); - mRecyclerView.mState.onViewIgnored(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(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(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 (DEBUG) { - Log.d(TAG, "ignoring view " + viewHolder); - } - return; - } - if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() && - !mRecyclerView.mAdapter.hasStableIds()) { - removeViewAt(index); - recycler.recycleViewHolderInternal(viewHolder); - } else { - detachViewAt(index); - recycler.scrapView(view); - } - } - - /** - * 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 - * @param remove Whether scrapped views should be removed from ViewGroup or not. This - * method will invalidate RecyclerView if it removes any scrapped child. - */ - void removeAndRecycleScrapInt(Recycler recycler, boolean remove) { - final int scrapCount = recycler.getScrapCount(); - for (int i = 0; i < scrapCount; i++) { - final View scrap = recycler.getScrapViewAt(i); - if (getChildViewHolderInt(scrap).shouldIgnore()) { - continue; - } - if (remove) { - mRecyclerView.removeDetachedView(scrap, false); - } - recycler.quickRecycleScrapView(scrap); - } - recycler.clearScrap(); - if (remove && 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(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(), - getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, - canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), - getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, - canScrollVertically()); - child.measure(widthSpec, heightSpec); - } - - /** - * 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(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(), - getPaddingLeft() + getPaddingRight() + - lp.leftMargin + lp.rightMargin + widthUsed, lp.width, - canScrollHorizontally()); - final int heightSpec = getChildMeasureSpec(getHeight(), - getPaddingTop() + getPaddingBottom() + - lp.topMargin + lp.bottomMargin + heightUsed, lp.height, - canScrollVertically()); - 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 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 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.FILL_PARENT) { - resultSize = size; - resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.WRAP_CONTENT) { - resultSize = size; - resultMode = MeasureSpec.AT_MOST; - } - } - 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(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(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 #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) - */ - public void layoutDecorated(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); - } - - /** - * 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(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(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(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(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(View child, 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(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(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(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(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. 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 - */ - public View onFocusSearchFailed(View focused, int direction, Recycler recycler, - 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, Recycler, 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. - */ - public View onInterceptFocusSearch(View focused, int direction) { - return null; - } - - /** - * Called when a child of the RecyclerView wants a particular rectangle to be positioned - * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(View, - * 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(RecyclerView parent, View child, Rect rect, - boolean immediate) { - 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; - final int childTop = child.getTop() + rect.top; - final int childRight = childLeft + rect.right; - final int childBottom = childTop + rect.bottom; - - 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. - final int dx; - if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) { - dx = offScreenRight != 0 ? offScreenRight : offScreenLeft; - } else { - dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight; - } - - // Favor bringing the top into view over the bottom - final int dy = offScreenTop; -// final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom; - if (dx != 0 || dy != 0) { - if (immediate) { - parent.scrollBy(dx, dy); - } else { -// parent.smoothScrollBy(dx, dy); - } - return true; - } - return false; - } - - /** - * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} - */ - @Deprecated - public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { - return false; - } - - /** - * 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(RecyclerView parent, State state, View child, - View focused) { - return onRequestChildFocus(parent, child, focused); - } - - /** - * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. - * 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 #setAdapter(Adapter)} is called with {@code null}. - */ - public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { - } - - /** - * Called to populate focusable views within the RecyclerView. - * - *

The LayoutManager implementation should return true if the default - * behavior of {@link ViewGroup#addFocusables(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(RecyclerView recyclerView, ArrayList views, - int direction, int focusableMode) { - return false; - } - - /** - * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving - * detailed information on what has actually changed. - * - * @param recyclerView - */ - public void onItemsChanged(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.) - * - * @param recyclerView - * @param positionStart - * @param itemCount - */ - public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { - } - - /** - * Called when items have been removed from the adapter. - * - * @param recyclerView - * @param positionStart - * @param itemCount - */ - public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { - } - - /** - * Called when items have been changed in the adapter. - * - * @param recyclerView - * @param positionStart - * @param itemCount - */ - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int 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. - * - * @param recyclerView - * @param from - * @param to - * @param itemCount - */ - public void onItemsMoved(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(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(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(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(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(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(State state) { - return 0; - } - - /** - * Measure the attached RecyclerView. Implementations must call - * {@link #setMeasuredDimension(int, int)} before returning. - * - *

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 MeasureSpec} - * @param heightSpec Height {@link MeasureSpec} - */ - public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { - final int widthMode = MeasureSpec.getMode(widthSpec); - final int heightMode = MeasureSpec.getMode(heightSpec); - final int widthSize = MeasureSpec.getSize(widthSpec); - final int heightSize = MeasureSpec.getSize(heightSpec); - - int width = 0; - int height = 0; - - switch (widthMode) { - case MeasureSpec.EXACTLY: - case MeasureSpec.AT_MOST: - width = widthSize; - break; - case MeasureSpec.UNSPECIFIED: - default: - width = getMinimumWidth(); - break; - } - - switch (heightMode) { - case MeasureSpec.EXACTLY: - case MeasureSpec.AT_MOST: - height = heightSize; - break; - case MeasureSpec.UNSPECIFIED: - default: - height = getMinimumHeight(); - break; - } - - setMeasuredDimension(width, height); - } - - /** - * {@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()} - */ - public int getMinimumWidth() { - return ViewCompat.getMinimumWidth(mRecyclerView); - } - - /** - * @return The host RecyclerView's {@link View#getMinimumHeight()} - */ - 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 - */ - public Parcelable onSaveInstanceState() { - return null; - } - - - public void onRestoreInstanceState(Parcelable state) { - - } - - void stopSmoothScroller() { - if (mSmoothScroller != null) { - mSmoothScroller.stop(); - } - } - - private 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(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 - * AccessibilityNodeInfoCompat.CollectionInfoCompat}. - *

- * You should override - * {@link #getRowCountForAccessibility(Recycler, State)}, - * {@link #getColumnCountForAccessibility(Recycler, State)}, - * {@link #isLayoutHierarchical(Recycler, State)} and - * {@link #getSelectionModeForAccessibility(Recycler, 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(Recycler, State) - * @see #getColumnCountForAccessibility(Recycler, State) - * @see #isLayoutHierarchical(Recycler, State) - * @see #getSelectionModeForAccessibility(Recycler, State) - */ - public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state, - AccessibilityNodeInfoCompat info) { - info.setClassName(RecyclerView.class.getName()); - if (ViewCompat.canScrollVertically(mRecyclerView, -1) || - ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); - info.setScrollable(true); - } - if (ViewCompat.canScrollVertically(mRecyclerView, 1) || - ViewCompat.canScrollHorizontally(mRecyclerView, 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(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(AccessibilityEvent) - */ - public void onInitializeAccessibilityEvent(Recycler recycler, State state, - AccessibilityEvent event) { - final AccessibilityRecordCompat record = AccessibilityEventCompat - .asRecord(event); - if (mRecyclerView == null || record == null) { - return; - } - record.setScrollable(ViewCompat.canScrollVertically(mRecyclerView, 1) - || ViewCompat.canScrollVertically(mRecyclerView, -1) - || ViewCompat.canScrollHorizontally(mRecyclerView, -1) - || ViewCompat.canScrollHorizontally(mRecyclerView, 1)); - - if (mRecyclerView.mAdapter != null) { - record.setItemCount(mRecyclerView.mAdapter.getItemCount()); - } - } - - // called by accessibility delegate - void onInitializeAccessibilityNodeInfoForItem(View host, - AccessibilityNodeInfoCompat info) { - 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(Recycler recycler, State state, - View host, AccessibilityNodeInfoCompat info) { - int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; - int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; - final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo - = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(rowIndexGuess, 1, - columnIndexGuess, 1, false, false); - info.setCollectionItemInfo(itemInfo); - } - - /** - * 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(Recycler recycler, 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(Recycler recycler, State state) { - if (mRecyclerView == null || mRecyclerView.mAdapter == null) { - return 1; - } - return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 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(Recycler recycler, State state) { - if (mRecyclerView == null || mRecyclerView.mAdapter == null) { - return 1; - } - return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 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(Recycler recycler, State state) { - return false; - } - - // called by accessibility delegate - boolean performAccessibilityAction(int action, 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, Bundle) - */ - public boolean performAccessibilityAction(Recycler recycler, State state, int action, - Bundle args) { - if (mRecyclerView == null) { - return false; - } - int vScroll = 0, hScroll = 0; - switch (action) { - case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: - if (ViewCompat.canScrollVertically(mRecyclerView, -1)) { - vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); - } - if (ViewCompat.canScrollHorizontally(mRecyclerView, -1)) { - hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); - } - break; - case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: - if (ViewCompat.canScrollVertically(mRecyclerView, 1)) { - vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); - } - if (ViewCompat.canScrollHorizontally(mRecyclerView, 1)) { - hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); - } - break; - } - if (vScroll == 0 && hScroll == 0) { - return false; - } - mRecyclerView.scrollBy(hScroll, vScroll); - return true; - } - - // called by accessibility delegate - boolean performAccessibilityActionForItem(View view, int action, Bundle args) { - return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, - view, action, args); - } - - /** - * Called by AccessibilityDelegate when an accessibility action is requested on one of the - * chidren 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, Bundle) - */ - public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view, - int action, Bundle args) { - return false; - } - } - - private void removeFromDisappearingList(View child) { - mDisappearingViewsInLayoutPass.remove(child); - } - - private void addToDisappearingList(View child) { - if (!mDisappearingViewsInLayoutPass.contains(child)) { - mDisappearingViewsInLayoutPass.add(child); - } - } - - /** - * 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, State) onDraw()} - * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, - * State)}.

- */ - public static abstract 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(Canvas c, RecyclerView parent, State state) { - onDraw(c, parent); - } - - /** - * @deprecated - * Override {@link #onDraw(Canvas, RecyclerView, State)} - */ - @Deprecated - public void onDraw(Canvas c, 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(Canvas c, RecyclerView parent, State state) { - onDrawOver(c, parent); - } - - /** - * @deprecated - * Override {@link #onDrawOver(Canvas, RecyclerView, State)} - */ - @Deprecated - public void onDrawOver(Canvas c, RecyclerView parent) { - } - - - /** - * @deprecated - * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} - */ - @Deprecated - public void getItemOffsets(Rect outRect, int itemPosition, 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.

- * - * @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(Rect outRect, View view, RecyclerView parent, State state) { - getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewPosition(), - 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.

- */ - 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. - */ - public boolean onInterceptTouchEvent(RecyclerView rv, 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. - */ - public void onTouchEvent(RecyclerView rv, MotionEvent e); - } - - /** - * An OnScrollListener can be set on a RecyclerView to receive messages - * when a scrolling event has occurred on that RecyclerView. - * - * @see RecyclerView#setOnScrollListener(OnScrollListener) - */ - abstract static public 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(RecyclerView recyclerView, int newState){} - - /** - * Callback method to be invoked when the RecyclerView has been scrolled. This will be - * called after the scroll has completed. - * - * @param recyclerView The RecyclerView which scrolled. - * @param dx The amount of horizontal scroll. - * @param dy The amount of vertical scroll. - */ - public void onScrolled(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. - * - * @param holder The ViewHolder containing the view that was recycled - */ - public void onViewRecycled(ViewHolder holder); - } - - /** - * 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 static abstract class ViewHolder { - public final View itemView; - 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's contents have changed. This flag is used as an indication that - * change animations may be used, if supported by the ItemAnimator. - */ - static final int FLAG_CHANGED = 1 << 6; - - /** - * 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; - - private int mFlags; - - private int mIsRecyclableCount = 0; - - // If non-null, view is currently considered scrap and may be reused for other data by the - // scrap container. - private Recycler mScrapContainer = null; - - public ViewHolder(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; - } - - public final int getPosition() { - return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; - } - - /** - * 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 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 stopIgnoring() { - mFlags = mFlags & ~FLAG_IGNORE; - } - - void setScrapContainer(Recycler recycler) { - mScrapContainer = recycler; - } - - boolean isInvalid() { - return (mFlags & FLAG_INVALID) != 0; - } - - boolean needsUpdate() { - return (mFlags & FLAG_UPDATE) != 0; - } - - boolean isChanged() { - return (mFlags & FLAG_CHANGED) != 0; - } - - boolean isBound() { - return (mFlags & FLAG_BOUND) != 0; - } - - boolean isRemoved() { - return (mFlags & FLAG_REMOVED) != 0; - } - - void setFlags(int flags, int mask) { - mFlags = (mFlags & ~mask) | (flags & mask); - } - - void addFlags(int flags) { - mFlags |= flags; - } - - void resetInternal() { - mFlags = 0; - mPosition = NO_POSITION; - mOldPosition = NO_POSITION; - mItemId = NO_ID; - mPreLayoutPosition = NO_POSITION; - mIsRecyclableCount = 0; - mShadowedHolder = null; - mShadowingHolder = null; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("ViewHolder{" + - Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId + - ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); - if (isScrap()) sb.append(" scrap"); - if (isInvalid()) sb.append(" invalid"); - if (!isBound()) sb.append(" unbound"); - if (needsUpdate()) sb.append(" update"); - if (isRemoved()) sb.append(" removed"); - if (shouldIgnore()) sb.append(" ignored"); - if (isChanged()) sb.append(" changed"); - if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); - if (itemView.getParent() == null) sb.append(" no parent"); - sb.append("}"); - return sb.toString(); - } - - /** - * Informs the recycler whether this item can be recycled. Views which are not - * recyclable will not be reused for other items until setIsRecyclable() is - * later set to true. Calls to setIsRecyclable() should always be paired (one - * call to setIsRecyclabe(false) should always be matched with a later call to - * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally - * reference-counted. - * - * @param recyclable Whether this item is available to be recycled. Default value - * is true. - */ - public final void setIsRecyclable(boolean recyclable) { - mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; - if (mIsRecyclableCount < 0) { - mIsRecyclableCount = 0; - if (DEBUG) { - throw new RuntimeException("isRecyclable decremented below 0: " + - "unmatched pair of setIsRecyable() calls for " + this); - } - Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " + - "unmatched pair of setIsRecyable() calls for " + this); - } else if (!recyclable && mIsRecyclableCount == 1) { - mFlags |= FLAG_NOT_RECYCLABLE; - } else if (recyclable && mIsRecyclableCount == 0) { - mFlags &= ~FLAG_NOT_RECYCLABLE; - } - if (DEBUG) { - Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); - } - } - - /** - * @see {@link #setIsRecyclable(boolean)} - * - * @return true if this item is available to be recycled, false otherwise. - */ - public final boolean isRecyclable() { - return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && - !ViewCompat.hasTransientState(itemView); - } - } - - /** - * {@link MarginLayoutParams LayoutParams} subclass for children of - * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged - * to create their own subclass of this LayoutParams class - * to store any additional required per-child view metadata about the layout. - */ - public static class LayoutParams extends MarginLayoutParams { - ViewHolder mViewHolder; - final Rect mDecorInsets = new Rect(); - boolean mInsetsDirty = true; - // Flag is set to true if the view is bound while it is detached from RV. - // In this case, we need to manually call invalidate after view is added to guarantee that - // invalidation is populated through the View hierarchy - boolean mPendingInvalidate = false; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(LayoutParams source) { - super((ViewGroup.LayoutParams) source); - } - - /** - * Returns true if the view this LayoutParams is attached to needs to have its content - * updated from the corresponding adapter. - * - * @return true if the view should have its content updated - */ - public boolean viewNeedsUpdate() { - return mViewHolder.needsUpdate(); - } - - /** - * Returns true if the view this LayoutParams is attached to is now representing - * potentially invalid data. A LayoutManager should scrap/recycle it. - * - * @return true if the view is invalid - */ - public boolean isViewInvalid() { - return mViewHolder.isInvalid(); - } - - /** - * Returns true if the adapter data item corresponding to the view this LayoutParams - * is attached to has been removed from the data set. A LayoutManager may choose to - * treat it differently in order to animate its outgoing or disappearing state. - * - * @return true if the item the view corresponds to was removed from the data set - */ - public boolean isItemRemoved() { - return mViewHolder.isRemoved(); - } - - /** - * Returns true if the adapter data item corresponding to the view this LayoutParams - * is attached to has been changed in the data set. A LayoutManager may choose to - * treat it differently in order to animate its changing state. - * - * @return true if the item the view corresponds to was changed in the data set - */ - public boolean isItemChanged() { - return mViewHolder.isChanged(); - } - - /** - * Returns the position that the view this LayoutParams is attached to corresponds to. - * - * @return the adapter position this view was bound from - */ - public int getViewPosition() { - return mViewHolder.getPosition(); - } - } - - /** - * Observer base class for watching changes to an {@link Adapter}. - * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. - */ - public static abstract class AdapterDataObserver { - public void onChanged() { - // Do nothing - } - - public void onItemRangeChanged(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeInserted(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeRemoved(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - // do nothing - } - } - - /** - *

Base class for smooth scrolling. Handles basic tracking of the target view position and - * provides methods to trigger a programmatic scroll.

- * - * @see LinearSmoothScroller - */ - public static abstract class SmoothScroller { - - private int mTargetPosition = RecyclerView.NO_POSITION; - - private RecyclerView mRecyclerView; - - private LayoutManager mLayoutManager; - - private boolean mPendingInitialRun; - - private boolean mRunning; - - private View mTargetView; - - private final Action mRecyclingAction; - - public SmoothScroller() { - mRecyclingAction = new Action(0, 0); - } - - /** - * Starts a smooth scroll for the given target position. - *

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

- * - *

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

- */ - void start(RecyclerView recyclerView, LayoutManager layoutManager) { - mRecyclerView = recyclerView; - mLayoutManager = layoutManager; - if (mTargetPosition == RecyclerView.NO_POSITION) { - throw new IllegalArgumentException("Invalid target position"); - } - mRecyclerView.mState.mTargetPosition = mTargetPosition; - mRunning = true; - mPendingInitialRun = true; - mTargetView = findViewByPosition(getTargetPosition()); - onStart(); - mRecyclerView.mViewFlinger.postOnAnimation(); - } - - public void setTargetPosition(int targetPosition) { - mTargetPosition = targetPosition; - } - - /** - * @return The LayoutManager to which this SmoothScroller is attached - */ - public LayoutManager getLayoutManager() { - return mLayoutManager; - } - - /** - * Stops running the SmoothScroller in each animation callback. Note that this does not - * cancel any existing {@link Action} updated by - * {@link #onTargetFound(View, State, Action)} or - * {@link #onSeekTargetStep(int, int, State, Action)}. - */ - final protected void stop() { - if (!mRunning) { - return; - } - onStop(); - mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; - mTargetView = null; - mTargetPosition = RecyclerView.NO_POSITION; - mPendingInitialRun = false; - mRunning = false; - // trigger a cleanup - mLayoutManager.onSmoothScrollerStopped(this); - // clear references to avoid any potential leak by a custom smooth scroller - mLayoutManager = null; - mRecyclerView = null; - } - - /** - * Returns true if SmoothScroller has been started but has not received the first - * animation - * callback yet. - * - * @return True if this SmoothScroller is waiting to start - */ - public boolean isPendingInitialRun() { - return mPendingInitialRun; - } - - - /** - * @return True if SmoothScroller is currently active - */ - public boolean isRunning() { - return mRunning; - } - - /** - * Returns the adapter position of the target item - * - * @return Adapter position of the target item or - * {@link RecyclerView#NO_POSITION} if no target view is set. - */ - public int getTargetPosition() { - return mTargetPosition; - } - - private void onAnimation(int dx, int dy) { - if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) { - stop(); - } - mPendingInitialRun = false; - if (mTargetView != null) { - // verify target position - if (getChildPosition(mTargetView) == mTargetPosition) { - onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction); - mRecyclingAction.runIfNecessary(mRecyclerView); - stop(); - } else { - Log.e(TAG, "Passed over target position while smooth scrolling."); - mTargetView = null; - } - } - if (mRunning) { - onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction); - mRecyclingAction.runIfNecessary(mRecyclerView); - } - } - - /** - * @see RecyclerView#getChildPosition(View) - */ - public int getChildPosition(View view) { - return mRecyclerView.getChildPosition(view); - } - - /** - * @see LayoutManager#getChildCount() - */ - public int getChildCount() { - return mRecyclerView.mLayout.getChildCount(); - } - - /** - * @see LayoutManager#findViewByPosition(int) - */ - public View findViewByPosition(int position) { - return mRecyclerView.mLayout.findViewByPosition(position); - } - - /** - * @see RecyclerView#scrollToPosition(int) - */ - public void instantScrollToPosition(int position) { - mRecyclerView.scrollToPosition(position); - } - - protected void onChildAttachedToWindow(View child) { - if (getChildPosition(child) == getTargetPosition()) { - mTargetView = child; - if (DEBUG) { - Log.d(TAG, "smooth scroll target view has been attached"); - } - } - } - - /** - * Normalizes the vector. - * @param scrollVector The vector that points to the target scroll position - */ - protected void normalize(PointF scrollVector) { - final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y * - scrollVector.y); - scrollVector.x /= magnitute; - scrollVector.y /= magnitute; - } - - /** - * Called when smooth scroll is started. This might be a good time to do setup. - */ - abstract protected void onStart(); - - /** - * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. - * @see #stop() - */ - abstract protected void onStop(); - - /** - *

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

- *

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

- * - * @param dx Last scroll amount horizontally - * @param dy Last scroll amount verticaully - * @param state Transient state of RecyclerView - * @param action If you want to trigger a new smooth scroll and cancel the previous one, - * update this object. - */ - abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action); - - /** - * Called when the target position is laid out. This is the last callback SmoothScroller - * will receive and it should update the provided {@link Action} to define the scroll - * details towards the target view. - * @param targetView The view element which render the target position. - * @param state Transient state of RecyclerView - * @param action Action instance that you should update to define final scroll action - * towards the targetView - * @return An {@link Action} to finalize the smooth scrolling - */ - abstract protected void onTargetFound(View targetView, State state, Action action); - - /** - * Holds information about a smooth scroll request by a {@link SmoothScroller}. - */ - public static class Action { - - public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; - - private int mDx; - - private int mDy; - - private int mDuration; - - private Interpolator mInterpolator; - - private boolean changed = false; - - // we track this variable to inform custom implementer if they are updating the action - // in every animation callback - private int consecutiveUpdates = 0; - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - */ - public Action(int dx, int dy) { - this(dx, dy, UNDEFINED_DURATION, null); - } - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - */ - public Action(int dx, int dy, int duration) { - this(dx, dy, duration, null); - } - - /** - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - * @param interpolator Interpolator to be used when calculating scroll position in each - * animation step - */ - public Action(int dx, int dy, int duration, Interpolator interpolator) { - mDx = dx; - mDy = dy; - mDuration = duration; - mInterpolator = interpolator; - } - private void runIfNecessary(RecyclerView recyclerView) { - if (changed) { - validate(); - if (mInterpolator == null) { - if (mDuration == UNDEFINED_DURATION) { - recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); - } else { - recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); - } - } else { - recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator); - } - consecutiveUpdates ++; - if (consecutiveUpdates > 10) { - // A new action is being set in every animation step. This looks like a bad - // implementation. Inform developer. - Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" - + " you are not changing it unless necessary"); - } - changed = false; - } else { - consecutiveUpdates = 0; - } - } - - private void validate() { - if (mInterpolator != null && mDuration < 1) { - throw new IllegalStateException("If you provide an interpolator, you must" - + " set a positive duration"); - } else if (mDuration < 1) { - throw new IllegalStateException("Scroll duration must be a positive number"); - } - } - - public int getDx() { - return mDx; - } - - public void setDx(int dx) { - changed = true; - mDx = dx; - } - - public int getDy() { - return mDy; - } - - public void setDy(int dy) { - changed = true; - mDy = dy; - } - - public int getDuration() { - return mDuration; - } - - public void setDuration(int duration) { - changed = true; - mDuration = duration; - } - - public Interpolator getInterpolator() { - return mInterpolator; - } - - /** - * Sets the interpolator to calculate scroll steps - * @param interpolator The interpolator to use. If you specify an interpolator, you must - * also set the duration. - * @see #setDuration(int) - */ - public void setInterpolator(Interpolator interpolator) { - changed = true; - mInterpolator = interpolator; - } - - /** - * Updates the action with given parameters. - * @param dx Pixels to scroll horizontally - * @param dy Pixels to scroll vertically - * @param duration Duration of the animation in milliseconds - * @param interpolator Interpolator to be used when calculating scroll position in each - * animation step - */ - public void update(int dx, int dy, int duration, Interpolator interpolator) { - mDx = dx; - mDy = dy; - mDuration = duration; - mInterpolator = interpolator; - changed = true; - } - } - } - - static class AdapterDataObservable extends Observable { - public boolean hasObservers() { - return !mObservers.isEmpty(); - } - - public void notifyChanged() { - // since onChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onChanged(); - } - } - - public void notifyItemRangeChanged(int positionStart, int itemCount) { - // since onItemRangeChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeChanged(positionStart, itemCount); - } - } - - public void notifyItemRangeInserted(int positionStart, int itemCount) { - // since onItemRangeInserted() is implemented by the app, it could do anything, - // including removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeInserted(positionStart, itemCount); - } - } - - public void notifyItemRangeRemoved(int positionStart, int itemCount) { - // since onItemRangeRemoved() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); - } - } - - public void notifyItemMoved(int fromPosition, int toPosition) { - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); - } - } - } - - static class SavedState extends BaseSavedState { - - Parcelable mLayoutState; - - /** - * called by CREATOR - */ - SavedState(Parcel in) { - super(in); - mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader()); - } - - /** - * Called by onSaveInstanceState - */ - SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeParcelable(mLayoutState, 0); - } - - private void copyFrom(SavedState other) { - mLayoutState = other.mLayoutState; - } - - public static final Creator CREATOR - = new Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - /** - *

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

- *

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

- *

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

- */ - public static class State { - - private int mTargetPosition = RecyclerView.NO_POSITION; - ArrayMap mPreLayoutHolderMap = - new ArrayMap(); - ArrayMap mPostLayoutHolderMap = - new ArrayMap(); - // nullable - ArrayMap mOldChangedHolders = new ArrayMap(); - - private SparseArray mData; - - /** - * Number of items adapter has. - */ - int mItemCount = 0; - - /** - * Number of items adapter had in the previous layout. - */ - private int mPreviousLayoutItemCount = 0; - - /** - * Number of items that were NOT laid out but has been deleted from the adapter after the - * previous layout. - */ - private int mDeletedInvisibleItemCountSincePreviousLayout = 0; - - private boolean mStructureChanged = false; - - private boolean mInPreLayout = false; - - private boolean mRunSimpleAnimations = false; - - private boolean mRunPredictiveAnimations = false; - - State reset() { - mTargetPosition = RecyclerView.NO_POSITION; - if (mData != null) { - mData.clear(); - } - mItemCount = 0; - mStructureChanged = false; - return this; - } - - public boolean isPreLayout() { - return mInPreLayout; - } - - /** - * Returns whether RecyclerView will run predictive animations in this layout pass - * or not. - * - * @return true if RecyclerView is calculating predictive animations to be run at the end - * of the layout pass. - */ - public boolean willRunPredictiveAnimations() { - return mRunPredictiveAnimations; - } - - /** - * Returns whether RecyclerView will run simple animations in this layout pass - * or not. - * - * @return true if RecyclerView is calculating simple animations to be run at the end of - * the layout pass. - */ - public boolean willRunSimpleAnimations() { - return mRunSimpleAnimations; - } - - /** - * Removes the mapping from the specified id, if there was any. - * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to - * preserve cross functionality and avoid conflicts. - */ - public void remove(int resourceId) { - if (mData == null) { - return; - } - mData.remove(resourceId); - } - - /** - * Gets the Object mapped from the specified id, or null - * if no such data exists. - * - * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* - * to - * preserve cross functionality and avoid conflicts. - */ - public T get(int resourceId) { - if (mData == null) { - return null; - } - return (T) mData.get(resourceId); - } - - /** - * Adds a mapping from the specified id to the specified value, replacing the previous - * mapping from the specified key if there was one. - * - * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to - * preserve cross functionality and avoid conflicts. - * @param data The data you want to associate with the resourceId. - */ - public void put(int resourceId, Object data) { - if (mData == null) { - mData = new SparseArray(); - } - mData.put(resourceId, data); - } - - /** - * If scroll is triggered to make a certain item visible, this value will return the - * adapter index of that item. - * @return Adapter index of the target item or - * {@link RecyclerView#NO_POSITION} if there is no target - * position. - */ - public int getTargetScrollPosition() { - return mTargetPosition; - } - - /** - * Returns if current scroll has a target position. - * @return true if scroll is being triggered to make a certain position visible - * @see #getTargetScrollPosition() - */ - public boolean hasTargetScrollPosition() { - return mTargetPosition != RecyclerView.NO_POSITION; - } - - /** - * @return true if the structure of the data set has changed since the last call to - * onLayoutChildren, false otherwise - */ - public boolean didStructureChange() { - return mStructureChanged; - } - - /** - * Returns the total number of items that can be laid out. Note that this number is not - * necessarily equal to the number of items in the adapter, so you should always use this - * number for your position calculations and never access the adapter directly. - *

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

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

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

- * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. - * - * @return The number of items currently available - * @see LayoutManager#getItemCount() - */ - public int getItemCount() { - return mInPreLayout ? - (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) : - mItemCount; - } - - public void onViewRecycled(ViewHolder holder) { - mPreLayoutHolderMap.remove(holder); - mPostLayoutHolderMap.remove(holder); - if (mOldChangedHolders != null) { - removeFrom(mOldChangedHolders, holder); - } - // holder cannot be in new list. - } - - public void onViewIgnored(ViewHolder holder) { - onViewRecycled(holder); - } - - private void removeFrom(ArrayMap holderMap, ViewHolder holder) { - for (int i = holderMap.size() - 1; i >= 0; i --) { - if (holder == holderMap.valueAt(i)) { - holderMap.removeAt(i); - return; - } - } - } - - @Override - public String toString() { - return "State{" + - "mTargetPosition=" + mTargetPosition + - ", mPreLayoutHolderMap=" + mPreLayoutHolderMap + - ", mPostLayoutHolderMap=" + mPostLayoutHolderMap + - ", mData=" + mData + - ", mItemCount=" + mItemCount + - ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount + - ", mDeletedInvisibleItemCountSincePreviousLayout=" - + mDeletedInvisibleItemCountSincePreviousLayout + - ", mStructureChanged=" + mStructureChanged + - ", mInPreLayout=" + mInPreLayout + - ", mRunSimpleAnimations=" + mRunSimpleAnimations + - ", mRunPredictiveAnimations=" + mRunPredictiveAnimations + - '}'; - } - } - - /** - * Internal listener that manages items after animations finish. This is how items are - * retained (not recycled) during animations, but allowed to be recycled afterwards. - * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() - * method on the animator's listener when it is done animating any item. - */ - private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { - - @Override - public void onRemoveFinished(ViewHolder item) { - item.setIsRecyclable(true); - removeAnimatingView(item.itemView); - removeDetachedView(item.itemView, false); - } - - @Override - public void onAddFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (item.isRecyclable()) { - removeAnimatingView(item.itemView); - } - } - - @Override - public void onMoveFinished(ViewHolder item) { - item.setIsRecyclable(true); - if (item.isRecyclable()) { - removeAnimatingView(item.itemView); - } - } - - @Override - public void onChangeFinished(ViewHolder item) { - item.setIsRecyclable(true); - /** - * We check both shadowed and shadowing because a ViewHolder may get both roles at the - * same time. - * - * Assume this flow: - * item X is represented by VH_1. Then itemX changes, so we create VH_2 . - * RV sets the following and calls item animator: - * VH_1.shadowed = VH_2; - * VH_1.mChanged = true; - * VH_2.shadowing =VH_1; - * - * Then, before the first change finishes, item changes again so we create VH_3. - * RV sets the following and calls item animator: - * VH_2.shadowed = VH_3 - * VH_2.mChanged = true - * VH_3.shadowing = VH_2 - * - * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has - * both shadowing and shadowed fields set. Shadowing information is obsolete now - * because the first animation where VH_2 is newViewHolder is not valid anymore. - * We ended up in this case because VH_2 played both roles. On the other hand, - * we DO NOT want to clear its changed flag. - * - * If second change was simply reverting first change, we would find VH_1 in - * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before - * re-using - */ - if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh - item.mShadowedHolder = null; - item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags); - } - // always null this because an OldViewHolder can never become NewViewHolder w/o being - // recycled. - item.mShadowingHolder = null; - if (item.isRecyclable()) { - removeAnimatingView(item.itemView); - } - } - }; - - /** - * This class defines the animations that take place on items as changes are made - * to the adapter. - * - * Subclasses of ItemAnimator can be used to implement custom animations for actions on - * ViewHolder items. The RecyclerView will manage retaining these items while they - * are being animated, but implementors must call the appropriate "Starting" - * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)}, - * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or - * {@link #dispatchAddStarting(ViewHolder)}) - * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)}, - * {@link #dispatchMoveFinished(ViewHolder)}, - * {@link #dispatchChangeFinished(ViewHolder, boolean)}, - * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is - * being started and ended. - * - *

By default, RecyclerView uses {@link DefaultItemAnimator}

- * - * @see #setItemAnimator(ItemAnimator) - */ - public static abstract class ItemAnimator { - - private ItemAnimatorListener mListener = null; - private ArrayList mFinishedListeners = - new ArrayList(); - - private long mAddDuration = 120; - private long mRemoveDuration = 120; - private long mMoveDuration = 250; - private long mChangeDuration = 250; - - private boolean mSupportsChangeAnimations = false; - - /** - * Gets the current duration for which all move animations will run. - * - * @return The current move duration - */ - public long getMoveDuration() { - return mMoveDuration; - } - - /** - * Sets the duration for which all move animations will run. - * - * @param moveDuration The move duration - */ - public void setMoveDuration(long moveDuration) { - mMoveDuration = moveDuration; - } - - /** - * Gets the current duration for which all add animations will run. - * - * @return The current add duration - */ - public long getAddDuration() { - return mAddDuration; - } - - /** - * Sets the duration for which all add animations will run. - * - * @param addDuration The add duration - */ - public void setAddDuration(long addDuration) { - mAddDuration = addDuration; - } - - /** - * Gets the current duration for which all remove animations will run. - * - * @return The current remove duration - */ - public long getRemoveDuration() { - return mRemoveDuration; - } - - /** - * Sets the duration for which all remove animations will run. - * - * @param removeDuration The remove duration - */ - public void setRemoveDuration(long removeDuration) { - mRemoveDuration = removeDuration; - } - - /** - * Gets the current duration for which all change animations will run. - * - * @return The current change duration - */ - public long getChangeDuration() { - return mChangeDuration; - } - - /** - * Sets the duration for which all change animations will run. - * - * @param changeDuration The change duration - */ - public void setChangeDuration(long changeDuration) { - mChangeDuration = changeDuration; - } - - /** - * Returns whether this ItemAnimator supports animations of change events. - * - * @return true if change animations are supported, false otherwise - */ - public boolean getSupportsChangeAnimations() { - return mSupportsChangeAnimations; - } - - /** - * Sets whether this ItemAnimator supports animations of item change events. - * By default, ItemAnimator only supports animations when items are added or removed. - * By setting this property to true, actions on the data set which change the - * contents of items may also be animated. What those animations are is left - * up to the discretion of the ItemAnimator subclass, in its - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation. - * The value of this property is false by default. - * - * @see Adapter#notifyItemChanged(int) - * @see Adapter#notifyItemRangeChanged(int, int) - * - * @param supportsChangeAnimations true if change animations are supported by - * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator - * will not receive a call to - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur. - */ - public void setSupportsChangeAnimations(boolean supportsChangeAnimations) { - mSupportsChangeAnimations = supportsChangeAnimations; - } - - /** - * Internal only: - * Sets the listener that must be called when the animator is finished - * animating the item (or immediately if no animation happens). This is set - * internally and is not intended to be set by external code. - * - * @param listener The listener that must be called. - */ - void setListener(ItemAnimatorListener listener) { - mListener = listener; - } - - /** - * Called when there are pending animations waiting to be started. This state - * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and - * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the - * RecyclerView that the ItemAnimator wants to be called later to start the - * associated animations. runPendingAnimations() will be scheduled to be run - * on the next frame. - */ - abstract public void runPendingAnimations(); - - /** - * Called when an item is removed from the RecyclerView. Implementors can choose - * whether and how to animate that change, but must always call - * {@link #dispatchRemoveFinished(ViewHolder)} when done, either - * immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - *

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

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

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

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

- * Implementers can choose whether and how to animate changes, but must always call - * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder, - * either immediately (if no animation will occur) or after the animation actually finishes. - * The return value indicates whether an animation has been set up and whether the - * ItemAnimator's {@link #runPendingAnimations()} method should be called at the - * next opportunity. This mechanism allows ItemAnimator to set up individual animations - * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()}, - * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, - * {@link #animateRemove(ViewHolder) animateRemove()}, and - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one, - * then start the animations together in the later call to {@link #runPendingAnimations()}. - * - * @param oldHolder The original item that changed. - * @param newHolder The new item that was created with the changed content. Might be null - * @param fromLeft Left of the old view holder - * @param fromTop Top of the old view holder - * @param toLeft Left of the new view holder - * @param toTop Top of the new view holder - * @return true if a later call to {@link #runPendingAnimations()} is requested, - * false otherwise. - */ - abstract public boolean animateChange(ViewHolder oldHolder, - ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop); - - - /** - * Method to be called by subclasses when a remove animation is done. - * - * @param item The item which has been removed - */ - public final void dispatchRemoveFinished(ViewHolder item) { - onRemoveFinished(item); - if (mListener != null) { - mListener.onRemoveFinished(item); - } - } - - /** - * Method to be called by subclasses when a move animation is done. - * - * @param item The item which has been moved - */ - public final void dispatchMoveFinished(ViewHolder item) { - onMoveFinished(item); - if (mListener != null) { - mListener.onMoveFinished(item); - } - } - - /** - * Method to be called by subclasses when an add animation is done. - * - * @param item The item which has been added - */ - public final void dispatchAddFinished(ViewHolder item) { - onAddFinished(item); - if (mListener != null) { - mListener.onAddFinished(item); - } - } - - /** - * Method to be called by subclasses when a change animation is done. - * - * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int) - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) { - onChangeFinished(item, oldItem); - if (mListener != null) { - mListener.onChangeFinished(item); - } - } - - /** - * Method to be called by subclasses when a remove animation is being started. - * - * @param item The item being removed - */ - public final void dispatchRemoveStarting(ViewHolder item) { - onRemoveStarting(item); - } - - /** - * Method to be called by subclasses when a move animation is being started. - * - * @param item The item being moved - */ - public final void dispatchMoveStarting(ViewHolder item) { - onMoveStarting(item); - } - - /** - * Method to be called by subclasses when an add animation is being started. - * - * @param item The item being added - */ - public final void dispatchAddStarting(ViewHolder item) { - onAddStarting(item); - } - - /** - * Method to be called by subclasses when a change animation is being started. - * - * @param item The item which has been changed (this method must be called for - * each non-null ViewHolder passed into - * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}). - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) { - onChangeStarting(item, oldItem); - } - - /** - * Method called when an animation on a view should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on the item - * are canceled and affected properties are set to their end values. - * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} - * should be called since the animations are effectively done when this - * method is called. - * - * @param item The item for which an animation should be stopped. - */ - abstract public void endAnimation(ViewHolder item); - - /** - * Method called when all item animations should be ended immediately. - * This could happen when other events, like scrolling, occur, so that - * animating views can be quickly put into their proper end locations. - * Implementations should ensure that any animations running on any items - * are canceled and affected properties are set to their end values. - * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)} - * should be called since the animations are effectively done when this - * method is called. - */ - abstract public void endAnimations(); - - /** - * Method which returns whether there are any item animations currently running. - * This method can be used to determine whether to delay other actions until - * animations end. - * - * @return true if there are any item animations currently running, false otherwise. - */ - abstract public boolean isRunning(); - - /** - * Like {@link #isRunning()}, this method returns whether there are any item - * animations currently running. Addtionally, the listener passed in will be called - * when there are no item animations running, either immediately (before the method - * returns) if no animations are currently running, or when the currently running - * animations are {@link #dispatchAnimationsFinished() finished}. - * - *

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

- * - * @param listener A listener to be called immediately if no animations are running - * or later when currently-running animations have finished. A null listener is - * equivalent to calling {@link #isRunning()}. - * @return true if there are any item animations currently running, false otherwise. - */ - public final boolean isRunning(ItemAnimatorFinishedListener listener) { - boolean running = isRunning(); - if (listener != null) { - if (!running) { - listener.onAnimationsFinished(); - } else { - mFinishedListeners.add(listener); - } - } - return running; - } - - /** - * The interface to be implemented by listeners to animation events from this - * ItemAnimator. This is used internally and is not intended for developers to - * create directly. - */ - interface ItemAnimatorListener { - void onRemoveFinished(ViewHolder item); - void onAddFinished(ViewHolder item); - void onMoveFinished(ViewHolder item); - void onChangeFinished(ViewHolder item); - } - - /** - * This method should be called by ItemAnimator implementations to notify - * any listeners that all pending and active item animations are finished. - */ - public final void dispatchAnimationsFinished() { - final int count = mFinishedListeners.size(); - for (int i = 0; i < count; ++i) { - mFinishedListeners.get(i).onAnimationsFinished(); - } - mFinishedListeners.clear(); - } - - /** - * This interface is used to inform listeners when all pending or running animations - * in an ItemAnimator are finished. This can be used, for example, to delay an action - * in a data set until currently-running animations are complete. - * - * @see #isRunning(ItemAnimatorFinishedListener) - */ - public interface ItemAnimatorFinishedListener { - void onAnimationsFinished(); - } - - /** - * Called when a remove animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onRemoveStarting(ViewHolder item) {} - - /** - * Called when a remove animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onRemoveFinished(ViewHolder item) {} - - /** - * Called when an add animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onAddStarting(ViewHolder item) {} - - /** - * Called when an add animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onAddFinished(ViewHolder item) {} - - /** - * Called when a move animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onMoveStarting(ViewHolder item) {} - - /** - * Called when a move animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - */ - public void onMoveFinished(ViewHolder item) {} - - /** - * Called when a change animation is being started on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public void onChangeStarting(ViewHolder item, boolean oldItem) {} - - /** - * Called when a change animation has ended on the given ViewHolder. - * The default implementation does nothing. Subclasses may wish to override - * this method to handle any ViewHolder-specific operations linked to animation - * lifecycles. - * - * @param item The ViewHolder being animated. - * @param oldItem true if this is the old item that was changed, false if - * it is the new item that replaced the old item. - */ - public void onChangeFinished(ViewHolder item, boolean oldItem) {} - - } - - /** - * Internal data structure that holds information about an item's bounds. - * This information is used in calculating item animations. - */ - private static class ItemHolderInfo { - ViewHolder holder; - int left, top, right, bottom; - - ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) { - this.holder = holder; - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } - } -} diff --git a/app/src/main/java/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java b/app/src/main/java/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java deleted file mode 100644 index ed7dfd6f63..0000000000 --- a/app/src/main/java/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.os.Bundle; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.view.View; -import android.view.accessibility.AccessibilityEvent; - -/** - * The AccessibilityDelegate used by RecyclerView. - *

- * This class handles basic accessibility actions and delegates them to LayoutManager. - */ -public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat { - final RecyclerView mRecyclerView; - - - public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) { - mRecyclerView = recyclerView; - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - if (mRecyclerView.getLayoutManager() != null) { - return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args); - } - - return false; - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setClassName(RecyclerView.class.getName()); - if (mRecyclerView.getLayoutManager() != null) { - mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info); - } - } - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - event.setClassName(RecyclerView.class.getName()); - if (host instanceof RecyclerView) { - RecyclerView rv = (RecyclerView) host; - if (rv.getLayoutManager() != null) { - rv.getLayoutManager().onInitializeAccessibilityEvent(event); - } - } - } - - AccessibilityDelegateCompat getItemDelegate() { - return mItemDelegate; - } - - final AccessibilityDelegateCompat mItemDelegate = new AccessibilityDelegateCompat() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - if (mRecyclerView.getLayoutManager() != null) { - mRecyclerView.getLayoutManager(). - onInitializeAccessibilityNodeInfoForItem(host, info); - } - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - if (mRecyclerView.getLayoutManager() != null) { - return mRecyclerView.getLayoutManager(). - performAccessibilityActionForItem(host, action, args); - } - return false; - } - }; -} diff --git a/app/src/main/java/android/support/v7/widget/ScrollbarHelper.java b/app/src/main/java/android/support/v7/widget/ScrollbarHelper.java deleted file mode 100644 index 0903f6414a..0000000000 --- a/app/src/main/java/android/support/v7/widget/ScrollbarHelper.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.view.View; - -/** - * A helper class to do scroll offset calculations. - */ -class ScrollbarHelper { - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled, boolean reverseLayout) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild)); - final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild)); - final int itemsBefore = reverseLayout - ? Math.max(0, state.getItemCount() - maxPosition - 1) - : Math.max(0, minPosition - 1); - if (!smoothScrollbarEnabled) { - return itemsBefore; - } - final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild)); - final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1; - final float avgSizePerRow = (float) laidOutArea / itemRange; - - return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding() - - orientation.getDecoratedStart(startChild))); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1; - } - final int extend = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - return Math.min(orientation.getTotalSpace(), extend); - } - - /** - * @param startChild View closest to start of the list. (top or left) - * @param endChild View closest to end of the list (bottom or right) - */ - static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation, - View startChild, View endChild, RecyclerView.LayoutManager lm, - boolean smoothScrollbarEnabled) { - if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null || - endChild == null) { - return 0; - } - if (!smoothScrollbarEnabled) { - return state.getItemCount(); - } - // smooth scrollbar enabled. try to estimate better. - final int laidOutArea = orientation.getDecoratedEnd(endChild) - - orientation.getDecoratedStart(startChild); - final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) - + 1; - // estimate a size for full list. - return (int) ((float) laidOutArea / laidOutRange * state.getItemCount()); - } -} diff --git a/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java b/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java deleted file mode 100644 index 0389012358..0000000000 --- a/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java +++ /dev/null @@ -1,2634 +0,0 @@ -/* - * Copyright (C) 2014 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 android.support.v7.widget; - -import android.content.Context; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.List; - -import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD; -import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL; -import static android.support.v7.widget.LayoutState.LAYOUT_END; -import static android.support.v7.widget.LayoutState.LAYOUT_START; -import static android.support.v7.widget.RecyclerView.NO_POSITION; - -/** - * A LayoutManager that lays out children in a staggered grid formation. - * It supports horizontal & vertical layout as well as an ability to layout children in reverse. - *

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

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

- * After scrolling stops, we check this flag and if it is set, re-layout. - */ - private boolean mLaidOutInvalidFullSpan = false; - - /** - * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}. - * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)} - */ - private boolean mSmoothScrollbarEnabled = true; - - private final Runnable checkForGapsRunnable = new Runnable() { - @Override - public void run() { - checkForGaps(); - } - }; - - /** - * Creates a StaggeredGridLayoutManager with given parameters. - * - * @param spanCount If orientation is vertical, spanCount is number of columns. If - * orientation is horizontal, spanCount is number of rows. - * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL} - */ - public StaggeredGridLayoutManager(int spanCount, int orientation) { - mOrientation = orientation; - setSpanCount(spanCount); - } - - /** - * Checks for gaps in the UI that may be caused by adapter changes. - *

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

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

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

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

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

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

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

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

- * Not that this value is not affected by RecyclerView's layout direction. - * - * @return True if layout is reversed, false otherwise - * @see #setReverseLayout(boolean) - */ - public boolean getReverseLayout() { - return mReverseLayout; - } - - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - ensureOrientationHelper(); - // Update adapter size. - mLazySpanLookup.mAdapterSize = state.getItemCount(); - - final AnchorInfo anchorInfo = mAnchorInfo; - anchorInfo.reset(); - - if (mPendingSavedState != null) { - applyPendingSavedState(anchorInfo); - } else { - resolveShouldLayoutReverse(); - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - } - - updateAnchorInfoForLayout(state, anchorInfo); - - if (mPendingSavedState == null) { - if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd || - isLayoutRTL() != mLastLayoutRTL) { - mLazySpanLookup.clear(); - anchorInfo.mInvalidateOffsets = true; - } - } - - if (getChildCount() > 0 && (mPendingSavedState == null || - mPendingSavedState.mSpanOffsetsSize < 1)) { - if (anchorInfo.mInvalidateOffsets) { - for (int i = 0; i < mSpanCount; i++) { - // Scroll to position is set, clear. - mSpans[i].clear(); - if (anchorInfo.mOffset != INVALID_OFFSET) { - mSpans[i].setLine(anchorInfo.mOffset); - } - } - } else { - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset); - } - } - } - detachAndScrapAttachedViews(recycler); - mLaidOutInvalidFullSpan = false; - updateMeasureSpecs(); - if (anchorInfo.mLayoutFromEnd) { - // Layout start. - updateLayoutStateToFillStart(anchorInfo.mPosition, state); - fill(recycler, mLayoutState, state); - // Layout end. - updateLayoutStateToFillEnd(anchorInfo.mPosition, state); - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state); - } else { - // Layout end. - updateLayoutStateToFillEnd(anchorInfo.mPosition, state); - fill(recycler, mLayoutState, state); - // Layout start. - updateLayoutStateToFillStart(anchorInfo.mPosition, state); - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state); - } - - if (getChildCount() > 0) { - if (mShouldReverseLayout) { - fixEndGap(recycler, state, true); - fixStartGap(recycler, state, false); - } else { - fixStartGap(recycler, state, true); - fixEndGap(recycler, state, false); - } - } - - if (!state.isPreLayout()) { - if (getChildCount() > 0 && mPendingScrollPosition != NO_POSITION && - mLaidOutInvalidFullSpan) { - ViewCompat.postOnAnimation(getChildAt(0), checkForGapsRunnable); - } - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - } - mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd; - mLastLayoutRTL = isLayoutRTL(); - mPendingSavedState = null; // we don't need this anymore - } - - private void applyPendingSavedState(AnchorInfo anchorInfo) { - if (DEBUG) { - Log.d(TAG, "found saved state: " + mPendingSavedState); - } - if (mPendingSavedState.mSpanOffsetsSize > 0) { - if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) { - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].clear(); - int line = mPendingSavedState.mSpanOffsets[i]; - if (line != Span.INVALID_LINE) { - if (mPendingSavedState.mAnchorLayoutFromEnd) { - line += mPrimaryOrientation.getEndAfterPadding(); - } else { - line += mPrimaryOrientation.getStartAfterPadding(); - } - } - mSpans[i].setLine(line); - } - } else { - mPendingSavedState.invalidateSpanInfo(); - mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition; - } - } - mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL; - setReverseLayout(mPendingSavedState.mReverseLayout); - resolveShouldLayoutReverse(); - - if (mPendingSavedState.mAnchorPosition != NO_POSITION) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd; - } else { - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; - } - if (mPendingSavedState.mSpanLookupSize > 1) { - mLazySpanLookup.mData = mPendingSavedState.mSpanLookup; - mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems; - } - } - - void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) { - if (updateAnchorFromPendingData(state, anchorInfo)) { - return; - } - if (updateAnchorFromChildren(state, anchorInfo)) { - return; - } - if (DEBUG) { - Log.d(TAG, "Deciding anchor info from fresh state"); - } - anchorInfo.assignCoordinateFromPadding(); - anchorInfo.mPosition = 0; - } - - private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) { - // We don't recycle views out of adapter order. This way, we can rely on the first or - // last child as the anchor position. - // Layout direction may change but we should select the child depending on the latest - // layout direction. Otherwise, we'll choose the wrong child. - anchorInfo.mPosition = mLastLayoutFromEnd - ? findLastReferenceChildPosition(state.getItemCount()) - : findFirstReferenceChildPosition(state.getItemCount()); - anchorInfo.mOffset = INVALID_OFFSET; - return true; - } - - boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) { - // Validate scroll position if exists. - if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) { - return false; - } - // Validate it. - if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - return false; - } - - if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION - || mPendingSavedState.mSpanOffsetsSize < 1) { - // If item is visible, make it fully visible. - final View child = findViewByPosition(mPendingScrollPosition); - if (child != null) { - // Use regular anchor position, offset according to pending offset and target - // child - anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition() - : getFirstChildPosition(); - - if (mPendingScrollPositionOffset != INVALID_OFFSET) { - if (anchorInfo.mLayoutFromEnd) { - final int target = mPrimaryOrientation.getEndAfterPadding() - - mPendingScrollPositionOffset; - anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child); - } else { - final int target = mPrimaryOrientation.getStartAfterPadding() + - mPendingScrollPositionOffset; - anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child); - } - return true; - } - - // no offset provided. Decide according to the child location - final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child); - if (childSize > mPrimaryOrientation.getTotalSpace()) { - // Item does not fit. Fix depending on layout direction. - anchorInfo.mOffset = anchorInfo.mLayoutFromEnd - ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - return true; - } - - final int startGap = mPrimaryOrientation.getDecoratedStart(child) - - mPrimaryOrientation.getStartAfterPadding(); - if (startGap < 0) { - anchorInfo.mOffset = -startGap; - return true; - } - final int endGap = mPrimaryOrientation.getEndAfterPadding() - - mPrimaryOrientation.getDecoratedEnd(child); - if (endGap < 0) { - anchorInfo.mOffset = endGap; - return true; - } - // child already visible. just layout as usual - anchorInfo.mOffset = INVALID_OFFSET; - } else { - // Child is not visible. Set anchor coordinate depending on in which direction - // child will be visible. - anchorInfo.mPosition = mPendingScrollPosition; - if (mPendingScrollPositionOffset == INVALID_OFFSET) { - final int position = calculateScrollDirectionForPosition( - anchorInfo.mPosition); - anchorInfo.mLayoutFromEnd = position == LAYOUT_END; - anchorInfo.assignCoordinateFromPadding(); - } else { - anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset); - } - anchorInfo.mInvalidateOffsets = true; - } - } else { - anchorInfo.mOffset = INVALID_OFFSET; - anchorInfo.mPosition = mPendingScrollPosition; - } - return true; - } - - void updateMeasureSpecs() { - mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount; - mFullSizeSpec = View.MeasureSpec.makeMeasureSpec( - mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY); - if (mOrientation == VERTICAL) { - mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY); - mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } else { - mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY); - mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } - } - - @Override - public boolean supportsPredictiveItemAnimations() { - return mPendingSavedState == null; - } - - /** - * Returns the adapter position of the first visible view for each span. - *

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

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

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

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

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

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

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

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

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

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

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

- * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those - * views are ignored in this method. - * - * @param into An array to put the results into. If you don't provide any, LayoutManager will - * create a new one. - * @return The adapter position of the last fully visible item in each span. If a span does not - * have any items, {@link RecyclerView#NO_POSITION} is returned for that span. - * @see #findFirstCompletelyVisibleItemPositions(int[]) - * @see #findLastVisibleItemPositions(int[]) - */ - public int[] findLastCompletelyVisibleItemPositions(int[] into) { - if (into == null) { - into = new int[mSpanCount]; - } else if (into.length < mSpanCount) { - throw new IllegalArgumentException("Provided int[]'s size must be more than or equal" - + " to span count. Expected:" + mSpanCount + ", array size:" + into.length); - } - for (int i = 0; i < mSpanCount; i++) { - into[i] = mSpans[i].findLastCompletelyVisibleItemPosition(); - } - return into; - } - - @Override - public int computeHorizontalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - private int computeScrollOffset(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled, mShouldReverseLayout); - } - - @Override - public int computeVerticalScrollOffset(RecyclerView.State state) { - return computeScrollOffset(state); - } - - @Override - public int computeHorizontalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - private int computeScrollExtent(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled); - } - - @Override - public int computeVerticalScrollExtent(RecyclerView.State state) { - return computeScrollExtent(state); - } - - @Override - public int computeHorizontalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private int computeScrollRange(RecyclerView.State state) { - if (getChildCount() == 0) { - return 0; - } - return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), - this, mSmoothScrollbarEnabled); - } - - @Override - public int computeVerticalScrollRange(RecyclerView.State state) { - return computeScrollRange(state); - } - - private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) { - if (lp.mFullSpan) { - if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(child, mFullSizeSpec, mHeightSpec); - } else { - measureChildWithDecorationsAndMargin(child, mWidthSpec, mFullSizeSpec); - } - } else { - measureChildWithDecorationsAndMargin(child, mWidthSpec, mHeightSpec); - } - } - - private void measureChildWithDecorationsAndMargin(View child, int widthSpec, - int heightSpec) { - final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + insets.left, - lp.rightMargin + insets.right); - heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + insets.top, - lp.bottomMargin + insets.bottom); - child.measure(widthSpec, heightSpec); - } - - private int updateSpecWithExtra(int spec, int startInset, int endInset) { - if (startInset == 0 && endInset == 0) { - return spec; - } - final int mode = View.MeasureSpec.getMode(spec); - if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { - return View.MeasureSpec.makeMeasureSpec( - View.MeasureSpec.getSize(spec) - startInset - endInset, mode); - } - return spec; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof SavedState) { - mPendingSavedState = (SavedState) state; - requestLayout(); - } else if (DEBUG) { - Log.d(TAG, "invalid saved state class"); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (mPendingSavedState != null) { - return new SavedState(mPendingSavedState); - } - SavedState state = new SavedState(); - state.mReverseLayout = mReverseLayout; - state.mAnchorLayoutFromEnd = mLastLayoutFromEnd; - state.mLastLayoutRTL = mLastLayoutRTL; - - if (mLazySpanLookup != null && mLazySpanLookup.mData != null) { - state.mSpanLookup = mLazySpanLookup.mData; - state.mSpanLookupSize = state.mSpanLookup.length; - state.mFullSpanItems = mLazySpanLookup.mFullSpanItems; - } else { - state.mSpanLookupSize = 0; - } - - if (getChildCount() > 0) { - state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition() - : getFirstChildPosition(); - state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt(); - state.mSpanOffsetsSize = mSpanCount; - state.mSpanOffsets = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - int line; - if (mLastLayoutFromEnd) { - line = mSpans[i].getEndLine(Span.INVALID_LINE); - if (line != Span.INVALID_LINE) { - line -= mPrimaryOrientation.getEndAfterPadding(); - } - } else { - line = mSpans[i].getStartLine(Span.INVALID_LINE); - if (line != Span.INVALID_LINE) { - line -= mPrimaryOrientation.getStartAfterPadding(); - } - } - state.mSpanOffsets[i] = line; - } - } else { - state.mAnchorPosition = NO_POSITION; - state.mVisibleAnchorPosition = NO_POSITION; - state.mSpanOffsetsSize = 0; - } - if (DEBUG) { - Log.d(TAG, "saved state:\n" + state); - } - return state; - } - - @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 sglp = (LayoutParams) lp; - if (mOrientation == HORIZONTAL) { - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1, - -1, -1, - sglp.mFullSpan, false)); - } else { // VERTICAL - info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain( - -1, -1, - sglp.getSpanIndex(), sglp.mFullSpan ? mSpanCount : 1, - sglp.mFullSpan, false)); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - if (getChildCount() > 0) { - final AccessibilityRecordCompat record = AccessibilityEventCompat - .asRecord(event); - final View start = findFirstVisibleItemClosestToStart(false); - final View end = findFirstVisibleItemClosestToEnd(false); - if (start == null || end == null) { - return; - } - final int startPos = getPosition(start); - final int endPos = getPosition(end); - if (startPos < endPos) { - record.setFromIndex(startPos); - record.setToIndex(endPos); - } else { - record.setFromIndex(endPos); - record.setToIndex(startPos); - } - } - } - - /** - * Finds the first fully visible child to be used as an anchor child if span count changes when - * state is restored. - */ - int findFirstVisibleItemPositionInt() { - final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) : - findFirstVisibleItemClosestToStart(true); - return first == null ? NO_POSITION : getPosition(first); - } - - @Override - public int getRowCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == HORIZONTAL) { - return mSpanCount; - } - return super.getRowCountForAccessibility(recycler, state); - } - - @Override - public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, - RecyclerView.State state) { - if (mOrientation == VERTICAL) { - return mSpanCount; - } - return super.getColumnCountForAccessibility(recycler, state); - } - - View findFirstVisibleItemClosestToStart(boolean fullyVisible) { - final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); - final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); - final int limit = getChildCount(); - for (int i = 0; i < limit; i ++) { - final View child = getChildAt(i); - if ((!fullyVisible || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart) - && mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) { - return child; - } - } - return null; - } - - View findFirstVisibleItemClosestToEnd(boolean fullyVisible) { - final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); - final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); - for (int i = getChildCount() - 1; i >= 0; i --) { - final View child = getChildAt(i); - if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart && (!fullyVisible - || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd)) { - return child; - } - } - return null; - } - - private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean canOffsetChildren) { - final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding()); - int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine; - int fixOffset; - if (gap > 0) { - fixOffset = -scrollBy(-gap, recycler, state); - } else { - return; // nothing to fix - } - gap -= fixOffset; - if (canOffsetChildren && gap > 0) { - mPrimaryOrientation.offsetChildren(gap); - } - } - - private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state, - boolean canOffsetChildren) { - final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding()); - int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding(); - int fixOffset; - if (gap > 0) { - fixOffset = scrollBy(gap, recycler, state); - } else { - return; // nothing to fix - } - gap -= fixOffset; - if (canOffsetChildren && gap > 0) { - mPrimaryOrientation.offsetChildren(-gap); - } - } - - private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) { - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition = anchorPosition; - if (isSmoothScrolling()) { - final int targetPos = state.getTargetScrollPosition(); - if (mShouldReverseLayout == targetPos < anchorPosition) { - mLayoutState.mExtra = 0; - } else { - mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace(); - } - } else { - mLayoutState.mExtra = 0; - } - mLayoutState.mLayoutDirection = LAYOUT_START; - mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL - : ITEM_DIRECTION_HEAD; - } - - private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) { - mLayoutState.mAvailable = 0; - mLayoutState.mCurrentPosition = anchorPosition; - if (isSmoothScrolling()) { - final int targetPos = state.getTargetScrollPosition(); - if (mShouldReverseLayout == targetPos > anchorPosition) { - mLayoutState.mExtra = 0; - } else { - mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace(); - } - } else { - mLayoutState.mExtra = 0; - } - mLayoutState.mLayoutDirection = LAYOUT_END; - mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD - : ITEM_DIRECTION_TAIL; - } - - @Override - public void offsetChildrenHorizontal(int dx) { - super.offsetChildrenHorizontal(dx); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].onOffset(dx); - } - } - - @Override - public void offsetChildrenVertical(int dy) { - super.offsetChildrenVertical(dy); - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].onOffset(dy); - } - } - - @Override - public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE); - } - - @Override - public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD); - } - - @Override - public void onItemsChanged(RecyclerView recyclerView) { - mLazySpanLookup.clear(); - requestLayout(); - } - - @Override - public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { - handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE); - } - - @Override - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { - handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE); - } - - /** - * Checks whether it should invalidate span assignments in response to an adapter change. - */ - private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) { - int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition(); - mLazySpanLookup.invalidateAfter(positionStart); - switch (cmd) { - case AdapterHelper.UpdateOp.ADD: - mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition); - break; - case AdapterHelper.UpdateOp.REMOVE: - mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition); - break; - case AdapterHelper.UpdateOp.MOVE: - // TODO optimize - mLazySpanLookup.offsetForRemoval(positionStart, 1); - mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1); - break; - } - - if (positionStart + itemCountOrToPosition <= minPosition) { - return; - - } - int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition(); - if (positionStart <= maxPosition) { - requestLayout(); - } - } - - private int fill(RecyclerView.Recycler recycler, LayoutState layoutState, - RecyclerView.State state) { - mRemainingSpans.set(0, mSpanCount, true); - // The target position we are trying to reach. - final int targetLine; - /* - * The line until which we can recycle, as long as we add views. - * Keep in mind, it is still the line in layout direction which means; to calculate the - * actual recycle line, we should subtract/add the size in orientation. - */ - final int recycleLine; - // Line of the furthest row. - if (layoutState.mLayoutDirection == LAYOUT_END) { - // ignore padding for recycler - recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable; - targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding(); - - } else { // LAYOUT_START - // ignore padding for recycler - recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable; - targetLine = recycleLine - mLayoutState.mExtra - - mPrimaryOrientation.getStartAfterPadding(); - } - updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine); - - // the default coordinate to add new view. - final int defaultNewViewLine = mShouldReverseLayout - ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - - while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) { - View view = layoutState.next(recycler); - LayoutParams lp = ((LayoutParams) view.getLayoutParams()); - if (layoutState.mLayoutDirection == LAYOUT_END) { - addView(view); - } else { - addView(view, 0); - } - measureChildWithDecorationsAndMargin(view, lp); - - final int position = lp.getViewPosition(); - final int spanIndex = mLazySpanLookup.getSpan(position); - Span currentSpan; - boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID; - if (assignSpan) { - currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState); - mLazySpanLookup.setSpan(position, currentSpan); - if (DEBUG) { - Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position); - } - } else { - if (DEBUG) { - Log.d(TAG, "using " + spanIndex + " for pos " + position); - } - currentSpan = mSpans[spanIndex]; - } - final int start; - final int end; - - if (layoutState.mLayoutDirection == LAYOUT_END) { - start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine) - : currentSpan.getEndLine(defaultNewViewLine); - end = start + mPrimaryOrientation.getDecoratedMeasurement(view); - if (assignSpan && lp.mFullSpan) { - LazySpanLookup.FullSpanItem fullSpanItem; - fullSpanItem = createFullSpanItemFromEnd(start); - fullSpanItem.mGapDir = LAYOUT_START; - fullSpanItem.mPosition = position; - mLazySpanLookup.addFullSpanItem(fullSpanItem); - } - } else { - end = lp.mFullSpan ? getMinStart(defaultNewViewLine) - : currentSpan.getStartLine(defaultNewViewLine); - start = end - mPrimaryOrientation.getDecoratedMeasurement(view); - if (assignSpan && lp.mFullSpan) { - LazySpanLookup.FullSpanItem fullSpanItem; - fullSpanItem = createFullSpanItemFromStart(end); - fullSpanItem.mGapDir = LAYOUT_END; - fullSpanItem.mPosition = position; - mLazySpanLookup.addFullSpanItem(fullSpanItem); - } - } - - // check if this item may create gaps in the future - if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD && assignSpan) { - mLaidOutInvalidFullSpan = true; - } - - lp.mSpan = currentSpan; - attachViewToSpans(view, lp, layoutState); - final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding() - : currentSpan.mIndex * mSizePerSpan + - mSecondaryOrientation.getStartAfterPadding(); - final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view); - if (mOrientation == VERTICAL) { - layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end); - } else { - layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd); - } - - if (lp.mFullSpan) { - updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine); - } else { - updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine); - } - recycle(recycler, mLayoutState, currentSpan, recycleLine); - } - if (DEBUG) { - Log.d(TAG, "fill, " + getChildCount()); - } - if (mLayoutState.mLayoutDirection == LAYOUT_START) { - final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding()); - return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart)); - } else { - final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding()); - return Math.max(0, mLayoutState.mAvailable + (max - recycleLine)); - } - } - - private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) { - LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem(); - fsi.mGapPerSpan = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop); - } - return fsi; - } - - private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) { - LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem(); - fsi.mGapPerSpan = new int[mSpanCount]; - for (int i = 0; i < mSpanCount; i++) { - fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom; - } - return fsi; - } - - private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) { - if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) { - if (lp.mFullSpan) { - appendViewToAllSpans(view); - } else { - lp.mSpan.appendToSpan(view); - } - } else { - if (lp.mFullSpan) { - prependViewToAllSpans(view); - } else { - lp.mSpan.prependToSpan(view); - } - } - } - - private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState, - Span updatedSpan, int recycleLine) { - if (layoutState.mLayoutDirection == LAYOUT_START) { - // calculate recycle line - int maxStart = getMaxStart(updatedSpan.getStartLine()); - recycleFromEnd(recycler, Math.max(recycleLine, maxStart) + - (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding())); - } else { - // calculate recycle line - int minEnd = getMinEnd(updatedSpan.getEndLine()); - recycleFromStart(recycler, Math.min(recycleLine, minEnd) - - (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding())); - } - } - - private void appendViewToAllSpans(View view) { - // traverse in reverse so that we end up assigning full span items to 0 - for (int i = mSpanCount - 1; i >= 0; i--) { - mSpans[i].appendToSpan(view); - } - } - - private void prependViewToAllSpans(View view) { - // traverse in reverse so that we end up assigning full span items to 0 - for (int i = mSpanCount - 1; i >= 0; i--) { - mSpans[i].prependToSpan(view); - } - } - - private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (DEBUG) { - Log.d(TAG, "layout decorated pos: " + lp.getViewPosition() + ", span:" - + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan - + ". l:" + left + ",t:" + top - + ", r:" + right + ", b:" + bottom); - } - layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin - , bottom - lp.bottomMargin); - } - - private void updateAllRemainingSpans(int layoutDir, int targetLine) { - for (int i = 0; i < mSpanCount; i++) { - if (mSpans[i].mViews.isEmpty()) { - continue; - } - updateRemainingSpans(mSpans[i], layoutDir, targetLine); - } - } - - private void updateRemainingSpans(Span span, int layoutDir, int targetLine) { - final int deletedSize = span.getDeletedSize(); - if (layoutDir == LAYOUT_START) { - final int line = span.getStartLine(); - if (line + deletedSize < targetLine) { - mRemainingSpans.set(span.mIndex, false); - } - } else { - final int line = span.getEndLine(); - if (line - deletedSize > targetLine) { - mRemainingSpans.set(span.mIndex, false); - } - } - } - - private int getMaxStart(int def) { - int maxStart = mSpans[0].getStartLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanStart = mSpans[i].getStartLine(def); - if (spanStart > maxStart) { - maxStart = spanStart; - } - } - return maxStart; - } - - private int getMinStart(int def) { - int minStart = mSpans[0].getStartLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanStart = mSpans[i].getStartLine(def); - if (spanStart < minStart) { - minStart = spanStart; - } - } - return minStart; - } - - private int getMaxEnd(int def) { - int maxEnd = mSpans[0].getEndLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanEnd = mSpans[i].getEndLine(def); - if (spanEnd > maxEnd) { - maxEnd = spanEnd; - } - } - return maxEnd; - } - - private int getMinEnd(int def) { - int minEnd = mSpans[0].getEndLine(def); - for (int i = 1; i < mSpanCount; i++) { - final int spanEnd = mSpans[i].getEndLine(def); - if (spanEnd < minEnd) { - minEnd = spanEnd; - } - } - return minEnd; - } - - private void recycleFromStart(RecyclerView.Recycler recycler, int line) { - if (DEBUG) { - Log.d(TAG, "recycling from start for line " + line); - } - while (getChildCount() > 0) { - View child = getChildAt(0); - if (mPrimaryOrientation.getDecoratedEnd(child) < line) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mFullSpan) { - for (int j = 0; j < mSpanCount; j++) { - mSpans[j].popStart(); - } - } else { - lp.mSpan.popStart(); - } - removeAndRecycleView(child, recycler); - } else { - return;// done - } - } - } - - private void recycleFromEnd(RecyclerView.Recycler recycler, int line) { - final int childCount = getChildCount(); - int i; - for (i = childCount - 1; i >= 0; i--) { - View child = getChildAt(i); - if (mPrimaryOrientation.getDecoratedStart(child) > line) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mFullSpan) { - for (int j = 0; j < mSpanCount; j++) { - mSpans[j].popEnd(); - } - } else { - lp.mSpan.popEnd(); - } - removeAndRecycleView(child, recycler); - } else { - return;// done - } - } - } - - /** - * @return True if last span is the first one we want to fill - */ - private boolean preferLastSpan(int layoutDir) { - if (mOrientation == HORIZONTAL) { - return (layoutDir == LAYOUT_START) != mShouldReverseLayout; - } - return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL(); - } - - /** - * Finds the span for the next view. - */ - private Span getNextSpan(LayoutState layoutState) { - final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection); - final int startIndex, endIndex, diff; - if (preferLastSpan) { - startIndex = mSpanCount - 1; - endIndex = -1; - diff = -1; - } else { - startIndex = 0; - endIndex = mSpanCount; - diff = 1; - } - if (layoutState.mLayoutDirection == LAYOUT_END) { - Span min = null; - int minLine = Integer.MAX_VALUE; - final int defaultLine = mPrimaryOrientation.getStartAfterPadding(); - for (int i = startIndex; i != endIndex; i += diff) { - final Span other = mSpans[i]; - int otherLine = other.getEndLine(defaultLine); - if (otherLine < minLine) { - min = other; - minLine = otherLine; - } - } - return min; - } else { - Span max = null; - int maxLine = Integer.MIN_VALUE; - final int defaultLine = mPrimaryOrientation.getEndAfterPadding(); - for (int i = startIndex; i != endIndex; i += diff) { - final Span other = mSpans[i]; - int otherLine = other.getStartLine(defaultLine); - if (otherLine > maxLine) { - max = other; - maxLine = otherLine; - } - } - return max; - } - } - - @Override - public boolean canScrollVertically() { - return mOrientation == VERTICAL; - } - - @Override - public boolean canScrollHorizontally() { - return mOrientation == HORIZONTAL; - } - - @Override - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - return scrollBy(dx, recycler, state); - } - - @Override - public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, - RecyclerView.State state) { - return scrollBy(dy, recycler, state); - } - - private int calculateScrollDirectionForPosition(int position) { - if (getChildCount() == 0) { - return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START; - } - final int firstChildPos = getFirstChildPosition(); - return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END; - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - final int direction = calculateScrollDirectionForPosition(targetPosition); - if (direction == 0) { - return null; - } - if (mOrientation == HORIZONTAL) { - return new PointF(direction, 0); - } else { - return new PointF(0, direction); - } - } - }; - scroller.setTargetPosition(position); - startSmoothScroll(scroller); - } - - @Override - public void scrollToPosition(int position) { - if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) { - mPendingSavedState.invalidateAnchorPositionInfo(); - } - mPendingScrollPosition = position; - mPendingScrollPositionOffset = INVALID_OFFSET; - requestLayout(); - } - - /** - * Scroll to the specified adapter position with the given offset from layout start. - *

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

- * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}. - * - * @param position Index (starting at 0) of the reference item. - * @param offset The distance (in pixels) between the start edge of the item view and - * start edge of the RecyclerView. - * @see #setReverseLayout(boolean) - * @see #scrollToPosition(int) - */ - public void scrollToPositionWithOffset(int position, int offset) { - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchorPositionInfo(); - } - mPendingScrollPosition = position; - mPendingScrollPositionOffset = offset; - requestLayout(); - } - - int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { - ensureOrientationHelper(); - final int referenceChildPosition; - if (dt > 0) { // layout towards end - mLayoutState.mLayoutDirection = LAYOUT_END; - mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD - : ITEM_DIRECTION_TAIL; - referenceChildPosition = getLastChildPosition(); - } else { - mLayoutState.mLayoutDirection = LAYOUT_START; - mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL - : ITEM_DIRECTION_HEAD; - referenceChildPosition = getFirstChildPosition(); - } - mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; - final int absDt = Math.abs(dt); - mLayoutState.mAvailable = absDt; - mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0; - int consumed = fill(recycler, mLayoutState, state); - final int totalScroll; - if (absDt < consumed) { - totalScroll = dt; - } else if (dt < 0) { - totalScroll = -consumed; - } else { // dt > 0 - totalScroll = consumed; - } - if (DEBUG) { - Log.d(TAG, "asked " + dt + " scrolled" + totalScroll); - } - - mPrimaryOrientation.offsetChildren(-totalScroll); - // always reset this if we scroll for a proper save instance state - mLastLayoutFromEnd = mShouldReverseLayout; - return totalScroll; - } - - private int getLastChildPosition() { - final int childCount = getChildCount(); - return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1)); - } - - private int getFirstChildPosition() { - final int childCount = getChildCount(); - return childCount == 0 ? 0 : getPosition(getChildAt(0)); - } - - /** - * Finds the first View that can be used as an anchor View. - * - * @return Position of the View or 0 if it cannot find any such View. - */ - private int findFirstReferenceChildPosition(int itemCount) { - final int limit = getChildCount(); - for (int i = 0; i < limit; i++) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - return position; - } - } - return 0; - } - - /** - * Finds the last View that can be used as an anchor View. - * - * @return Position of the View or 0 if it cannot find any such View. - */ - private int findLastReferenceChildPosition(int itemCount) { - for (int i = getChildCount() - 1; i >= 0; i--) { - final View view = getChildAt(i); - final int position = getPosition(view); - if (position >= 0 && position < itemCount) { - return position; - } - } - return 0; - } - - @Override - public RecyclerView.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { - return new LayoutParams(c, attrs); - } - - @Override - public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { - if (lp instanceof ViewGroup.MarginLayoutParams) { - return new LayoutParams((ViewGroup.MarginLayoutParams) lp); - } else { - return new LayoutParams(lp); - } - } - - @Override - public boolean checkLayoutParams(RecyclerView.LayoutParams lp) { - return lp instanceof LayoutParams; - } - - public int getOrientation() { - return mOrientation; - } - - - /** - * LayoutParams used by StaggeredGridLayoutManager. - */ - public static class LayoutParams extends RecyclerView.LayoutParams { - - /** - * Span Id for Views that are not laid out yet. - */ - public static final int INVALID_SPAN_ID = -1; - - // Package scope to be able to access from tests. - Span mSpan; - - boolean mFullSpan; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(RecyclerView.LayoutParams source) { - super(source); - } - - /** - * When set to true, the item will layout using all span area. That means, if orientation - * is vertical, the view will have full width; if orientation is horizontal, the view will - * have full height. - * - * @param fullSpan True if this item should traverse all spans. - * @see #isFullSpan() - */ - public void setFullSpan(boolean fullSpan) { - mFullSpan = fullSpan; - } - - /** - * Returns whether this View occupies all available spans or just one. - * - * @return True if the View occupies all spans or false otherwise. - * @see #setFullSpan(boolean) - */ - public boolean isFullSpan() { - return mFullSpan; - } - - /** - * Returns the Span index to which this View is assigned. - * - * @return The Span index of the View. If View is not yet assigned to any span, returns - * {@link #INVALID_SPAN_ID}. - */ - public final int getSpanIndex() { - if (mSpan == null) { - return INVALID_SPAN_ID; - } - return mSpan.mIndex; - } - } - - // Package scoped to access from tests. - class Span { - - static final int INVALID_LINE = Integer.MIN_VALUE; - private ArrayList mViews = new ArrayList(); - int mCachedStart = INVALID_LINE; - int mCachedEnd = INVALID_LINE; - int mDeletedSize = 0; - final int mIndex; - - private Span(int index) { - mIndex = index; - } - - int getStartLine(int def) { - if (mCachedStart != INVALID_LINE) { - return mCachedStart; - } - if (mViews.size() == 0) { - return def; - } - calculateCachedStart(); - return mCachedStart; - } - - void calculateCachedStart() { - final View startView = mViews.get(0); - final LayoutParams lp = getLayoutParams(startView); - mCachedStart = mPrimaryOrientation.getDecoratedStart(startView); - if (lp.mFullSpan) { - LazySpanLookup.FullSpanItem fsi = mLazySpanLookup - .getFullSpanItem(lp.getViewPosition()); - if (fsi != null && fsi.mGapDir == LAYOUT_START) { - mCachedStart -= fsi.getGapForSpan(mIndex); - } - } - } - - // Use this one when default value does not make sense and not having a value means a bug. - int getStartLine() { - if (mCachedStart != INVALID_LINE) { - return mCachedStart; - } - calculateCachedStart(); - return mCachedStart; - } - - int getEndLine(int def) { - if (mCachedEnd != INVALID_LINE) { - return mCachedEnd; - } - final int size = mViews.size(); - if (size == 0) { - return def; - } - calculateCachedEnd(); - return mCachedEnd; - } - - void calculateCachedEnd() { - final View endView = mViews.get(mViews.size() - 1); - final LayoutParams lp = getLayoutParams(endView); - mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView); - if (lp.mFullSpan) { - LazySpanLookup.FullSpanItem fsi = mLazySpanLookup - .getFullSpanItem(lp.getViewPosition()); - if (fsi != null && fsi.mGapDir == LAYOUT_END) { - mCachedEnd += fsi.getGapForSpan(mIndex); - } - } - } - - // Use this one when default value does not make sense and not having a value means a bug. - int getEndLine() { - if (mCachedEnd != INVALID_LINE) { - return mCachedEnd; - } - calculateCachedEnd(); - return mCachedEnd; - } - - void prependToSpan(View view) { - LayoutParams lp = getLayoutParams(view); - lp.mSpan = this; - mViews.add(0, view); - mCachedStart = INVALID_LINE; - if (mViews.size() == 1) { - mCachedEnd = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view); - } - } - - void appendToSpan(View view) { - LayoutParams lp = getLayoutParams(view); - lp.mSpan = this; - mViews.add(view); - mCachedEnd = INVALID_LINE; - if (mViews.size() == 1) { - mCachedStart = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view); - } - } - - // Useful method to preserve positions on a re-layout. - void cacheReferenceLineAndClear(boolean reverseLayout, int offset) { - int reference; - if (reverseLayout) { - reference = getEndLine(INVALID_LINE); - } else { - reference = getStartLine(INVALID_LINE); - } - clear(); - if (reference == INVALID_LINE) { - return; - } - if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) || - (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding()) ) { - return; - } - if (offset != INVALID_OFFSET) { - reference += offset; - } - mCachedStart = mCachedEnd = reference; - } - - void clear() { - mViews.clear(); - invalidateCache(); - mDeletedSize = 0; - } - - void invalidateCache() { - mCachedStart = INVALID_LINE; - mCachedEnd = INVALID_LINE; - } - - void setLine(int line) { - mCachedEnd = mCachedStart = line; - } - - void popEnd() { - final int size = mViews.size(); - View end = mViews.remove(size - 1); - final LayoutParams lp = getLayoutParams(end); - lp.mSpan = null; - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end); - } - if (size == 1) { - mCachedStart = INVALID_LINE; - } - mCachedEnd = INVALID_LINE; - } - - void popStart() { - View start = mViews.remove(0); - final LayoutParams lp = getLayoutParams(start); - lp.mSpan = null; - if (mViews.size() == 0) { - mCachedEnd = INVALID_LINE; - } - if (lp.isItemRemoved() || lp.isItemChanged()) { - mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start); - } - mCachedStart = INVALID_LINE; - } - - public int getDeletedSize() { - return mDeletedSize; - } - - LayoutParams getLayoutParams(View view) { - return (LayoutParams) view.getLayoutParams(); - } - - void onOffset(int dt) { - if (mCachedStart != INVALID_LINE) { - mCachedStart += dt; - } - if (mCachedEnd != INVALID_LINE) { - mCachedEnd += dt; - } - } - - // normalized offset is how much this span can scroll - int getNormalizedOffset(int dt, int targetStart, int targetEnd) { - if (mViews.size() == 0) { - return 0; - } - if (dt < 0) { - final int endSpace = getEndLine() - targetEnd; - if (endSpace <= 0) { - return 0; - } - return -dt > endSpace ? -endSpace : dt; - } else { - final int startSpace = targetStart - getStartLine(); - if (startSpace <= 0) { - return 0; - } - return startSpace < dt ? startSpace : dt; - } - } - - /** - * Returns if there is no child between start-end lines - * - * @param start The start line - * @param end The end line - * @return true if a new child can be added between start and end - */ - boolean isEmpty(int start, int end) { - final int count = mViews.size(); - for (int i = 0; i < count; i++) { - final View view = mViews.get(i); - if (mPrimaryOrientation.getDecoratedStart(view) < end && - mPrimaryOrientation.getDecoratedEnd(view) > start) { - return false; - } - } - return true; - } - - public int findFirstVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(mViews.size() -1, -1, false) - : findOneVisibleChild(0, mViews.size(), false); - } - - public int findFirstCompletelyVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(mViews.size() -1, -1, true) - : findOneVisibleChild(0, mViews.size(), true); - } - - public int findLastVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(0, mViews.size(), false) - : findOneVisibleChild(mViews.size() -1, -1, false); - } - - public int findLastCompletelyVisibleItemPosition() { - return mReverseLayout - ? findOneVisibleChild(0, mViews.size(), true) - : findOneVisibleChild(mViews.size() -1, -1, true); - } - - int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) { - final int start = mPrimaryOrientation.getStartAfterPadding(); - final int end = mPrimaryOrientation.getEndAfterPadding(); - final int next = toIndex > fromIndex ? 1 : -1; - for (int i = fromIndex; i != toIndex; i += next) { - final View child = mViews.get(i); - final int childStart = mPrimaryOrientation.getDecoratedStart(child); - final int childEnd = mPrimaryOrientation.getDecoratedEnd(child); - if (childStart < end && childEnd > start) { - if (completelyVisible) { - if (childStart >= start && childEnd <= end) { - return getPosition(child); - } - } else { - return getPosition(child); - } - } - } - return NO_POSITION; - } - } - - /** - * An array of mappings from adapter position to span. - * This only grows when a write happens and it grows up to the size of the adapter. - */ - static class LazySpanLookup { - - private static final int MIN_SIZE = 10; - int[] mData; - int mAdapterSize; // we don't want to grow beyond that, unless it grows - List mFullSpanItems; - - - /** - * Invalidates everything after this position, including full span information - */ - int forceInvalidateAfter(int position) { - if (mFullSpanItems != null) { - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= position) { - mFullSpanItems.remove(i); - } - } - } - return invalidateAfter(position); - } - - /** - * returns end position for invalidation. - */ - int invalidateAfter(int position) { - if (mData == null) { - return RecyclerView.NO_POSITION; - } - if (position >= mData.length) { - return RecyclerView.NO_POSITION; - } - int endPosition = invalidateFullSpansAfter(position); - if (endPosition == RecyclerView.NO_POSITION) { - Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID); - return mData.length; - } else { - // just invalidate items in between - Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID); - return endPosition + 1; - } - } - - int getSpan(int position) { - if (mData == null || position >= mData.length) { - return LayoutParams.INVALID_SPAN_ID; - } else { - return mData[position]; - } - } - - void setSpan(int position, Span span) { - ensureSize(position); - mData[position] = span.mIndex; - } - - int sizeForPosition(int position) { - int len = mData.length; - while (len <= position) { - len *= 2; - } - if (len > mAdapterSize) { - len = mAdapterSize; - } - return len; - } - - void ensureSize(int position) { - if (mData == null) { - mData = new int[Math.max(position, MIN_SIZE) + 1]; - Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID); - } else if (position >= mData.length) { - int[] old = mData; - mData = new int[sizeForPosition(position)]; - System.arraycopy(old, 0, mData, 0, old.length); - Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID); - } - } - - void clear() { - if (mData != null) { - Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID); - } - mFullSpanItems = null; - } - - void offsetForRemoval(int positionStart, int itemCount) { - if (mData == null || positionStart >= mData.length) { - return; - } - ensureSize(positionStart + itemCount); - System.arraycopy(mData, positionStart + itemCount, mData, positionStart, - mData.length - positionStart - itemCount); - Arrays.fill(mData, mData.length - itemCount, mData.length, - LayoutParams.INVALID_SPAN_ID); - offsetFullSpansForRemoval(positionStart, itemCount); - } - - private void offsetFullSpansForRemoval(int positionStart, int itemCount) { - if (mFullSpanItems == null) { - return; - } - final int end = positionStart + itemCount; - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition < positionStart) { - continue; - } - if (fsi.mPosition < end) { - mFullSpanItems.remove(i); - } else { - fsi.mPosition -= itemCount; - } - } - } - - void offsetForAddition(int positionStart, int itemCount) { - if (mData == null || positionStart >= mData.length) { - return; - } - ensureSize(positionStart + itemCount); - System.arraycopy(mData, positionStart, mData, positionStart + itemCount, - mData.length - positionStart - itemCount); - Arrays.fill(mData, positionStart, positionStart + itemCount, - LayoutParams.INVALID_SPAN_ID); - offsetFullSpansForAddition(positionStart, itemCount); - } - - private void offsetFullSpansForAddition(int positionStart, int itemCount) { - if (mFullSpanItems == null) { - return; - } - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition < positionStart) { - continue; - } - fsi.mPosition += itemCount; - } - } - - /** - * Returns when invalidation should end. e.g. hitting a full span position. - * Returned position SHOULD BE invalidated. - */ - private int invalidateFullSpansAfter(int position) { - if (mFullSpanItems == null) { - return RecyclerView.NO_POSITION; - } - final FullSpanItem item = getFullSpanItem(position); - // if there is an fsi at this position, get rid of it. - if (item != null) { - mFullSpanItems.remove(item); - } - int nextFsiIndex = -1; - final int count = mFullSpanItems.size(); - for (int i = 0; i < count; i++) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= position) { - nextFsiIndex = i; - break; - } - } - if (nextFsiIndex != -1) { - FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex); - mFullSpanItems.remove(nextFsiIndex); - return fsi.mPosition; - } - return RecyclerView.NO_POSITION; - } - - public void addFullSpanItem(FullSpanItem fullSpanItem) { - if (mFullSpanItems == null) { - mFullSpanItems = new ArrayList(); - } - final int size = mFullSpanItems.size(); - for (int i = 0; i < size; i++) { - FullSpanItem other = mFullSpanItems.get(i); - if (other.mPosition == fullSpanItem.mPosition) { - if (DEBUG) { - throw new IllegalStateException("two fsis for same position"); - } else { - mFullSpanItems.remove(i); - } - } - if (other.mPosition >= fullSpanItem.mPosition) { - mFullSpanItems.add(i, fullSpanItem); - return; - } - } - // if it is not added to a position. - mFullSpanItems.add(fullSpanItem); - } - - public FullSpanItem getFullSpanItem(int position) { - if (mFullSpanItems == null) { - return null; - } - for (int i = mFullSpanItems.size() - 1; i >= 0; i--) { - final FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition == position) { - return fsi; - } - } - return null; - } - - /** - * @param minPos inclusive - * @param maxPos exclusive - * @param gapDir if not 0, returns FSIs on in that direction - */ - public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir) { - if (mFullSpanItems == null) { - return null; - } - for (int i = 0; i < mFullSpanItems.size(); i++) { - FullSpanItem fsi = mFullSpanItems.get(i); - if (fsi.mPosition >= maxPos) { - return null; - } - if (fsi.mPosition >= minPos && (gapDir == 0 || fsi.mGapDir == gapDir)) { - return fsi; - } - } - return null; - } - - /** - * We keep information about full span items because they may create gaps in the UI. - */ - static class FullSpanItem implements Parcelable { - - int mPosition; - int mGapDir; - int[] mGapPerSpan; - - public FullSpanItem(Parcel in) { - mPosition = in.readInt(); - mGapDir = in.readInt(); - int spanCount = in.readInt(); - if (spanCount > 0) { - mGapPerSpan = new int[spanCount]; - in.readIntArray(mGapPerSpan); - } - } - - public FullSpanItem() { - } - - int getGapForSpan(int spanIndex) { - return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex]; - } - - public void invalidateSpanGaps() { - mGapPerSpan = null; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mPosition); - dest.writeInt(mGapDir); - if (mGapPerSpan != null && mGapPerSpan.length > 0) { - dest.writeInt(mGapPerSpan.length); - dest.writeIntArray(mGapPerSpan); - } else { - dest.writeInt(0); - } - } - - @Override - public String toString() { - return "FullSpanItem{" + - "mPosition=" + mPosition + - ", mGapDir=" + mGapDir + - ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) + - '}'; - } - - public static final Creator CREATOR - = new Creator() { - @Override - public FullSpanItem createFromParcel(Parcel in) { - return new FullSpanItem(in); - } - - @Override - public FullSpanItem[] newArray(int size) { - return new FullSpanItem[size]; - } - }; - } - } - - static class SavedState implements Parcelable { - - int mAnchorPosition; - int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated - int mSpanOffsetsSize; - int[] mSpanOffsets; - int mSpanLookupSize; - int[] mSpanLookup; - List mFullSpanItems; - boolean mReverseLayout; - boolean mAnchorLayoutFromEnd; - boolean mLastLayoutRTL; - - public SavedState() { - } - - SavedState(Parcel in) { - mAnchorPosition = in.readInt(); - mVisibleAnchorPosition = in.readInt(); - mSpanOffsetsSize = in.readInt(); - if (mSpanOffsetsSize > 0) { - mSpanOffsets = new int[mSpanOffsetsSize]; - in.readIntArray(mSpanOffsets); - } - - mSpanLookupSize = in.readInt(); - if (mSpanLookupSize > 0) { - mSpanLookup = new int[mSpanLookupSize]; - in.readIntArray(mSpanLookup); - } - mReverseLayout = in.readInt() == 1; - mAnchorLayoutFromEnd = in.readInt() == 1; - mLastLayoutRTL = in.readInt() == 1; - mFullSpanItems = in.readArrayList( - LazySpanLookup.FullSpanItem.class.getClassLoader()); - } - - public SavedState(SavedState other) { - mSpanOffsetsSize = other.mSpanOffsetsSize; - mAnchorPosition = other.mAnchorPosition; - mVisibleAnchorPosition = other.mVisibleAnchorPosition; - mSpanOffsets = other.mSpanOffsets; - mSpanLookupSize = other.mSpanLookupSize; - mSpanLookup = other.mSpanLookup; - mReverseLayout = other.mReverseLayout; - mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd; - mLastLayoutRTL = other.mLastLayoutRTL; - mFullSpanItems = other.mFullSpanItems; - } - - void invalidateSpanInfo() { - mSpanOffsets = null; - mSpanOffsetsSize = 0; - mSpanLookupSize = 0; - mSpanLookup = null; - mFullSpanItems = null; - } - - void invalidateAnchorPositionInfo() { - mSpanOffsets = null; - mSpanOffsetsSize = 0; - mAnchorPosition = NO_POSITION; - mVisibleAnchorPosition = NO_POSITION; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAnchorPosition); - dest.writeInt(mVisibleAnchorPosition); - dest.writeInt(mSpanOffsetsSize); - if (mSpanOffsetsSize > 0) { - dest.writeIntArray(mSpanOffsets); - } - dest.writeInt(mSpanLookupSize); - if (mSpanLookupSize > 0) { - dest.writeIntArray(mSpanLookup); - } - dest.writeInt(mReverseLayout ? 1 : 0); - dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0); - dest.writeInt(mLastLayoutRTL ? 1 : 0); - dest.writeList(mFullSpanItems); - } - - public static final Creator CREATOR - = new Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - /** - * Data class to hold the information about an anchor position which is used in onLayout call. - */ - private class AnchorInfo { - - int mPosition; - int mOffset; - boolean mLayoutFromEnd; - boolean mInvalidateOffsets; - - void reset() { - mPosition = NO_POSITION; - mOffset = INVALID_OFFSET; - mLayoutFromEnd = false; - mInvalidateOffsets = false; - } - - void assignCoordinateFromPadding() { - mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding() - : mPrimaryOrientation.getStartAfterPadding(); - } - - void assignCoordinateFromPadding(int addedDistance) { - if (mLayoutFromEnd) { - mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance; - } else { - mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance; - } - } - } -} diff --git a/app/src/main/java/com/gh/base/BaseDetailActivity.java b/app/src/main/java/com/gh/base/BaseDetailActivity.java index 0aad2dc2ca..b108e4726c 100644 --- a/app/src/main/java/com/gh/base/BaseDetailActivity.java +++ b/app/src/main/java/com/gh/base/BaseDetailActivity.java @@ -2,7 +2,6 @@ package com.gh.base; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; @@ -61,8 +60,6 @@ public abstract class BaseDetailActivity extends BaseActivity implements View.On protected String downloadAddWord; protected String downloadOffText; - protected Handler handler = new Handler(); - private DataWatcher dataWatcher = new DataWatcher() { @Override public void onDataChanged(DownloadEntity downloadEntity) { diff --git a/app/src/main/java/com/gh/base/BaseFragment.java b/app/src/main/java/com/gh/base/BaseFragment.java index b07c78f62e..dfe581bc14 100644 --- a/app/src/main/java/com/gh/base/BaseFragment.java +++ b/app/src/main/java/com/gh/base/BaseFragment.java @@ -1,7 +1,6 @@ package com.gh.base; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -21,8 +20,6 @@ public class BaseFragment extends Fragment implements OnCallBackListener { protected View view; - protected Handler handler = new Handler(); - protected boolean isEverpause; protected void init(int layout) { diff --git a/app/src/main/java/com/gh/base/HomeFragment.java b/app/src/main/java/com/gh/base/HomeFragment.java index 85f040c6d1..f30571b47b 100644 --- a/app/src/main/java/com/gh/base/HomeFragment.java +++ b/app/src/main/java/com/gh/base/HomeFragment.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -42,8 +41,6 @@ public class HomeFragment extends Fragment implements View.OnClickListener { protected View view; - protected Handler handler = new Handler(); - private TextView downloadHint; private TextView searchHint; diff --git a/app/src/main/java/com/gh/common/util/Installation.java b/app/src/main/java/com/gh/common/util/Installation.java index 49c62f05d2..73db2a30e4 100644 --- a/app/src/main/java/com/gh/common/util/Installation.java +++ b/app/src/main/java/com/gh/common/util/Installation.java @@ -18,7 +18,7 @@ public class Installation { private static String sID = null; public synchronized static String getUUID(Context context) { - String imei = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId(); + String imei = ((TelephonyManager) context.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId(); if (!TextUtils.isEmpty(imei)) { return imei; } diff --git a/app/src/main/java/com/gh/common/util/MessageShareUtils.java b/app/src/main/java/com/gh/common/util/MessageShareUtils.java index 4514a8193c..c4e41ea0fa 100644 --- a/app/src/main/java/com/gh/common/util/MessageShareUtils.java +++ b/app/src/main/java/com/gh/common/util/MessageShareUtils.java @@ -12,7 +12,6 @@ import android.graphics.Matrix; import android.net.Uri; import android.os.Bundle; import android.os.Environment; -import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.Gravity; @@ -60,8 +59,6 @@ public class MessageShareUtils { private static IWXAPI api; private static Tencent mTencent; - private Handler handler; - private int[] arrLogo = {R.drawable.share_wechat_logo, R.drawable.share_wechatmoments_logo, R.drawable.share_qq_logo , R.drawable.share_qzone_logo, R.drawable.share_save}; private String[] arrLabel = {"微信好友", "朋友圈", "QQ好友", "QQ空间", "保存"}; diff --git a/app/src/main/java/com/gh/common/util/PackageUtils.java b/app/src/main/java/com/gh/common/util/PackageUtils.java index 415c7a673c..4a693c3abf 100644 --- a/app/src/main/java/com/gh/common/util/PackageUtils.java +++ b/app/src/main/java/com/gh/common/util/PackageUtils.java @@ -30,7 +30,7 @@ public class PackageUtils { * 根据路径,获取apk的包名 */ public static String getPackageNameByPath(Context context, String path) { - PackageManager packageManager = context.getPackageManager(); + PackageManager packageManager = context.getApplicationContext().getPackageManager(); PackageInfo info = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES); if (info != null) { @@ -45,7 +45,7 @@ public class PackageUtils { */ public static Object getMetaData(Context context, String packageName, String name) { try { - Bundle metaDate = context.getPackageManager().getApplicationInfo( + Bundle metaDate = context.getApplicationContext().getPackageManager().getApplicationInfo( packageName, PackageManager.GET_META_DATA).metaData; if (metaDate != null) { return metaDate.get(name); @@ -61,7 +61,7 @@ public class PackageUtils { */ public static String getApkSignatureByPackageName(Context context, String packageName) { try { - PackageInfo packageInfo = context.getPackageManager() + PackageInfo packageInfo = context.getApplicationContext().getPackageManager() .getPackageInfo(packageName, PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; return parseSignature(signatures[0].toByteArray())[0]; @@ -139,7 +139,7 @@ public class PackageUtils { */ public static long getInstalledTime(Context context, String packageName) { try { - PackageInfo packageInfo = context.getPackageManager() + PackageInfo packageInfo = context.getApplicationContext().getPackageManager() .getPackageInfo(packageName, 0); return packageInfo.firstInstallTime; } catch (NameNotFoundException e) { @@ -171,7 +171,7 @@ public class PackageUtils { */ public static String getVersionName(Context context) { try { - PackageInfo pkgInfo = context.getPackageManager().getPackageInfo( + PackageInfo pkgInfo = context.getApplicationContext().getPackageManager().getPackageInfo( context.getPackageName(), 0); return pkgInfo.versionName; } catch (NameNotFoundException e) { @@ -185,7 +185,7 @@ public class PackageUtils { */ public static String getVersionCode(Context context) { try { - PackageInfo pkgInfo = context.getPackageManager().getPackageInfo( + PackageInfo pkgInfo = context.getApplicationContext().getPackageManager().getPackageInfo( context.getPackageName(), 0); return String.valueOf(pkgInfo.versionCode); } catch (NameNotFoundException e) { @@ -199,7 +199,7 @@ public class PackageUtils { */ public static String getVersionByPackage(Context context, String packageName) { try { - return context.getPackageManager().getPackageInfo(packageName, + return context.getApplicationContext().getPackageManager().getPackageInfo(packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionName; } catch (NameNotFoundException e) { e.printStackTrace(); @@ -212,7 +212,7 @@ public class PackageUtils { */ public static ArrayList getAllPackageName(Context context) { ArrayList list = new ArrayList<>(); - List packageInfos = context.getPackageManager().getInstalledPackages(0); + List packageInfos = context.getApplicationContext().getPackageManager().getInstalledPackages(0); for (PackageInfo packageInfo : packageInfos) { if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { if (!context.getPackageName().equals(packageInfo.packageName)) { @@ -229,7 +229,7 @@ public class PackageUtils { public static void launchApplicationByPackageName(Context context, String packageName) { try { - Intent intent = context.getPackageManager().getLaunchIntentForPackage( + Intent intent = context.getApplicationContext().getPackageManager().getLaunchIntentForPackage( packageName); if (intent != null) { context.startActivity(intent); @@ -248,7 +248,7 @@ public class PackageUtils { public static String getNameByPackageName(Context context, String packageName) { try { - PackageManager pm = context.getPackageManager(); + PackageManager pm = context.getApplicationContext().getPackageManager(); ApplicationInfo applicationInfo = pm.getApplicationInfo( packageName, 0); return applicationInfo.loadLabel(pm).toString(); @@ -262,7 +262,7 @@ public class PackageUtils { * 根据包名,判断是否已安装该游戏 */ public static boolean isInstalled(Context context, String packageName) { - Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); + Intent intent = context.getApplicationContext().getPackageManager().getLaunchIntentForPackage(packageName); return intent != null; } diff --git a/app/src/main/java/com/gh/download/DownloadService.java b/app/src/main/java/com/gh/download/DownloadService.java index c5166cd6f2..df0e5e7801 100644 --- a/app/src/main/java/com/gh/download/DownloadService.java +++ b/app/src/main/java/com/gh/download/DownloadService.java @@ -213,4 +213,9 @@ public class DownloadService extends Service { Utils.log(DownloadService.class.getSimpleName(), "removeAndCheckNext==>" + downloadEntity); } + @Override + public void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } } diff --git a/app/src/main/java/com/gh/gamecenter/AboutActivity.java b/app/src/main/java/com/gh/gamecenter/AboutActivity.java index 7c7d6a9045..348b682609 100644 --- a/app/src/main/java/com/gh/gamecenter/AboutActivity.java +++ b/app/src/main/java/com/gh/gamecenter/AboutActivity.java @@ -63,4 +63,10 @@ public class AboutActivity extends BaseActivity { startActivity(intent); } } + + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } } diff --git a/app/src/main/java/com/gh/gamecenter/ChooseReceiverActivity.java b/app/src/main/java/com/gh/gamecenter/ChooseReceiverActivity.java index 56307306ce..702aa50d42 100644 --- a/app/src/main/java/com/gh/gamecenter/ChooseReceiverActivity.java +++ b/app/src/main/java/com/gh/gamecenter/ChooseReceiverActivity.java @@ -198,8 +198,8 @@ public class ChooseReceiverActivity extends BaseActivity implements View.OnClick isStopScan = false; isDestroy = false; - if (HotspotManager.isApOn(this)) { - HotspotManager.disableAp(this); + if (HotspotManager.isApOn(getApplicationContext())) { + HotspotManager.disableAp(getApplicationContext()); } if(!WifiMgr.getInstance(this).isWifiEnable()) { //wifi未打开的情况 @@ -597,6 +597,7 @@ public class ChooseReceiverActivity extends BaseActivity implements View.OnClick super.onDestroy(); isDestroy = true; isStopScan = true; + handler.removeCallbacksAndMessages(null); } /** diff --git a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java index e1c6cb1c6d..b1ee6c60cd 100644 --- a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java +++ b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java @@ -217,5 +217,7 @@ public class CropImageActivity extends BaseActivity { if (reference != null) { reference.get().recycle(); } + + handler.removeCallbacksAndMessages(null); } } diff --git a/app/src/main/java/com/gh/gamecenter/DownloadManagerActivity.java b/app/src/main/java/com/gh/gamecenter/DownloadManagerActivity.java index adc3526236..16bcc103eb 100644 --- a/app/src/main/java/com/gh/gamecenter/DownloadManagerActivity.java +++ b/app/src/main/java/com/gh/gamecenter/DownloadManagerActivity.java @@ -119,6 +119,12 @@ public class DownloadManagerActivity extends BaseFragmentActivity implements downloadmanager_viewPager.setCurrentItem(currentItem); } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } + @Override public void onPageSelected(int position) { if (position == 1) { diff --git a/app/src/main/java/com/gh/gamecenter/FileReceiverActivity.java b/app/src/main/java/com/gh/gamecenter/FileReceiverActivity.java index 999cb22fe6..ba9a60366e 100644 --- a/app/src/main/java/com/gh/gamecenter/FileReceiverActivity.java +++ b/app/src/main/java/com/gh/gamecenter/FileReceiverActivity.java @@ -153,8 +153,6 @@ public class FileReceiverActivity extends BaseActivity implements FileReceiverAd if (mCurFileInfo == null) { return -100; } - Utils.log("===============" + fileInfo.getFileTag() + "====" + mCurFileInfo.getFileTag()); - Utils.log("===============" + fileInfo.getName() + "====" + mCurFileInfo.getName()); if (fileInfo.getFileTag().equals(mCurFileInfo.getFileTag())) { return i; } @@ -544,6 +542,7 @@ public class FileReceiverActivity extends BaseActivity implements FileReceiverAd protected void onDestroy() { super.onDestroy(); isDestroy = true; + handler.removeCallbacksAndMessages(null); } @OnClick({R.id.actionbar_rl_back, R.id.sender_keep_send, R.id.sender_back}) @@ -578,7 +577,7 @@ public class FileReceiverActivity extends BaseActivity implements FileReceiverAd } //关闭热点 - HotspotManager.initUserAp(this); + HotspotManager.initUserAp(getApplicationContext()); if (isOpenWifi) { WifiMgr.getInstance(this).openWifi(); diff --git a/app/src/main/java/com/gh/gamecenter/FileSenderActivity.java b/app/src/main/java/com/gh/gamecenter/FileSenderActivity.java index a15b216a53..3d549189d9 100644 --- a/app/src/main/java/com/gh/gamecenter/FileSenderActivity.java +++ b/app/src/main/java/com/gh/gamecenter/FileSenderActivity.java @@ -404,6 +404,7 @@ public class FileSenderActivity extends BaseActivity implements FileSenderAdapte protected void onDestroy() { super.onDestroy(); isDestroy = true; + handler.removeCallbacksAndMessages(null); } @OnClick({R.id.actionbar_rl_back, R.id.sender_keep_send, R.id.sender_back}) diff --git a/app/src/main/java/com/gh/gamecenter/LibaoDetailActivity.java b/app/src/main/java/com/gh/gamecenter/LibaoDetailActivity.java index b39cdef007..c81ff3f68f 100644 --- a/app/src/main/java/com/gh/gamecenter/LibaoDetailActivity.java +++ b/app/src/main/java/com/gh/gamecenter/LibaoDetailActivity.java @@ -35,7 +35,7 @@ public class LibaoDetailActivity extends BaseDetailActivity implements LibaoDeta private boolean mIsScroll; - Handler handler = new Handler(){ + private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); @@ -96,8 +96,6 @@ public class LibaoDetailActivity extends BaseDetailActivity implements LibaoDeta } } - handler.sendEmptyMessageDelayed(0, 5000); - reuse_no_connection.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/app/src/main/java/com/gh/gamecenter/MainActivity.java b/app/src/main/java/com/gh/gamecenter/MainActivity.java index a2e8e8ee77..8dbeb3401a 100644 --- a/app/src/main/java/com/gh/gamecenter/MainActivity.java +++ b/app/src/main/java/com/gh/gamecenter/MainActivity.java @@ -1424,6 +1424,8 @@ public class MainActivity extends BaseFragmentActivity implements OnClickListene if (sp.getBoolean("changeDefaultIcon", false) && sp.getInt("default_user_icon", 0) != 0) { postDefaultIcon(true, sp.getInt("default_user_icon", 0)); } + + handler.removeCallbacksAndMessages(null); } // 提交默认头像 diff --git a/app/src/main/java/com/gh/gamecenter/NewsDetailActivity.java b/app/src/main/java/com/gh/gamecenter/NewsDetailActivity.java index dc2bd656b4..f32e6889b2 100644 --- a/app/src/main/java/com/gh/gamecenter/NewsDetailActivity.java +++ b/app/src/main/java/com/gh/gamecenter/NewsDetailActivity.java @@ -243,7 +243,13 @@ public class NewsDetailActivity extends BaseActivity implements OnClickListener }); } -// @Override + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } + + // @Override // protected void onActivityResult(int requestCode, int resultCode, Intent data) { // super.onActivityResult(requestCode, resultCode, data); // // 刷新评论数 diff --git a/app/src/main/java/com/gh/gamecenter/NewsSearchActivity.java b/app/src/main/java/com/gh/gamecenter/NewsSearchActivity.java index d040f4134b..acb0218877 100644 --- a/app/src/main/java/com/gh/gamecenter/NewsSearchActivity.java +++ b/app/src/main/java/com/gh/gamecenter/NewsSearchActivity.java @@ -158,6 +158,12 @@ public class NewsSearchActivity extends BaseActivity { } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } + private void loadNewsData(final int page) { // DataUtils.onEvent(this, "游戏新闻搜索", searchKey); // DataCollectionUtils.uploadSearch(this, searchKey, "游戏新闻搜索"); diff --git a/app/src/main/java/com/gh/gamecenter/ReceiverWaitingActivity.java b/app/src/main/java/com/gh/gamecenter/ReceiverWaitingActivity.java index aedc8fd225..28fee291bc 100644 --- a/app/src/main/java/com/gh/gamecenter/ReceiverWaitingActivity.java +++ b/app/src/main/java/com/gh/gamecenter/ReceiverWaitingActivity.java @@ -160,11 +160,11 @@ public class ReceiverWaitingActivity extends BaseActivity { mFileInfos = new ArrayList<>(); - HotspotManager.initApData(this); // 记录原始热点信息 + HotspotManager.initApData(getApplicationContext()); // 记录原始热点信息 //初始化热点 - if(HotspotManager.isApOn(this)){ - HotspotManager.disableAp(this); + if(HotspotManager.isApOn(getApplicationContext())){ + HotspotManager.disableAp(getApplicationContext()); } mWifiAPBroadcastReceiver = new WifiAPBroadcastReceiver() { @@ -188,7 +188,7 @@ public class ReceiverWaitingActivity extends BaseActivity { IntentFilter filter = new IntentFilter(WifiAPBroadcastReceiver.ACTION_WIFI_AP_STATE_CHANGED); registerReceiver(mWifiAPBroadcastReceiver, filter); - HotspotManager.isApOn(this); + HotspotManager.isApOn(getApplicationContext()); mySsid = sp.getString("hotspotName", null); if (TextUtils.isEmpty(mySsid)) { @@ -211,7 +211,7 @@ public class ReceiverWaitingActivity extends BaseActivity { } mUserIcon.setImageURI(UserIconUtils.getUserIcon(sp.getInt("default_user_icon", 1))); - HotspotManager.configApState(this, mySsid); // change Ap state :boolean + HotspotManager.configApState(getApplicationContext(), mySsid); // change Ap state :boolean findViewById(R.id.actionbar_rl_back).setOnClickListener( new View.OnClickListener() { @@ -219,7 +219,7 @@ public class ReceiverWaitingActivity extends BaseActivity { public void onClick(View v) { colsePage(); //关闭热点 - HotspotManager.initUserAp(ReceiverWaitingActivity.this); + HotspotManager.initUserAp(getApplicationContext()); if (isOpenWifi) { WifiMgr.getInstance(ReceiverWaitingActivity.this).openWifi(); @@ -373,7 +373,7 @@ public class ReceiverWaitingActivity extends BaseActivity { colsePage(); //关闭热点 - HotspotManager.initUserAp(ReceiverWaitingActivity.this); + HotspotManager.initUserAp(getApplicationContext()); if (isOpenWifi) { WifiMgr.getInstance(this).openWifi(); @@ -406,6 +406,7 @@ public class ReceiverWaitingActivity extends BaseActivity { protected void onDestroy() { super.onDestroy(); isDestroy = true; + mHandler.removeCallbacksAndMessages(null); } diff --git a/app/src/main/java/com/gh/gamecenter/SearchActivity.java b/app/src/main/java/com/gh/gamecenter/SearchActivity.java index 1821723dce..877915850f 100644 --- a/app/src/main/java/com/gh/gamecenter/SearchActivity.java +++ b/app/src/main/java/com/gh/gamecenter/SearchActivity.java @@ -24,7 +24,6 @@ import android.widget.TextView.OnEditorActionListener; import com.gh.base.BaseFragmentActivity; import com.gh.common.constant.Config; import com.gh.common.util.DataCollectionUtils; -import com.gh.common.util.DataUtils; import com.gh.common.util.DisplayUtils; import com.gh.gamecenter.db.SearchHistoryDao; import com.gh.gamecenter.eventbus.EBSearch; @@ -114,6 +113,12 @@ public class SearchActivity extends BaseFragmentActivity { } } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } + public void setActionBarLayout() { int actionbar_height = getSharedPreferences(Config.PREFERENCE, Context.MODE_PRIVATE).getInt("actionbar_height", diff --git a/app/src/main/java/com/gh/gamecenter/SettingActivity.java b/app/src/main/java/com/gh/gamecenter/SettingActivity.java index 8a92be828d..c285b1fd9e 100644 --- a/app/src/main/java/com/gh/gamecenter/SettingActivity.java +++ b/app/src/main/java/com/gh/gamecenter/SettingActivity.java @@ -294,4 +294,9 @@ public class SettingActivity extends BaseActivity implements OnClickListener { saveCurrentSetting(); } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } } diff --git a/app/src/main/java/com/gh/gamecenter/ShareCardActivity.java b/app/src/main/java/com/gh/gamecenter/ShareCardActivity.java index aff6c5e1b1..2612e655ed 100644 --- a/app/src/main/java/com/gh/gamecenter/ShareCardActivity.java +++ b/app/src/main/java/com/gh/gamecenter/ShareCardActivity.java @@ -39,7 +39,7 @@ public class ShareCardActivity extends BaseActivity { @BindView(R.id.reuse_actionbar) RelativeLayout mActionbar; @BindView(R.id.sharecard_bottom) LinearLayout mShareBottomLl; - Handler handler = new Handler(); + private Handler handler = new Handler(); String gameName; String gameIconUrl; @@ -102,6 +102,12 @@ public class ShareCardActivity extends BaseActivity { } } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } + private void createQrCode() { new Thread(new Runnable() { @Override diff --git a/app/src/main/java/com/gh/gamecenter/ShareGhWfifActivity.java b/app/src/main/java/com/gh/gamecenter/ShareGhWfifActivity.java index 8185e06091..bd87f0ade6 100644 --- a/app/src/main/java/com/gh/gamecenter/ShareGhWfifActivity.java +++ b/app/src/main/java/com/gh/gamecenter/ShareGhWfifActivity.java @@ -93,7 +93,7 @@ public class ShareGhWfifActivity extends BaseActivity { WifiMgr.getInstance(this).closeWifi(); } - HotspotManager.initApData(this); // 记录原始热点信息 + HotspotManager.initApData(getApplicationContext()); // 记录原始热点信息 initHotSpotView(0); initHotSpot(); @@ -118,7 +118,7 @@ public class ShareGhWfifActivity extends BaseActivity { IntentFilter filter = new IntentFilter(WifiAPBroadcastReceiver.ACTION_WIFI_AP_STATE_CHANGED); registerReceiver(mWifiAPBroadcastReceiver, filter); - HotspotManager.isApOn(this); + HotspotManager.isApOn(getApplicationContext()); mySsid = sp.getString("hotspotName", null); if (TextUtils.isEmpty(mySsid)) { @@ -139,7 +139,7 @@ public class ShareGhWfifActivity extends BaseActivity { mySsid = mySsid + default_user_icon; sp.edit().putString("hotspotName", mySsid).apply(); } - HotspotManager.configApState(this, mySsid); // change Ap state :boolean + HotspotManager.configApState(getApplicationContext(), mySsid); // change Ap state :boolean } /** @@ -335,7 +335,7 @@ public class ShareGhWfifActivity extends BaseActivity { } } - HotspotManager.initUserAp(ShareGhWfifActivity.this); + HotspotManager.initUserAp(getApplicationContext()); if (isOpenWifi) { WifiMgr.getInstance(ShareGhWfifActivity.this).openWifi(); } diff --git a/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.java b/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.java index b425c18f9a..4af20f01f3 100644 --- a/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.java +++ b/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.java @@ -331,4 +331,9 @@ public class SplashScreenActivity extends BaseActivity { finish(); } + @Override + protected void onDestroy() { + super.onDestroy(); + handler.removeCallbacksAndMessages(null); + } } diff --git a/app/src/main/java/com/gh/gamecenter/ViewImageActivity.java b/app/src/main/java/com/gh/gamecenter/ViewImageActivity.java index 8a62797405..d59c9f617b 100644 --- a/app/src/main/java/com/gh/gamecenter/ViewImageActivity.java +++ b/app/src/main/java/com/gh/gamecenter/ViewImageActivity.java @@ -432,5 +432,6 @@ public class ViewImageActivity extends BaseActivity implements OnPageChangeListe protected void onDestroy() { super.onDestroy(); viewimage_vp_show.onDestory(); // 注销EventBus + handler.removeCallbacksAndMessages(null); } } diff --git a/app/src/main/java/com/gh/gamecenter/VoteActivity.java b/app/src/main/java/com/gh/gamecenter/VoteActivity.java index a61392b7ec..c273926a08 100644 --- a/app/src/main/java/com/gh/gamecenter/VoteActivity.java +++ b/app/src/main/java/com/gh/gamecenter/VoteActivity.java @@ -146,7 +146,7 @@ public class VoteActivity extends BaseActivity { dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { - InputMethodManager imm = (InputMethodManager) VoteActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(input.getWindowToken(), 0); } }); @@ -163,7 +163,7 @@ public class VoteActivity extends BaseActivity { } catch (InterruptedException e) { e.printStackTrace(); } - InputMethodManager imm = (InputMethodManager) VoteActivity.this + InputMethodManager imm = (InputMethodManager) getApplicationContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(input, InputMethodManager.SHOW_FORCED); } diff --git a/app/src/main/java/com/gh/gamecenter/adapter/CleanApkAdapter.java b/app/src/main/java/com/gh/gamecenter/adapter/CleanApkAdapter.java index b6b95c4a2c..a71830e86c 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/CleanApkAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/adapter/CleanApkAdapter.java @@ -53,14 +53,12 @@ public class CleanApkAdapter extends RecyclerView.Adapter id = dao.queryForAll(); - for (LibaoInfo libaoInfo : id) { - if (libaoId.equals(libaoInfo.getId())) { - Utils.log(libaoId + "======"+ libaoInfo.getId() +"====" + libaoInfo.getStatus()); - } - } - List infos = dao.queryBuilder().where().eq("id", libaoId).and().gt("status", "tao").query(); + List infos = dao.queryBuilder().where().eq("libaoId", libaoId).and().gt("status", "tao").query(); if (infos.size() > 0) { return true; } else { diff --git a/app/src/main/java/com/gh/gamecenter/db/info/LibaoInfo.java b/app/src/main/java/com/gh/gamecenter/db/info/LibaoInfo.java index 3350159c7f..3705bae2f6 100644 --- a/app/src/main/java/com/gh/gamecenter/db/info/LibaoInfo.java +++ b/app/src/main/java/com/gh/gamecenter/db/info/LibaoInfo.java @@ -16,7 +16,7 @@ import java.util.UUID; @DatabaseTable(tableName = "tb_libao") public class LibaoInfo implements Serializable { - @DatabaseField(id = true, columnName = "id") + @DatabaseField(id = true, columnName = "id") // 这不是礼包ID 是随机生成不重复的 private String id; @DatabaseField(columnName = "libaoId") diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.java b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.java index 9f8fc2fcd6..d7bffa7dab 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.java +++ b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.java @@ -53,6 +53,8 @@ public class GameEntity { private boolean libaoExists; private boolean active; + @SerializedName("server_type") + private String serverType; @SerializedName("server") private KaiFuServerEntity serverEntity; @@ -220,6 +222,14 @@ public class GameEntity { this.active = active; } + public String getServerType() { + return serverType; + } + + public void setServerType(String serverType) { + this.serverType = serverType; + } + public KaiFuServerEntity getServerEntity() { return serverEntity; } diff --git a/app/src/main/java/com/gh/gamecenter/entity/LibaoEntity.java b/app/src/main/java/com/gh/gamecenter/entity/LibaoEntity.java index c67d49288f..dcb5b2ca09 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/LibaoEntity.java +++ b/app/src/main/java/com/gh/gamecenter/entity/LibaoEntity.java @@ -200,6 +200,7 @@ public class LibaoEntity { libaoEntity.setCode(code); libaoEntity.setAvailable(libaoStatusEntity.getAvailable()); libaoEntity.setTotal(libaoStatusEntity.getTotal()); + libaoEntity.setBeforeStatus(libaoStatusEntity.getBeforeStatus()); return libaoEntity; } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragment.java b/app/src/main/java/com/gh/gamecenter/game/GameFragment.java index 4c8474542c..ac9dc7b70d 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragment.java +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragment.java @@ -83,7 +83,7 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR fm_game_rv_list.setVisibility(View.VISIBLE); fm_game_pb_loading.setVisibility(View.VISIBLE); reuse_no_connection.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } }); @@ -106,7 +106,6 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR } }); - handler.sendEmptyMessage(0); } @@ -156,7 +155,7 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR } } } else if ("plugin".equals(status.getStatus())) { - handler.postDelayed(initPluginRunnable, 100); + view.postDelayed(initPluginRunnable, 100); } } @@ -164,7 +163,7 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR @Override public void run() { if (adapter.isInitPlugin()) { - handler.postDelayed(initPluginRunnable, 100); + view.postDelayed(initPluginRunnable, 100); } else { adapter.initPlugin(); } @@ -235,7 +234,7 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR fm_game_rv_list.setVisibility(View.VISIBLE); fm_game_pb_loading.setVisibility(View.GONE); reuse_no_connection.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } else if (adapter.isNetworkError()) { adapter.setNetworkError(false); adapter.notifyItemChanged(adapter.getItemCount() - 1); @@ -303,7 +302,7 @@ public class GameFragment extends HomeFragment implements SwipeRefreshLayout.OnR @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.java index 71d1a23862..3d4afc06b2 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.java @@ -97,8 +97,6 @@ class GameFragmentAdapter extends RecyclerView.Adapter private int itemCount; private int pluginSize; - private int cardMargin; - private boolean isNetworkError; private boolean isSlideError; private boolean isListError; @@ -140,8 +138,6 @@ class GameFragmentAdapter extends RecyclerView.Adapter topHeight = (int) (outMetrics.widthPixels / 16f * 10.5f); - cardMargin = (int) context.getResources().getDimension(R.dimen.cardview_margin); - initSubjectDigest(true); } @@ -758,6 +754,19 @@ class GameFragmentAdapter extends RecyclerView.Adapter holder.gameOrder.setVisibility(View.GONE); } + String serverType = gameEntity.getServerType(); + if (TextUtils.isEmpty(serverType)) { + holder.gameServerType.setVisibility(View.GONE); + } else { + holder.gameServerType.setVisibility(View.VISIBLE); + holder.gameServerType.setText(serverType); + if ("删测".equals(serverType) || "内测".equals(serverType)) { + holder.gameServerType.setBackgroundResource(R.drawable.textview_cancel_up); + } else { + holder.gameServerType.setBackgroundResource(R.drawable.textview_orange_up); + } + } + ImageUtils.display(holder.gameThumb, gameEntity.getIcon()); holder.gameName.setText(gameEntity.getName()); if (gameEntity.getApk() == null || gameEntity.getApk().isEmpty()) { @@ -850,6 +859,19 @@ class GameFragmentAdapter extends RecyclerView.Adapter holder.gameOrder.setVisibility(View.GONE); } + String serverType = gameEntity.getServerType(); + if (TextUtils.isEmpty(serverType)) { + holder.gameServerType.setVisibility(View.GONE); + } else { + holder.gameServerType.setVisibility(View.VISIBLE); + holder.gameServerType.setText(serverType); + if ("删测".equals(serverType) || "内测".equals(serverType)) { + holder.gameServerType.setBackgroundResource(R.drawable.textview_cancel_up); + } else { + holder.gameServerType.setBackgroundResource(R.drawable.textview_orange_up); + } + } + ImageUtils.display(holder.gameThumb, gameEntity.getIcon()); if (gameEntity.isPluggable()) { holder.gameName.setText(String.format("%s - %s", gameEntity.getName(), diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/FuLiAdapter.java b/app/src/main/java/com/gh/gamecenter/gamedetail/FuLiAdapter.java index 31a0af5425..6ebe19cb30 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/FuLiAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/FuLiAdapter.java @@ -22,6 +22,7 @@ import com.gh.common.util.DisplayUtils; import com.gh.common.util.ImageUtils; import com.gh.common.util.NewsUtils; import com.gh.common.util.QQUtils; +import com.gh.common.util.Utils; import com.gh.common.view.GridDivider; import com.gh.common.view.MarqueeView; import com.gh.gamecenter.R; @@ -122,12 +123,23 @@ public class FuLiAdapter extends RecyclerView.Adapter{ public Object call(List list) { LibaoDao libaoDao = new LibaoDao(mContext); for (LibaoInfo libaoInfo : libaoDao.getAll()) { - for (int i = 0; i < list.size(); i++) { - if (libaoInfo.getLibaoId().equals(list.get(i).getId())) { + for (LibaoStatusEntity libaoStatusEntity : list) { + + if (TextUtils.isEmpty(libaoInfo.getLibaoId()) || TextUtils.isEmpty(libaoStatusEntity.getId())) { + continue; + } + + if (TextUtils.isEmpty(libaoStatusEntity.getBeforeStatus())) { + Utils.log("==========zzzz" + libaoStatusEntity.getStatus()); + libaoStatusEntity.setBeforeStatus(libaoStatusEntity.getStatus()); + } + + + if (libaoInfo.getLibaoId().equals(libaoStatusEntity.getId())) { if ("ling".equals(libaoInfo.getStatus()) || "linged".equals(libaoInfo.getStatus())) { - list.get(i).setStatus("linged"); + libaoStatusEntity.setStatus("linged"); } else { - list.get(i).setStatus("taoed"); + libaoStatusEntity.setStatus("taoed"); } } } @@ -145,6 +157,16 @@ public class FuLiAdapter extends RecyclerView.Adapter{ libaoEntity.setStatus(libaoStatusEntity.getStatus()); libaoEntity.setAvailable(libaoStatusEntity.getAvailable()); libaoEntity.setTotal(libaoStatusEntity.getTotal()); + + String beforeStatus = libaoStatusEntity.getBeforeStatus(); + if (libaoEntity.isReuse() && libaoEntity.getRemainCount() > 0 + && libaoDao.isCanLing(libaoEntity.getId(), mContext) + && ("ling".equals(beforeStatus) || "tao".equals(beforeStatus))) { // 判断是否可以重复领取 + libaoEntity.setStatus(libaoStatusEntity.getBeforeStatus()); + } else { + libaoEntity.setStatus(libaoStatusEntity.getStatus()); + } + libaoEntity.setBeforeStatus(libaoStatusEntity.getBeforeStatus()); } } } @@ -250,13 +272,16 @@ public class FuLiAdapter extends RecyclerView.Adapter{ int height = fuLiFragment.getHeight(); int i = height - mRecyclerView.getHeight() - DisplayUtils.dip2px(mContext, 75); - if (offsetH == 0 && i < 0 || i<0 &&offsetH < i) { - offsetH = i; - } + Utils.log((mRecyclerView.getHeight() - DisplayUtils.dip2px(mContext, 75)) + "====="+ i +"=====" + height); + if (height != 0) { + if (offsetH == 0 && i < 0 || i<0 && offsetH < i) { + offsetH = i; + } - if (-offsetH > DisplayUtils.dip2px(mContext, 37)) { - ViewGroup.LayoutParams layoutParams = viewHolder.itemView.getLayoutParams(); - layoutParams.height = -offsetH; + if (-offsetH > DisplayUtils.dip2px(mContext, 37)) { + ViewGroup.LayoutParams layoutParams = viewHolder.itemView.getLayoutParams(); + layoutParams.height = -offsetH; + } } viewHolder.itemView.setOnClickListener(new View.OnClickListener() { diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailKaiFuAdapter.java b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailKaiFuAdapter.java index 50507f482c..4806506497 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailKaiFuAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailKaiFuAdapter.java @@ -16,8 +16,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.gh.common.util.DisplayUtils; -import com.gh.gamecenter.SuggestionActivity; +import com.gh.common.util.Utils; import com.gh.gamecenter.R; +import com.gh.gamecenter.SuggestionActivity; import com.gh.gamecenter.entity.CalendarEntity; import com.gh.gamecenter.entity.KaiFuServerEntity; import com.gh.gamecenter.eventbus.EBReuse; @@ -58,6 +59,7 @@ public class GameDetailKaiFuAdapter extends RecyclerView.Adapter statusList = (List) response; for (LibaoInfo libaoInfo : mLibaoDao.getAll()) { - for (int i = 0; i < statusList.size(); i++) { - LibaoStatusEntity libaoStatusEntity = statusList.get(i); + for (LibaoStatusEntity libaoStatusEntity : statusList) { + if (TextUtils.isEmpty(libaoInfo.getLibaoId()) || TextUtils.isEmpty(libaoStatusEntity.getId())) { + continue; + } + if (TextUtils.isEmpty(libaoStatusEntity.getBeforeStatus())) { + Utils.log("==========zzzz" + libaoStatusEntity.getStatus()); libaoStatusEntity.setBeforeStatus(libaoStatusEntity.getStatus()); } - if (TextUtils.isEmpty(libaoInfo.getLibaoId()) || TextUtils.isEmpty(libaoStatusEntity.getId())) { - continue; - } - String curStatus = libaoStatusEntity.getStatus(); - if (libaoInfo.getLibaoId().equals(libaoStatusEntity.getId())) { - if (false && mLibaoDao.isCanLing(libaoInfo.getLibaoId(), mContext) - && ("ling".equals(curStatus) || "tao".equals(curStatus))) { // 重复领取 - } else if (false){ // TODO 可以重复领取但礼包已经不可用 - if (mLibaoDao.isExistTao(libaoInfo.getLibaoId())) { - libaoStatusEntity.setStatus("taoed"); - } else { - libaoStatusEntity.setStatus("linged"); - } + if (libaoInfo.getLibaoId().equals(libaoStatusEntity.getId())) { + if ("ling".equals(libaoInfo.getStatus()) || "linged".equals(libaoInfo.getStatus())) { + libaoStatusEntity.setStatus("linged"); } else { - if ("ling".equals(libaoInfo.getStatus()) || "linged".equals(libaoInfo.getStatus())) { - libaoStatusEntity.setStatus("linged"); - } else { - libaoStatusEntity.setStatus("taoed"); - } + libaoStatusEntity.setStatus("taoed"); } } } @@ -201,10 +191,18 @@ class Libao1FragmentAdapter extends RecyclerView.Adapter 0 + && mLibaoDao.isCanLing(libaoEntity.getId(), mContext) + && ("ling".equals(beforeStatus) || "tao".equals(beforeStatus))) { // 判断是否可以重复领取 + libaoEntity.setStatus(libaoStatusEntity.getBeforeStatus()); + } else { + libaoEntity.setStatus(libaoStatusEntity.getStatus()); + } + libaoEntity.setBeforeStatus(libaoStatusEntity.getBeforeStatus()); } } } diff --git a/app/src/main/java/com/gh/gamecenter/libao/Libao2Fragment.java b/app/src/main/java/com/gh/gamecenter/libao/Libao2Fragment.java index ae6b4d4fa4..a10d15e96c 100644 --- a/app/src/main/java/com/gh/gamecenter/libao/Libao2Fragment.java +++ b/app/src/main/java/com/gh/gamecenter/libao/Libao2Fragment.java @@ -92,7 +92,7 @@ public class Libao2Fragment extends BaseFragment implements SwipeRefreshLayout.O @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @OnClick(R.id.reuse_no_connection) @@ -101,7 +101,7 @@ public class Libao2Fragment extends BaseFragment implements SwipeRefreshLayout.O mRecyclerView.setVisibility(View.VISIBLE); mLoadingLayout.setVisibility(View.VISIBLE); mNoConnectionLayout.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override diff --git a/app/src/main/java/com/gh/gamecenter/libao/Libao2FragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/libao/Libao2FragmentAdapter.java index 7ccae6e37f..d0769406e2 100644 --- a/app/src/main/java/com/gh/gamecenter/libao/Libao2FragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/libao/Libao2FragmentAdapter.java @@ -266,9 +266,14 @@ class Libao2FragmentAdapter extends RecyclerView.Adapter 0 && mLibaoDao.isCanLing(libaoEntity.getId(), mContext) - && ("ling".equals(beforeStatus) || "tao".equals(beforeStatus))) { + && ("ling".equals(beforeStatus) || "tao".equals(beforeStatus))) { // 判断是否可以重复领取 libaoEntity.setStatus(libaoStatusEntity.getBeforeStatus()); } else { libaoEntity.setStatus(libaoStatusEntity.getStatus()); } + libaoEntity.setBeforeStatus(libaoStatusEntity.getBeforeStatus()); } } } diff --git a/app/src/main/java/com/gh/gamecenter/libao/Libao3Fragment.java b/app/src/main/java/com/gh/gamecenter/libao/Libao3Fragment.java index 41ffb38c4c..14be3606b4 100644 --- a/app/src/main/java/com/gh/gamecenter/libao/Libao3Fragment.java +++ b/app/src/main/java/com/gh/gamecenter/libao/Libao3Fragment.java @@ -58,7 +58,7 @@ public class Libao3Fragment extends BaseFragment implements SwipeRefreshLayout.O @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override diff --git a/app/src/main/java/com/gh/gamecenter/message/CommentFragment.java b/app/src/main/java/com/gh/gamecenter/message/CommentFragment.java index 2dc7ae770e..c721bc11de 100644 --- a/app/src/main/java/com/gh/gamecenter/message/CommentFragment.java +++ b/app/src/main/java/com/gh/gamecenter/message/CommentFragment.java @@ -97,7 +97,7 @@ public class CommentFragment extends BaseFragment implements SwipeRefreshLayout. mRecyclerview.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.VISIBLE); mNoConnection.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -119,6 +119,7 @@ public class CommentFragment extends BaseFragment implements SwipeRefreshLayout. @Override public void loadEmpty() { super.loadEmpty(); + mRefresh.setRefreshing(false); mNoneData.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.GONE); } @@ -134,6 +135,6 @@ public class CommentFragment extends BaseFragment implements SwipeRefreshLayout. @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/message/CommentFragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/message/CommentFragmentAdapter.java index ce198a8c97..b37e46ae3d 100644 --- a/app/src/main/java/com/gh/gamecenter/message/CommentFragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/message/CommentFragmentAdapter.java @@ -126,45 +126,6 @@ public class CommentFragmentAdapter extends RecyclerView.Adapter>() { -// @Override -// public Observable call(String token) { -// return null; // TODO -// } -// }) -// .subscribeOn(Schedulers.io()) -// .observeOn(Schedulers.io()) -// .subscribe(new Response(){ -// @Override -// public void onResponse(ResponseBody response) { -// super.onResponse(response); -// mReceiveIds.clear(); -// Utils.log("=======postDataReceive::onResponse" ); -// } -// -// @Override -// public void onError(Throwable e) { -// super.onError(e); -// Utils.log("=======postDataReceive::" + e.toString()); -// } -// -// @Override -// public void onFailure(HttpException e) { -// super.onFailure(e); -// if (e != null && e.code() == 401) { -// postDataReceive(false); -// } -// } -// }); -// } - @Override public int getItemViewType(int position) { if (position == getItemCount() - 1) { diff --git a/app/src/main/java/com/gh/gamecenter/message/KeFuFragment.java b/app/src/main/java/com/gh/gamecenter/message/KeFuFragment.java index 5ba3b3c9d4..7978a47cee 100644 --- a/app/src/main/java/com/gh/gamecenter/message/KeFuFragment.java +++ b/app/src/main/java/com/gh/gamecenter/message/KeFuFragment.java @@ -84,7 +84,7 @@ public class KeFuFragment extends BaseFragment implements SwipeRefreshLayout.OnR mRecyclerview.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.VISIBLE); mNoConnection.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -105,7 +105,7 @@ public class KeFuFragment extends BaseFragment implements SwipeRefreshLayout.OnR @Override public void loadEmpty() { super.loadEmpty(); - mRefresh.setRefreshing(true); + mRefresh.setRefreshing(false); mNoneData.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.GONE); } @@ -121,6 +121,6 @@ public class KeFuFragment extends BaseFragment implements SwipeRefreshLayout.OnR @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/message/MessageFragment.java b/app/src/main/java/com/gh/gamecenter/message/MessageFragment.java index 5c6064130b..c3db5f86bf 100644 --- a/app/src/main/java/com/gh/gamecenter/message/MessageFragment.java +++ b/app/src/main/java/com/gh/gamecenter/message/MessageFragment.java @@ -96,7 +96,7 @@ public class MessageFragment extends BaseFragment implements ViewPager.OnPageCha initMessageUnread(true); - handler.postDelayed(new Runnable() { + view.postDelayed(new Runnable() { @Override public void run() { EventBus.getDefault().post(new EBUISwitch("MessageFragment", mMeaasgeViewPager.getCurrentItem())); diff --git a/app/src/main/java/com/gh/gamecenter/message/NoticeFragment.java b/app/src/main/java/com/gh/gamecenter/message/NoticeFragment.java index b2cf73de54..fbc15d0f84 100644 --- a/app/src/main/java/com/gh/gamecenter/message/NoticeFragment.java +++ b/app/src/main/java/com/gh/gamecenter/message/NoticeFragment.java @@ -84,7 +84,7 @@ public class NoticeFragment extends BaseFragment implements SwipeRefreshLayout.O mRecyclerview.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.VISIBLE); mNoConnection.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -106,6 +106,7 @@ public class NoticeFragment extends BaseFragment implements SwipeRefreshLayout.O @Override public void loadEmpty() { super.loadEmpty(); + mRefresh.setRefreshing(false); mNoneData.setVisibility(View.VISIBLE); mLoadingPb.setVisibility(View.GONE); } @@ -121,6 +122,6 @@ public class NoticeFragment extends BaseFragment implements SwipeRefreshLayout.O @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/news/News1Fragment.java b/app/src/main/java/com/gh/gamecenter/news/News1Fragment.java index 9503c6f38f..7ddddcb219 100644 --- a/app/src/main/java/com/gh/gamecenter/news/News1Fragment.java +++ b/app/src/main/java/com/gh/gamecenter/news/News1Fragment.java @@ -75,7 +75,7 @@ public class News1Fragment extends BaseFragment implements SwipeRefreshLayout.On recyclerView.setVisibility(View.VISIBLE); loadingLayout.setVisibility(View.VISIBLE); noConnectionLayout.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -118,7 +118,7 @@ public class News1Fragment extends BaseFragment implements SwipeRefreshLayout.On @Override public void onRefresh() { // 刷新 - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/news/News3Fragment.java b/app/src/main/java/com/gh/gamecenter/news/News3Fragment.java index e68f93b8c3..d713009dc0 100644 --- a/app/src/main/java/com/gh/gamecenter/news/News3Fragment.java +++ b/app/src/main/java/com/gh/gamecenter/news/News3Fragment.java @@ -75,7 +75,7 @@ public class News3Fragment extends BaseFragment implements SwipeRefreshLayout.On recyclerView.setVisibility(View.VISIBLE); loadingLayout.setVisibility(View.VISIBLE); noConnectionLayout.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -118,7 +118,7 @@ public class News3Fragment extends BaseFragment implements SwipeRefreshLayout.On @Override public void onRefresh() { // 刷新 - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } } diff --git a/app/src/main/java/com/gh/gamecenter/news/News4Fragment.java b/app/src/main/java/com/gh/gamecenter/news/News4Fragment.java index d62dfeb39b..369150a4a5 100644 --- a/app/src/main/java/com/gh/gamecenter/news/News4Fragment.java +++ b/app/src/main/java/com/gh/gamecenter/news/News4Fragment.java @@ -152,7 +152,7 @@ public class News4Fragment extends BaseFragment implements SwipeRefreshLayout.On recyclerView.setVisibility(View.VISIBLE); loadingLayout.setVisibility(View.VISIBLE); noConnectionLayout.setVisibility(View.GONE); - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } @Override @@ -236,7 +236,7 @@ public class News4Fragment extends BaseFragment implements SwipeRefreshLayout.On @Override public void onRefresh() { - handler.postDelayed(runnable, 1000); + view.postDelayed(runnable, 1000); } // 推荐关注改为手机安装的游戏+光环助手 diff --git a/app/src/main/java/com/gh/gamecenter/news/News4FragmentAdapter.java b/app/src/main/java/com/gh/gamecenter/news/News4FragmentAdapter.java index 2abd4bfba8..cb49809923 100644 --- a/app/src/main/java/com/gh/gamecenter/news/News4FragmentAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/news/News4FragmentAdapter.java @@ -542,6 +542,10 @@ public class News4FragmentAdapter extends RecyclerView.Adapter + android:layout_height="wrap_content"> - + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true"> - + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + - + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + + - diff --git a/libraries/MiPush/src/main/AndroidManifest.xml b/libraries/MiPush/src/main/AndroidManifest.xml index 52eccf3aaf..32099f9d76 100644 --- a/libraries/MiPush/src/main/AndroidManifest.xml +++ b/libraries/MiPush/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - diff --git a/libraries/QQShare/src/main/AndroidManifest.xml b/libraries/QQShare/src/main/AndroidManifest.xml index 52eccf3aaf..f40d865f9d 100644 --- a/libraries/QQShare/src/main/AndroidManifest.xml +++ b/libraries/QQShare/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - diff --git a/libraries/TalkingData/src/main/AndroidManifest.xml b/libraries/TalkingData/src/main/AndroidManifest.xml index 52eccf3aaf..1a509ace72 100644 --- a/libraries/TalkingData/src/main/AndroidManifest.xml +++ b/libraries/TalkingData/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - diff --git a/libraries/WechatShare/src/main/AndroidManifest.xml b/libraries/WechatShare/src/main/AndroidManifest.xml index d169d902dd..47b603e280 100644 --- a/libraries/WechatShare/src/main/AndroidManifest.xml +++ b/libraries/WechatShare/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package = "com.gh.wechat" > diff --git a/libraries/WeiboShare/src/main/AndroidManifest.xml b/libraries/WeiboShare/src/main/AndroidManifest.xml index 52eccf3aaf..57ade52b09 100644 --- a/libraries/WeiboShare/src/main/AndroidManifest.xml +++ b/libraries/WeiboShare/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ -