Suppress layout handling during animateOut()
When playing video, data change of rows will automatically
pulls row in. This CL suppresses layout when animateOut()
until a specific animateIn() is called.
Bug: 35399351
Test: testAnimateOutBlockLayout testAnimateOutBlockSmoothScroll
testAnimateOutBlockScrollTo
Change-Id: Ie91137687e96f0d48a674c410041b9412c8945d6
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index a2480d4..9438ce5 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -704,6 +704,7 @@
public void onRequestChildFocus(View child, View focused) {
if (child != mRootView.getFocusedChild()) {
if (child.getId() == R.id.details_fragment_root) {
+ slideInGridView();
showTitle(true);
} else if (child.getId() == R.id.video_surface_container) {
slideOutGridView();
@@ -774,4 +775,9 @@
}
}
+ void slideInGridView() {
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().animateIn();
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
index d2e0ef2..79ab727 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -707,6 +707,7 @@
public void onRequestChildFocus(View child, View focused) {
if (child != mRootView.getFocusedChild()) {
if (child.getId() == R.id.details_fragment_root) {
+ slideInGridView();
showTitle(true);
} else if (child.getId() == R.id.video_surface_container) {
slideOutGridView();
@@ -777,4 +778,9 @@
}
}
+ void slideInGridView() {
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().animateIn();
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 005a441..b19458b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -1004,21 +1004,39 @@
/**
* Temporarily slide out child views to bottom (for VerticalGridView) or end
- * (for HorizontalGridView). The views will be automatically slide-in in next
- * {@link #smoothScrollToPosition(int)} or {@link #scrollToPosition(int)}.
+ * (for HorizontalGridView). Layout and scrolling will be suppressed until
+ * {@link #animateIn()} is called.
*/
public void animateOut() {
mLayoutManager.slideOut();
}
/**
- * @deprecated No longer needed. Children being slide out by {@link #animateOut()} will be
- * slide in next focus or (smooth)scrollToPosition action.
+ * Undo animateOut() and slide in child views.
*/
- @Deprecated
public void animateIn() {
+ mLayoutManager.slideIn();
}
+ @Override
+ public void scrollToPosition(int position) {
+ // dont abort the animateOut() animation, just record the position
+ if (mLayoutManager.mIsSlidingChildViews) {
+ mLayoutManager.setSelectionWithSub(position, 0, 0);
+ return;
+ }
+ super.scrollToPosition(position);
+ }
+
+ @Override
+ public void smoothScrollToPosition(int position) {
+ // dont abort the animateOut() animation, just record the position
+ if (mLayoutManager.mIsSlidingChildViews) {
+ mLayoutManager.setSelectionWithSub(position, 0, 0);
+ return;
+ }
+ super.smoothScrollToPosition(position);
+ }
/**
* Sets the number of items to prefetch in
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 5569836..fc7c890 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -16,6 +16,7 @@
import static android.support.v7.widget.RecyclerView.HORIZONTAL;
import static android.support.v7.widget.RecyclerView.NO_ID;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
import static android.support.v7.widget.RecyclerView.VERTICAL;
import android.content.Context;
@@ -379,6 +380,7 @@
// Represents whether child views are temporarily sliding out
boolean mIsSlidingChildViews;
+ boolean mLayoutEatenInSliding;
String getTag() {
return TAG + ":" + mBaseGridView.getId();
@@ -1738,8 +1740,31 @@
return mGrid.appendOneColumnVisibleItems();
}
+ void slideIn() {
+ if (mIsSlidingChildViews) {
+ mIsSlidingChildViews = false;
+ scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
+ if (mLayoutEatenInSliding) {
+ mLayoutEatenInSliding = false;
+ if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
+ mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == SCROLL_STATE_IDLE) {
+ mBaseGridView.removeOnScrollListener(this);
+ requestLayout();
+ }
+ }
+ });
+ } else {
+ requestLayout();
+ }
+ }
+ }
+ }
+
/**
- * Temporarily slide out child and will be auto slide-in in next scrollToView().
+ * Temporarily slide out child and block layout and scroll requests.
*/
void slideOut() {
if (mIsSlidingChildViews) {
@@ -1936,6 +1961,10 @@
return;
}
+ if (mIsSlidingChildViews) {
+ mLayoutEatenInSliding = true;
+ return;
+ }
if (!mLayoutEnabled) {
discardLayoutInfo();
removeAndRecycleAllViews(recycler);
@@ -2385,7 +2414,7 @@
public void setSelection(int position, int subposition, boolean smooth,
int primaryScrollExtra) {
- if (mIsSlidingChildViews || mFocusPosition != position && position != NO_POSITION
+ if (mFocusPosition != position && position != NO_POSITION
|| subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
scrollToSelection(position, subposition, smooth, primaryScrollExtra);
}
@@ -2404,7 +2433,7 @@
mFocusPosition = position;
mSubFocusPosition = subposition;
mFocusPositionOffset = Integer.MIN_VALUE;
- if (!mLayoutEnabled) {
+ if (!mLayoutEnabled || mIsSlidingChildViews) {
return;
}
if (smooth) {
@@ -2651,17 +2680,19 @@
}
/**
- * Scroll to a given child view and change mFocusPosition.
+ * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
*/
void scrollToView(View view, boolean smooth) {
scrollToView(view, view == null ? null : view.findFocus(), smooth);
}
/**
- * Scroll to a given child view and change mFocusPosition.
+ * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
*/
private void scrollToView(View view, View childView, boolean smooth) {
- mIsSlidingChildViews = false;
+ if (mIsSlidingChildViews) {
+ return;
+ }
int newFocusPosition = getPositionByView(view);
int newSubFocusPosition = getSubPositionByView(view, childView);
if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
index 6678101..2536f0a 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
@@ -278,6 +278,13 @@
}
+ void changeItem(int position, int itemValue) {
+ mItemLengths[position] = itemValue;
+ if (mGridView.getAdapter() != null) {
+ mGridView.getAdapter().notifyItemChanged(position);
+ }
+ }
+
void addItems(int index, int[] items) {
int length = items.length;
if (mItemLengths.length < mNumItems + length) {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 7dab382..8bb53a4 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -2988,8 +2988,28 @@
assertTrue(selectedPosition2 < selectedPosition1);
}
+ void slideInAndWaitIdle() throws Throwable {
+ slideInAndWaitIdle(5000);
+ }
+
+ void slideInAndWaitIdle(long timeout) throws Throwable {
+ // animateIn() would reset position
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.animateIn();
+ }
+ });
+ PollingCheck.waitFor(timeout, new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return !mGridView.getLayoutManager().isSmoothScrolling()
+ && mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+ }
+ });
+ }
+
@Test
- public void testAnimateOutResetByScrollTo() throws Throwable {
+ public void testAnimateOutBlockScrollTo() throws Throwable {
Intent intent = new Intent();
intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.vertical_linear_with_button_onleft);
@@ -3012,12 +3032,14 @@
mGridView.animateOut();
}
});
+ // wait until sliding out.
PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
@Override
public boolean canProceed() {
return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
}
});
+ // scrollToPosition() should not affect slideOut status
mActivityTestRule.runOnUiThread(new Runnable() {
public void run() {
mGridView.scrollToPosition(0);
@@ -3029,13 +3051,180 @@
return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
}
});
+ assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+ >= mGridView.getHeight());
+ slideInAndWaitIdle();
assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
mGridView.getChildAt(0).getTop());
}
@Test
- public void testAnimateOutResetByFocusChange() throws Throwable {
+ public void testAnimateOutBlockSmoothScrolling() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button_onleft);
+ int[] items = new int[30];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = 300;
+ }
+ intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ initActivity(intent);
+
+ assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+ mGridView.getChildAt(0).getTop());
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.animateOut();
+ }
+ });
+ // wait until sliding out.
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+ }
+ });
+ // smoothScrollToPosition() should not affect slideOut status
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.smoothScrollToPosition(29);
+ }
+ });
+ PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+ }
+ });
+ assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+ >= mGridView.getHeight());
+
+ slideInAndWaitIdle();
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+ assertSame("Scrolled to last child",
+ mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
+ }
+
+ @Test
+ public void testAnimateOutBlockLongScrollTo() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button_onleft);
+ int[] items = new int[30];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = 300;
+ }
+ intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ initActivity(intent);
+
+ assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+ mGridView.getChildAt(0).getTop());
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.animateOut();
+ }
+ });
+ // wait until sliding out.
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+ }
+ });
+ // smoothScrollToPosition() should not affect slideOut status
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.scrollToPosition(29);
+ }
+ });
+ PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+ }
+ });
+ assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+ >= mGridView.getHeight());
+
+ slideInAndWaitIdle();
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+ assertSame("Scrolled to last child",
+ mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
+ }
+
+ @Test
+ public void testAnimateOutBlockLayout() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button_onleft);
+ int[] items = new int[100];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = 300;
+ }
+ intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ initActivity(intent);
+
+ assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+ mGridView.getChildAt(0).getTop());
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.animateOut();
+ }
+ });
+ // wait until sliding out.
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+ }
+ });
+ // change adapter should not affect slideOut status
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.changeItem(0, 200);
+ }
+ });
+ PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+ }
+ });
+ assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+ >= mGridView.getHeight());
+ assertEquals("onLayout suppressed during slide out", 300,
+ mGridView.getChildAt(0).getHeight());
+
+ slideInAndWaitIdle();
+ assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+ mGridView.getChildAt(0).getTop());
+ // size of item should be updated immediately after slide in animation finishes:
+ PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
+ @Override
+ public boolean canProceed() {
+ return 200 == mGridView.getChildAt(0).getHeight();
+ }
+ });
+ }
+
+ @Test
+ public void testAnimateOutBlockFocusChange() throws Throwable {
Intent intent = new Intent();
intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.vertical_linear_with_button_onleft);
@@ -3077,13 +3266,16 @@
return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
}
});
+ assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+ >= mGridView.getHeight());
+ slideInAndWaitIdle();
assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
mGridView.getChildAt(0).getTop());
}
@Test
- public void testHorizontalAnimateOutResetByScrollTo() throws Throwable {
+ public void testHorizontalAnimateOutBlockScrollTo() throws Throwable {
Intent intent = new Intent();
intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.horizontal_linear);
@@ -3124,8 +3316,13 @@
}
});
+ assertTrue("First view is slided out", mGridView.getChildAt(0).getLeft()
+ > mGridView.getWidth());
+
+ slideInAndWaitIdle();
assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
mGridView.getChildAt(0).getLeft());
+
}
@Test
@@ -3172,6 +3369,9 @@
}
});
+ assertTrue("First view is slided out", mGridView.getChildAt(0).getRight() < 0);
+
+ slideInAndWaitIdle();
assertEquals("First view is aligned with padding right",
mGridView.getWidth() - mGridView.getPaddingRight(),
mGridView.getChildAt(0).getRight());