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 17 package androidx.recyclerview.widget; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotEquals; 21 22 import android.content.Context; 23 import android.support.test.InstrumentationRegistry; 24 import android.support.test.filters.SmallTest; 25 import android.support.test.runner.AndroidJUnit4; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.ViewConfiguration; 29 import android.view.ViewGroup; 30 import android.widget.TextView; 31 32 import androidx.annotation.NonNull; 33 import androidx.core.view.InputDeviceCompat; 34 import androidx.core.view.ViewConfigurationCompat; 35 36 import org.junit.Before; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 40 @SmallTest 41 @RunWith(AndroidJUnit4.class) 42 public class RecyclerViewOnGenericMotionEventTest { 43 44 TestRecyclerView mRecyclerView; 45 46 @Before 47 public void setUp() throws Exception { 48 mRecyclerView = new TestRecyclerView(getContext()); 49 } 50 51 private Context getContext() { 52 return InstrumentationRegistry.getContext(); 53 } 54 55 private void layout() { 56 mRecyclerView.layout(0, 0, 320, 320); 57 } 58 59 @Test 60 public void rotaryEncoderVerticalScroll() { 61 MockLayoutManager layoutManager = new MockLayoutManager(true, true); 62 mRecyclerView.setLayoutManager(layoutManager); 63 layout(); 64 TouchUtils.scrollView( 65 MotionEvent.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER, mRecyclerView); 66 assertTotalScroll(0, (int) (-2f * getScaledVerticalScrollFactor())); 67 } 68 69 @Test 70 public void rotaryEncoderHorizontalScroll() { 71 // The encoder is one-dimensional, and can only scroll horizontally if vertical scrolling 72 // is not enabled. 73 MockLayoutManager layoutManager = new MockLayoutManager(true, false); 74 mRecyclerView.setLayoutManager(layoutManager); 75 layout(); 76 TouchUtils.scrollView( 77 MotionEvent.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER, mRecyclerView); 78 assertTotalScroll((int) (2f * getScaledHorizontalScrollFactor()), 0); 79 } 80 81 @Test 82 public void pointerVerticalScroll() { 83 MockLayoutManager layoutManager = new MockLayoutManager(true, true); 84 mRecyclerView.setLayoutManager(layoutManager); 85 layout(); 86 TouchUtils.scrollView( 87 MotionEvent.AXIS_VSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView); 88 assertTotalScroll(0, (int) (-2f * getScaledVerticalScrollFactor())); 89 } 90 91 @Test 92 public void pointerHorizontalScroll() { 93 MockLayoutManager layoutManager = new MockLayoutManager(true, true); 94 mRecyclerView.setLayoutManager(layoutManager); 95 layout(); 96 TouchUtils.scrollView( 97 MotionEvent.AXIS_HSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView); 98 assertTotalScroll((int) (2f * getScaledHorizontalScrollFactor()), 0); 99 } 100 101 @Test 102 public void nonZeroScaledVerticalScrollFactor() { 103 assertNotEquals(0, getScaledVerticalScrollFactor()); 104 } 105 106 @Test 107 public void nonZeroScaledHorizontalScrollFactor() { 108 assertNotEquals(0, getScaledHorizontalScrollFactor()); 109 } 110 111 private void assertTotalScroll(int x, int y) { 112 assertEquals("x total scroll", x, mRecyclerView.mTotalX); 113 assertEquals("y total scroll", y, mRecyclerView.mTotalY); 114 } 115 116 private static MotionEvent obtainScrollMotionEvent(int axis, int axisValue, int inputDevice) { 117 MotionEvent.PointerProperties[] pointerProperties = { new MotionEvent.PointerProperties() }; 118 MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); 119 coords.setAxisValue(axis, axisValue); 120 MotionEvent.PointerCoords[] pointerCoords = { coords }; 121 float xPrecision = 1; 122 float yPrecision = 1; 123 int deviceId = 0; 124 int edgeFlags = 0; 125 int flags = 0; 126 return MotionEvent.obtain(0, System.currentTimeMillis(), MotionEvent.ACTION_SCROLL, 127 1, pointerProperties, pointerCoords, 0, 0, xPrecision, yPrecision, deviceId, 128 edgeFlags, inputDevice, flags); 129 } 130 131 private float getScaledVerticalScrollFactor() { 132 return ViewConfigurationCompat.getScaledVerticalScrollFactor( 133 ViewConfiguration.get(getContext()), getContext()); 134 } 135 136 private float getScaledHorizontalScrollFactor() { 137 return ViewConfigurationCompat.getScaledHorizontalScrollFactor( 138 ViewConfiguration.get(getContext()), getContext()); 139 } 140 141 static class MockLayoutManager extends RecyclerView.LayoutManager { 142 143 private final boolean mCanScrollHorizontally; 144 145 private final boolean mCanScrollVertically; 146 147 MockLayoutManager(boolean canScrollHorizontally, boolean canScrollVertically) { 148 mCanScrollHorizontally = canScrollHorizontally; 149 mCanScrollVertically = canScrollVertically; 150 } 151 152 @Override 153 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 154 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 155 ViewGroup.LayoutParams.WRAP_CONTENT); 156 } 157 158 @Override 159 public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, 160 RecyclerView.State state) { 161 return dx; 162 } 163 164 @Override 165 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, 166 RecyclerView.State state) { 167 return dy; 168 } 169 170 @Override 171 public boolean canScrollHorizontally() { 172 return mCanScrollHorizontally; 173 } 174 175 @Override 176 public boolean canScrollVertically() { 177 return mCanScrollVertically; 178 } 179 } 180 181 static class MockAdapter extends RecyclerView.Adapter { 182 183 private int mCount = 0; 184 185 MockAdapter(int count) { 186 this.mCount = count; 187 } 188 189 @Override 190 public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 191 return new MockViewHolder(new TextView(parent.getContext())); 192 } 193 194 @Override 195 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 196 197 } 198 199 @Override 200 public int getItemCount() { 201 return mCount; 202 } 203 } 204 205 static class MockViewHolder extends RecyclerView.ViewHolder { 206 MockViewHolder(View itemView) { 207 super(itemView); 208 } 209 } 210 211 private static class TestRecyclerView extends RecyclerView { 212 int mTotalX = 0; 213 int mTotalY = 0; 214 215 TestRecyclerView(Context context) { 216 super(context); 217 } 218 219 boolean scrollByInternal(int x, int y, MotionEvent ev) { 220 mTotalX += x; 221 mTotalY += y; 222 return super.scrollByInternal(x, y, ev); 223 } 224 } 225 } 226