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