blob: 134f193adf5671811bdc08280968f5e2208a9629 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.recyclerview.widget;
import static androidx.recyclerview.widget.RecyclerView.VERTICAL;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.StateListDrawable;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.R;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class RecyclerViewFastScrollerTest extends BaseRecyclerViewInstrumentationTest {
private static final int FLAG_HORIZONTAL = 1;
private static final int FLAG_VERTICAL = 1 << 1;
private int mScrolledByY = -1000;
private int mScrolledByX = -1000;
private FastScroller mScroller;
private boolean mHide;
private void setContentView(final int layoutId) throws Throwable {
final Activity activity = mActivityRule.getActivity();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setContentView(layoutId);
}
});
}
@Test
public void xml_fastScrollEnabled_startsInvisibleAndAtTop() throws Throwable {
arrangeWithXml();
assertTrue("Expected centerY to start == 0", mScroller.mVerticalThumbCenterY == 0);
assertFalse("Expected thumb to start invisible", mScroller.isVisible());
}
@Test
public void scrollBy_displaysAndMovesFastScrollerThumb() throws Throwable {
arrangeWithXml();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.scrollBy(0, 400);
}
});
assertTrue("Expected centerY to be > 0" + mScroller.mVerticalThumbCenterY,
mScroller.mVerticalThumbCenterY > 0);
assertTrue("Expected thumb to be visible", mScroller.isVisible());
}
@Test
public void ui_dragsThumb_scrollsRecyclerView() throws Throwable {
arrangeWithXml();
// RecyclerView#scrollBy(int, int) used to cause the scroller thumb to show up.
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.scrollBy(0, 1);
mRecyclerView.scrollBy(0, -1);
}
});
int[] absoluteCoords = new int[2];
mRecyclerView.getLocationOnScreen(absoluteCoords);
TouchUtils.drag(InstrumentationRegistry.getInstrumentation(), mRecyclerView.getWidth() - 10,
mRecyclerView.getWidth() - 10, mScroller.mVerticalThumbCenterY + absoluteCoords[1],
mRecyclerView.getHeight() + absoluteCoords[1], 100);
assertTrue("Expected dragging thumb to move recyclerView",
mRecyclerView.computeVerticalScrollOffset() > 0);
}
@Test
public void properCleanUp() throws Throwable {
mRecyclerView = new RecyclerView(getActivity());
final Activity activity = mActivityRule.getActivity();
final CountDownLatch latch = new CountDownLatch(1);
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setContentView(
androidx.recyclerview.test.R.layout.fast_scrollbar_test_rv);
mRecyclerView = (RecyclerView) activity.findViewById(
androidx.recyclerview.test.R.id.recycler_view);
LinearLayoutManager layout = new LinearLayoutManager(activity.getBaseContext());
layout.setOrientation(VERTICAL);
mRecyclerView.setLayoutManager(layout);
mRecyclerView.setAdapter(new TestAdapter(50));
Resources res = getActivity().getResources();
mScroller = new FastScroller(mRecyclerView, (StateListDrawable) res.getDrawable(
androidx.recyclerview.test.R.drawable.fast_scroll_thumb_drawable),
res.getDrawable(
androidx.recyclerview.test.R.drawable
.fast_scroll_track_drawable),
(StateListDrawable) res.getDrawable(
androidx.recyclerview.test.R.drawable
.fast_scroll_thumb_drawable),
res.getDrawable(
androidx.recyclerview.test.R.drawable
.fast_scroll_track_drawable),
res.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
res.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
res.getDimensionPixelOffset(R.dimen.fastscroll_margin)) {
@Override
public void show() {
// Overriden to avoid animation calls in instrumentation thread
}
@Override
public void hide(int duration) {
latch.countDown();
mHide = true;
}
};
}
});
waitForIdleScroll(mRecyclerView);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.scrollBy(0, 400);
mScroller.attachToRecyclerView(new RecyclerView(getActivity()));
}
});
assertFalse(latch.await(2, TimeUnit.SECONDS));
assertFalse(mHide);
}
@Test
public void inflationTest() throws Throwable {
setContentView(androidx.recyclerview.test.R.layout.fast_scrollbar_test_rv);
getInstrumentation().waitForIdleSync();
RecyclerView view = (RecyclerView) getActivity().findViewById(
androidx.recyclerview.test.R.id.recycler_view);
assertTrue(view.getItemDecorationCount() == 1);
assertTrue(view.getItemDecorationAt(0) instanceof FastScroller);
FastScroller scroller = (FastScroller) view.getItemDecorationAt(0);
assertNotNull(scroller.getHorizontalThumbDrawable());
assertNotNull(scroller.getHorizontalTrackDrawable());
assertNotNull(scroller.getVerticalThumbDrawable());
assertNotNull(scroller.getVerticalTrackDrawable());
}
@Test
public void removeFastScrollerSuccessful() throws Throwable {
setContentView(androidx.recyclerview.test.R.layout.fast_scrollbar_test_rv);
getInstrumentation().waitForIdleSync();
final RecyclerView view = (RecyclerView) getActivity().findViewById(
androidx.recyclerview.test.R.id.recycler_view);
assertTrue(view.getItemDecorationCount() == 1);
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
view.removeItemDecorationAt(0);
assertTrue(view.getItemDecorationCount() == 0);
}
});
}
@UiThreadTest
@Test
public void initWithBadDrawables() throws Throwable {
arrangeWithCode();
Throwable exception = null;
try {
mRecyclerView.initFastScroller(null, null, null, null);
} catch (Throwable t) {
exception = t;
}
assertTrue(exception instanceof IllegalArgumentException);
}
@Test
public void verticalScrollUpdatesFastScrollThumb() throws Throwable {
scrollUpdatesFastScrollThumb(FLAG_VERTICAL);
}
@Test
public void horizontalScrollUpdatesFastScrollThumb() throws Throwable {
scrollUpdatesFastScrollThumb(FLAG_HORIZONTAL);
}
private void scrollUpdatesFastScrollThumb(int direction) throws Throwable {
arrangeWithCode();
mScroller.updateScrollPosition(direction == FLAG_VERTICAL ? 0 : 250,
direction == FLAG_VERTICAL ? 250 : 0);
if (direction == FLAG_VERTICAL) {
assertTrue("Expected 250 for centerY, got " + mScroller.mVerticalThumbCenterY,
mScroller.mVerticalThumbCenterY == 250);
assertTrue("Expected 250 for thumb height, got " + mScroller.mVerticalThumbHeight,
mScroller.mVerticalThumbHeight == 250);
} else if (direction == FLAG_HORIZONTAL) {
assertTrue("Expected 250 for centerX, got " + mScroller.mHorizontalThumbCenterX,
mScroller.mHorizontalThumbCenterX == 250);
assertTrue("Expected 250 for thumb width, got " + mScroller.mHorizontalThumbWidth,
mScroller.mHorizontalThumbWidth == 250);
}
assertTrue(mScroller.isVisible());
mScroller.updateScrollPosition(direction == FLAG_VERTICAL ? 0 : 42,
direction == FLAG_VERTICAL ? 42 : 0);
if (direction == FLAG_VERTICAL) {
assertTrue("Expected 146 for centerY, got " + mScroller.mVerticalThumbCenterY,
mScroller.mVerticalThumbCenterY == 146);
assertTrue("Expected 250 for thumb height, got " + mScroller.mVerticalThumbHeight,
mScroller.mVerticalThumbHeight == 250);
} else if (direction == FLAG_HORIZONTAL) {
assertTrue("Expected 146 for centerX, got " + mScroller.mHorizontalThumbCenterX,
mScroller.mHorizontalThumbCenterX == 146);
assertTrue("Expected 250 for thumb width, got " + mScroller.mHorizontalThumbWidth,
mScroller.mHorizontalThumbWidth == 250);
}
assertTrue(mScroller.isVisible());
}
@Test
public void draggingDoesNotTriggerFastScrollIfNotInThumb() throws Throwable {
arrangeWithCode();
mScroller.updateScrollPosition(0, 250);
final MotionEvent downEvent = MotionEvent.obtain(10, 10, MotionEvent.ACTION_DOWN, 250, 250,
0);
assertFalse(mScroller.onInterceptTouchEvent(mRecyclerView, downEvent));
final MotionEvent moveEvent = MotionEvent.obtain(10, 10, MotionEvent.ACTION_MOVE, 250, 275,
0);
assertFalse(mScroller.onInterceptTouchEvent(mRecyclerView, moveEvent));
}
@Test
public void verticalDraggingFastScrollThumbDoesActualScrolling() throws Throwable {
draggingFastScrollThumbDoesActualScrolling(FLAG_VERTICAL);
}
@Test
public void horizontalDraggingFastScrollThumbDoesActualScrolling() throws Throwable {
draggingFastScrollThumbDoesActualScrolling(FLAG_HORIZONTAL);
}
private void draggingFastScrollThumbDoesActualScrolling(int direction) throws Throwable {
arrangeWithCode();
mScroller.updateScrollPosition(direction == FLAG_VERTICAL ? 0 : 250,
direction == FLAG_VERTICAL ? 250 : 0);
final MotionEvent downEvent = MotionEvent.obtain(10, 10, MotionEvent.ACTION_DOWN,
direction == FLAG_VERTICAL ? 500 : 250, direction == FLAG_VERTICAL ? 250 : 500, 0);
assertTrue(mScroller.onInterceptTouchEvent(mRecyclerView, downEvent));
assertTrue(mScroller.isDragging());
final MotionEvent moveEvent = MotionEvent.obtain(10, 10, MotionEvent.ACTION_MOVE,
direction == FLAG_VERTICAL ? 500 : 221, direction == FLAG_VERTICAL ? 221 : 500, 0);
mScroller.onTouchEvent(mRecyclerView, moveEvent);
if (direction == FLAG_VERTICAL) {
assertTrue("Expected to get -29, but got " + mScrolledByY, mScrolledByY == -29);
} else {
assertTrue("Expected to get -29, but got " + mScrolledByX, mScrolledByX == -29);
}
}
private void arrangeWithXml() throws Throwable {
final TestActivity activity = mActivityRule.getActivity();
final TestedFrameLayout testedFrameLayout = activity.getContainer();
RecyclerView recyclerView = (RecyclerView) LayoutInflater.from(activity).inflate(
androidx.recyclerview.test.R.layout.fast_scrollbar_test_rv,
testedFrameLayout,
false);
LinearLayoutManager layout = new LinearLayoutManager(activity.getBaseContext());
layout.setOrientation(VERTICAL);
recyclerView.setLayoutManager(layout);
recyclerView.setAdapter(new TestAdapter(50));
mScroller = (FastScroller) recyclerView.getItemDecorationAt(0);
testedFrameLayout.expectLayouts(1);
testedFrameLayout.expectDraws(1);
setRecyclerView(recyclerView);
testedFrameLayout.waitForLayout(2);
testedFrameLayout.waitForDraw(2);
}
private void arrangeWithCode() throws Exception {
final int width = 500;
final int height = 500;
mRecyclerView = new RecyclerView(getActivity()) {
@Override
public int computeVerticalScrollRange() {
return 1000;
}
@Override
public int computeVerticalScrollExtent() {
return 500;
}
@Override
public int computeVerticalScrollOffset() {
return 250;
}
@Override
public int computeHorizontalScrollRange() {
return 1000;
}
@Override
public int computeHorizontalScrollExtent() {
return 500;
}
@Override
public int computeHorizontalScrollOffset() {
return 250;
}
@Override
public void scrollBy(int x, int y) {
mScrolledByY = y;
mScrolledByX = x;
}
};
mRecyclerView.setAdapter(new TestAdapter(50));
mRecyclerView.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
mRecyclerView.layout(0, 0, width, height);
Resources res = getActivity().getResources();
mScroller = new FastScroller(mRecyclerView, (StateListDrawable) res.getDrawable(
androidx.recyclerview.test.R.drawable.fast_scroll_thumb_drawable),
res.getDrawable(
androidx.recyclerview.test.R.drawable.fast_scroll_track_drawable),
(StateListDrawable) res.getDrawable(
androidx.recyclerview.test.R.drawable.fast_scroll_thumb_drawable),
res.getDrawable(
androidx.recyclerview.test.R.drawable.fast_scroll_track_drawable),
res.getDimensionPixelSize(R.dimen.fastscroll_default_thickness),
res.getDimensionPixelSize(R.dimen.fastscroll_minimum_range),
res.getDimensionPixelOffset(R.dimen.fastscroll_margin)) {
@Override
public void show() {
// Overriden to avoid animation calls in instrumentation thread
}
@Override
public void hide(int duration) {
mHide = true;
}
};
mRecyclerView.mEnableFastScroller = true;
// Draw it once so height/width gets updated
mScroller.onDrawOver(null, mRecyclerView, null);
}
private static class TestAdapter extends RecyclerView.Adapter {
private int mItemCount;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
ViewHolder(TextView v) {
super(v);
mTextView = v;
}
@Override
public String toString() {
return super.toString() + " '" + mTextView.getText();
}
}
TestAdapter(int itemCount) {
mItemCount = itemCount;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
final ViewHolder h = new ViewHolder(new TextView(parent.getContext()));
h.mTextView.setMinimumHeight(128);
h.mTextView.setPadding(20, 0, 20, 0);
h.mTextView.setFocusable(true);
h.mTextView.setBackgroundColor(Color.BLUE);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(
LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
lp.leftMargin = 10;
lp.rightMargin = 5;
lp.topMargin = 20;
lp.bottomMargin = 15;
h.mTextView.setLayoutParams(lp);
return h;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
holder.itemView.setTag("pos " + position);
}
@Override
public int getItemCount() {
return mItemCount;
}
}
}