Home | History | Annotate | Download | only in widget
      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 
     19 import android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.util.AttributeSet;
     22 import android.util.Log;
     23 import android.view.View;
     24 
     25 import java.util.ArrayList;
     26 import java.util.HashSet;
     27 import java.util.List;
     28 import java.util.Set;
     29 import java.util.concurrent.CountDownLatch;
     30 import java.util.concurrent.TimeUnit;
     31 import static org.junit.Assert.*;
     32 
     33 /**
     34  * Base class for animation related tests.
     35  */
     36 public class BaseRecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
     37 
     38     protected static final boolean DEBUG = false;
     39 
     40     protected static final String TAG = "RecyclerViewAnimationsTest";
     41 
     42     AnimationLayoutManager mLayoutManager;
     43 
     44     TestAdapter mTestAdapter;
     45 
     46     public BaseRecyclerViewAnimationsTest() {
     47         super(DEBUG);
     48     }
     49 
     50     RecyclerView setupBasic(int itemCount) throws Throwable {
     51         return setupBasic(itemCount, 0, itemCount);
     52     }
     53 
     54     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
     55             throws Throwable {
     56         return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
     57     }
     58 
     59     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
     60             TestAdapter testAdapter)
     61             throws Throwable {
     62         final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
     63         recyclerView.setHasFixedSize(true);
     64         if (testAdapter == null) {
     65             mTestAdapter = new TestAdapter(itemCount);
     66         } else {
     67             mTestAdapter = testAdapter;
     68         }
     69         recyclerView.setAdapter(mTestAdapter);
     70         recyclerView.setItemAnimator(createItemAnimator());
     71         mLayoutManager = new AnimationLayoutManager();
     72         recyclerView.setLayoutManager(mLayoutManager);
     73         mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
     74         mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
     75 
     76         mLayoutManager.expectLayouts(1);
     77         recyclerView.expectDraw(1);
     78         setRecyclerView(recyclerView);
     79         mLayoutManager.waitForLayout(2);
     80         recyclerView.waitForDraw(1);
     81         mLayoutManager.mOnLayoutCallbacks.reset();
     82         getInstrumentation().waitForIdleSync();
     83         checkForMainThreadException();
     84         assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
     85         assertEquals("all expected children should be laid out", firstLayoutItemCount,
     86                 mLayoutManager.getChildCount());
     87         return recyclerView;
     88     }
     89 
     90     protected RecyclerView.ItemAnimator createItemAnimator() {
     91         return new DefaultItemAnimator();
     92     }
     93 
     94     public TestRecyclerView getTestRecyclerView() {
     95         return (TestRecyclerView) mRecyclerView;
     96     }
     97 
     98     class AnimationLayoutManager extends TestLayoutManager {
     99 
    100         protected int mTotalLayoutCount = 0;
    101         private String log;
    102 
    103         OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
    104         };
    105 
    106 
    107 
    108         @Override
    109         public boolean supportsPredictiveItemAnimations() {
    110             return true;
    111         }
    112 
    113         public String getLog() {
    114             return log;
    115         }
    116 
    117         private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
    118             StringBuilder builder = new StringBuilder();
    119             builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
    120             builder.append("\nViewHolders:\n");
    121             for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
    122                 builder.append(vh).append("\n");
    123             }
    124             builder.append("scrap:\n");
    125             for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
    126                 builder.append(vh).append("\n");
    127             }
    128 
    129             if (state.isPreLayout() && !done) {
    130                 log = "\n" + builder.toString();
    131             } else {
    132                 log += "\n" + builder.toString();
    133             }
    134             return log;
    135         }
    136 
    137         @Override
    138         public void expectLayouts(int count) {
    139             super.expectLayouts(count);
    140             mOnLayoutCallbacks.mLayoutCount = 0;
    141         }
    142 
    143         public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
    144             mOnLayoutCallbacks = onLayoutCallbacks;
    145         }
    146 
    147         @Override
    148         public final void onLayoutChildren(RecyclerView.Recycler recycler,
    149                 RecyclerView.State state) {
    150             try {
    151                 mTotalLayoutCount++;
    152                 prepareLog(recycler, state, false);
    153                 if (state.isPreLayout()) {
    154                     validateOldPositions(recycler, state);
    155                 } else {
    156                     validateClearedOldPositions(recycler, state);
    157                 }
    158                 mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
    159                 prepareLog(recycler, state, true);
    160             } finally {
    161                 layoutLatch.countDown();
    162             }
    163         }
    164 
    165         private void validateClearedOldPositions(RecyclerView.Recycler recycler,
    166                 RecyclerView.State state) {
    167             if (getTestRecyclerView() == null) {
    168                 return;
    169             }
    170             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
    171                 assertEquals("there should NOT be an old position in post layout",
    172                         RecyclerView.NO_POSITION, viewHolder.mOldPosition);
    173                 assertEquals("there should NOT be a pre layout position in post layout",
    174                         RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
    175             }
    176         }
    177 
    178         private void validateOldPositions(RecyclerView.Recycler recycler,
    179                 RecyclerView.State state) {
    180             if (getTestRecyclerView() == null) {
    181                 return;
    182             }
    183             for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
    184                 if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
    185                     assertTrue("there should be an old position in pre-layout",
    186                             viewHolder.mOldPosition != RecyclerView.NO_POSITION);
    187                 }
    188             }
    189         }
    190 
    191         public int getTotalLayoutCount() {
    192             return mTotalLayoutCount;
    193         }
    194 
    195         @Override
    196         public boolean canScrollVertically() {
    197             return true;
    198         }
    199 
    200         @Override
    201         public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
    202                 RecyclerView.State state) {
    203             mOnLayoutCallbacks.onScroll(dy, recycler, state);
    204             return super.scrollVerticallyBy(dy, recycler, state);
    205         }
    206 
    207         public void onPostDispatchLayout() {
    208             mOnLayoutCallbacks.postDispatchLayout();
    209         }
    210     }
    211 
    212     abstract class OnLayoutCallbacks {
    213 
    214         int mLayoutMin = Integer.MIN_VALUE;
    215 
    216         int mLayoutItemCount = Integer.MAX_VALUE;
    217 
    218         int expectedPreLayoutItemCount = -1;
    219 
    220         int expectedPostLayoutItemCount = -1;
    221 
    222         int mDeletedViewCount;
    223 
    224         int mLayoutCount = 0;
    225 
    226         void setExpectedItemCounts(int preLayout, int postLayout) {
    227             expectedPreLayoutItemCount = preLayout;
    228             expectedPostLayoutItemCount = postLayout;
    229         }
    230 
    231         void reset() {
    232             mLayoutMin = Integer.MIN_VALUE;
    233             mLayoutItemCount = Integer.MAX_VALUE;
    234             expectedPreLayoutItemCount = -1;
    235             expectedPostLayoutItemCount = -1;
    236             mLayoutCount = 0;
    237         }
    238 
    239         void beforePreLayout(RecyclerView.Recycler recycler,
    240                 AnimationLayoutManager lm, RecyclerView.State state) {
    241             mDeletedViewCount = 0;
    242             for (int i = 0; i < lm.getChildCount(); i++) {
    243                 View v = lm.getChildAt(i);
    244                 if (lm.getLp(v).isItemRemoved()) {
    245                     mDeletedViewCount++;
    246                 }
    247             }
    248         }
    249 
    250         void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
    251                 RecyclerView.State state) {
    252             if (DEBUG) {
    253                 Log.d(TAG, "item count " + state.getItemCount());
    254             }
    255             lm.detachAndScrapAttachedViews(recycler);
    256             final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
    257             final int count = mLayoutItemCount
    258                     == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
    259             lm.layoutRange(recycler, start, start + count);
    260             assertEquals("correct # of children should be laid out",
    261                     count, lm.getChildCount());
    262             lm.assertVisibleItemPositions();
    263         }
    264 
    265         private void assertNoPreLayoutPosition(RecyclerView.Recycler recycler) {
    266             for (RecyclerView.ViewHolder vh : recycler.mAttachedScrap) {
    267                 assertPreLayoutPosition(vh);
    268             }
    269         }
    270 
    271         private void assertNoPreLayoutPosition(RecyclerView.LayoutManager lm) {
    272             for (int i = 0; i < lm.getChildCount(); i ++) {
    273                 final RecyclerView.ViewHolder vh = mRecyclerView
    274                         .getChildViewHolder(lm.getChildAt(i));
    275                 assertPreLayoutPosition(vh);
    276             }
    277         }
    278 
    279         private void assertPreLayoutPosition(RecyclerView.ViewHolder vh) {
    280             assertEquals("in post layout, there should not be a view holder w/ a pre "
    281                     + "layout position", RecyclerView.NO_POSITION, vh.mPreLayoutPosition);
    282             assertEquals("in post layout, there should not be a view holder w/ an old "
    283                     + "layout position", RecyclerView.NO_POSITION, vh.mOldPosition);
    284         }
    285 
    286         void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
    287                 RecyclerView.State state) {
    288             if (state.isPreLayout()) {
    289                 if (expectedPreLayoutItemCount != -1) {
    290                     assertEquals("on pre layout, state should return abstracted adapter size",
    291                             expectedPreLayoutItemCount, state.getItemCount());
    292                 }
    293                 beforePreLayout(recycler, lm, state);
    294             } else {
    295                 if (expectedPostLayoutItemCount != -1) {
    296                     assertEquals("on post layout, state should return real adapter size",
    297                             expectedPostLayoutItemCount, state.getItemCount());
    298                 }
    299                 beforePostLayout(recycler, lm, state);
    300             }
    301             if (!state.isPreLayout()) {
    302                 assertNoPreLayoutPosition(recycler);
    303             }
    304             doLayout(recycler, lm, state);
    305             if (state.isPreLayout()) {
    306                 afterPreLayout(recycler, lm, state);
    307             } else {
    308                 afterPostLayout(recycler, lm, state);
    309                 assertNoPreLayoutPosition(lm);
    310             }
    311             mLayoutCount++;
    312         }
    313 
    314         void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    315                 RecyclerView.State state) {
    316         }
    317 
    318         void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    319                 RecyclerView.State state) {
    320         }
    321 
    322         void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
    323                 RecyclerView.State state) {
    324         }
    325 
    326         void postDispatchLayout() {
    327         }
    328 
    329         public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    330 
    331         }
    332     }
    333 
    334     class TestRecyclerView extends RecyclerView {
    335 
    336         CountDownLatch drawLatch;
    337 
    338         public TestRecyclerView(Context context) {
    339             super(context);
    340         }
    341 
    342         public TestRecyclerView(Context context, AttributeSet attrs) {
    343             super(context, attrs);
    344         }
    345 
    346         public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    347             super(context, attrs, defStyle);
    348         }
    349 
    350         @Override
    351         void initAdapterManager() {
    352             super.initAdapterManager();
    353             mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
    354                 @Override
    355                 public void run() {
    356                     validatePostUpdateOp();
    357                 }
    358             };
    359         }
    360 
    361         @Override
    362         boolean isAccessibilityEnabled() {
    363             return true;
    364         }
    365 
    366         public void expectDraw(int count) {
    367             drawLatch = new CountDownLatch(count);
    368         }
    369 
    370         public void waitForDraw(long timeout) throws Throwable {
    371             drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
    372             assertEquals("all expected draws should happen at the expected time frame",
    373                     0, drawLatch.getCount());
    374         }
    375 
    376         List<ViewHolder> collectViewHolders() {
    377             List<ViewHolder> holders = new ArrayList<ViewHolder>();
    378             final int childCount = getChildCount();
    379             for (int i = 0; i < childCount; i++) {
    380                 ViewHolder holder = getChildViewHolderInt(getChildAt(i));
    381                 if (holder != null) {
    382                     holders.add(holder);
    383                 }
    384             }
    385             return holders;
    386         }
    387 
    388 
    389         private void validateViewHolderPositions() {
    390             final Set<Integer> existingOffsets = new HashSet<Integer>();
    391             int childCount = getChildCount();
    392             StringBuilder log = new StringBuilder();
    393             for (int i = 0; i < childCount; i++) {
    394                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
    395                 TestViewHolder tvh = (TestViewHolder) vh;
    396                 log.append(tvh.mBoundItem).append(vh)
    397                         .append(" hidden:")
    398                         .append(mChildHelper.mHiddenViews.contains(vh.itemView))
    399                         .append("\n");
    400             }
    401             for (int i = 0; i < childCount; i++) {
    402                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
    403                 if (vh.isInvalid()) {
    404                     continue;
    405                 }
    406                 if (vh.getLayoutPosition() < 0) {
    407                     LayoutManager lm = getLayoutManager();
    408                     for (int j = 0; j < lm.getChildCount(); j ++) {
    409                         assertNotSame("removed view holder should not be in LM's child list",
    410                                 vh.itemView, lm.getChildAt(j));
    411                     }
    412                 } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
    413                     if (!existingOffsets.add(vh.getLayoutPosition())) {
    414                         throw new IllegalStateException("view holder position conflict for "
    415                                 + "existing views " + vh + "\n" + log);
    416                     }
    417                 }
    418             }
    419         }
    420 
    421         void validatePostUpdateOp() {
    422             try {
    423                 validateViewHolderPositions();
    424                 if (super.mState.isPreLayout()) {
    425                     validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
    426                 }
    427                 validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
    428             } catch (Throwable t) {
    429                 postExceptionToInstrumentation(t);
    430             }
    431         }
    432 
    433 
    434 
    435         private void validateAdapterPosition(AnimationLayoutManager lm) {
    436             for (ViewHolder vh : collectViewHolders()) {
    437                 if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
    438                     assertEquals("adapter position calculations should match view holder "
    439                                     + "pre layout:" + mState.isPreLayout()
    440                                     + " positions\n" + vh + "\n" + lm.getLog(),
    441                             mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
    442                 }
    443             }
    444         }
    445 
    446         // ensures pre layout positions are continuous block. This is not necessarily a case
    447         // but valid in test RV
    448         private void validatePreLayoutSequence(AnimationLayoutManager lm) {
    449             Set<Integer> preLayoutPositions = new HashSet<Integer>();
    450             for (ViewHolder vh : collectViewHolders()) {
    451                 assertTrue("pre layout positions should be distinct " + lm.getLog(),
    452                         preLayoutPositions.add(vh.mPreLayoutPosition));
    453             }
    454             int minPos = Integer.MAX_VALUE;
    455             for (Integer pos : preLayoutPositions) {
    456                 if (pos < minPos) {
    457                     minPos = pos;
    458                 }
    459             }
    460             for (int i = 1; i < preLayoutPositions.size(); i++) {
    461                 assertNotNull("next position should exist " + lm.getLog(),
    462                         preLayoutPositions.contains(minPos + i));
    463             }
    464         }
    465 
    466         @Override
    467         protected void dispatchDraw(Canvas canvas) {
    468             super.dispatchDraw(canvas);
    469             if (drawLatch != null) {
    470                 drawLatch.countDown();
    471             }
    472         }
    473 
    474         @Override
    475         void dispatchLayout() {
    476             try {
    477                 super.dispatchLayout();
    478                 if (getLayoutManager() instanceof AnimationLayoutManager) {
    479                     ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
    480                 }
    481             } catch (Throwable t) {
    482                 postExceptionToInstrumentation(t);
    483             }
    484 
    485         }
    486 
    487 
    488     }
    489 
    490     abstract class AdapterOps {
    491 
    492         final public void run(TestAdapter adapter) throws Throwable {
    493             onRun(adapter);
    494         }
    495 
    496         abstract void onRun(TestAdapter testAdapter) throws Throwable;
    497     }
    498 
    499     static class CollectPositionResult {
    500 
    501         // true if found in scrap
    502         public RecyclerView.ViewHolder scrapResult;
    503 
    504         public RecyclerView.ViewHolder adapterResult;
    505 
    506         static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
    507             CollectPositionResult cpr = new CollectPositionResult();
    508             cpr.scrapResult = viewHolder;
    509             return cpr;
    510         }
    511 
    512         static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
    513             CollectPositionResult cpr = new CollectPositionResult();
    514             cpr.adapterResult = viewHolder;
    515             return cpr;
    516         }
    517 
    518         @Override
    519         public String toString() {
    520             return "CollectPositionResult{" +
    521                     "scrapResult=" + scrapResult +
    522                     ", adapterResult=" + adapterResult +
    523                     '}';
    524         }
    525     }
    526 
    527     static class PositionConstraint {
    528 
    529         public static enum Type {
    530             scrap,
    531             adapter,
    532             adapterScrap /*first pass adapter, second pass scrap*/
    533         }
    534 
    535         Type mType;
    536 
    537         int mOldPos; // if VH
    538 
    539         int mPreLayoutPos;
    540 
    541         int mPostLayoutPos;
    542 
    543         int mValidateCount = 0;
    544 
    545         public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
    546             PositionConstraint constraint = new PositionConstraint();
    547             constraint.mType = Type.scrap;
    548             constraint.mOldPos = oldPos;
    549             constraint.mPreLayoutPos = preLayoutPos;
    550             constraint.mPostLayoutPos = postLayoutPos;
    551             return constraint;
    552         }
    553 
    554         public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
    555             PositionConstraint constraint = new PositionConstraint();
    556             constraint.mType = Type.adapterScrap;
    557             constraint.mOldPos = RecyclerView.NO_POSITION;
    558             constraint.mPreLayoutPos = preLayoutPos;
    559             constraint.mPostLayoutPos = position;// adapter pos does not change
    560             return constraint;
    561         }
    562 
    563         public static PositionConstraint adapter(int position) {
    564             PositionConstraint constraint = new PositionConstraint();
    565             constraint.mType = Type.adapter;
    566             constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
    567             constraint.mOldPos = RecyclerView.NO_POSITION;
    568             constraint.mPostLayoutPos = position;// adapter pos does not change
    569             return constraint;
    570         }
    571 
    572         public void assertValidate() {
    573             int expectedValidate = 0;
    574             if (mPreLayoutPos >= 0) {
    575                 expectedValidate ++;
    576             }
    577             if (mPostLayoutPos >= 0) {
    578                 expectedValidate ++;
    579             }
    580             assertEquals("should run all validates", expectedValidate, mValidateCount);
    581         }
    582 
    583         @Override
    584         public String toString() {
    585             return "Cons{" +
    586                     "t=" + mType.name() +
    587                     ", old=" + mOldPos +
    588                     ", pre=" + mPreLayoutPos +
    589                     ", post=" + mPostLayoutPos +
    590                     '}';
    591         }
    592 
    593         public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
    594             mValidateCount ++;
    595             assertNotNull(this + ": result should not be null\n" + log, result);
    596             RecyclerView.ViewHolder viewHolder;
    597             if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
    598                 assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
    599                 viewHolder = result.scrapResult;
    600             } else {
    601                 assertNotNull(this + ": result should come from adapter\n"  + log,
    602                         result.adapterResult);
    603                 assertEquals(this + ": old position should be none when it came from adapter\n" + log,
    604                         RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
    605                 viewHolder = result.adapterResult;
    606             }
    607             if (state.isPreLayout()) {
    608                 assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
    609                         viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
    610                                 viewHolder.mPreLayoutPosition);
    611                 assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
    612                         viewHolder.getLayoutPosition());
    613                 if (mType == Type.scrap) {
    614                     assertEquals(this + ": old position should match\n" + log, mOldPos,
    615                             result.scrapResult.getOldPosition());
    616                 }
    617             } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
    618                     .isRemoved()) {
    619                 assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
    620                         + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
    621             }
    622         }
    623     }
    624 
    625     static class LoggingInfo extends RecyclerView.ItemAnimator.ItemHolderInfo {
    626         final RecyclerView.ViewHolder viewHolder;
    627         @RecyclerView.ItemAnimator.AdapterChanges
    628         final int changeFlags;
    629         final List<Object> payloads;
    630 
    631         LoggingInfo(RecyclerView.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
    632             this.viewHolder = viewHolder;
    633             this.changeFlags = changeFlags;
    634             if (payloads != null) {
    635                 this.payloads = new ArrayList<>();
    636                 this.payloads.addAll(payloads);
    637             } else {
    638                 this.payloads = null;
    639             }
    640             setFrom(viewHolder);
    641         }
    642 
    643         @Override
    644         public String toString() {
    645             return "LoggingInfo{" +
    646                     "changeFlags=" + changeFlags +
    647                     ", payloads=" + payloads +
    648                     '}';
    649         }
    650     }
    651 
    652     static class AnimateChange extends AnimateLogBase {
    653 
    654         final RecyclerView.ViewHolder newHolder;
    655 
    656         public AnimateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
    657                 LoggingInfo pre, LoggingInfo post) {
    658             super(oldHolder, pre, post);
    659             this.newHolder = newHolder;
    660         }
    661     }
    662 
    663     static class AnimatePersistence extends AnimateLogBase {
    664 
    665         public AnimatePersistence(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    666                 LoggingInfo post) {
    667             super(viewHolder, pre, post);
    668         }
    669     }
    670 
    671     static class AnimateAppearance extends AnimateLogBase {
    672         public AnimateAppearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    673                 LoggingInfo post) {
    674             super(viewHolder, pre, post);
    675         }
    676     }
    677 
    678     static class AnimateDisappearance extends AnimateLogBase {
    679         public AnimateDisappearance(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    680                 LoggingInfo post) {
    681             super(viewHolder, pre, post);
    682         }
    683     }
    684     static class AnimateLogBase {
    685 
    686         public final RecyclerView.ViewHolder viewHolder;
    687         public final LoggingInfo preInfo;
    688         public final LoggingInfo postInfo;
    689 
    690         public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
    691                 LoggingInfo postInfo) {
    692             this.viewHolder = viewHolder;
    693             this.preInfo = pre;
    694             this.postInfo = postInfo;
    695         }
    696 
    697         public String log() {
    698             return getClass().getSimpleName() + "[" +  log(preInfo) + " - " + log(postInfo) + "]";
    699         }
    700 
    701         public String log(LoggingInfo info) {
    702             return info == null ? "null" : info.toString();
    703         }
    704 
    705         @Override
    706         public boolean equals(Object o) {
    707             if (this == o) {
    708                 return true;
    709             }
    710             if (o == null || getClass() != o.getClass()) {
    711                 return false;
    712             }
    713 
    714             AnimateLogBase that = (AnimateLogBase) o;
    715 
    716             if (viewHolder != null ? !viewHolder.equals(that.viewHolder)
    717                     : that.viewHolder != null) {
    718                 return false;
    719             }
    720             if (preInfo != null ? !preInfo.equals(that.preInfo) : that.preInfo != null) {
    721                 return false;
    722             }
    723             return !(postInfo != null ? !postInfo.equals(that.postInfo) : that.postInfo != null);
    724 
    725         }
    726 
    727         @Override
    728         public int hashCode() {
    729             int result = viewHolder != null ? viewHolder.hashCode() : 0;
    730             result = 31 * result + (preInfo != null ? preInfo.hashCode() : 0);
    731             result = 31 * result + (postInfo != null ? postInfo.hashCode() : 0);
    732             return result;
    733         }
    734     }
    735 }
    736