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