1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.support.v7.widget;
17 
18 import android.support.annotation.NonNull;
19 import android.support.annotation.Nullable;
20 import android.support.annotation.VisibleForTesting;
21 import android.support.v4.util.ArrayMap;
22 import android.support.v4.util.LongSparseArray;
23 import android.support.v4.util.Pools;
24 import android.view.View;
25 
26 import static android.support.v7.widget.RecyclerView.ViewHolder;
27 import static android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
28 
29 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
30 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
31 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
32 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
33 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
34 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
35 import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_POST;
36 /**
37  * This class abstracts all tracking for Views to run animations
38  *
39  * @hide
40  */
41 class ViewInfoStore {
42 
43     private static final boolean DEBUG = false;
44 
45     /**
46      * View data records for pre-layout
47      */
48     @VisibleForTesting
49     final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
50 
51     @VisibleForTesting
52     final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
53 
54     /**
55      * Clears the state and all existing tracking data
56      */
clear()57     void clear() {
58         mLayoutHolderMap.clear();
59         mOldChangedHolders.clear();
60     }
61 
62     /**
63      * Adds the item information to the prelayout tracking
64      * @param holder The ViewHolder whose information is being saved
65      * @param info The information to save
66      */
addToPreLayout(ViewHolder holder, ItemHolderInfo info)67     void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
68         InfoRecord record = mLayoutHolderMap.get(holder);
69         if (record == null) {
70             record = InfoRecord.obtain();
71             mLayoutHolderMap.put(holder, record);
72         }
73         record.preInfo = info;
74         record.flags |= FLAG_PRE;
75     }
76 
isDisappearing(ViewHolder holder)77     boolean isDisappearing(ViewHolder holder) {
78         final InfoRecord record = mLayoutHolderMap.get(holder);
79         return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
80     }
81 
82     /**
83      * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
84      *
85      * @param vh The ViewHolder whose information is being queried
86      * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
87      */
88     @Nullable
popFromPreLayout(ViewHolder vh)89     ItemHolderInfo popFromPreLayout(ViewHolder vh) {
90         return popFromLayoutStep(vh, FLAG_PRE);
91     }
92 
93     /**
94      * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
95      *
96      * @param vh The ViewHolder whose information is being queried
97      * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
98      */
99     @Nullable
popFromPostLayout(ViewHolder vh)100     ItemHolderInfo popFromPostLayout(ViewHolder vh) {
101         return popFromLayoutStep(vh, FLAG_POST);
102     }
103 
popFromLayoutStep(ViewHolder vh, int flag)104     private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
105         int index = mLayoutHolderMap.indexOfKey(vh);
106         if (index < 0) {
107             return null;
108         }
109         final InfoRecord record = mLayoutHolderMap.valueAt(index);
110         if (record != null && (record.flags & flag) != 0) {
111             record.flags &= ~flag;
112             final ItemHolderInfo info;
113             if (flag == FLAG_PRE) {
114                 info = record.preInfo;
115             } else if (flag == FLAG_POST) {
116                 info = record.postInfo;
117             } else {
118                 throw new IllegalArgumentException("Must provide flag PRE or POST");
119             }
120             // if not pre-post flag is left, clear.
121             if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
122                 mLayoutHolderMap.removeAt(index);
123                 InfoRecord.recycle(record);
124             }
125             return info;
126         }
127         return null;
128     }
129 
130     /**
131      * Adds the given ViewHolder to the oldChangeHolders list
132      * @param key The key to identify the ViewHolder.
133      * @param holder The ViewHolder to store
134      */
addToOldChangeHolders(long key, ViewHolder holder)135     void addToOldChangeHolders(long key, ViewHolder holder) {
136         mOldChangedHolders.put(key, holder);
137     }
138 
139     /**
140      * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
141      * LayoutManager during a pre-layout pass. We distinguish them from other views that were
142      * already in the pre-layout so that ItemAnimator can choose to run a different animation for
143      * them.
144      *
145      * @param holder The ViewHolder to store
146      * @param info The information to save
147      */
addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info)148     void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
149         InfoRecord record = mLayoutHolderMap.get(holder);
150         if (record == null) {
151             record = InfoRecord.obtain();
152             mLayoutHolderMap.put(holder, record);
153         }
154         record.flags |= FLAG_APPEAR;
155         record.preInfo = info;
156     }
157 
158     /**
159      * Checks whether the given ViewHolder is in preLayout list
160      * @param viewHolder The ViewHolder to query
161      *
162      * @return True if the ViewHolder is present in preLayout, false otherwise
163      */
isInPreLayout(ViewHolder viewHolder)164     boolean isInPreLayout(ViewHolder viewHolder) {
165         final InfoRecord record = mLayoutHolderMap.get(viewHolder);
166         return record != null && (record.flags & FLAG_PRE) != 0;
167     }
168 
169     /**
170      * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
171      * null.
172      * @param key The key to be used to find the ViewHolder.
173      *
174      * @return A ViewHolder if exists or null if it does not exist.
175      */
getFromOldChangeHolders(long key)176     ViewHolder getFromOldChangeHolders(long key) {
177         return mOldChangedHolders.get(key);
178     }
179 
180     /**
181      * Adds the item information to the post layout list
182      * @param holder The ViewHolder whose information is being saved
183      * @param info The information to save
184      */
addToPostLayout(ViewHolder holder, ItemHolderInfo info)185     void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
186         InfoRecord record = mLayoutHolderMap.get(holder);
187         if (record == null) {
188             record = InfoRecord.obtain();
189             mLayoutHolderMap.put(holder, record);
190         }
191         record.postInfo = info;
192         record.flags |= FLAG_POST;
193     }
194 
195     /**
196      * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
197      * This list holds such items so that we can animate / recycle these ViewHolders properly.
198      *
199      * @param holder The ViewHolder which disappeared during a layout.
200      */
addToDisappearedInLayout(ViewHolder holder)201     void addToDisappearedInLayout(ViewHolder holder) {
202         InfoRecord record = mLayoutHolderMap.get(holder);
203         if (record == null) {
204             record = InfoRecord.obtain();
205             mLayoutHolderMap.put(holder, record);
206         }
207         record.flags |= FLAG_DISAPPEARED;
208     }
209 
210     /**
211      * Removes a ViewHolder from disappearing list.
212      * @param holder The ViewHolder to be removed from the disappearing list.
213      */
removeFromDisappearedInLayout(ViewHolder holder)214     void removeFromDisappearedInLayout(ViewHolder holder) {
215         InfoRecord record = mLayoutHolderMap.get(holder);
216         if (record == null) {
217             return;
218         }
219         record.flags &= ~FLAG_DISAPPEARED;
220     }
221 
process(ProcessCallback callback)222     void process(ProcessCallback callback) {
223         for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) {
224             final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
225             final InfoRecord record = mLayoutHolderMap.removeAt(index);
226             if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
227                 // Appeared then disappeared. Not useful for animations.
228                 callback.unused(viewHolder);
229             } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
230                 // Set as "disappeared" by the LayoutManager (addDisappearingView)
231                 if (record.preInfo == null) {
232                     // similar to appear disappear but happened between different layout passes.
233                     // this can happen when the layout manager is using auto-measure
234                     callback.unused(viewHolder);
235                 } else {
236                     callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
237                 }
238             } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
239                 // Appeared in the layout but not in the adapter (e.g. entered the viewport)
240                 callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
241             } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
242                 // Persistent in both passes. Animate persistence
243                 callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
244             } else if ((record.flags & FLAG_PRE) != 0) {
245                 // Was in pre-layout, never been added to post layout
246                 callback.processDisappeared(viewHolder, record.preInfo, null);
247             } else if ((record.flags & FLAG_POST) != 0) {
248                 // Was not in pre-layout, been added to post layout
249                 callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
250             } else if ((record.flags & FLAG_APPEAR) != 0) {
251                 // Scrap view. RecyclerView will handle removing/recycling this.
252             } else if (DEBUG) {
253                 throw new IllegalStateException("record without any reasonable flag combination:/");
254             }
255             InfoRecord.recycle(record);
256         }
257     }
258 
259     /**
260      * Removes the ViewHolder from all list
261      * @param holder The ViewHolder which we should stop tracking
262      */
removeViewHolder(ViewHolder holder)263     void removeViewHolder(ViewHolder holder) {
264         for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
265             if (holder == mOldChangedHolders.valueAt(i)) {
266                 mOldChangedHolders.removeAt(i);
267                 break;
268             }
269         }
270         final InfoRecord info = mLayoutHolderMap.remove(holder);
271         if (info != null) {
272             InfoRecord.recycle(info);
273         }
274     }
275 
onDetach()276     void onDetach() {
277         InfoRecord.drainCache();
278     }
279 
onViewDetached(ViewHolder viewHolder)280     public void onViewDetached(ViewHolder viewHolder) {
281         removeFromDisappearedInLayout(viewHolder);
282     }
283 
284     interface ProcessCallback {
processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @Nullable ItemHolderInfo postInfo)285         void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
286                 @Nullable ItemHolderInfo postInfo);
processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo, ItemHolderInfo postInfo)287         void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
288                 ItemHolderInfo postInfo);
processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo)289         void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
290                 @NonNull ItemHolderInfo postInfo);
unused(ViewHolder holder)291         void unused(ViewHolder holder);
292     }
293 
294     static class InfoRecord {
295         // disappearing list
296         static final int FLAG_DISAPPEARED = 1;
297         // appear in pre layout list
298         static final int FLAG_APPEAR = 1 << 1;
299         // pre layout, this is necessary to distinguish null item info
300         static final int FLAG_PRE = 1 << 2;
301         // post layout, this is necessary to distinguish null item info
302         static final int FLAG_POST = 1 << 3;
303         static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
304         static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
305         static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
306         int flags;
307         @Nullable ItemHolderInfo preInfo;
308         @Nullable ItemHolderInfo postInfo;
309         static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
310 
InfoRecord()311         private InfoRecord() {
312         }
313 
obtain()314         static InfoRecord obtain() {
315             InfoRecord record = sPool.acquire();
316             return record == null ? new InfoRecord() : record;
317         }
318 
recycle(InfoRecord record)319         static void recycle(InfoRecord record) {
320             record.flags = 0;
321             record.preInfo = null;
322             record.postInfo = null;
323             sPool.release(record);
324         }
325 
drainCache()326         static void drainCache() {
327             //noinspection StatementWithEmptyBody
328             while (sPool.acquire() != null);
329         }
330     }
331 }
332