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