1d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project 3d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 4d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * you may not use this file except in compliance with the License. 6d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * You may obtain a copy of the License at 7d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 8d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * 10d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * See the License for the specific language governing permissions and 14d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar * limitations under the License. 15d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar */ 16d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget; 18d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 19151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiriimport static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiriimport static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 22c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL; 23c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; 24c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport static org.hamcrest.CoreMatchers.is; 26e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertEquals; 27b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport static org.junit.Assert.assertFalse; 28e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertNotNull; 29b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport static org.junit.Assert.assertNull; 30e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertSame; 319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport static org.junit.Assert.assertThat; 32e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport static org.junit.Assert.assertTrue; 330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.Color; 359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.drawable.ColorDrawable; 369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.graphics.drawable.StateListDrawable; 37fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiriimport android.os.Build; 389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.support.test.filters.LargeTest; 39fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiriimport android.support.test.filters.SdkSuppress; 408edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyarimport android.util.Log; 419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiriimport android.util.StateSet; 42d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport android.view.View; 43504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport android.view.ViewGroup; 44a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent; 45d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 46c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull; 47c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.AccessibilityDelegateCompat; 48c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.ViewCompat; 49c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 50e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikasimport org.junit.Test; 51e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas 52d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.ArrayList; 53d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyarimport java.util.List; 54b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport java.util.concurrent.CountDownLatch; 55b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiriimport java.util.concurrent.TimeUnit; 56504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyarimport java.util.concurrent.atomic.AtomicInteger; 57d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 598edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar/** 608edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Includes tests for {@link LinearLayoutManager}. 618edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * <p> 628edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * Since most UI tests are not practical, these tests are focused on internal data representation 638edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * and stability of LinearLayoutManager in response to different events (state change, scrolling 648edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar * etc) where it is very hard to do manual testing. 658edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar */ 669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri@LargeTest 67999c3976674d20b0de5425490bdfe7415b9c6af2Yigit Boyarpublic class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest { 688edcb0bdeaba6931f9d8154f0c81f57da7ddab2aYigit Boyar 69046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard /** 70046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard * Tests that the LinearLayoutManager retains the focused element after multiple measure 71046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard * calls to the RecyclerView. There was a bug where the focused view was lost when the soft 72046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard * keyboard opened. This test simulates the measure/layout events triggered by the opening 73046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard * of the soft keyboard by making two calls to measure. A simulation was done because using 74046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard * the soft keyboard in the test caused many issues on API levels 15, 17 and 19. 75046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard */ 76151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri @Test 77046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable { 78046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 79046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Arrange. 80046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 81046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final RecyclerView recyclerView = inflateWrappedRV(); 82046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard ViewGroup.LayoutParams lp = recyclerView.getLayoutParams(); 83151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri lp.height = WRAP_CONTENT; 84151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri lp.width = MATCH_PARENT; 85046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.setHasFixedSize(true); 86151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 87046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final FocusableAdapter focusableAdapter = 88046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard new FocusableAdapter(50); 89046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.setAdapter(focusableAdapter); 90151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 91046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false); 92046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.setLayoutManager(mLayoutManager); 93151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 94151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri mLayoutManager.expectLayouts(1); 95151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri mActivityRule.runOnUiThread(new Runnable() { 96151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri @Override 97151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri public void run() { 98046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard getActivity().getContainer().addView(recyclerView); 99151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri } 100151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri }); 101046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.waitForLayout(3); 102046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 103046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard int width = recyclerView.getWidth(); 104046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard int height = recyclerView.getHeight(); 105046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final int widthMeasureSpec = 106046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 107046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final int fullHeightMeasureSpec = 108046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST); 109046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView 110046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // was previously laid out with the full height version. 111046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final int fullHeightMinusOneMeasureSpec = 112046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST); 113046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard final int halfHeightMeasureSpec = 114046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST); 115046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 116046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Act 1. 117046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 118046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard View toFocus = findLastFullyVisibleChild(recyclerView); 119046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard int focusIndex = recyclerView.getChildAdapterPosition(toFocus); 120046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 121046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard requestFocus(toFocus, false); 122046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 123046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.expectLayouts(1); 124151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri mActivityRule.runOnUiThread(new Runnable() { 125151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri @Override 126151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri public void run() { 127046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec); 128046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec); 129046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.layout( 130046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 131046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 132046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredWidth(), 133046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredHeight()); 134151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri } 135151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri }); 136046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.waitForLayout(3); 137151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 138046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Verify 1. 139151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 140151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 141151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri toFocus.hasFocus(), is(true)); 142151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri // Testing for partial visibility instead of full visibility since TextView calls 143151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect 144151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri // containing the content area. This rect is guaranteed to be fully visible whereas a 145151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri // portion of TextView could be out of bounds. 146046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard assertThat("Child view at adapter pos " + focusIndex + " should be fully visible.", 147046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard isViewPartiallyInBound(recyclerView, toFocus), is(true)); 148046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 149046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Act 2. 150046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 151046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.expectLayouts(1); 152046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mActivityRule.runOnUiThread(new Runnable() { 153151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri @Override 154046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard public void run() { 155046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec); 156046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.layout( 157046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 158046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 159046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredWidth(), 160046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredHeight()); 161151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri } 162151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri }); 163046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.waitForLayout(3); 164046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 165046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Verify 2. 166046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 167151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 168151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri toFocus.hasFocus(), is(true)); 169151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.", 170046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard isViewPartiallyInBound(recyclerView, toFocus)); 171046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 172046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Act 3. 173151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 174151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri // Now focus on the first fully visible EditText. 175046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard toFocus = findFirstFullyVisibleChild(recyclerView); 176046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard focusIndex = recyclerView.getChildAdapterPosition(toFocus); 177046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 178046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard requestFocus(toFocus, false); 179046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 180046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.expectLayouts(1); 181046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mActivityRule.runOnUiThread(new Runnable() { 182151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri @Override 183046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard public void run() { 184046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec); 185046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec); 186046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.layout( 187046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 188046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 0, 189046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredWidth(), 190046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard recyclerView.getMeasuredHeight()); 191151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri } 192151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri }); 193046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard mLayoutManager.waitForLayout(3); 194046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 195046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard // Assert 3. 196046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard 197151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.", 198046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard isViewPartiallyInBound(recyclerView, toFocus)); 199151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri } 200151e43e7057619d714bc0f91ac7e2139ee334058Keyvan Amiri 2010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void topUnfocusableViewsVisibility() throws Throwable { 2039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of child views that can be visible at any time. 2049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleChildCount = 5; 2059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveFocusablesCount = 2; 2069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveUnFocusablesCount = 18; 2079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final TestAdapter adapter = new TestAdapter( 2089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri consecutiveFocusablesCount + consecutiveUnFocusablesCount) { 2099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 2109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 2128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 2139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 2149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 2159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 2169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 2179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 2189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 2199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 2209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 2219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 2229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 2259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 2269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 2279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 2308a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 2319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 2329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 2339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position < consecutiveFocusablesCount) { 2349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 2359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 2369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 2379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 2389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 2399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // This height ensures that some portion of #visibleChildCount'th child is 2419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // off-bounds, creating more interesting test scenario. 2429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumHeight((mAttachedRv.getHeight() 2439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount); 2449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }; 2469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(VERTICAL, false, false).adapter(adapter).reverseLayout(true), 2479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri false); 2489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitForFirstLayout(); 2499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 2519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 0; 2529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View newFocused = mRecyclerView.getChildAt(focusIndex); 2539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri requestFocus(newFocused, true); 2549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 2559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 2569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 2579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 2589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the item (whether focusable or not) that just becomes fully 2609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // visible after focusSearch. 2619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int visibleIndex = 0; 2629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the above adapter position 2639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 2649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Navigate up through the focusable and unfocusable chunks. The focusable items should 2669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // become focused one by one until hitting the last focusable item, at which point, 2679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // unfocusable items should become visible on the screen until the currently focused item 2689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // stays on the screen. 2699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i = 0; i < adapter.getItemCount(); i++) { 2709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_UP, true); 2719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 2729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1)); 2739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex); 2749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2, 2759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri (visibleIndex + 1)); 2769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex); 2779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 2799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 2809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 2819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, toFocus.itemView)); 2829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.", 2839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 2849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 2869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 2889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void bottomUnfocusableViewsVisibility() throws Throwable { 2899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of child views that can be visible at any time. 2909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleChildCount = 5; 2919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveFocusablesCount = 2; 2929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveUnFocusablesCount = 18; 2939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final TestAdapter adapter = new TestAdapter( 2949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri consecutiveFocusablesCount + consecutiveUnFocusablesCount) { 2959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 2969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 2979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 2988a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 2999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 3009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 3019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 3029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 3039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 3049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 3059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 3069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 3079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 3089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 3119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 3129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 3139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 3168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 3179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 3189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 3199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position < consecutiveFocusablesCount) { 3209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 3219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 3229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 3239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 3249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 3259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // This height ensures that some portion of #visibleChildCount'th child is 3279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // off-bounds, creating more interesting test scenario. 3289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumHeight((mAttachedRv.getHeight() 3299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount); 3309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }; 3329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(VERTICAL, false, false).adapter(adapter), false); 3339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitForFirstLayout(); 3349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 3369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 0; 3379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View newFocused = mRecyclerView.getChildAt(focusIndex); 3389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri requestFocus(newFocused, true); 3399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 3409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 3419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 3429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 3439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the item (whether focusable or not) that just becomes fully 3459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // visible after focusSearch. 3469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int visibleIndex = 0; 3479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the above adapter position 3489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 3499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Navigate down through the focusable and unfocusable chunks. The focusable items should 3519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // become focused one by one until hitting the last focusable item, at which point, 3529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // unfocusable items should become visible on the screen until the currently focused item 3539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // stays on the screen. 3549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i = 0; i < adapter.getItemCount(); i++) { 3559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_DOWN, true); 3569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 3579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1)); 3589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex); 3599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2, 3609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri (visibleIndex + 1)); 3619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex); 3629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 3649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 3659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 3669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, toFocus.itemView)); 3679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.", 3689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 3699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 3739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void leftUnfocusableViewsVisibility() throws Throwable { 3749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of child views that can be visible at any time. 3759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleChildCount = 5; 3769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveFocusablesCount = 2; 3779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveUnFocusablesCount = 18; 3789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final TestAdapter adapter = new TestAdapter( 3799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri consecutiveFocusablesCount + consecutiveUnFocusablesCount) { 3809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 3819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 3838a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 3849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 3859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 3869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 3879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 3889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 3899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 3909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 3919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 3929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 3939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 3959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 3969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 3979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 3989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 3999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 4018a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 4029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 4039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 4049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position < consecutiveFocusablesCount) { 4059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 4069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 4079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 4089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 4099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 4109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // This width ensures that some portion of #visibleChildCount'th child is 4129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // off-bounds, creating more interesting test scenario. 4139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumWidth((mAttachedRv.getWidth() 4149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + mAttachedRv.getWidth() / (2 * visibleChildCount)) / visibleChildCount); 4159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }; 4179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(HORIZONTAL, false, false).adapter(adapter).reverseLayout(true), 4189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri false); 4199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitForFirstLayout(); 4209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 4229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 0; 4239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View newFocused = mRecyclerView.getChildAt(focusIndex); 4249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri requestFocus(newFocused, true); 4259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 4269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 4279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 4289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 4299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the item (whether focusable or not) that just becomes fully 4319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // visible after focusSearch. 4329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int visibleIndex = 0; 4339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the above adapter position 4349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 4359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Navigate left through the focusable and unfocusable chunks. The focusable items should 4379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // become focused one by one until hitting the last focusable item, at which point, 4389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // unfocusable items should become visible on the screen until the currently focused item 4399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // stays on the screen. 4409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i = 0; i < adapter.getItemCount(); i++) { 4419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_LEFT, true); 4429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 4439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1)); 4449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex); 4459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2, 4469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri (visibleIndex + 1)); 4479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex); 4489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 4509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 4519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 4529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, toFocus.itemView)); 4539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.", 4549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 4559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 4599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void rightUnfocusableViewsVisibility() throws Throwable { 4609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of child views that can be visible at any time. 4619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleChildCount = 5; 4629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveFocusablesCount = 2; 4639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int consecutiveUnFocusablesCount = 18; 4649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final TestAdapter adapter = new TestAdapter( 4659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri consecutiveFocusablesCount + consecutiveUnFocusablesCount) { 4669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 4679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 4698a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 4709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 4719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 4729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 4739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 4749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 4759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 4769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 4779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 4789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 4799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 4829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 4839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 4849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 4878a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 4889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 4899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 4909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position < consecutiveFocusablesCount) { 4919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 4929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 4939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 4949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 4959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 4969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 4979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // This width ensures that some portion of #visibleChildCount'th child is 4989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // off-bounds, creating more interesting test scenario. 4999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumWidth((mAttachedRv.getWidth() 5009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + mAttachedRv.getWidth() / (2 * visibleChildCount)) / visibleChildCount); 5019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }; 5039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(HORIZONTAL, false, false).adapter(adapter), false); 5049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitForFirstLayout(); 5059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 5079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 0; 5089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View newFocused = mRecyclerView.getChildAt(focusIndex); 5099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri requestFocus(newFocused, true); 5109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 5119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 5129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 5139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 5149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the item (whether focusable or not) that just becomes fully 5169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // visible after focusSearch. 5179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int visibleIndex = 0; 5189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the above adapter position 5199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 5209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Navigate right through the focusable and unfocusable chunks. The focusable items should 5229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // become focused one by one until hitting the last focusable item, at which point, 5239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // unfocusable items should become visible on the screen until the currently focused item 5249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // stays on the screen. 5259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i = 0; i < adapter.getItemCount(); i++) { 5269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_RIGHT, true); 5279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 5289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex = Math.min(consecutiveFocusablesCount - 1, (focusIndex + 1)); 5299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex); 5309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri visibleIndex = Math.min(consecutiveFocusablesCount + visibleChildCount - 2, 5319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri (visibleIndex + 1)); 5329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(visibleIndex); 5339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 5359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toFocus.itemView.hasFocus(), is(true)); 5369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 5379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, toFocus.itemView)); 5389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + visibleIndex + " should be fully visible.", 5399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 5409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 543fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri // Run this test on Jelly Bean and newer because clearFocus on API 15 will call 544fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri // requestFocus in ViewRootImpl when clearChildFocus is called. Whereas, in API 16 and above, 545fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri // this call is delayed until after onFocusChange callback is called. Thus on API 16+, there's a 546fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri // transient state of no child having focus during which onFocusChange is executed. This 547fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri // transient state does not exist on API 15-. 548fb3bd63eb148d98d9024cf4058baea071d696d52Keyvan Amiri @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) 5499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 550b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri public void unfocusableScrollingWhenFocusCleared() throws Throwable { 551b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // The maximum number of child views that can be visible at any time. 552b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final int visibleChildCount = 5; 553b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final int consecutiveFocusablesCount = 2; 554b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final int consecutiveUnFocusablesCount = 18; 555b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final TestAdapter adapter = new TestAdapter( 556b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri consecutiveFocusablesCount + consecutiveUnFocusablesCount) { 557b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri RecyclerView mAttachedRv; 558b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 559b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Override 5608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 561b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 562b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // Good to have colors for debugging 563b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri StateListDrawable stl = new StateListDrawable(); 564b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 565b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri new ColorDrawable(Color.RED)); 566b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 567b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri //noinspection deprecation used to support kitkat tests 568b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 569b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri return testViewHolder; 570b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 571b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 572b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Override 573b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 574b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri mAttachedRv = recyclerView; 575b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 576b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 577b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Override 5788a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 579b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri int position) { 580b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri super.onBindViewHolder(holder, position); 581b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri if (position < consecutiveFocusablesCount) { 582b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri holder.itemView.setFocusable(true); 583b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri holder.itemView.setFocusableInTouchMode(true); 584b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } else { 585b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri holder.itemView.setFocusable(false); 586b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri holder.itemView.setFocusableInTouchMode(false); 587b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 588b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // This height ensures that some portion of #visibleChildCount'th child is 589b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // off-bounds, creating more interesting test scenario. 590b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri holder.itemView.setMinimumHeight((mAttachedRv.getHeight() 591b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri + mAttachedRv.getHeight() / (2 * visibleChildCount)) / visibleChildCount); 592b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 593b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri }; 594b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri setupByConfig(new Config(VERTICAL, false, false).adapter(adapter), false); 595b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri waitForFirstLayout(); 596b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 597b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // adapter position of the currently focused item. 598b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri int focusIndex = 0; 599b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri View newFocused = mRecyclerView.getChildAt(focusIndex); 600b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri requestFocus(newFocused, true); 601b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 602b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri focusIndex); 603b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 604b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri toFocus.itemView.hasFocus(), is(true)); 605b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 606b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final View nextView = focusSearch(mRecyclerView.getFocusedChild(), View.FOCUS_DOWN, true); 607b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri focusIndex++; 608b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertThat("Child at position " + focusIndex + " should be focused", 609b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView.hasFocus(), 610b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri is(true)); 611b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri final CountDownLatch focusLatch = new CountDownLatch(1); 612b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri mActivityRule.runOnUiThread(new Runnable() { 613b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Override 614b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri public void run() { 615b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri nextView.setOnFocusChangeListener(new View.OnFocusChangeListener(){ 616b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Override 617b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri public void onFocusChange(View v, boolean hasFocus) { 618b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertNull("Focus just got cleared and no children should be holding" 619046a8a022958d8d21c32ad424e07d592fe87a8bdshepshapard + " focus now.", mRecyclerView.getFocusedChild()); 620b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri try { 621b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // Calling focusSearch should be a no-op here since even though there 622b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // are unfocusable views down to scroll to, none of RV's children hold 623b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // focus at this stage. 624b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri View focusedChild = focusSearch(v, View.FOCUS_DOWN, true); 625b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertNull("Calling focusSearch should be no-op when no children hold" 626b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri + "focus", focusedChild); 627b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // No scrolling should have happened, so any unfocusables that were 628b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri // invisible should still be invisible. 629b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri RecyclerView.ViewHolder unforcusablePartiallyVisibleChild = 630b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri mRecyclerView.findViewHolderForAdapterPosition( 631b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri visibleChildCount - 1); 632b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertFalse("Child view at adapter pos " + (visibleChildCount - 1) 633b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri + " should not be fully visible.", 634b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri isViewFullyInBound(mRecyclerView, 635b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri unforcusablePartiallyVisibleChild.itemView)); 636b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } catch (Throwable t) { 637b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri postExceptionToInstrumentation(t); 638b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 639b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 640b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri }); 641b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri nextView.clearFocus(); 642b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri focusLatch.countDown(); 643b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 644b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri }); 645b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertTrue(focusLatch.await(2, TimeUnit.SECONDS)); 646b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri assertThat("Child at position " + focusIndex + " should no longer be focused", 647b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView.hasFocus(), 648b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri is(false)); 649b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri } 650b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri 651b174744db6a70f384d44caeb43e10854652e81b9Keyvan Amiri @Test 6520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItem() throws Throwable { 6531f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6541f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout( 6551f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar false), 100, 0); 6561f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6571f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6580a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6590a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemReverse() throws Throwable { 6601f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6611f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout(true), 100, 6621f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 0); 6631f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6641f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6650a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6660a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemStackFromEnd() throws Throwable { 6671f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6681f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(false), 100, 6691f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 99); 6701f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6711f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemStackFromEndAndReverse() throws Throwable { 6741f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6751f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(true), 100, 6761f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 99); 6771f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6781f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6800a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemHorizontal() throws Throwable { 6811f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6821f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout( 6831f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar false), 100, 0); 6841f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6851f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemReverseHorizontal() throws Throwable { 6881f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6891f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout(true), 6901f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 100, 0); 6911f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6921f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 6930a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 6940a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemStackFromEndHorizontal() throws Throwable { 6951f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 6961f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(false), 6971f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 100, 99); 6981f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 6991f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 7000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 7010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeAnchorItemStackFromEndAndReverseHorizontal() throws Throwable { 7021f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar removeAnchorItemTest( 7031f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(true), 100, 7041f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 99); 7051f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7061f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 7071f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar /** 7081f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar * This tests a regression where predictive animations were not working as expected when the 7091f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar * first item is removed and there aren't any more items to add from that direction. 7101f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar * First item refers to the default anchor item. 7111f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar */ 7121f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar public void removeAnchorItemTest(final Config config, int adapterSize, 7131f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar final int removePos) throws Throwable { 7141f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar config.adapter(new TestAdapter(adapterSize) { 7151f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar @Override 7168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 7171f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar int position) { 7181f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar super.onBindViewHolder(holder, position); 7191f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 7201f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (!(lp instanceof ViewGroup.MarginLayoutParams)) { 7211f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar lp = new ViewGroup.MarginLayoutParams(0, 0); 7221f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar holder.itemView.setLayoutParams(lp); 7231f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7241f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; 7251f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar final int maxSize; 7261f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (config.mOrientation == HORIZONTAL) { 7271f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar maxSize = mRecyclerView.getWidth(); 728e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT; 7291f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } else { 7301f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar maxSize = mRecyclerView.getHeight(); 731e12dfa03641ad9cf0ddf272675bbe7d1198adbfdAurimas Liutikas mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT; 7321f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7331f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 7341f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar final int desiredSize; 7351f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (position == removePos) { 7361f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar // make it large 7371f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar desiredSize = maxSize / 4; 7381f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } else { 7391f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar // make it small 7401f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar desiredSize = maxSize / 8; 7411f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7421f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (config.mOrientation == HORIZONTAL) { 7431f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mlp.width = desiredSize; 7441f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } else { 7451f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mlp.height = desiredSize; 7461f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7471f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7481f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar }); 7491f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar setupByConfig(config, true); 7501f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar final int childCount = mLayoutManager.getChildCount(); 7511f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar RecyclerView.ViewHolder toBeRemoved = null; 7521f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar List<RecyclerView.ViewHolder> toBeMoved = new ArrayList<RecyclerView.ViewHolder>(); 7531f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar for (int i = 0; i < childCount; i++) { 7541f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar View child = mLayoutManager.getChildAt(i); 7551f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child); 7561f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (holder.getAdapterPosition() == removePos) { 7571f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar toBeRemoved = holder; 7581f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } else { 7591f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar toBeMoved.add(holder); 7601f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7611f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7621f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertNotNull("test sanity", toBeRemoved); 7631f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertEquals("test sanity", childCount - 1, toBeMoved.size()); 7641f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar LoggingItemAnimator loggingItemAnimator = new LoggingItemAnimator(); 7651f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mRecyclerView.setItemAnimator(loggingItemAnimator); 7661f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.reset(); 7671f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.expectRunPendingAnimationsCall(1); 7681f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mLayoutManager.expectLayouts(2); 7691f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mTestAdapter.deleteAndNotify(removePos, 1); 7701f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mLayoutManager.waitForLayout(1); 7711f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.waitForPendingAnimationsCall(2); 7721f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertTrue("removed child should receive remove animation", 7731f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.mRemoveVHs.contains(toBeRemoved)); 7741f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar for (RecyclerView.ViewHolder vh : toBeMoved) { 7751f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertTrue("view holder should be in moved list", 7761f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.mMoveVHs.contains(vh)); 7771f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7781f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar List<RecyclerView.ViewHolder> newHolders = new ArrayList<RecyclerView.ViewHolder>(); 7791f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar for (int i = 0; i < mLayoutManager.getChildCount(); i++) { 7801f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar View child = mLayoutManager.getChildAt(i); 7811f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(child); 7821f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar if (toBeRemoved != holder && !toBeMoved.contains(holder)) { 7831f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar newHolders.add(holder); 7841f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7851f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7861f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertTrue("some new children should show up for the new space", newHolders.size() > 0); 7871f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertEquals("no items should receive animate add since they are not new", 0, 7881f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.mAddVHs.size()); 7891f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar for (RecyclerView.ViewHolder holder : newHolders) { 7901f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertTrue("new holder should receive a move animation", 7911f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar loggingItemAnimator.mMoveVHs.contains(holder)); 7921f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7931f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar assertTrue("control against adding too many children due to bad layout state preparation." 7941f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar + " initial:" + childCount + ", current:" + mRecyclerView.getChildCount(), 7951f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar mRecyclerView.getChildCount() <= childCount + 3 /*1 for removed view, 2 for its size*/); 7961f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar } 7971f5c7b76bfc3da85513e6d2c6aa1058339f0c625Yigit Boyar 79808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu void waitOneCycle() throws Throwable { 79908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mActivityRule.runOnUiThread(new Runnable() { 80008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 80108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void run() { 80208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 80308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu }); 80408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 80508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 806b84e677c6a8709e68db54ecc0475038592277051Dake Gu @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT) 80708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Test 80808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void hiddenNoneRemoveViewAccessibility() throws Throwable { 80908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final Config config = new Config(); 81008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu int adapterSize = 1000; 81108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final boolean[] firstItemSpecialSize = new boolean[] {false}; 81208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu TestAdapter adapter = new TestAdapter(adapterSize) { 81308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 8148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 81508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu int position) { 81608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu super.onBindViewHolder(holder, position); 81708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 81808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu if (!(lp instanceof ViewGroup.MarginLayoutParams)) { 81908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu lp = new ViewGroup.MarginLayoutParams(0, 0); 82008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu holder.itemView.setLayoutParams(lp); 82108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 82208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; 82308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final int maxSize; 82408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu if (config.mOrientation == HORIZONTAL) { 82508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu maxSize = mRecyclerView.getWidth(); 82608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT; 82708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } else { 82808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu maxSize = mRecyclerView.getHeight(); 82908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT; 83008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 83108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 83208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final int desiredSize; 83308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu if (position == 0 && firstItemSpecialSize[0]) { 83408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu desiredSize = maxSize / 3; 83508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } else { 83608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu desiredSize = maxSize / 8; 83708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 83808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu if (config.mOrientation == HORIZONTAL) { 83908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mlp.width = desiredSize; 84008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } else { 84108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mlp.height = desiredSize; 84208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 84308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 84408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 84508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 84608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void onBindViewHolder(TestViewHolder holder, 84708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu int position, List<Object> payloads) { 84808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu onBindViewHolder(holder, position); 84908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 85008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu }; 85108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu adapter.setHasStableIds(false); 85208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu config.adapter(adapter); 85308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu setupByConfig(config, true); 85408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final DummyItemAnimator itemAnimator = new DummyItemAnimator(); 85508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mRecyclerView.setItemAnimator(itemAnimator); 85608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 85708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // push last item out by increasing first item's size 85808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final int childBeingPushOut = mLayoutManager.getChildCount() - 1; 85908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu RecyclerView.ViewHolder itemViewHolder = mRecyclerView 86008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu .findViewHolderForAdapterPosition(childBeingPushOut); 86108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final int originalAccessibility = ViewCompat.getImportantForAccessibility( 86208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu itemViewHolder.itemView); 86308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == originalAccessibility 86408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == originalAccessibility); 86508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 86608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu itemAnimator.expect(DummyItemAnimator.MOVE_START, 1); 86708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mActivityRule.runOnUiThread(new Runnable() { 86808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 86908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void run() { 87008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu firstItemSpecialSize[0] = true; 87108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mTestAdapter.notifyItemChanged(0, "XXX"); 87208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 87308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu }); 87408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // wait till itemAnimator starts which will block itemView's accessibility 87508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu itemAnimator.waitFor(DummyItemAnimator.MOVE_START); 87608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // RV Changes accessiblity after onMoveStart, so wait one more cycle. 87708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu waitOneCycle(); 87808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu assertTrue(itemAnimator.getMovesAnimations().contains(itemViewHolder)); 87908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu assertEquals(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, 88008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu ViewCompat.getImportantForAccessibility(itemViewHolder.itemView)); 88108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 88208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // notify Change again to run predictive animation. 88308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mLayoutManager.expectLayouts(2); 88408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mActivityRule.runOnUiThread(new Runnable() { 88508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 88608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void run() { 88708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mTestAdapter.notifyItemChanged(0, "XXX"); 88808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 88908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu }); 89008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mLayoutManager.waitForLayout(1); 89108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu mActivityRule.runOnUiThread(new Runnable() { 89208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu @Override 89308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu public void run() { 89408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu itemAnimator.endAnimations(); 89508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 89608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu }); 89708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // scroll to the view being pushed out, it should get same view from cache as the item 89808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // in adapter does not change. 89908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu smoothScrollToPosition(childBeingPushOut); 90008169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu RecyclerView.ViewHolder itemViewHolder2 = mRecyclerView 90108169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu .findViewHolderForAdapterPosition(childBeingPushOut); 90208169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu assertSame(itemViewHolder, itemViewHolder2); 90308169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu // the important for accessibility should be reset to YES/AUTO: 90408169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu final int newAccessibility = ViewCompat.getImportantForAccessibility( 90508169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu itemViewHolder.itemView); 90608169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == newAccessibility 90708169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == newAccessibility); 90808169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu } 90908169a21843e8a9b2d27a44cb4a1d8c49f051dcbDake Gu 9100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9112ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu public void layoutFrozenBug70402422() throws Throwable { 9122ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu final Config config = new Config(); 9132ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu TestAdapter adapter = new TestAdapter(2); 9142ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu adapter.setHasStableIds(false); 9152ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu config.adapter(adapter); 9162ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu setupByConfig(config, true); 9172ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu final DummyItemAnimator itemAnimator = new DummyItemAnimator(); 9182ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mRecyclerView.setItemAnimator(itemAnimator); 9192ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu 9202ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu final View firstItemView = mRecyclerView 9212ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu .findViewHolderForAdapterPosition(0).itemView; 9222ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu 9232ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu itemAnimator.expect(DummyItemAnimator.REMOVE_START, 1); 9242ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mTestAdapter.deleteAndNotify(1, 1); 9252ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu itemAnimator.waitFor(DummyItemAnimator.REMOVE_START); 9262ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu 9272ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mActivityRule.runOnUiThread(new Runnable() { 9282ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu @Override 9292ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu public void run() { 9302ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mRecyclerView.setLayoutFrozen(true); 9312ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu } 9322ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu }); 9332ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu // requestLayout during item animation, which should be eaten by setLayoutFrozen(true) 9342ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mActivityRule.runOnUiThread(new Runnable() { 9352ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu @Override 9362ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu public void run() { 9372ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu firstItemView.requestLayout(); 9382ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu } 9392ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu }); 9402ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu assertTrue(firstItemView.isLayoutRequested()); 9412ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu assertFalse(mRecyclerView.isLayoutRequested()); 9422ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mActivityRule.runOnUiThread(new Runnable() { 9432ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu @Override 9442ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu public void run() { 9452ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu itemAnimator.endAnimations(); 9462ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu } 9472ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu }); 9482ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu // When setLayoutFrozen(false), the firstItemView should run a layout pass and clear 9492ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu // isLayoutRequested() flag. 9502ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mLayoutManager.expectLayouts(1); 9512ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mActivityRule.runOnUiThread(new Runnable() { 9522ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu @Override 9532ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu public void run() { 9542ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mRecyclerView.setLayoutFrozen(false); 9552ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu } 9562ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu }); 9572ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu mLayoutManager.waitForLayout(1); 9582ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu assertFalse(firstItemView.isLayoutRequested()); 9592ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu assertFalse(mRecyclerView.isLayoutRequested()); 9602ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu } 9612ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu 9622ac806c3b183b78492fa9cf89b199892d078c4d3Dake Gu @Test 9630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepFocusOnRelayout() throws Throwable { 964310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true); 965310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar int center = (mLayoutManager.findLastVisibleItemPosition() 966310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar - mLayoutManager.findFirstVisibleItemPosition()) / 2; 967115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center); 968310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView); 969959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar requestFocus(vh.itemView, true); 970310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertTrue("view should have the focus", vh.itemView.hasFocus()); 971310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar // add a bunch of items right before that view, make sure it keeps its position 972310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.expectLayouts(2); 973310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar final int childCountToAdd = mRecyclerView.getChildCount() * 2; 974310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mTestAdapter.addAndNotify(center, childCountToAdd); 975310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar center += childCountToAdd; // offset item 976310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForLayout(2); 977310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.waitForAnimationsToEnd(20); 978115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center); 979310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertNotNull("focused child should stay in layout", postVH); 980310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertSame("same view holder should be kept for unchanged child", vh, postVH); 981310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar assertEquals("focused child's screen position should stay unchanged", top, 982310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView)); 983310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar } 984310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar 9850a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepFullFocusOnResize() throws Throwable { 987542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true); 988542f1260934df280985294eaef1ec8469863281fYigit Boyar } 989542f1260934df280985294eaef1ec8469863281fYigit Boyar 9900a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9910a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepPartialFocusOnResize() throws Throwable { 992542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false); 993542f1260934df280985294eaef1ec8469863281fYigit Boyar } 994542f1260934df280985294eaef1ec8469863281fYigit Boyar 9950a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9960a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepReverseFullFocusOnResize() throws Throwable { 997542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true); 998542f1260934df280985294eaef1ec8469863281fYigit Boyar } 999542f1260934df280985294eaef1ec8469863281fYigit Boyar 10000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepReversePartialFocusOnResize() throws Throwable { 1002542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false); 1003542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1004542f1260934df280985294eaef1ec8469863281fYigit Boyar 10050a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10060a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepStackFromEndFullFocusOnResize() throws Throwable { 1007542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true); 1008542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1009542f1260934df280985294eaef1ec8469863281fYigit Boyar 10100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void keepStackFromEndPartialFocusOnResize() throws Throwable { 1012542f1260934df280985294eaef1ec8469863281fYigit Boyar keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false); 1013542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1014542f1260934df280985294eaef1ec8469863281fYigit Boyar 1015542f1260934df280985294eaef1ec8469863281fYigit Boyar public void keepFocusOnResizeTest(final Config config, boolean fullyVisible) throws Throwable { 1016542f1260934df280985294eaef1ec8469863281fYigit Boyar setupByConfig(config, true); 1017542f1260934df280985294eaef1ec8469863281fYigit Boyar final int targetPosition; 1018542f1260934df280985294eaef1ec8469863281fYigit Boyar if (config.mStackFromEnd) { 1019542f1260934df280985294eaef1ec8469863281fYigit Boyar targetPosition = mLayoutManager.findFirstVisibleItemPosition(); 1020542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 1021542f1260934df280985294eaef1ec8469863281fYigit Boyar targetPosition = mLayoutManager.findLastVisibleItemPosition(); 1022542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1023542f1260934df280985294eaef1ec8469863281fYigit Boyar final OrientationHelper helper = mLayoutManager.mOrientationHelper; 1024542f1260934df280985294eaef1ec8469863281fYigit Boyar final RecyclerView.ViewHolder vh = mRecyclerView 1025542f1260934df280985294eaef1ec8469863281fYigit Boyar .findViewHolderForLayoutPosition(targetPosition); 1026542f1260934df280985294eaef1ec8469863281fYigit Boyar 1027542f1260934df280985294eaef1ec8469863281fYigit Boyar // scroll enough to offset the child 1028542f1260934df280985294eaef1ec8469863281fYigit Boyar int startMargin = helper.getDecoratedStart(vh.itemView) - 1029542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 1030542f1260934df280985294eaef1ec8469863281fYigit Boyar int endMargin = helper.getEndAfterPadding() - 1031542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 1032542f1260934df280985294eaef1ec8469863281fYigit Boyar Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin); 1033959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar requestFocus(vh.itemView, true); 1034959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar assertTrue("view should gain the focus", vh.itemView.hasFocus()); 1035542f1260934df280985294eaef1ec8469863281fYigit Boyar // scroll enough to offset the child 1036542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(vh.itemView) - 1037542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 1038542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - 1039542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 1040542f1260934df280985294eaef1ec8469863281fYigit Boyar 1041542f1260934df280985294eaef1ec8469863281fYigit Boyar Log.d(TAG, "start margin " + startMargin + " , end margin:" + endMargin); 1042542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("View should become fully visible", startMargin >= 0 && endMargin >= 0); 1043542f1260934df280985294eaef1ec8469863281fYigit Boyar 1044542f1260934df280985294eaef1ec8469863281fYigit Boyar int expectedOffset = 0; 1045542f1260934df280985294eaef1ec8469863281fYigit Boyar boolean offsetAtStart = false; 1046542f1260934df280985294eaef1ec8469863281fYigit Boyar if (!fullyVisible) { 1047542f1260934df280985294eaef1ec8469863281fYigit Boyar // move it a bit such that it is no more fully visible 1048542f1260934df280985294eaef1ec8469863281fYigit Boyar final int childSize = helper 1049542f1260934df280985294eaef1ec8469863281fYigit Boyar .getDecoratedMeasurement(vh.itemView); 1050542f1260934df280985294eaef1ec8469863281fYigit Boyar expectedOffset = childSize / 3; 1051542f1260934df280985294eaef1ec8469863281fYigit Boyar if (startMargin < endMargin) { 1052542f1260934df280985294eaef1ec8469863281fYigit Boyar scrollBy(expectedOffset); 1053542f1260934df280985294eaef1ec8469863281fYigit Boyar offsetAtStart = true; 1054542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 1055542f1260934df280985294eaef1ec8469863281fYigit Boyar scrollBy(-expectedOffset); 1056542f1260934df280985294eaef1ec8469863281fYigit Boyar offsetAtStart = false; 1057542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1058542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(vh.itemView) - 1059542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getStartAfterPadding(); 1060542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - 1061542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedEnd(vh.itemView); 1062542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("test sanity, view should not be fully visible", startMargin < 0 1063542f1260934df280985294eaef1ec8469863281fYigit Boyar || endMargin < 0); 1064542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1065542f1260934df280985294eaef1ec8469863281fYigit Boyar 1066542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.expectLayouts(1); 106742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 1068542f1260934df280985294eaef1ec8469863281fYigit Boyar @Override 1069542f1260934df280985294eaef1ec8469863281fYigit Boyar public void run() { 1070542f1260934df280985294eaef1ec8469863281fYigit Boyar final ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams(); 1071542f1260934df280985294eaef1ec8469863281fYigit Boyar if (config.mOrientation == HORIZONTAL) { 1072542f1260934df280985294eaef1ec8469863281fYigit Boyar layoutParams.width = mRecyclerView.getWidth() / 2; 1073542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 1074542f1260934df280985294eaef1ec8469863281fYigit Boyar layoutParams.height = mRecyclerView.getHeight() / 2; 1075542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1076542f1260934df280985294eaef1ec8469863281fYigit Boyar mRecyclerView.setLayoutParams(layoutParams); 1077542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1078542f1260934df280985294eaef1ec8469863281fYigit Boyar }); 1079542f1260934df280985294eaef1ec8469863281fYigit Boyar Thread.sleep(100); 1080542f1260934df280985294eaef1ec8469863281fYigit Boyar // add a bunch of items right before that view, make sure it keeps its position 1081542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.waitForLayout(2); 1082542f1260934df280985294eaef1ec8469863281fYigit Boyar mLayoutManager.waitForAnimationsToEnd(20); 1083542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("view should preserve the focus", vh.itemView.hasFocus()); 1084542f1260934df280985294eaef1ec8469863281fYigit Boyar final RecyclerView.ViewHolder postVH = mRecyclerView 1085542f1260934df280985294eaef1ec8469863281fYigit Boyar .findViewHolderForLayoutPosition(targetPosition); 1086542f1260934df280985294eaef1ec8469863281fYigit Boyar assertNotNull("focused child should stay in layout", postVH); 1087542f1260934df280985294eaef1ec8469863281fYigit Boyar assertSame("same view holder should be kept for unchanged child", vh, postVH); 1088542f1260934df280985294eaef1ec8469863281fYigit Boyar View focused = postVH.itemView; 1089542f1260934df280985294eaef1ec8469863281fYigit Boyar 1090542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin = helper.getDecoratedStart(focused) - helper.getStartAfterPadding(); 1091542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin = helper.getEndAfterPadding() - helper.getDecoratedEnd(focused); 1092542f1260934df280985294eaef1ec8469863281fYigit Boyar 1093542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child should be somewhat visible", 1094542f1260934df280985294eaef1ec8469863281fYigit Boyar helper.getDecoratedStart(focused) < helper.getEndAfterPadding() 1095542f1260934df280985294eaef1ec8469863281fYigit Boyar && helper.getDecoratedEnd(focused) > helper.getStartAfterPadding()); 1096542f1260934df280985294eaef1ec8469863281fYigit Boyar if (fullyVisible) { 1097542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child end should stay fully visible", 1098542f1260934df280985294eaef1ec8469863281fYigit Boyar endMargin >= 0); 1099542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("focused child start should stay fully visible", 1100542f1260934df280985294eaef1ec8469863281fYigit Boyar startMargin >= 0); 1101542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 1102542f1260934df280985294eaef1ec8469863281fYigit Boyar if (offsetAtStart) { 1103542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("start should preserve its offset", startMargin < 0); 1104542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("end should be visible", endMargin >= 0); 1105542f1260934df280985294eaef1ec8469863281fYigit Boyar } else { 1106542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("end should preserve its offset", endMargin < 0); 1107542f1260934df280985294eaef1ec8469863281fYigit Boyar assertTrue("start should be visible", startMargin >= 0); 1108542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1109542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1110542f1260934df280985294eaef1ec8469863281fYigit Boyar } 1111542f1260934df280985294eaef1ec8469863281fYigit Boyar 11120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 11130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollToPositionWithPredictive() throws Throwable { 11146e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET); 11156e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 11166e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(3, 20); 11176e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 11186e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 11196e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar LinearLayoutManager.INVALID_OFFSET); 11206e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 11216e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10); 11226e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 11236e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 11240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 11250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void recycleDuringAnimations() throws Throwable { 1126504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final AtomicInteger childCount = new AtomicInteger(0); 1127504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestAdapter adapter = new TestAdapter(300) { 1128504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 11298a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 1130504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int viewType) { 1131504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 1132504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 1133504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 1134504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder); 1135504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1136504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return testViewHolder; 1137504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1138504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 1139310e95e1c6dfe4f26ef594233e65e1ff83e0f1ffYigit Boyar setupByConfig(new Config(VERTICAL, false, false).itemCount(300) 1140504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar .adapter(adapter), true); 1141504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1142504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() { 1143504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 1144504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public void putRecycledView(RecyclerView.ViewHolder scrap) { 1145504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar super.putRecycledView(scrap); 1146504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar int cnt = childCount.decrementAndGet(); 1147504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 1148504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap); 1149504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1150504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1151504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1152504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar @Override 1153504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar public RecyclerView.ViewHolder getRecycledView(int viewType) { 1154504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType); 1155504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (recycledView != null) { 1156504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar final int cnt = childCount.incrementAndGet(); 1157504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar if (DEBUG) { 1158504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView); 1159504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1160504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1161504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar return recycledView; 1162504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1163504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar }; 1164504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500); 1165504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.setRecycledViewPool(pool); 1166504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1167504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1168504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now keep adding children to trigger more children being created etc. 1169504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 1170504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(15, 1); 1171504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(15); 1172504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1173504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 1174504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 1175504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 1176504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 1177504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1178504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar // now trigger lots of add again, followed by a scroll to position 1179504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar for (int i = 0; i < 100; i ++) { 1180504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar adapter.addAndNotify(5 + (i % 3) * 3, 1); 1181504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar Thread.sleep(25); 1182504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 118367ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard 118467ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard final AtomicInteger lastVisiblePosition = new AtomicInteger(); 118567ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard mActivityRule.runOnUiThread(new Runnable() { 118667ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard @Override 118767ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard public void run() { 118867ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard lastVisiblePosition.set(mLayoutManager.findLastVisibleItemPosition()); 118967ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard } 119067ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard }); 119167ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard 119267ca5f28982af51750dd07f94757d3f6ad5fe7fashepshapard smoothScrollToPosition(lastVisiblePosition.get() + 20); 1193504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar waitForAnimations(2); 1194504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar getInstrumentation().waitForIdleSync(); 1195504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar assertEquals("Children count should add up", childCount.get(), 1196504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size()); 1197504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar } 1198504c54ea52c1b2aae6f8f4ae128f1dcaac7e3f6aYigit Boyar 1199d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar 12000a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12010a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void dontRecycleChildrenOnDetach() throws Throwable { 120249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(false), true); 120342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 120449c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 120549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 120649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 120734a33c72e21528b39fe39ca1048ebb9c0d617647Yigit Boyar ((ViewGroup)mRecyclerView.getParent()).removeView(mRecyclerView); 120849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("No views are recycled", recyclerSize, 120949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 121049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 121149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 121249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 121349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 12140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12150a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void recycleChildrenOnDetach() throws Throwable { 121649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar setupByConfig(new Config().recycleChildrenOnDetach(true), true); 121749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar final int childCount = mLayoutManager.getChildCount(); 121842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 121949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar @Override 122049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar public void run() { 122149c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size(); 122249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews( 122349c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mTestAdapter.getItemViewType(0), recyclerSize + childCount); 122434a33c72e21528b39fe39ca1048ebb9c0d617647Yigit Boyar ((ViewGroup)mRecyclerView.getParent()).removeView(mRecyclerView); 122549c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar assertEquals("All children should be recycled", childCount + recyclerSize, 122649c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar mRecyclerView.mRecycler.getRecycledViewPool().size()); 122749c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 122849c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar }); 122949c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar } 123049c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar 12310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollAndClear() throws Throwable { 12333d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev setupByConfig(new Config(), true); 12343d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 12353d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev assertTrue("Children not laid out", mLayoutManager.collectChildCoordinates().size() > 0); 12363d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 12373d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev mLayoutManager.expectLayouts(1); 123842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 12393d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev @Override 12403d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev public void run() { 12413d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev mLayoutManager.scrollToPositionWithOffset(1, 0); 12423d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev mTestAdapter.clearOnUIThread(); 12433d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev } 12443d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev }); 12453d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev mLayoutManager.waitForLayout(2); 12463d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 12473d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev assertEquals("Remaining children", 0, mLayoutManager.collectChildCoordinates().size()); 12483d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev } 12493d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 12503d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 12510a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12520a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void accessibilityPositions() throws Throwable { 1253a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar setupByConfig(new Config(VERTICAL, false, false), true); 1254a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityDelegateCompat delegateCompat = mRecyclerView 1255a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar .getCompatAccessibilityDelegate(); 1256a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar final AccessibilityEvent event = AccessibilityEvent.obtain(); 125742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 1258a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar @Override 1259a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar public void run() { 1260a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event); 1261a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 1262a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar }); 1263a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have first position", 126414d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas event.getFromIndex(), 1265a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findFirstVisibleItemPosition()); 1266a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar assertEquals("result should have last position", 126714d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas event.getToIndex(), 1268a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar mLayoutManager.findLastVisibleItemPosition()); 1269a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyar } 1270d7848507d6c561ca8e17d1954653f4fd26b58f84Yigit Boyar} 1271