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 17package android.support.v7.widget.helper; 18 19import static android.support.v7.widget.helper.ItemTouchHelper.*; 20 21import static org.junit.Assert.*; 22 23import android.os.Build; 24import android.support.test.filters.SdkSuppress; 25import android.support.test.runner.AndroidJUnit4; 26import android.support.v4.view.ViewCompat; 27import android.support.v7.util.TouchUtils; 28import android.support.v7.widget.BaseRecyclerViewInstrumentationTest; 29import android.support.v7.widget.RecyclerView; 30import android.support.v7.widget.WrappedRecyclerView; 31import android.test.suitebuilder.annotation.MediumTest; 32import android.view.Gravity; 33import android.view.View; 34 35import org.junit.Test; 36import org.junit.runner.RunWith; 37 38import java.util.ArrayList; 39import java.util.List; 40 41@MediumTest 42@RunWith(AndroidJUnit4.class) 43public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest { 44 45 TestAdapter mAdapter; 46 47 TestLayoutManager mLayoutManager; 48 49 private LoggingCalback mCalback; 50 51 private LoggingItemTouchHelper mItemTouchHelper; 52 53 private WrappedRecyclerView mWrappedRecyclerView; 54 55 private Boolean mSetupRTL; 56 57 public ItemTouchHelperTest() { 58 super(false); 59 } 60 61 private RecyclerView setup(int dragDirs, int swipeDirs) throws Throwable { 62 mWrappedRecyclerView = inflateWrappedRV(); 63 mAdapter = new TestAdapter(10); 64 mLayoutManager = new TestLayoutManager() { 65 @Override 66 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 67 detachAndScrapAttachedViews(recycler); 68 layoutRange(recycler, 0, Math.min(5, state.getItemCount())); 69 layoutLatch.countDown(); 70 } 71 72 @Override 73 public boolean canScrollHorizontally() { 74 return false; 75 } 76 77 @Override 78 public boolean supportsPredictiveItemAnimations() { 79 return false; 80 } 81 }; 82 mWrappedRecyclerView.setFakeRTL(mSetupRTL); 83 mWrappedRecyclerView.setAdapter(mAdapter); 84 mWrappedRecyclerView.setLayoutManager(mLayoutManager); 85 mCalback = new LoggingCalback(dragDirs, swipeDirs); 86 mItemTouchHelper = new LoggingItemTouchHelper(mCalback); 87 runTestOnUiThread(new Runnable() { 88 @Override 89 public void run() { 90 mItemTouchHelper.attachToRecyclerView(mWrappedRecyclerView); 91 } 92 }); 93 94 return mWrappedRecyclerView; 95 } 96 97 @Test 98 public void swipeLeft() throws Throwable { 99 basicSwipeTest(LEFT, LEFT | RIGHT, -getActivity().getWindow().getDecorView().getWidth()); 100 } 101 102 @Test 103 public void swipeRight() throws Throwable { 104 basicSwipeTest(RIGHT, LEFT | RIGHT, getActivity().getWindow().getDecorView().getWidth()); 105 } 106 107 @Test 108 public void swipeStart() throws Throwable { 109 basicSwipeTest(START, START | END, -getActivity().getWindow().getDecorView().getWidth()); 110 } 111 112 @Test 113 public void swipeEnd() throws Throwable { 114 basicSwipeTest(END, START | END, getActivity().getWindow().getDecorView().getWidth()); 115 } 116 117 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1) 118 @Test 119 public void swipeStartInRTL() throws Throwable { 120 mSetupRTL = true; 121 basicSwipeTest(START, START | END, getActivity().getWindow().getDecorView().getWidth()); 122 } 123 124 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1) 125 @Test 126 public void swipeEndInRTL() throws Throwable { 127 mSetupRTL = true; 128 basicSwipeTest(END, START | END, -getActivity().getWindow().getDecorView().getWidth()); 129 } 130 131 private void setLayoutDirection(final View view, final int layoutDir) throws Throwable { 132 runTestOnUiThread(new Runnable() { 133 @Override 134 public void run() { 135 ViewCompat.setLayoutDirection(view, layoutDir); 136 } 137 }); 138 } 139 140 public void basicSwipeTest(int dir, int swipeDirs, int targetX) throws Throwable { 141 final RecyclerView recyclerView = setup(0, swipeDirs); 142 mLayoutManager.expectLayouts(1); 143 setRecyclerView(recyclerView); 144 mLayoutManager.waitForLayout(1); 145 146 final RecyclerView.ViewHolder target = mRecyclerView 147 .findViewHolderForAdapterPosition(1); 148 TouchUtils.dragViewToX(getInstrumentation(), target.itemView, Gravity.CENTER, targetX); 149 Thread.sleep(100); //wait for animation end 150 final SwipeRecord swipe = mCalback.getSwipe(target); 151 assertNotNull(swipe); 152 assertEquals(dir, swipe.dir); 153 assertEquals(1, mItemTouchHelper.mRecoverAnimations.size()); 154 assertEquals(1, mItemTouchHelper.mPendingCleanup.size()); 155 // get rid of the view 156 mLayoutManager.expectLayouts(1); 157 mAdapter.deleteAndNotify(1, 1); 158 mLayoutManager.waitForLayout(1); 159 waitForAnimations(); 160 assertEquals(0, mItemTouchHelper.mRecoverAnimations.size()); 161 assertEquals(0, mItemTouchHelper.mPendingCleanup.size()); 162 assertTrue(mCalback.isCleared(target)); 163 } 164 165 private void waitForAnimations() throws InterruptedException { 166 while (mRecyclerView.getItemAnimator().isRunning()) { 167 Thread.sleep(100); 168 } 169 } 170 171 private static class LoggingCalback extends SimpleCallback { 172 173 private List<MoveRecord> mMoveRecordList = new ArrayList<MoveRecord>(); 174 175 private List<SwipeRecord> mSwipeRecords = new ArrayList<SwipeRecord>(); 176 177 private List<RecyclerView.ViewHolder> mCleared = new ArrayList<RecyclerView.ViewHolder>(); 178 179 LoggingCalback(int dragDirs, int swipeDirs) { 180 super(dragDirs, swipeDirs); 181 } 182 183 @Override 184 public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, 185 RecyclerView.ViewHolder target) { 186 mMoveRecordList.add(new MoveRecord(viewHolder, target)); 187 return true; 188 } 189 190 @Override 191 public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 192 mSwipeRecords.add(new SwipeRecord(viewHolder, direction)); 193 } 194 195 public MoveRecord getMove(RecyclerView.ViewHolder vh) { 196 for (MoveRecord move : mMoveRecordList) { 197 if (move.from == vh) { 198 return move; 199 } 200 } 201 return null; 202 } 203 204 @Override 205 public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 206 super.clearView(recyclerView, viewHolder); 207 mCleared.add(viewHolder); 208 } 209 210 public SwipeRecord getSwipe(RecyclerView.ViewHolder vh) { 211 for (SwipeRecord swipe : mSwipeRecords) { 212 if (swipe.viewHolder == vh) { 213 return swipe; 214 } 215 } 216 return null; 217 } 218 219 public boolean isCleared(RecyclerView.ViewHolder vh) { 220 return mCleared.contains(vh); 221 } 222 } 223 224 private static class LoggingItemTouchHelper extends ItemTouchHelper { 225 226 public LoggingItemTouchHelper(Callback callback) { 227 super(callback); 228 } 229 } 230 231 private static class SwipeRecord { 232 233 RecyclerView.ViewHolder viewHolder; 234 235 int dir; 236 237 public SwipeRecord(RecyclerView.ViewHolder viewHolder, int dir) { 238 this.viewHolder = viewHolder; 239 this.dir = dir; 240 } 241 } 242 243 private static class MoveRecord { 244 245 final int fromPos, toPos; 246 247 RecyclerView.ViewHolder from, to; 248 249 MoveRecord(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) { 250 this.from = from; 251 this.to = to; 252 fromPos = from.getAdapterPosition(); 253 toPos = to.getAdapterPosition(); 254 } 255 } 256} 257