147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal *
447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * Licensed under the Apache License, Version 2.0 (the "License");
547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * you may not use this file except in compliance with the License.
647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * You may obtain a copy of the License at
747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal *
847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal *      http://www.apache.org/licenses/LICENSE-2.0
947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal *
1047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * Unless required by applicable law or agreed to in writing, software
1147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * distributed under the License is distributed on an "AS IS" BASIS,
1247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * See the License for the specific language governing permissions and
1447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * limitations under the License.
1547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal */
1647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
1847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
1947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
20ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
2147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
2247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.hamcrest.CoreMatchers.is;
2347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.hamcrest.MatcherAssert.assertThat;
2447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.junit.Assert.assertEquals;
2547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.junit.Assert.assertNotNull;
2647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.junit.Assert.assertNull;
2747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport static org.junit.Assert.assertTrue;
2847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
2947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.content.Context;
3047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.support.test.filters.MediumTest;
3147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.support.test.runner.AndroidJUnit4;
3247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.view.MotionEvent;
3347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.view.ViewGroup;
3447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport android.widget.EdgeEffect;
3547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
36c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
37c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.InputDeviceCompat;
38c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
3947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport org.junit.Before;
4047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport org.junit.Test;
4147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalimport org.junit.runner.RunWith;
4247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
4347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal/**
4447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal * Tests custom edge effect are properly applied when scrolling.
4547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal */
4647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal@MediumTest
4747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal@RunWith(AndroidJUnit4.class)
4847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyalpublic class CustomEdgeEffectTest extends BaseRecyclerViewInstrumentationTest {
4947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
5047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private static final int NUM_ITEMS = 10;
5147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
5247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private LinearLayoutManager mLayoutManager;
5347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private RecyclerView mRecyclerView;
5447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
5547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    @Before
5647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    public void setup() throws Throwable {
5747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mLayoutManager = new LinearLayoutManager(getActivity());
5847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mLayoutManager.ensureLayoutState();
5947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
6047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView = new RecyclerView(getActivity());
6147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView.setLayoutManager(mLayoutManager);
6247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView.setAdapter(new TestAdapter(NUM_ITEMS) {
6347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
6447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            @Override
658a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
6647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                    int viewType) {
6747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                TestViewHolder holder = super.onCreateViewHolder(parent, viewType);
6847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                holder.itemView.setMinimumHeight(mRecyclerView.getMeasuredHeight() * 2 / NUM_ITEMS);
6947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                return holder;
7047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            }
7147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        });
7247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        setRecyclerView(mRecyclerView);
7347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        getInstrumentation().waitForIdleSync();
7447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertThat("Test sanity", mRecyclerView.getChildCount() > 0, is(true));
7547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
7647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
7747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    @Test
7847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    public void testEdgeEffectDirections() throws Throwable {
7947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        TestEdgeEffectFactory factory = new TestEdgeEffectFactory();
8047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView.setEdgeEffectFactory(factory);
8147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollToPosition(0);
8247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        waitForIdleScroll(mRecyclerView);
8347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollViewBy(3);
8447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertNull(factory.mBottom);
8547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertNotNull(factory.mTop);
8647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertTrue(factory.mTop.mPullDistance > 0);
8747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
8847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollToPosition(NUM_ITEMS - 1);
8947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        waitForIdleScroll(mRecyclerView);
9047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollViewBy(-3);
9147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
9247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertNotNull(factory.mBottom);
9347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertTrue(factory.mBottom.mPullDistance > 0);
9447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
9547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
9647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    @Test
9747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    public void testEdgeEffectReplaced() throws Throwable {
9847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        TestEdgeEffectFactory factory1 = new TestEdgeEffectFactory();
9947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView.setEdgeEffectFactory(factory1);
10047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollToPosition(0);
10147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        waitForIdleScroll(mRecyclerView);
10247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
10347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollViewBy(3);
10447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertNotNull(factory1.mTop);
10547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        float oldPullDistance = factory1.mTop.mPullDistance;
10647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
10747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        waitForIdleScroll(mRecyclerView);
10847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        TestEdgeEffectFactory factory2 = new TestEdgeEffectFactory();
10947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mRecyclerView.setEdgeEffectFactory(factory2);
11047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        scrollViewBy(30);
11147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertNotNull(factory2.mTop);
11247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
11347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertTrue(factory2.mTop.mPullDistance > oldPullDistance);
11447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        assertEquals(oldPullDistance, factory1.mTop.mPullDistance, 0.1f);
11547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
11647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
11747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private void scrollViewBy(final int value) throws Throwable {
11847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        mActivityRule.runOnUiThread(new Runnable() {
11947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            @Override
12047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            public void run() {
12147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                TouchUtils.scrollView(MotionEvent.AXIS_VSCROLL, value,
12247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                        InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView);
12347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            }
12447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        });
12547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
12647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
12747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private class TestEdgeEffectFactory extends EdgeEffectFactory {
12847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
12947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        TestEdgeEffect mTop, mBottom;
13047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
13147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        @NonNull
13247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        @Override
13347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
13447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            TestEdgeEffect effect = new TestEdgeEffect(view.getContext());
13547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            if (direction == EdgeEffectFactory.DIRECTION_TOP) {
13647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                mTop = effect;
13747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            } else if (direction == EdgeEffectFactory.DIRECTION_BOTTOM) {
13847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal                mBottom = effect;
13947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            }
14047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            return effect;
14147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        }
14247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
14347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
14447ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    private class TestEdgeEffect extends EdgeEffect {
14547ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
14647ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        private float mPullDistance;
14747ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
14847ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        TestEdgeEffect(Context context) {
14947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            super(context);
15047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        }
15147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal
15247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        @Override
15347ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        public void onPull(float deltaDistance, float displacement) {
154d0697af515e2e6c949b52b4ad1805058c22fb9b9Sunny Goyal            onPull(deltaDistance);
155d0697af515e2e6c949b52b4ad1805058c22fb9b9Sunny Goyal        }
156d0697af515e2e6c949b52b4ad1805058c22fb9b9Sunny Goyal
157d0697af515e2e6c949b52b4ad1805058c22fb9b9Sunny Goyal        @Override
158d0697af515e2e6c949b52b4ad1805058c22fb9b9Sunny Goyal        public void onPull(float deltaDistance) {
15947ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal            mPullDistance = deltaDistance;
16047ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal        }
16147ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal    }
16247ab6d82dd22a73520a9b22766765a47cb28e9afSunny Goyal}
163