DefaultItemAnimatorTest.java revision 42e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790
1/* 2 * Copyright (C) 2014 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 17package android.support.v7.widget; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertFalse; 21import static org.junit.Assert.assertNotNull; 22import static org.junit.Assert.assertTrue; 23 24import android.support.test.filters.MediumTest; 25import android.support.test.runner.AndroidJUnit4; 26import android.util.Log; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.TextView; 30 31import org.junit.Before; 32import org.junit.Test; 33import org.junit.runner.RunWith; 34 35import java.util.ArrayList; 36import java.util.Arrays; 37import java.util.HashSet; 38import java.util.List; 39import java.util.Set; 40import java.util.concurrent.Semaphore; 41import java.util.concurrent.TimeUnit; 42 43@MediumTest 44@RunWith(AndroidJUnit4.class) 45public class DefaultItemAnimatorTest extends BaseRecyclerViewInstrumentationTest { 46 47 private static final String TAG = "DefaultItemAnimatorTest"; 48 Throwable mainThreadException; 49 50 DefaultItemAnimator mAnimator; 51 Adapter mAdapter; 52 ViewGroup mDummyParent; 53 List<RecyclerView.ViewHolder> mExpectedItems = new ArrayList<RecyclerView.ViewHolder>(); 54 55 Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>(); 56 Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>(); 57 Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>(); 58 Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>(); 59 60 Semaphore mExpectedItemCount = new Semaphore(0); 61 62 @Before 63 public void setUp() throws Exception { 64 mAnimator = new DefaultItemAnimator() { 65 @Override 66 public void onRemoveFinished(RecyclerView.ViewHolder item) { 67 try { 68 assertTrue(mRemoveFinished.add(item)); 69 onFinished(item); 70 } catch (Throwable t) { 71 postExceptionToInstrumentation(t); 72 } 73 } 74 75 @Override 76 public void onAddFinished(RecyclerView.ViewHolder item) { 77 try { 78 assertTrue(mAddFinished.add(item)); 79 onFinished(item); 80 } catch (Throwable t) { 81 postExceptionToInstrumentation(t); 82 } 83 } 84 85 @Override 86 public void onMoveFinished(RecyclerView.ViewHolder item) { 87 try { 88 assertTrue(mMoveFinished.add(item)); 89 onFinished(item); 90 } catch (Throwable t) { 91 postExceptionToInstrumentation(t); 92 } 93 } 94 95 @Override 96 public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { 97 try { 98 assertTrue(mChangeFinished.add(item)); 99 onFinished(item); 100 } catch (Throwable t) { 101 postExceptionToInstrumentation(t); 102 } 103 } 104 105 private void onFinished(RecyclerView.ViewHolder item) { 106 assertNotNull(mExpectedItems.remove(item)); 107 mExpectedItemCount.release(1); 108 } 109 }; 110 mAdapter = new Adapter(20); 111 mDummyParent = getActivity().getContainer(); 112 } 113 114 void checkForMainThreadException() throws Throwable { 115 if (mainThreadException != null) { 116 throw mainThreadException; 117 } 118 } 119 120 @Test 121 public void reUseWithPayload() { 122 RecyclerView.ViewHolder vh = new ViewHolder(new TextView(getActivity())); 123 assertFalse(mAnimator.canReuseUpdatedViewHolder(vh, new ArrayList<>())); 124 assertTrue(mAnimator.canReuseUpdatedViewHolder(vh, Arrays.asList((Object) "a"))); 125 } 126 127 void expectItems(RecyclerView.ViewHolder... viewHolders) { 128 mExpectedItems.addAll(Arrays.asList(viewHolders)); 129 } 130 131 void runAndWait(int itemCount, int seconds) throws Throwable { 132 runAndWait(itemCount, seconds, null); 133 } 134 135 void runAndWait(int itemCount, int seconds, final ThrowingRunnable postRun) throws Throwable { 136 mActivityRule.runOnUiThread(new Runnable() { 137 @Override 138 public void run() { 139 mAnimator.runPendingAnimations(); 140 if (postRun != null) { 141 try { 142 postRun.run(); 143 } catch (Throwable e) { 144 throw new RuntimeException(e); 145 } 146 } 147 } 148 }); 149 waitForItems(itemCount, seconds); 150 checkForMainThreadException(); 151 } 152 153 void waitForItems(int itemCount, int seconds) throws InterruptedException { 154 assertTrue("all vh animations should end", 155 mExpectedItemCount.tryAcquire(itemCount, seconds, TimeUnit.SECONDS)); 156 assertEquals("all expected finish events should happen", 0, mExpectedItems.size()); 157 // wait one more second for unwanted 158 assertFalse("should not receive any more permits", 159 mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS)); 160 } 161 162 @Test 163 public void animateAdd() throws Throwable { 164 ViewHolder vh = createViewHolder(1); 165 expectItems(vh); 166 assertTrue(animateAdd(vh)); 167 assertTrue(mAnimator.isRunning()); 168 runAndWait(1, 1); 169 } 170 171 @Test 172 public void animateRemove() throws Throwable { 173 ViewHolder vh = createViewHolder(1); 174 expectItems(vh); 175 assertTrue(animateRemove(vh)); 176 assertTrue(mAnimator.isRunning()); 177 runAndWait(1, 1); 178 } 179 180 @Test 181 public void animateMove() throws Throwable { 182 ViewHolder vh = createViewHolder(1); 183 expectItems(vh); 184 assertTrue(animateMove(vh, 0, 0, 100, 100)); 185 assertTrue(mAnimator.isRunning()); 186 runAndWait(1, 1); 187 } 188 189 @Test 190 public void animateChange() throws Throwable { 191 ViewHolder vh = createViewHolder(1); 192 ViewHolder vh2 = createViewHolder(2); 193 expectItems(vh, vh2); 194 assertTrue(animateChange(vh, vh2, 0, 0, 100, 100)); 195 assertTrue(mAnimator.isRunning()); 196 runAndWait(2, 1); 197 } 198 199 public void cancelBefore(int count, final RecyclerView.ViewHolder... toCancel) 200 throws Throwable { 201 cancelTest(true, count, toCancel); 202 } 203 204 public void cancelAfter(int count, final RecyclerView.ViewHolder... toCancel) 205 throws Throwable { 206 cancelTest(false, count, toCancel); 207 } 208 209 public void cancelTest(boolean before, int count, final RecyclerView.ViewHolder... toCancel) throws Throwable { 210 if (before) { 211 endAnimations(toCancel); 212 runAndWait(count, 1); 213 } else { 214 runAndWait(count, 1, new ThrowingRunnable() { 215 @Override 216 public void run() throws Throwable { 217 endAnimations(toCancel); 218 } 219 }); 220 } 221 } 222 223 @Test 224 public void cancelAddBefore() throws Throwable { 225 final ViewHolder vh = createViewHolder(1); 226 expectItems(vh); 227 assertTrue(animateAdd(vh)); 228 cancelBefore(1, vh); 229 } 230 231 @Test 232 public void cancelAddAfter() throws Throwable { 233 final ViewHolder vh = createViewHolder(1); 234 expectItems(vh); 235 assertTrue(animateAdd(vh)); 236 cancelAfter(1, vh); 237 } 238 239 @Test 240 public void cancelMoveBefore() throws Throwable { 241 ViewHolder vh = createViewHolder(1); 242 expectItems(vh); 243 assertTrue(animateMove(vh, 10, 10, 100, 100)); 244 cancelBefore(1, vh); 245 } 246 247 @Test 248 public void cancelMoveAfter() throws Throwable { 249 ViewHolder vh = createViewHolder(1); 250 expectItems(vh); 251 assertTrue(animateMove(vh, 10, 10, 100, 100)); 252 cancelAfter(1, vh); 253 } 254 255 @Test 256 public void cancelRemove() throws Throwable { 257 ViewHolder vh = createViewHolder(1); 258 expectItems(vh); 259 assertTrue(animateRemove(vh)); 260 endAnimations(vh); 261 runAndWait(1, 1); 262 } 263 264 @Test 265 public void cancelChangeOldBefore() throws Throwable { 266 cancelChangeOldTest(true); 267 } 268 @Test 269 public void cancelChangeOldAfter() throws Throwable { 270 cancelChangeOldTest(false); 271 } 272 273 public void cancelChangeOldTest(boolean before) throws Throwable { 274 ViewHolder vh = createViewHolder(1); 275 ViewHolder vh2 = createViewHolder(1); 276 expectItems(vh, vh2); 277 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 278 cancelTest(before, 2, vh); 279 } 280 281 @Test 282 public void cancelChangeNewBefore() throws Throwable { 283 cancelChangeNewTest(true); 284 } 285 286 @Test 287 public void cancelChangeNewAfter() throws Throwable { 288 cancelChangeNewTest(false); 289 } 290 291 public void cancelChangeNewTest(boolean before) throws Throwable { 292 ViewHolder vh = createViewHolder(1); 293 ViewHolder vh2 = createViewHolder(1); 294 expectItems(vh, vh2); 295 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 296 cancelTest(before, 2, vh2); 297 } 298 299 @Test 300 public void cancelChangeBothBefore() throws Throwable { 301 cancelChangeBothTest(true); 302 } 303 304 @Test 305 public void cancelChangeBothAfter() throws Throwable { 306 cancelChangeBothTest(false); 307 } 308 309 public void cancelChangeBothTest(boolean before) throws Throwable { 310 ViewHolder vh = createViewHolder(1); 311 ViewHolder vh2 = createViewHolder(1); 312 expectItems(vh, vh2); 313 assertTrue(animateChange(vh, vh2, 20, 20, 100, 100)); 314 cancelTest(before, 2, vh, vh2); 315 } 316 317 void endAnimations(final RecyclerView.ViewHolder... vhs) throws Throwable { 318 mActivityRule.runOnUiThread(new Runnable() { 319 @Override 320 public void run() { 321 for (RecyclerView.ViewHolder vh : vhs) { 322 mAnimator.endAnimation(vh); 323 } 324 } 325 }); 326 } 327 328 boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable { 329 final boolean[] result = new boolean[1]; 330 mActivityRule.runOnUiThread(new Runnable() { 331 @Override 332 public void run() { 333 result[0] = mAnimator.animateAdd(vh); 334 } 335 }); 336 return result[0]; 337 } 338 339 boolean animateRemove(final RecyclerView.ViewHolder vh) throws Throwable { 340 final boolean[] result = new boolean[1]; 341 mActivityRule.runOnUiThread(new Runnable() { 342 @Override 343 public void run() { 344 result[0] = mAnimator.animateRemove(vh); 345 } 346 }); 347 return result[0]; 348 } 349 350 boolean animateMove(final RecyclerView.ViewHolder vh, final int fromX, final int fromY, 351 final int toX, final int toY) throws Throwable { 352 final boolean[] result = new boolean[1]; 353 mActivityRule.runOnUiThread(new Runnable() { 354 @Override 355 public void run() { 356 result[0] = mAnimator.animateMove(vh, fromX, fromY, toX, toY); 357 } 358 }); 359 return result[0]; 360 } 361 362 boolean animateChange(final RecyclerView.ViewHolder oldHolder, 363 final RecyclerView.ViewHolder newHolder, 364 final int fromX, final int fromY, final int toX, final int toY) throws Throwable { 365 final boolean[] result = new boolean[1]; 366 mActivityRule.runOnUiThread(new Runnable() { 367 @Override 368 public void run() { 369 result[0] = mAnimator.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY); 370 } 371 }); 372 return result[0]; 373 } 374 375 private ViewHolder createViewHolder(final int pos) throws Throwable { 376 final ViewHolder vh = mAdapter.createViewHolder(mDummyParent, 1); 377 mActivityRule.runOnUiThread(new Runnable() { 378 @Override 379 public void run() { 380 mAdapter.bindViewHolder(vh, pos); 381 mDummyParent.addView(vh.itemView); 382 } 383 }); 384 385 return vh; 386 } 387 388 void postExceptionToInstrumentation(Throwable t) { 389 if (mainThreadException == null) { 390 mainThreadException = t; 391 } else { 392 Log.e(TAG, "skipping secondary main thread exception", t); 393 } 394 } 395 396 397 private class Adapter extends RecyclerView.Adapter<ViewHolder> { 398 399 List<String> mItems; 400 401 private Adapter(int count) { 402 mItems = new ArrayList<>(); 403 for (int i = 0; i < count; i++) { 404 mItems.add("item-" + i); 405 } 406 } 407 408 @Override 409 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 410 return new ViewHolder(new TextView(parent.getContext())); 411 } 412 413 @Override 414 public void onBindViewHolder(ViewHolder holder, int position) { 415 holder.bind(mItems.get(position)); 416 } 417 418 @Override 419 public int getItemCount() { 420 return mItems.size(); 421 } 422 } 423 424 private class ViewHolder extends RecyclerView.ViewHolder { 425 426 String mBindedText; 427 428 public ViewHolder(View itemView) { 429 super(itemView); 430 } 431 432 public void bind(String text) { 433 mBindedText = text; 434 ((TextView) itemView).setText(text); 435 } 436 } 437 438 private interface ThrowingRunnable { 439 void run() throws Throwable; 440 } 441} 442