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