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