blob: 6784a853d0d1976a56a78c8b78f8fa964e33ce19 [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.internal.widget;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.LongSparseArray;
import android.util.Pools;
import static com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
import static com.android.internal.widget.RecyclerView.ViewHolder;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_POST;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
import com.android.internal.annotations.VisibleForTesting;
/**
* This class abstracts all tracking for Views to run animations.
*/
class ViewInfoStore {
private static final boolean DEBUG = false;
/**
* View data records for pre-layout
*/
@VisibleForTesting
final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
@VisibleForTesting
final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
/**
* Clears the state and all existing tracking data
*/
void clear() {
mLayoutHolderMap.clear();
mOldChangedHolders.clear();
}
/**
* Adds the item information to the prelayout tracking
* @param holder The ViewHolder whose information is being saved
* @param info The information to save
*/
void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
boolean isDisappearing(ViewHolder holder) {
final InfoRecord record = mLayoutHolderMap.get(holder);
return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
}
/**
* Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
*
* @param vh The ViewHolder whose information is being queried
* @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
*/
@Nullable
ItemHolderInfo popFromPreLayout(ViewHolder vh) {
return popFromLayoutStep(vh, FLAG_PRE);
}
/**
* Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
*
* @param vh The ViewHolder whose information is being queried
* @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
*/
@Nullable
ItemHolderInfo popFromPostLayout(ViewHolder vh) {
return popFromLayoutStep(vh, FLAG_POST);
}
private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
int index = mLayoutHolderMap.indexOfKey(vh);
if (index < 0) {
return null;
}
final InfoRecord record = mLayoutHolderMap.valueAt(index);
if (record != null && (record.flags & flag) != 0) {
record.flags &= ~flag;
final ItemHolderInfo info;
if (flag == FLAG_PRE) {
info = record.preInfo;
} else if (flag == FLAG_POST) {
info = record.postInfo;
} else {
throw new IllegalArgumentException("Must provide flag PRE or POST");
}
// if not pre-post flag is left, clear.
if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
mLayoutHolderMap.removeAt(index);
InfoRecord.recycle(record);
}
return info;
}
return null;
}
/**
* Adds the given ViewHolder to the oldChangeHolders list
* @param key The key to identify the ViewHolder.
* @param holder The ViewHolder to store
*/
void addToOldChangeHolders(long key, ViewHolder holder) {
mOldChangedHolders.put(key, holder);
}
/**
* Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
* LayoutManager during a pre-layout pass. We distinguish them from other views that were
* already in the pre-layout so that ItemAnimator can choose to run a different animation for
* them.
*
* @param holder The ViewHolder to store
* @param info The information to save
*/
void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.flags |= FLAG_APPEAR;
record.preInfo = info;
}
/**
* Checks whether the given ViewHolder is in preLayout list
* @param viewHolder The ViewHolder to query
*
* @return True if the ViewHolder is present in preLayout, false otherwise
*/
boolean isInPreLayout(ViewHolder viewHolder) {
final InfoRecord record = mLayoutHolderMap.get(viewHolder);
return record != null && (record.flags & FLAG_PRE) != 0;
}
/**
* Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
* null.
* @param key The key to be used to find the ViewHolder.
*
* @return A ViewHolder if exists or null if it does not exist.
*/
ViewHolder getFromOldChangeHolders(long key) {
return mOldChangedHolders.get(key);
}
/**
* Adds the item information to the post layout list
* @param holder The ViewHolder whose information is being saved
* @param info The information to save
*/
void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.postInfo = info;
record.flags |= FLAG_POST;
}
/**
* A ViewHolder might be added by the LayoutManager just to animate its disappearance.
* This list holds such items so that we can animate / recycle these ViewHolders properly.
*
* @param holder The ViewHolder which disappeared during a layout.
*/
void addToDisappearedInLayout(ViewHolder holder) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.flags |= FLAG_DISAPPEARED;
}
/**
* Removes a ViewHolder from disappearing list.
* @param holder The ViewHolder to be removed from the disappearing list.
*/
void removeFromDisappearedInLayout(ViewHolder holder) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
return;
}
record.flags &= ~FLAG_DISAPPEARED;
}
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
final InfoRecord record = mLayoutHolderMap.removeAt(index);
if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
// Appeared then disappeared. Not useful for animations.
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
if (record.preInfo == null) {
// similar to appear disappear but happened between different layout passes.
// this can happen when the layout manager is using auto-measure
callback.unused(viewHolder);
} else {
callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
}
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
// Persistent in both passes. Animate persistence
callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_PRE) != 0) {
// Was in pre-layout, never been added to post layout
callback.processDisappeared(viewHolder, record.preInfo, null);
} else if ((record.flags & FLAG_POST) != 0) {
// Was not in pre-layout, been added to post layout
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
} else if ((record.flags & FLAG_APPEAR) != 0) {
// Scrap view. RecyclerView will handle removing/recycling this.
} else if (DEBUG) {
throw new IllegalStateException("record without any reasonable flag combination:/");
}
InfoRecord.recycle(record);
}
}
/**
* Removes the ViewHolder from all list
* @param holder The ViewHolder which we should stop tracking
*/
void removeViewHolder(ViewHolder holder) {
for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
if (holder == mOldChangedHolders.valueAt(i)) {
mOldChangedHolders.removeAt(i);
break;
}
}
final InfoRecord info = mLayoutHolderMap.remove(holder);
if (info != null) {
InfoRecord.recycle(info);
}
}
void onDetach() {
InfoRecord.drainCache();
}
public void onViewDetached(ViewHolder viewHolder) {
removeFromDisappearedInLayout(viewHolder);
}
interface ProcessCallback {
void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@Nullable ItemHolderInfo postInfo);
void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
ItemHolderInfo postInfo);
void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo);
void unused(ViewHolder holder);
}
static class InfoRecord {
// disappearing list
static final int FLAG_DISAPPEARED = 1;
// appear in pre layout list
static final int FLAG_APPEAR = 1 << 1;
// pre layout, this is necessary to distinguish null item info
static final int FLAG_PRE = 1 << 2;
// post layout, this is necessary to distinguish null item info
static final int FLAG_POST = 1 << 3;
static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
int flags;
@Nullable ItemHolderInfo preInfo;
@Nullable ItemHolderInfo postInfo;
static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
private InfoRecord() {
}
static InfoRecord obtain() {
InfoRecord record = sPool.acquire();
return record == null ? new InfoRecord() : record;
}
static void recycle(InfoRecord record) {
record.flags = 0;
record.preInfo = null;
record.postInfo = null;
sPool.release(record);
}
static void drainCache() {
//noinspection StatementWithEmptyBody
while (sPool.acquire() != null);
}
}
}