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