ViewInfoStore.java revision e09e0b4ea04b6b6b0ef6c62979e8abdead0bf378
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 */
16package android.support.v7.widget;
17
18import android.support.annotation.NonNull;
19import android.support.annotation.Nullable;
20import android.support.annotation.VisibleForTesting;
21import android.support.v4.util.ArrayMap;
22import android.support.v4.util.LongSparseArray;
23import android.support.v4.util.Pools;
24
25import static android.support.v7.widget.RecyclerView.ViewHolder;
26import static android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
27
28import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
29import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
30import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
31import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
32import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
33import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
34import static android.support.v7.widget.ViewInfoStore.InfoRecord.FLAG_POST;
35/**
36 * This class abstracts all tracking for Views to run animations
37 *
38 * @hide
39 */
40class ViewInfoStore {
41
42    private static final boolean DEBUG = false;
43
44    /**
45     * View data records for pre-layout
46     */
47    @VisibleForTesting
48    final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
49
50    @VisibleForTesting
51    final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
52
53    /**
54     * Clears the state and all existing tracking data
55     */
56    void clear() {
57        mLayoutHolderMap.clear();
58        mOldChangedHolders.clear();
59    }
60
61    /**
62     * Adds the item information to the prelayout tracking
63     * @param holder The ViewHolder whose information is being saved
64     * @param info The information to save
65     */
66    void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
67        InfoRecord record = mLayoutHolderMap.get(holder);
68        if (record == null) {
69            record = InfoRecord.obtain();
70            mLayoutHolderMap.put(holder, record);
71        }
72        record.preInfo = info;
73        record.flags |= FLAG_PRE;
74    }
75
76    /**
77     * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
78     * @param vh The ViewHolder whose information is being queried
79     * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
80     */
81    @Nullable
82    ItemHolderInfo popFromPreLayout(ViewHolder vh) {
83        int index = mLayoutHolderMap.indexOfKey(vh);
84        if (index < 0) {
85            return null;
86        }
87        final InfoRecord record = mLayoutHolderMap.valueAt(index);
88        if (record != null && (record.flags & FLAG_PRE) != 0) {
89            record.flags &= ~FLAG_PRE;
90            final ItemHolderInfo info = record.preInfo;
91            if (record.flags == 0) {
92                mLayoutHolderMap.removeAt(index);
93                InfoRecord.recycle(record);
94            }
95            return info;
96        }
97        return null;
98    }
99
100    /**
101     * Adds the given ViewHolder to the oldChangeHolders list
102     * @param key The key to identify the ViewHolder.
103     * @param holder The ViewHolder to store
104     */
105    void addToOldChangeHolders(long key, ViewHolder holder) {
106        mOldChangedHolders.put(key, holder);
107    }
108
109    /**
110     * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
111     * LayoutManager during a pre-layout pass. We distinguish them from other views that were
112     * already in the pre-layout so that ItemAnimator can choose to run a different animation for
113     * them.
114     *
115     * @param holder The ViewHolder to store
116     * @param info The information to save
117     */
118    void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
119        InfoRecord record = mLayoutHolderMap.get(holder);
120        if (record == null) {
121            record = InfoRecord.obtain();
122            mLayoutHolderMap.put(holder, record);
123        }
124        record.flags |= FLAG_APPEAR;
125        record.preInfo = info;
126    }
127
128    /**
129     * Checks whether the given ViewHolder is in preLayout list
130     * @param viewHolder The ViewHolder to query
131     *
132     * @return True if the ViewHolder is present in preLayout, false otherwise
133     */
134    boolean isInPreLayout(ViewHolder viewHolder) {
135        final InfoRecord record = mLayoutHolderMap.get(viewHolder);
136        return record != null && (record.flags & FLAG_PRE) != 0;
137    }
138
139    /**
140     * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
141     * null.
142     * @param key The key to be used to find the ViewHolder.
143     *
144     * @return A ViewHolder if exists or null if it does not exist.
145     */
146    ViewHolder getFromOldChangeHolders(long key) {
147        return mOldChangedHolders.get(key);
148    }
149
150    /**
151     * Adds the item information to the post layout list
152     * @param holder The ViewHolder whose information is being saved
153     * @param info The information to save
154     */
155    void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
156        InfoRecord record = mLayoutHolderMap.get(holder);
157        if (record == null) {
158            record = InfoRecord.obtain();
159            mLayoutHolderMap.put(holder, record);
160        }
161        record.postInfo = info;
162        record.flags |= FLAG_POST;
163    }
164
165    /**
166     * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
167     * This list holds such items so that we can animate / recycle these ViewHolders properly.
168     *
169     * @param holder The ViewHolder which disappeared during a layout.
170     */
171    void addToDisappearedInLayout(ViewHolder holder) {
172        InfoRecord record = mLayoutHolderMap.get(holder);
173        if (record == null) {
174            record = InfoRecord.obtain();
175            mLayoutHolderMap.put(holder, record);
176        }
177        record.flags |= FLAG_DISAPPEARED;
178    }
179
180    /**
181     * Removes a ViewHolder from disappearing list.
182     * @param holder The ViewHolder to be removed from the disappearing list.
183     */
184    void removeFromDisappearedInLayout(ViewHolder holder) {
185        InfoRecord record = mLayoutHolderMap.get(holder);
186        if (record == null) {
187            return;
188        }
189        record.flags &= ~FLAG_DISAPPEARED;
190    }
191
192    void process(ProcessCallback callback) {
193        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) {
194            final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
195            final InfoRecord record = mLayoutHolderMap.removeAt(index);
196            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
197                // Appeared then disappeared. Not useful for animations.
198                callback.unused(viewHolder);
199            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
200                // Set as "disappeared" by the LayoutManager (addDisappearingView)
201                callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
202            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
203                // Appeared in the layout but not in the adapter (e.g. entered the viewport)
204                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
205            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
206                // Persistent in both passes. Animate persistence
207                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
208            } else if ((record.flags & FLAG_PRE) != 0) {
209                // Was in pre-layout, never been added to post layout
210                callback.processDisappeared(viewHolder, record.preInfo, null);
211            } else if ((record.flags & FLAG_POST) != 0) {
212                // Was not in pre-layout, been added to post layout
213                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
214            } else if ((record.flags & FLAG_APPEAR) != 0) {
215                // Scrap view. RecyclerView will handle removing/recycling this.
216            } else if (DEBUG) {
217                throw new IllegalStateException("record without any reasonable flag combination:/");
218            }
219            InfoRecord.recycle(record);
220        }
221    }
222
223    /**
224     * Removes the ViewHolder from all list
225     * @param holder The ViewHolder which we should stop tracking
226     */
227    void removeViewHolder(ViewHolder holder) {
228        for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
229            if (holder == mOldChangedHolders.valueAt(i)) {
230                mOldChangedHolders.removeAt(i);
231                break;
232            }
233        }
234        final InfoRecord info = mLayoutHolderMap.remove(holder);
235        if (info != null) {
236            InfoRecord.recycle(info);
237        }
238    }
239
240    void onDetach() {
241        InfoRecord.drainCache();
242    }
243
244    interface ProcessCallback {
245        void processDisappeared(ViewHolder viewHolder, ItemHolderInfo preInfo,
246                @Nullable ItemHolderInfo postInfo);
247        void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
248                ItemHolderInfo postInfo);
249        void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
250                @NonNull ItemHolderInfo postInfo);
251        void unused(ViewHolder holder);
252    }
253
254    static class InfoRecord {
255        // disappearing list
256        static final int FLAG_DISAPPEARED = 1;
257        // appear in pre layout list
258        static final int FLAG_APPEAR = 1 << 1;
259        // pre layout, this is necessary to distinguish null item info
260        static final int FLAG_PRE = 1 << 2;
261        // post layout, this is necessary to distinguish null item info
262        static final int FLAG_POST = 1 << 3;
263        static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
264        static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
265        static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
266        int flags;
267        @Nullable ItemHolderInfo preInfo;
268        @Nullable ItemHolderInfo postInfo;
269        static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
270
271        private InfoRecord() {
272        }
273
274        static InfoRecord obtain() {
275            InfoRecord record = sPool.acquire();
276            return record == null ? new InfoRecord() : record;
277        }
278
279        static void recycle(InfoRecord record) {
280            record.flags = 0;
281            record.preInfo = null;
282            record.postInfo = null;
283            sPool.release(record);
284        }
285
286        static void drainCache() {
287            //noinspection StatementWithEmptyBody
288            while (sPool.acquire() != null);
289        }
290    }
291}
292