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