12d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project 32d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * 42d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 52d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * you may not use this file except in compliance with the License. 62d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * You may obtain a copy of the License at 72d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * 82d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 92d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * 102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * Unless required by applicable law or agreed to in writing, software 112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * See the License for the specific language governing permissions and 142d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar * limitations under the License. 152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar */ 162d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget; 182d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; 20ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager 21bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar .GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS; 22ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE; 23ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.HORIZONTAL; 24ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams; 25bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 2620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapardimport static org.hamcrest.CoreMatchers.equalTo; 27bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertEquals; 28bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertFalse; 29bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertNotNull; 30bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertNull; 31bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertSame; 32bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertThat; 33bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport static org.junit.Assert.assertTrue; 340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 35f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.Color; 362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.graphics.Rect; 37f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.drawable.ColorDrawable; 38f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.graphics.drawable.StateListDrawable; 392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.os.Parcel; 402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.os.Parcelable; 4135232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikasimport android.support.test.filters.LargeTest; 42bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.text.TextUtils; 432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.util.Log; 44f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyarimport android.util.StateSet; 452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.view.View; 462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport android.view.ViewGroup; 47a910619e83d0052e1d81aa5fe532821a2f99d76cYigit Boyarimport android.view.accessibility.AccessibilityEvent; 48bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.widget.EditText; 49bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport android.widget.FrameLayout; 50bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 51c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull; 52c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.core.view.AccessibilityDelegateCompat; 53c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas 54bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport org.hamcrest.CoreMatchers; 55f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyarimport org.hamcrest.MatcherAssert; 56bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyarimport org.junit.Test; 572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 5842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyarimport java.util.HashMap; 592d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport java.util.Map; 602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyarimport java.util.UUID; 612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 6235232c6eaeb9b99f390cb8ef0ac83bf45fa0b3faAurimas Liutikas@LargeTest 630a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarpublic class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManagerTest { 6420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 6520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Test 6620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard public void layout_rvHasPaddingChildIsMatchParentVertical_childrenAreInsideParent() 6720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard throws Throwable { 6820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(VERTICAL, false); 6920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 7020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 7120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Test 7220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard public void layout_rvHasPaddingChildIsMatchParentHorizontal_childrenAreInsideParent() 7320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard throws Throwable { 7420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(HORIZONTAL, false); 7520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 7620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 7720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Test 7820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard public void layout_rvHasPaddingChildIsMatchParentVerticalFullSpan_childrenAreInsideParent() 7920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard throws Throwable { 8020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(VERTICAL, true); 8120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 8220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 8320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Test 8420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard public void layout_rvHasPaddingChildIsMatchParentHorizontalFullSpan_childrenAreInsideParent() 8520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard throws Throwable { 8620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent(HORIZONTAL, true); 8720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 8820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 8920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard private void layout_rvHasPaddingChildIsMatchParent_childrenAreInsideParent( 9020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard final int orientation, final boolean fullSpan) 9120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard throws Throwable { 9220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 9320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard setupByConfig(new Config(orientation, false, 1, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 9420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard new GridTestAdapter(10, orientation) { 9520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 968a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 9720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Override 988a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder( 998a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 10020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard View view = new View(parent.getContext()); 10120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard StaggeredGridLayoutManager.LayoutParams layoutParams = 10220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard new StaggeredGridLayoutManager.LayoutParams( 10320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard ViewGroup.LayoutParams.MATCH_PARENT, 10420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard ViewGroup.LayoutParams.MATCH_PARENT); 10520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard layoutParams.setFullSpan(fullSpan); 10620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard view.setLayoutParams(layoutParams); 10720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard return new TestViewHolder(view); 10820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 10920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 11020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Override 1118a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, int position) { 11220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard // No actual binding needed, but we need to override this to prevent default 11320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard // behavior of GridTestAdapter. 11420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 11520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard }); 11620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard mRecyclerView.setPadding(1, 2, 3, 4); 11720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 11820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard waitFirstLayout(); 11920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 12020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard mActivityRule.runOnUiThread(new Runnable() { 12120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard @Override 12220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard public void run() { 12320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard int childDimension; 12420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard int recyclerViewDimensionMinusPadding; 12520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard if (orientation == VERTICAL) { 12620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard childDimension = mRecyclerView.getChildAt(0).getHeight(); 12720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard recyclerViewDimensionMinusPadding = mRecyclerView.getHeight() 12820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard - mRecyclerView.getPaddingTop() 12920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard - mRecyclerView.getPaddingBottom(); 13020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } else { 13120a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard childDimension = mRecyclerView.getChildAt(0).getWidth(); 13220a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard recyclerViewDimensionMinusPadding = mRecyclerView.getWidth() 13320a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard - mRecyclerView.getPaddingLeft() 13420a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard - mRecyclerView.getPaddingRight(); 13520a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 13620a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard assertThat(childDimension, equalTo(recyclerViewDimensionMinusPadding)); 13720a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 13820a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard }); 13920a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard } 14020a3e1c6a73bf2179d189343abbd952fc73bf5f9shepshapard 141a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar @Test 142a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar public void forceLayoutOnDetach() throws Throwable { 143a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)); 144a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar waitFirstLayout(); 145a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar assertFalse("test sanity", mRecyclerView.isLayoutRequested()); 14642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 147a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar @Override 148a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar public void run() { 149a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar mLayoutManager.onDetachedFromWindow(mRecyclerView, mRecyclerView.mRecycler); 150a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar } 151a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar }); 152a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar assertTrue(mRecyclerView.isLayoutRequested()); 153a90fb62f06861beb3af9f9b3356ef0bb0685547cYigit Boyar } 1548a11e6829c522aa1efcc903afa4c01d337082eabChris Craik 1550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void areAllStartsTheSame() throws Throwable { 157f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300)); 158f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar waitFirstLayout(); 159f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(100); 160f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(1); 161f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.deleteAndNotify(0, 2); 1629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mLayoutManager.waitForLayout(2000); 163f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(0); 164f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual()); 165f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 166f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void areAllEndsTheSame() throws Throwable { 169f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300)); 170f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar waitFirstLayout(); 171f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(100); 172f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(1); 173f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.deleteAndNotify(0, 2); 174f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.waitForLayout(2); 175f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(0); 176f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual()); 177f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 178f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1790a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 180f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar public void getPositionsBeforeInitialization() throws Throwable { 181f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)); 182f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar int[] positions = mLayoutManager.findFirstCompletelyVisibleItemPositions(null); 183f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar MatcherAssert.assertThat(positions, 184f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar CoreMatchers.is(new int[]{RecyclerView.NO_POSITION, RecyclerView.NO_POSITION, 185f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar RecyclerView.NO_POSITION})); 186f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar } 187f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar 188f1811618b8c1a58374da7eb6093038d80c0ede52Yigit Boyar @Test 1890a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void findLastInUnevenDistribution() throws Throwable { 1906490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) 1916490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar .itemCount(5)); 192e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 1936490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar @Override 1946490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar void onBoundItem(TestViewHolder vh, int position) { 1956490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams(); 1966490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar if (position == 1) { 1976490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar lp.height = mRecyclerView.getHeight() - 10; 1986490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar } else { 1996490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar lp.height = 5; 2006490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar } 201e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar vh.itemView.setMinimumHeight(0); 2026490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar } 2036490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar }; 20452a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar waitFirstLayout(); 2056490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar int[] into = new int[2]; 2066490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar mLayoutManager.findFirstCompletelyVisibleItemPositions(into); 2076490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("first completely visible item from span 0 should be 0", 0, into[0]); 2086490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("first completely visible item from span 1 should be 1", 1, into[1]); 2096490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar mLayoutManager.findLastCompletelyVisibleItemPositions(into); 2106490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("last completely visible item from span 0 should be 4", 4, into[0]); 2116490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("last completely visible item from span 1 should be 1", 1, into[1]); 2126490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("first fully visible child should be at position", 2136490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 0, mRecyclerView.getChildViewHolder(mLayoutManager. 214e5874e666791a58d21eed482fb90e917445da873Chris Craik findFirstVisibleItemClosestToStart(true)).getPosition()); 2156490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("last fully visible child should be at position", 2166490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 4, mRecyclerView.getChildViewHolder(mLayoutManager. 217e5874e666791a58d21eed482fb90e917445da873Chris Craik findFirstVisibleItemClosestToEnd(true)).getPosition()); 2186490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 2196490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("first visible child should be at position", 2206490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 0, mRecyclerView.getChildViewHolder(mLayoutManager. 221e5874e666791a58d21eed482fb90e917445da873Chris Craik findFirstVisibleItemClosestToStart(false)).getPosition()); 2226490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar assertEquals("last visible child should be at position", 2236490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 4, mRecyclerView.getChildViewHolder(mLayoutManager. 224e5874e666791a58d21eed482fb90e917445da873Chris Craik findFirstVisibleItemClosestToEnd(false)).getPosition()); 2256490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar 22642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 22742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar 2280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void customWidthInHorizontal() throws Throwable { 23042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar customSizeInScrollDirectionTest( 23142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)); 23242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 23342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar 2340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void customHeightInVertical() throws Throwable { 23642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar customSizeInScrollDirectionTest( 23742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)); 23842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 23942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar 24042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar public void customSizeInScrollDirectionTest(final Config config) throws Throwable { 24142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar setupByConfig(config); 24242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar final Map<View, Integer> sizeMap = new HashMap<View, Integer>(); 243e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 24442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar @Override 24542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar void onBoundItem(TestViewHolder vh, int position) { 24642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar final ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams(); 24742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar final int size = 1 + position * 5; 24842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar if (config.mOrientation == HORIZONTAL) { 24942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar layoutParams.width = size; 25042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } else { 25142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar layoutParams.height = size; 25242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 25342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar sizeMap.put(vh.itemView, size); 25442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar if (position == 3) { 25542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar getLp(vh.itemView).setFullSpan(true); 25642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 25742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 25842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar 25942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar @Override 26042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar boolean assignRandomSize() { 26142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar return false; 26242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 26342e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar }; 26442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar waitFirstLayout(); 26542e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar assertTrue("[test sanity] some views should be laid out", sizeMap.size() > 0); 26642e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar for (int i = 0; i < mRecyclerView.getChildCount(); i++) { 26742e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar View child = mRecyclerView.getChildAt(i); 26842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar final int size = config.mOrientation == HORIZONTAL ? child.getWidth() 26942e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar : child.getHeight(); 27042e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar assertEquals("child " + i + " should have the size specified in its layout params", 27142e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar sizeMap.get(child).intValue(), size); 27242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 27352a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar checkForMainThreadException(); 27452a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar } 27552a47709e5ce81321bfa56b7595731a8e6835ef6Yigit Boyar 2760a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2770a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void gapHandlingWhenItemMovesToTop() throws Throwable { 278ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar gapHandlingWhenItemMovesToTopTest(); 279ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 280ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 2810a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2820a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void gapHandlingWhenItemMovesToTopWithFullSpan() throws Throwable { 283ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar gapHandlingWhenItemMovesToTopTest(0); 284ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 285ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 2860a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 2870a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void gapHandlingWhenItemMovesToTopWithFullSpan2() throws Throwable { 288ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar gapHandlingWhenItemMovesToTopTest(1); 289ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 290ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 291ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar public void gapHandlingWhenItemMovesToTopTest(int... fullSpanIndices) throws Throwable { 292ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar Config config = new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); 293ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar config.itemCount(3); 294ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar setupByConfig(config); 295e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 296ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar @Override 297ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar void onBoundItem(TestViewHolder vh, int position) { 298ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 299ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 300ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar @Override 301ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar boolean assignRandomSize() { 302ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar return false; 303ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 304ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar }; 305ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar for (int i : fullSpanIndices) { 306ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mAdapter.mFullSpanItems.add(i); 307ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 308ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar waitFirstLayout(); 309ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.expectLayouts(1); 310ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mAdapter.moveItem(1, 0, true); 311ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.waitForLayout(2); 312ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar final Map<Item, Rect> desiredPositions = mLayoutManager.collectChildCoordinates(); 313ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar // move back. 314ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.expectLayouts(1); 315ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mAdapter.moveItem(0, 1, true); 316ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.waitForLayout(2); 317ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.expectLayouts(2); 318ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mAdapter.moveAndNotify(1, 0); 319ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.waitForLayout(2); 320ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar Thread.sleep(1000); 321ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar getInstrumentation().waitForIdleSync(); 322ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar checkForMainThreadException(); 323ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar // item should be positioned properly 324ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar assertRectSetsEqual("final position after a move", desiredPositions, 325ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar mLayoutManager.collectChildCoordinates()); 326ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 327ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar } 328ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 3290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 3300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void focusSearchFailureUp() throws Throwable { 331f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearchFailure(false); 332f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 333f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 3340a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 3350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void focusSearchFailureDown() throws Throwable { 336f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearchFailure(true); 337f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 338f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 339bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar @Test 340bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar public void focusSearchFailureFromSubChild() throws Throwable { 341bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 342bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar new GridTestAdapter(1000, VERTICAL) { 343bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 3448a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull 345bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar @Override 3468a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder( 3478a11e6829c522aa1efcc903afa4c01d337082eabChris Craik @NonNull ViewGroup parent, int viewType) { 348bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar FrameLayout fl = new FrameLayout(parent.getContext()); 349bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar EditText editText = new EditText(parent.getContext()); 350bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar fl.addView(editText); 351bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar editText.setEllipsize(TextUtils.TruncateAt.END); 352bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar return new TestViewHolder(fl); 353bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar } 354bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 355bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar @Override 3568a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, int position) { 357bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar Item item = mItems.get(position); 35826039a32f6f04a92fe0450074dea21dd38258265Yigit Boyar holder.mBoundItem = item; 359bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar ((EditText) ((FrameLayout) holder.itemView).getChildAt(0)).setText( 360bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar item.mText + " (" + item.mId + ")"); 3619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 3629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 3639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 3649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 3659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 3669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation using this for kitkat tests 3679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setBackgroundDrawable(stl); 3689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (mOnBindCallback != null) { 3699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mOnBindCallback.onBoundItem(holder, position); 3709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 371bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar } 372bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar }); 373bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri mLayoutManager.expectLayouts(1); 374bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri setRecyclerView(mRecyclerView); 375bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri mLayoutManager.waitForLayout(10); 376bba8ae544c46ead758bdd90bd2184ae3fe2e29f8Keyvan Amiri getInstrumentation().waitForIdleSync(); 377bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar ViewGroup lastChild = (ViewGroup) mRecyclerView.getChildAt( 378bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar mRecyclerView.getChildCount() - 1); 379bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar RecyclerView.ViewHolder lastViewHolder = mRecyclerView.getChildViewHolder(lastChild); 380bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar View subChildToFocus = lastChild.getChildAt(0); 381959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar requestFocus(subChildToFocus, true); 382bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar assertThat("test sanity", subChildToFocus.isFocused(), CoreMatchers.is(true)); 383bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar focusSearch(subChildToFocus, View.FOCUS_FORWARD); 384959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar waitForIdleScroll(mRecyclerView); 385bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar checkForMainThreadException(); 386bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar View focusedChild = mRecyclerView.getFocusedChild(); 387bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar if (focusedChild == subChildToFocus.getParent()) { 388bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar focusSearch(focusedChild, View.FOCUS_FORWARD); 389959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar waitForIdleScroll(mRecyclerView); 390bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar focusedChild = mRecyclerView.getFocusedChild(); 391bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar } 392bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar RecyclerView.ViewHolder containingViewHolder = mRecyclerView.findContainingViewHolder( 393bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar focusedChild); 394bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar assertTrue("new focused view should have a larger position " 395bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar + lastViewHolder.getAdapterPosition() + " vs " 396bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar + containingViewHolder.getAdapterPosition(), 397bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar lastViewHolder.getAdapterPosition() < containingViewHolder.getAdapterPosition()); 398bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar } 399bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 400f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar public void focusSearchFailure(boolean scrollDown) throws Throwable { 401f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar int focusDir = scrollDown ? View.FOCUS_DOWN : View.FOCUS_UP; 402f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar setupByConfig(new Config(VERTICAL, !scrollDown, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) 403f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar , new GridTestAdapter(31, 1) { 404f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar RecyclerView mAttachedRv; 405bc61003e6a10872c23f0de155456cb2fbeef31a7Yigit Boyar 406f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar @Override 4078a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 408f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar int viewType) { 409f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 410f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar testViewHolder.itemView.setFocusable(true); 411f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar testViewHolder.itemView.setFocusableInTouchMode(true); 412f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar // Good to have colors for debugging 413f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar StateListDrawable stl = new StateListDrawable(); 414f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar stl.addState(new int[]{android.R.attr.state_focused}, 415f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar new ColorDrawable(Color.RED)); 416f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 4179152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar //noinspection deprecation used to support kitkat tests 4189152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar testViewHolder.itemView.setBackgroundDrawable(stl); 419f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar return testViewHolder; 420f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 421f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 422f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar @Override 423f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar public void onAttachedToRecyclerView(RecyclerView recyclerView) { 424f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar mAttachedRv = recyclerView; 425f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 426f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 427f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar @Override 4288a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 429f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar int position) { 430f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar super.onBindViewHolder(holder, position); 431f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3); 432f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 433f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar }); 434f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar /** 435f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 0 1 2 436f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 3 4 5 437f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 6 7 8 438f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 9 10 11 439f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 12 13 14 440f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 15 16 17 441f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 18 18 18 442f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 19 443f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 20 20 20 444f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 21 22 445f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 23 23 23 446f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 24 25 26 447f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 27 28 29 448f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar * 30 449f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar */ 450f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar mAdapter.mFullSpanItems.add(18); 451f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar mAdapter.mFullSpanItems.add(20); 452f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar mAdapter.mFullSpanItems.add(23); 453f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar waitFirstLayout(); 454f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar View viewToFocus = mRecyclerView.findViewHolderForAdapterPosition(1).itemView; 455959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar assertTrue(requestFocus(viewToFocus, true)); 456f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar assertSame(viewToFocus, mRecyclerView.getFocusedChild()); 457f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar int pos = 1; 458f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar View focusedView = viewToFocus; 459f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar while (pos < 16) { 460f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearchAndWaitForScroll(focusedView, focusDir); 461f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusedView = mRecyclerView.getFocusedChild(); 462f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar assertEquals(pos + 3, 463f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition()); 464f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar pos += 3; 465f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 466f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar for (int i : new int[]{18, 19, 20, 21, 23, 24}) { 467f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearchAndWaitForScroll(focusedView, focusDir); 468f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusedView = mRecyclerView.getFocusedChild(); 469f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition()); 470f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 471f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar // now move right 472f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearch(focusedView, View.FOCUS_RIGHT); 473959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar waitForIdleScroll(mRecyclerView); 474f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusedView = mRecyclerView.getFocusedChild(); 475f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar assertEquals(25, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition()); 476f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar for (int i : new int[]{28, 30}) { 477f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearchAndWaitForScroll(focusedView, focusDir); 478f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusedView = mRecyclerView.getFocusedChild(); 479f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition()); 480f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 481f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 482f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 483f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar private void focusSearchAndWaitForScroll(View focused, int dir) throws Throwable { 484f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar focusSearch(focused, dir); 485959f4c0fac89425a8a9842e82fc180ec736fffacYigit Boyar waitForIdleScroll(mRecyclerView); 486f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar } 487f89e1b82c74b7b85df3a349340a643f62fc5bfa1Yigit Boyar 4889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 4899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void topUnfocusableViewsVisibility() throws Throwable { 4909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of rows that can be fully in-bounds of RV. 4919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleRowCount = 5; 4929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int spanCount = 3; 4939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int lastFocusableIndex = 6; 4949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(VERTICAL, true, spanCount, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 4969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new GridTestAdapter(18, 1) { 4979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 4989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 4999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 5008a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 5019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int viewType) { 5029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 5039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusable(true); 5049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusableInTouchMode(true); 5059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 5069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 5079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 5089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 5099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 5109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 5119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 5129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 5139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 5169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 5179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 5189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 5218a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 5229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 5239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 5249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView 5259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri .getLayoutParams(); 5269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position <= lastFocusableIndex) { 5279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 5289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 5299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 5309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 5319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 5329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / visibleRowCount); 5349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.topMargin = 0; 5359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.leftMargin = 0; 5369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.rightMargin = 0; 5379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.bottomMargin = 0; 5389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position == 11) { 5399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.bottomMargin = 9; 5409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }); 5439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri /** 5459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 5469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 15 16 17 5479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 12 13 14 5489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 11 11 11 5499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 9 10 5509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 8 8 8 5519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 7 5529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 6 6 6 5539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 3 4 5 5549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 0 1 2 5559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri */ 5569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(6); 5579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(8); 5589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(11); 5599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitFirstLayout(); 5609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 5639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 1; 5649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 5659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 5669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View viewToFocus = toFocus.itemView; 5679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue(requestFocus(viewToFocus, true)); 5689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertSame(viewToFocus, mRecyclerView.getFocusedChild()); 5699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the unfocusable item that just became fully visible after focusSearch. 5719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 5729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View focusedView = viewToFocus; 5749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int actualFocusIndex = -1; 5759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // First, scroll until the last focusable row. 5769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{4, 6}) { 5779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP); 5789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 5799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 5809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should be at adapter position " + i + " whereas it's at " 5819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, i, actualFocusIndex); 5829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 5839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Further scroll up in order to make the unfocusable rows visible. This process should 5859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // continue until the currently focused item is still visible. The focused item should not 5869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // change in this loop. 5879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{9, 11, 11, 11}) { 5889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_UP); 5899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 5909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 5919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(i); 5929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 5939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should not be changed, whereas it's now at " 5949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, 6, actualFocusIndex); 5959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 5969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, focusedView)); 5979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + i + " should be fully visible.", 5989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 5999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 6039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void bottomUnfocusableViewsVisibility() throws Throwable { 6049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of rows that can be fully in-bounds of RV. 6059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleRowCount = 5; 6069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int spanCount = 3; 6079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int lastFocusableIndex = 6; 6089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(VERTICAL, false, spanCount, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 6109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new GridTestAdapter(18, 1) { 6119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 6129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 6148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 6159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int viewType) { 6169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 6179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusable(true); 6189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusableInTouchMode(true); 6199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 6209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 6219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 6229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 6239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 6249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 6259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 6269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 6279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 6309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 6319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 6329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 6358a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 6369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 6379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 6389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView 6399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri .getLayoutParams(); 6409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position <= lastFocusableIndex) { 6419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 6429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 6439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 6449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 6459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 6469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / visibleRowCount); 6489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.topMargin = 0; 6499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.leftMargin = 0; 6509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.rightMargin = 0; 6519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.bottomMargin = 0; 6529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position == 11) { 6539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.topMargin = 9; 6549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }); 6579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri /** 6599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 0 1 2 6609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 3 4 5 6619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 6 6 6 6629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 7 6639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 8 8 8 6649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 9 10 6659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 11 11 11 6669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 12 13 14 6679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 15 16 17 6689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri */ 6699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(6); 6709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(8); 6719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(11); 6729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitFirstLayout(); 6739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 6769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 1; 6779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 6789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 6799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View viewToFocus = toFocus.itemView; 6809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue(requestFocus(viewToFocus, true)); 6819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertSame(viewToFocus, mRecyclerView.getFocusedChild()); 6829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the unfocusable item that just became fully visible after focusSearch. 6849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 6859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View focusedView = viewToFocus; 6879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int actualFocusIndex = -1; 6889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // First, scroll until the last focusable row. 6899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{4, 6}) { 6909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN); 6919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 6929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 6939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should be at adapter position " + i + " whereas it's at " 6949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, i, actualFocusIndex); 6959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 6969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 6979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Further scroll down in order to make the unfocusable rows visible. This process should 6989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // continue until the currently focused item is still visible. The focused item should not 6999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // change in this loop. 7009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{9, 11, 11, 11}) { 7019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_DOWN); 7029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 7039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 7049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(i); 7059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should not be changed, whereas it's now at " 7079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, 6, actualFocusIndex); 7089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 7099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, focusedView)); 7109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + i + " should be fully visible.", 7119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 7129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 7169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void leftUnfocusableViewsVisibility() throws Throwable { 7179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of columns that can be fully in-bounds of RV. 7189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleColCount = 5; 7199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int spanCount = 3; 7209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int lastFocusableIndex = 6; 7219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Reverse layout so that views are placed from right to left. 7239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(HORIZONTAL, true, spanCount, 7249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 7259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new GridTestAdapter(18, 1) { 7269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 7279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 7298a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 7309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int viewType) { 7319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 7329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusable(true); 7339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusableInTouchMode(true); 7349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 7359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 7369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 7379c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 7389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 7399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 7409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 7419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 7429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 7459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 7469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 7479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 7508a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 7519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 7529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 7539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView 7549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri .getLayoutParams(); 7559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position <= lastFocusableIndex) { 7569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 7579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 7589c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 7599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 7609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 7619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumWidth(mAttachedRv.getWidth() / visibleColCount); 7639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.topMargin = 0; 7649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.leftMargin = 0; 7659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.rightMargin = 0; 7669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.bottomMargin = 0; 7679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position == 11) { 7689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.rightMargin = 9; 7699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 7719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }); 7729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri /** 7749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 15 12 11 9 8 7 6 3 0 7759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 16 13 11 10 8 6 4 1 7769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 17 14 11 8 6 5 2 7779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri */ 7789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(6); 7799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(8); 7809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(11); 7819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitFirstLayout(); 7829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 7859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 1; 7869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 7879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 7889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View viewToFocus = toFocus.itemView; 7899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue(requestFocus(viewToFocus, true)); 7909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertSame(viewToFocus, mRecyclerView.getFocusedChild()); 7919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the unfocusable item that just became fully visible after focusSearch. 7939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 7949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 7959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View focusedView = viewToFocus; 7969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int actualFocusIndex = -1; 7979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // First, scroll until the last focusable column. 7989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{4, 6}) { 7999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT); 8009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 8019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 8029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should be at adapter position " + i + " whereas it's at " 8039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, i, actualFocusIndex); 8049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Further scroll left in order to make the unfocusable columns visible. This process should 8079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // continue until the currently focused item is still visible. The focused item should not 8089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // change in this loop. 8099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{9, 11, 11, 11}) { 8109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_LEFT); 8119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 8129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 8139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(i); 8149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should not be changed, whereas it's now at " 8169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, 6, actualFocusIndex); 8179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 8189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, focusedView)); 8199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + i + " should be fully visible.", 8209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 8219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Test 8259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void rightUnfocusableViewsVisibility() throws Throwable { 8269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The maximum number of columns that can be fully in-bounds of RV. 8279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int visibleColCount = 5; 8289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int spanCount = 3; 8299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri final int lastFocusableIndex = 6; 8309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8319c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri setupByConfig(new Config(HORIZONTAL, false, spanCount, 8329c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS), 8339c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new GridTestAdapter(18, 1) { 8349c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView mAttachedRv; 8359c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8369c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 8378a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 8389c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int viewType) { 8399c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType); 8409c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusable(true); 8419c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setFocusableInTouchMode(true); 8429c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Good to have colors for debugging 8439c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri StateListDrawable stl = new StateListDrawable(); 8449c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(new int[]{android.R.attr.state_focused}, 8459c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri new ColorDrawable(Color.RED)); 8469c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE)); 8479c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri //noinspection deprecation used to support kitkat tests 8489c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri testViewHolder.itemView.setBackgroundDrawable(stl); 8499c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri return testViewHolder; 8509c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8519c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8529c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 8539c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri public void onAttachedToRecyclerView(RecyclerView recyclerView) { 8549c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAttachedRv = recyclerView; 8559c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8569c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8579c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri @Override 8588a11e6829c522aa1efcc903afa4c01d337082eabChris Craik public void onBindViewHolder(@NonNull TestViewHolder holder, 8599c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int position) { 8609c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri super.onBindViewHolder(holder, position); 8619c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView 8629c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri .getLayoutParams(); 8639c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position <= lastFocusableIndex) { 8649c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(true); 8659c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(true); 8669c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } else { 8679c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusable(false); 8689c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setFocusableInTouchMode(false); 8699c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8709c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri holder.itemView.setMinimumWidth(mAttachedRv.getWidth() / visibleColCount); 8719c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.topMargin = 0; 8729c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.leftMargin = 0; 8739c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.rightMargin = 0; 8749c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.bottomMargin = 0; 8759c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri if (position == 11) { 8769c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri lp.leftMargin = 9; 8779c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8789c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 8799c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri }); 8809c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8819c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri /** 8829c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 0 3 6 7 8 9 11 12 15 8839c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 1 4 6 8 10 11 13 16 8849c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri * 2 5 6 8 11 14 17 8859c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri */ 8869c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(6); 8879c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(8); 8889c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri mAdapter.mFullSpanItems.add(11); 8899c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri waitFirstLayout(); 8909c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8919c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 8929c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // adapter position of the currently focused item. 8939c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int focusIndex = 1; 8949c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toFocus = mRecyclerView.findViewHolderForAdapterPosition( 8959c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusIndex); 8969c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View viewToFocus = toFocus.itemView; 8979c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue(requestFocus(viewToFocus, true)); 8989c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertSame(viewToFocus, mRecyclerView.getFocusedChild()); 8999c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 9009c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // The VH of the unfocusable item that just became fully visible after focusSearch. 9019c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri RecyclerView.ViewHolder toVisible = null; 9029c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 9039c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri View focusedView = viewToFocus; 9049c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri int actualFocusIndex = -1; 9059c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // First, scroll until the last focusable column. 9069c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{4, 6}) { 9079c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT); 9089c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 9099c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 9109c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should be at adapter position " + i + " whereas it's at " 9119c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, i, actualFocusIndex); 9129c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 9139c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 9149c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // Further scroll right in order to make the unfocusable rows visible. This process should 9159c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // continue until the currently focused item is still visible. The focused item should not 9169c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri // change in this loop. 9179c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri for (int i : new int[]{9, 11, 11, 11}) { 9189c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusSearchAndWaitForScroll(focusedView, View.FOCUS_RIGHT); 9199c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri focusedView = mRecyclerView.getFocusedChild(); 9209c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri actualFocusIndex = mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition(); 9219c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri toVisible = mRecyclerView.findViewHolderForAdapterPosition(i); 9229c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri 9239c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertEquals("Focused view should not be changed, whereas it's now at " 9249c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri + actualFocusIndex, 6, actualFocusIndex); 9259c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Focused child should be at least partially visible.", 9269c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewPartiallyInBound(mRecyclerView, focusedView)); 9279c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri assertTrue("Child view at adapter pos " + i + " should be fully visible.", 9289c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri isViewFullyInBound(mRecyclerView, toVisible.itemView)); 9299c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 9309c0ad7d5adfbe51d85adcbc056b6183095d8aaedKeyvan Amiri } 931ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar 9320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollToPositionWithPredictive() throws Throwable { 9346e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET); 9356e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 9366e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 9376e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar LinearLayoutManager.INVALID_OFFSET); 9386e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 9396e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(9, 20); 9406e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar removeRecyclerView(); 9416e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10); 9426e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 9436e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9446e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 9456e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset) 9466e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throws Throwable { 9476e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar setupByConfig(new Config(StaggeredGridLayoutManager.VERTICAL, 9486e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar false, 3, StaggeredGridLayoutManager.GAP_HANDLING_NONE)); 9496e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar waitFirstLayout(); 9506e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.mOnLayoutListener = new OnLayoutListener() { 9516e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 9526e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar void after(RecyclerView.Recycler recycler, RecyclerView.State state) { 953d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar RecyclerView rv = mLayoutManager.mRecyclerView; 9546e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (state.isPreLayout()) { 9556e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position should still be pending", 9566e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollPosition, mLayoutManager.mPendingScrollPosition); 9576e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 9586e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("pending scroll position offset should still be pending", 9596e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset, mLayoutManager.mPendingScrollPositionOffset); 9606e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9616e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 962115ba0c7b2a14aa4cd0273952195e1d8f6468f87Yigit Boyar RecyclerView.ViewHolder vh = rv.findViewHolderForLayoutPosition(scrollPosition); 9636e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertNotNull("scroll to position should work", vh); 9646e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) { 9656e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar assertEquals("scroll offset should be applied properly", 9666e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getPaddingTop() + scrollOffset 9676e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar + ((RecyclerView.LayoutParams) vh.itemView 96842e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar .getLayoutParams()).topMargin, 9696e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.getDecoratedTop(vh.itemView)); 9706e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9716e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9726e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9736e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }; 9746e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.expectLayouts(2); 97542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 9766e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar @Override 9776e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar public void run() { 9786e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar try { 9796e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mAdapter.addAndNotify(0, 1); 9806e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) { 9816e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPosition(scrollPosition); 9826e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } else { 9836e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.scrollToPositionWithOffset(scrollPosition, 9846e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar scrollOffset); 9856e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9866e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 9876e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } catch (Throwable throwable) { 9886e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar throwable.printStackTrace(); 9896e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9906e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 9916e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9926e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar }); 9936e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar mLayoutManager.waitForLayout(2); 9946e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar checkForMainThreadException(); 9956e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar } 9966e83751247c5be0211d7bffaf057431c03dfef38Yigit Boyar 9970a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 9980a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void moveGapHandling() throws Throwable { 999f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar Config config = new Config().spanCount(2).itemCount(40); 1000f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar setupByConfig(config); 1001f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar waitFirstLayout(); 1002f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(2); 1003f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.moveAndNotify(4, 1); 1004f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.waitForLayout(2); 1005f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix()); 1006f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 1007f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 10080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void updateAfterFullSpan() throws Throwable { 1010f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar updateAfterFullSpanGapHandlingTest(0); 1011f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 1012f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 10130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void updateAfterFullSpan2() throws Throwable { 1015f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar updateAfterFullSpanGapHandlingTest(20); 1016f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 1017f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 10180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void temporaryGapHandling() throws Throwable { 1020f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar int fullSpanIndex = 200; 1021f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar setupByConfig(new Config().spanCount(2).itemCount(500)); 1022f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.mFullSpanItems.add(fullSpanIndex); 1023f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar waitFirstLayout(); 1024e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar smoothScrollToPosition(fullSpanIndex + 200);// go far away 1025e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNull("test sanity. full span item should not be visible", 1026e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mRecyclerView.findViewHolderForAdapterPosition(fullSpanIndex)); 1027f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(1); 1028f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.deleteAndNotify(fullSpanIndex + 1, 3); 1029f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.waitForLayout(1); 1030f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(0); 1031f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(1); 1032e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar smoothScrollToPosition(fullSpanIndex + 2 * (AVG_ITEM_PER_VIEW - 1)); 1033e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar String log = mLayoutManager.layoutToString("post gap"); 1034f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.assertNoLayout("if an interim gap is fixed, it should not cause a " 1035e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar + "relayout " + log, 2); 1036f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex); 1037e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNotNull("full span item should be there:\n" + log, fullSpan); 1038f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1); 1039e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNotNull("next view should be there\n" + log, view1); 1040f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2); 1041e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNotNull("+2 view should be there\n" + log, view2); 1042f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1043f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar LayoutParams lp1 = (LayoutParams) view1.getLayoutParams(); 1044f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar LayoutParams lp2 = (LayoutParams) view2.getLayoutParams(); 1045f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("view 1 span index", 0, lp1.getSpanIndex()); 1046f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("view 2 span index", 1, lp2.getSpanIndex()); 1047f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("no gap between span and view 1", 1048f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan), 1049f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1)); 1050f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("no gap between span and view 2", 1051f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan), 1052f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2)); 1053f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 1054f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1055f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar public void updateAfterFullSpanGapHandlingTest(int fullSpanIndex) throws Throwable { 1056f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar setupByConfig(new Config().spanCount(2).itemCount(100)); 1057f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.mFullSpanItems.add(fullSpanIndex); 1058f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar waitFirstLayout(); 1059f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(fullSpanIndex + 30); 1060f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.expectLayouts(1); 1061f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mAdapter.deleteAndNotify(fullSpanIndex + 1, 3); 1062f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.waitForLayout(1); 1063f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar smoothScrollToPosition(fullSpanIndex); 1064f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar // give it some time to fix the gap 1065f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar Thread.sleep(500); 1066f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex); 1067f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1068f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1); 1069f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2); 1070f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 1071f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar LayoutParams lp1 = (LayoutParams) view1.getLayoutParams(); 1072f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar LayoutParams lp2 = (LayoutParams) view2.getLayoutParams(); 1073f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("view 1 span index", 0, lp1.getSpanIndex()); 1074f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("view 2 span index", 1, lp2.getSpanIndex()); 1075f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("no gap between span and view 1", 1076f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan), 1077f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1)); 1078f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar assertEquals("no gap between span and view 2", 1079f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan), 1080f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2)); 1081f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar } 1082f009f639980abd669fd533e5bc6ed42f3d4ed9ffYigit Boyar 10830a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 10840a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void innerGapHandling() throws Throwable { 10852d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_NONE); 10862d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); 10872d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 10882d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 10892d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar public void innerGapHandlingTest(int strategy) throws Throwable { 10902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Config config = new Config().spanCount(3).itemCount(500); 10912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar setupByConfig(config); 10922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.setGapStrategy(strategy); 10932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.mFullSpanItems.add(100); 10942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.mFullSpanItems.add(104); 10952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.mViewsHaveEqualSize = true; 1096e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mAdapter.mOnBindCallback = new OnBindCallback() { 1097e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar @Override 1098e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar void onBoundItem(TestViewHolder vh, int position) { 1099e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar 1100e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar } 1101e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar 1102e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar @Override 1103e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar void onCreatedViewHolder(TestViewHolder vh) { 1104e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar super.onCreatedViewHolder(vh); 1105e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar //make sure we have enough views 1106e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mAdapter.mSizeReference = mRecyclerView.getHeight() / 5; 1107e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar } 1108e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar }; 11092d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar waitFirstLayout(); 11102d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 11112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar scrollToPosition(400); 11122d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 1113e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar View view400 = mLayoutManager.findViewByPosition(400); 1114e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNotNull("test sanity, scrollToPos should succeed", view400); 1115e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertTrue("test sanity, view should be visible top", 1116e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedStart(view400) >= 1117e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mLayoutManager.mPrimaryOrientation.getStartAfterPadding()); 1118e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertTrue("test sanity, view should be visible bottom", 1119e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mLayoutManager.mPrimaryOrientation.getDecoratedEnd(view400) <= 1120e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mLayoutManager.mPrimaryOrientation.getEndAfterPadding()); 11212d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(2); 11222d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.addAndNotify(101, 1); 11232d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 1124e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar checkForMainThreadException(); 11252d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar if (strategy == GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) { 11262d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 11272d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11282d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // state 11292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // now smooth scroll to 99 to trigger a layout around 100 1130e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar mLayoutManager.validateChildren(); 11312d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar smoothScrollToPosition(99); 11322d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar switch (strategy) { 11332d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar case GAP_HANDLING_NONE: 11342d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertSpans("gap handling:" + Config.gapStrategyName(strategy), new int[]{100, 0}, 11352d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar new int[]{101, 2}, new int[]{102, 0}, new int[]{103, 1}, new int[]{104, 2}, 11362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar new int[]{105, 0}); 11372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar break; 11382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS: 11392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 11402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertSpans("swap items between spans", new int[]{100, 0}, new int[]{101, 0}, 11412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar new int[]{102, 1}, new int[]{103, 2}, new int[]{104, 0}, new int[]{105, 0}); 11422d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar break; 11432d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11442d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 11452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 11470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 11480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void fullSizeSpans() throws Throwable { 11492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Config config = new Config().spanCount(5).itemCount(30); 11502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar setupByConfig(config); 11512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.mFullSpanItems.add(3); 11522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar waitFirstLayout(); 11532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertSpans("Testing full size span", new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 2}, 11542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar new int[]{3, 0}, new int[]{4, 0}, new int[]{5, 1}, new int[]{6, 2}, 11552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar new int[]{7, 3}, new int[]{8, 4}); 11562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 11582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar void assertSpans(String msg, int[]... childSpanTuples) { 1159e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar msg = msg + mLayoutManager.layoutToString("\n\n"); 11602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar for (int i = 0; i < childSpanTuples.length; i++) { 11612d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertSpan(msg, childSpanTuples[i][0], childSpanTuples[i][1]); 11622d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11632d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 11652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar void assertSpan(String msg, int childPosition, int expectedSpan) { 11662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar View view = mLayoutManager.findViewByPosition(childPosition); 1167e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar assertNotNull(msg + " view at position " + childPosition + " should exists", view); 11682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertEquals(msg + "[child:" + childPosition + "]", expectedSpan, 11692d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar getLp(view).mSpan.mIndex); 11702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 11712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 11720a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 11730a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void partialSpanInvalidation() throws Throwable { 1174d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar Config config = new Config().spanCount(5).itemCount(100); 1175d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar setupByConfig(config); 1176d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar for (int i = 20; i < mAdapter.getItemCount(); i += 20) { 1177d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar mAdapter.mFullSpanItems.add(i); 1178d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar } 1179d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar waitFirstLayout(); 1180d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar smoothScrollToPosition(50); 1181d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar int prevSpanId = mLayoutManager.mLazySpanLookup.mData[30]; 1182d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar mAdapter.changeAndNotify(15, 2); 1183d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar Thread.sleep(200); 1184d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar assertEquals("Invalidation should happen within full span item boundaries", prevSpanId, 1185d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar mLayoutManager.mLazySpanLookup.mData[30]); 1186d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar assertEquals("item in invalidated range should have clear span id", 1187d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]); 1188d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar smoothScrollToPosition(85); 1189d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar int[] prevSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 62, 85); 1190d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar mAdapter.deleteAndNotify(55, 2); 1191d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar Thread.sleep(200); 1192d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar assertEquals("item in invalidated range should have clear span id", 1193d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]); 1194d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar int[] newSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 60, 83); 1195d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar assertSpanAssignmentEquality("valid spans should be shifted for deleted item", prevSpans, 1196d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar newSpans, 0, 0, newSpans.length); 1197d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar } 1198d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar 1199d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar // Same as Arrays.copyOfRange but for API 7 1200d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar private int[] copyOfRange(int[] original, int from, int to) { 1201d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar int newLength = to - from; 120242e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar if (newLength < 0) { 1203d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar throw new IllegalArgumentException(from + " > " + to); 120442e9353bb9eb2747247e30e3612b227945acfd16Yigit Boyar } 1205d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar int[] copy = new int[newLength]; 1206d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar System.arraycopy(original, from, copy, 0, 1207d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar Math.min(original.length - from, newLength)); 1208d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar return copy; 1209d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar } 1210d7e2f2ab1d253133fa54f309e74a7ee384c33971Yigit Boyar 12110a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void spanReassignmentsOnItemChange() throws Throwable { 12132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Config config = new Config().spanCount(5); 12142d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar setupByConfig(config); 12152d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar waitFirstLayout(); 12162d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar smoothScrollToPosition(mAdapter.getItemCount() / 2); 12172d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar final int changePosition = mAdapter.getItemCount() / 4; 12182d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 12199152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar if (RecyclerView.POST_UPDATES_ON_ANIMATION) { 12209152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar mAdapter.changeAndNotify(changePosition, 1); 12219152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar mLayoutManager.assertNoLayout("no layout should happen when an invisible child is " 12229152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar + "updated", 1); 12239152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar } else { 12249152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar mAdapter.changeAndNotify(changePosition, 1); 12259152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar mLayoutManager.waitForLayout(1); 12269152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar } 12279152a7b54bab69ce0b216c1c7f4b4dcb36190a11Yigit Boyar 12282d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // delete an item before visible area 12292d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar int deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(0)) - 2; 1230ec60a348ce39889a99f7ca02da94a6867bfdf66fYigit Boyar assertTrue("test sanity", deletedPosition >= 0); 12312d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Map<Item, Rect> before = mLayoutManager.collectChildCoordinates(); 12322d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar if (DEBUG) { 12332d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Log.d(TAG, "before:"); 12342d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar for (Map.Entry<Item, Rect> entry : before.entrySet()) { 12352d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Log.d(TAG, entry.getKey().mAdapterIndex + ":" + entry.getValue()); 12362d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 12372d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 12382d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 12392d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.deleteAndNotify(deletedPosition, 1); 12402d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 12412d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertRectSetsEqual(config + " when an item towards the head of the list is deleted, it " 1242333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar + "should not affect the layout if it is not visible", before, 1243333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar mLayoutManager.collectChildCoordinates() 1244333382aecf6822cb1ec484e8d3e4e822c9bd0c8dYigit Boyar ); 12452d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(2)); 12462d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 12472d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mAdapter.deleteAndNotify(deletedPosition, 1); 12482d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 12492d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertRectSetsNotEqual(config + " when a visible item is deleted, it should affect the " 12502d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar + "layout", before, mLayoutManager.collectChildCoordinates()); 12512d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 12522d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 12532d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start1, int start2, 12542d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar int length) { 12552d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar for (int i = 0; i < length; i++) { 12562d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertEquals(msg + " ind1:" + (start1 + i) + ", ind2:" + (start2 + i), set1[start1 + i], 12572d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar set2[start2 + i]); 12582d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 12592d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 12602d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 12610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 12620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void spanCountChangeOnRestoreSavedState() throws Throwable { 12636939444416c6eb0114bc1216fb0f683055bef135Yigit Boyar Config config = new Config(HORIZONTAL, true, 5, GAP_HANDLING_NONE).itemCount(50); 12642d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar setupByConfig(config); 12652d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar waitFirstLayout(); 12662d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 12672d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar int beforeChildCount = mLayoutManager.getChildCount(); 12682d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Parcelable savedState = mRecyclerView.onSaveInstanceState(); 12692d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // we append a suffix to the parcelable to test out of bounds 12702d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar String parcelSuffix = UUID.randomUUID().toString(); 12712d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar Parcel parcel = Parcel.obtain(); 12722d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar savedState.writeToParcel(parcel, 0); 12732d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar parcel.writeString(parcelSuffix); 12742d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar removeRecyclerView(); 12752d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // reset for reading 12762d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar parcel.setDataPosition(0); 12772d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // re-create 12782d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); 12792d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar removeRecyclerView(); 12802d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 12812d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar RecyclerView restored = new RecyclerView(getActivity()); 12822d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation); 1283b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar mLayoutManager.setReverseLayout(config.mReverseLayout); 1284b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar mLayoutManager.setGapStrategy(config.mGapStrategy); 12852d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar restored.setLayoutManager(mLayoutManager); 12862d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar // use the same adapter for Rect matching 12872d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar restored.setAdapter(mAdapter); 12882d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar restored.onRestoreInstanceState(savedState); 12892d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.setSpanCount(1); 12902d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.expectLayouts(1); 12912d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar setRecyclerView(restored); 12922d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar mLayoutManager.waitForLayout(2); 12932d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertEquals("on saved state, reverse layout should be preserved", 12942d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar config.mReverseLayout, mLayoutManager.getReverseLayout()); 12952d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertEquals("on saved state, orientation should be preserved", 12962d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar config.mOrientation, mLayoutManager.getOrientation()); 1297b8403301bbec29129730f6cce3fe2fa3ee8e1e0bYigit Boyar assertEquals("after setting new span count, layout manager should keep new value", 12982d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 1, mLayoutManager.getSpanCount()); 12992d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertEquals("on saved state, gap strategy should be preserved", 13002d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar config.mGapStrategy, mLayoutManager.getGapStrategy()); 13012d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar assertTrue("when span count is dramatically changed after restore, # of child views " 13022d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar + "should change", beforeChildCount > mLayoutManager.getChildCount()); 13036939444416c6eb0114bc1216fb0f683055bef135Yigit Boyar // make sure SGLM can layout all children. is some span info is leaked, this would crash 13042d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar smoothScrollToPosition(mAdapter.getItemCount() - 1); 13052d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 1306e05fbd9cfe05496e82a3abe19e07e8745985e9a5Yigit Boyar 13070a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 13080a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void scrollAndClear() throws Throwable { 13090a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(new Config()); 13100a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 13112d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 13120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertTrue("Children not laid out", mLayoutManager.collectChildCoordinates().size() > 0); 13132d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 13140a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.expectLayouts(1); 131542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 13160a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Override 13170a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void run() { 13180a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.scrollToPositionWithOffset(1, 0); 13190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mAdapter.clearOnUIThread(); 13202d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 13210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar }); 13220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar mLayoutManager.waitForLayout(2); 13230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 13240a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals("Remaining children", 0, mLayoutManager.collectChildCoordinates().size()); 13252d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar } 13262d2e8d88103866b631eb0f3805da137ebcfb0275Yigit Boyar 13270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 13280a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void accessibilityPositions() throws Throwable { 13290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE)); 13300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar waitFirstLayout(); 13310a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final AccessibilityDelegateCompat delegateCompat = mRecyclerView 13320a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getCompatAccessibilityDelegate(); 13330a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final AccessibilityEvent event = AccessibilityEvent.obtain(); 133442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityRule.runOnUiThread(new Runnable() { 13356490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar @Override 13366490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar public void run() { 13370a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar delegateCompat.onInitializeAccessibilityEvent(mRecyclerView, event); 13386490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar } 13396490f4b25e7725acd06248f0247fb0c7634afcb1Yigit Boyar }); 13400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int start = mRecyclerView 13410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getChildLayoutPosition( 1342e5874e666791a58d21eed482fb90e917445da873Chris Craik mLayoutManager.findFirstVisibleItemClosestToStart(false)); 13430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar final int end = mRecyclerView 13440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar .getChildLayoutPosition( 1345e5874e666791a58d21eed482fb90e917445da873Chris Craik mLayoutManager.findFirstVisibleItemClosestToEnd(false)); 13460a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals("first item position should match", 134714d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas Math.min(start, end), event.getFromIndex()); 13480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar assertEquals("last item position should match", 134914d02ef06479168249fdfeea47bc105d05e88749Aurimas Liutikas Math.max(start, end), event.getToIndex()); 13503d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev 13513d8453880afb3e32c4c59c52b8b580f91d78b29fVladislav Kaznacheev } 135249c83b12201dde5b93d4eca3d44478e0c967a2e6Yigit Boyar} 1353