Commit 62542ee6 authored by Sinan Sahin's avatar Sinan Sahin Committed by Commit Bot

[Offline indicator v2] Add Java animations for grid tab switcher

This CL adds browser controls animations for the tab switcher. As
changing the margins in every animation frame would cause a relayout and
be expensive, we use the translationY property to animate the page. The
margins are applied only once at the start or end of the animation.

Bug: 1075640
Change-Id: Ic8af8fa9e043bf24bdedcf87ce2ca793f73baa6e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2269197
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784198}
parent d6c547b8
...@@ -28,16 +28,19 @@ class TabListContainerProperties { ...@@ -28,16 +28,19 @@ class TabListContainerProperties {
public static final PropertyModel.WritableBooleanPropertyKey ANIMATE_VISIBILITY_CHANGES = public static final PropertyModel.WritableBooleanPropertyKey ANIMATE_VISIBILITY_CHANGES =
new PropertyModel.WritableBooleanPropertyKey(); new PropertyModel.WritableBooleanPropertyKey();
public static final PropertyModel.WritableIntPropertyKey TOP_CONTROLS_HEIGHT = public static final PropertyModel.WritableIntPropertyKey TOP_MARGIN =
new PropertyModel.WritableIntPropertyKey();
public static final PropertyModel.WritableIntPropertyKey TRANSLATION_Y =
new PropertyModel.WritableIntPropertyKey(); new PropertyModel.WritableIntPropertyKey();
public static final PropertyModel.WritableIntPropertyKey BOTTOM_CONTROLS_HEIGHT = public static final PropertyModel.WritableIntPropertyKey BOTTOM_CONTROLS_HEIGHT =
new PropertyModel.WritableIntPropertyKey(); new PropertyModel.WritableIntPropertyKey();
public static final PropertyModel.WritableIntPropertyKey SHADOW_TOP_MARGIN = public static final PropertyModel.WritableIntPropertyKey SHADOW_TOP_OFFSET =
new PropertyModel.WritableIntPropertyKey(); new PropertyModel.WritableIntPropertyKey();
public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_VISIBLE, IS_INCOGNITO, public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_VISIBLE, IS_INCOGNITO,
VISIBILITY_LISTENER, INITIAL_SCROLL_INDEX, ANIMATE_VISIBILITY_CHANGES, VISIBILITY_LISTENER, INITIAL_SCROLL_INDEX, ANIMATE_VISIBILITY_CHANGES, TRANSLATION_Y,
TOP_CONTROLS_HEIGHT, BOTTOM_CONTROLS_HEIGHT, SHADOW_TOP_MARGIN}; TOP_MARGIN, BOTTOM_CONTROLS_HEIGHT, SHADOW_TOP_OFFSET};
} }
...@@ -9,8 +9,9 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerP ...@@ -9,8 +9,9 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerP
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.INITIAL_SCROLL_INDEX; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.INITIAL_SCROLL_INDEX;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.SHADOW_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.SHADOW_TOP_OFFSET;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TRANSLATION_Y;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
import android.widget.FrameLayout; import android.widget.FrameLayout;
...@@ -48,16 +49,21 @@ class TabListContainerViewBinder { ...@@ -48,16 +49,21 @@ class TabListContainerViewBinder {
// RecyclerView#scrollToPosition(int) behaves incorrectly first time after cold start. // RecyclerView#scrollToPosition(int) behaves incorrectly first time after cold start.
int index = (Integer) model.get(INITIAL_SCROLL_INDEX); int index = (Integer) model.get(INITIAL_SCROLL_INDEX);
((LinearLayoutManager) view.getLayoutManager()).scrollToPositionWithOffset(index, 0); ((LinearLayoutManager) view.getLayoutManager()).scrollToPositionWithOffset(index, 0);
} else if (TOP_CONTROLS_HEIGHT == propertyKey) { } else if (TOP_MARGIN == propertyKey) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams(); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
params.topMargin = model.get(TOP_CONTROLS_HEIGHT); final int newTopMargin = model.get(TOP_MARGIN);
if (newTopMargin == params.topMargin) return;
params.topMargin = newTopMargin;
view.requestLayout(); view.requestLayout();
} else if (TRANSLATION_Y == propertyKey) {
view.setTranslationY(model.get(TRANSLATION_Y));
} else if (BOTTOM_CONTROLS_HEIGHT == propertyKey) { } else if (BOTTOM_CONTROLS_HEIGHT == propertyKey) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams(); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
params.bottomMargin = model.get(BOTTOM_CONTROLS_HEIGHT); params.bottomMargin = model.get(BOTTOM_CONTROLS_HEIGHT);
view.requestLayout(); view.requestLayout();
} else if (SHADOW_TOP_MARGIN == propertyKey) { } else if (SHADOW_TOP_OFFSET == propertyKey) {
view.setShadowTopMargin(model.get(SHADOW_TOP_MARGIN)); view.setShadowTopOffset(model.get(SHADOW_TOP_OFFSET));
} }
} }
} }
...@@ -143,7 +143,7 @@ class TabListRecyclerView ...@@ -143,7 +143,7 @@ class TabListRecyclerView
private boolean mIsDynamicViewRegistered; private boolean mIsDynamicViewRegistered;
private long mLastDirtyTime; private long mLastDirtyTime;
private ImageView mShadowImageView; private ImageView mShadowImageView;
private int mShadowTopMargin; private int mShadowTopOffset;
private TabListOnScrollListener mScrollListener; private TabListOnScrollListener mScrollListener;
private final RemoveItemAnimator mRemoveItemAnimator = new RemoveItemAnimator(); private final RemoveItemAnimator mRemoveItemAnimator = new RemoveItemAnimator();
...@@ -229,8 +229,8 @@ class TabListRecyclerView ...@@ -229,8 +229,8 @@ class TabListRecyclerView
res.getDimensionPixelSize( res.getDimensionPixelSize(
org.chromium.chrome.R.dimen.toolbar_shadow_height), org.chromium.chrome.R.dimen.toolbar_shadow_height),
Gravity.TOP); Gravity.TOP);
params.topMargin = mShadowTopMargin;
mShadowImageView.setLayoutParams(params); mShadowImageView.setLayoutParams(params);
mShadowImageView.setTranslationY(mShadowTopOffset);
FrameLayout parent = (FrameLayout) getParent(); FrameLayout parent = (FrameLayout) getParent();
parent.addView(mShadowImageView); parent.addView(mShadowImageView);
} else if (getParent() instanceof RelativeLayout) { } else if (getParent() instanceof RelativeLayout) {
...@@ -255,31 +255,24 @@ class TabListRecyclerView ...@@ -255,31 +255,24 @@ class TabListRecyclerView
} }
} }
void setShadowTopMargin(int shadowTopMargin) { void setShadowTopOffset(int shadowTopOffset) {
mShadowTopMargin = shadowTopMargin; mShadowTopOffset = shadowTopOffset;
if (mShadowImageView != null && getParent() instanceof FrameLayout) { if (mShadowImageView != null && getParent() instanceof FrameLayout) {
final ViewGroup.MarginLayoutParams layoutParams = // Since the shadow has no functionality, other than just existing visually, we can use
((ViewGroup.MarginLayoutParams) mShadowImageView.getLayoutParams()); // translationY to position it using the top offset. This is preferable to setting a
layoutParams.topMargin = shadowTopMargin; // margin because translation doesn't require a relayout.
mShadowImageView.setLayoutParams(layoutParams); mShadowImageView.setTranslationY(mShadowTopOffset);
// Wait for a layout and set the shadow visibility using the newly computed scroll // Set the shadow visibility using the newly computed scroll offset in case the new
// offset in case the new layout requires us to toggle the shadow visibility. E.g. the // layout requires us to toggle the shadow visibility. E.g. the height increases and the
// height increases and the grid isn't scrolled anymore. // grid isn't scrolled anymore.
mShadowImageView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
final int scrollOffset = computeVerticalScrollOffset(); final int scrollOffset = computeVerticalScrollOffset();
if (scrollOffset == 0) { if (scrollOffset == 0) {
setShadowVisibility(false); setShadowVisibility(false);
} else if (scrollOffset > 0) { } else if (scrollOffset > 0) {
setShadowVisibility(true); setShadowVisibility(true);
} }
v.removeOnLayoutChangeListener(this);
}
});
} }
} }
......
...@@ -9,8 +9,9 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerP ...@@ -9,8 +9,9 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerP
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.INITIAL_SCROLL_INDEX; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.INITIAL_SCROLL_INDEX;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.SHADOW_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.SHADOW_TOP_OFFSET;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TRANSLATION_Y;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER; import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
import android.graphics.Bitmap; import android.graphics.Bitmap;
...@@ -302,19 +303,20 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView ...@@ -302,19 +303,20 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView
}; };
mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() { mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
@Override
public void onContentOffsetChanged(int offset) {}
@Override @Override
public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset, public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {} int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
if (mMode == TabListCoordinator.TabListMode.CAROUSEL) return;
updateTopControlsProperties();
}
@Override @Override
public void onTopControlsHeightChanged( public void onTopControlsHeightChanged(
int topControlsHeight, int topControlsMinHeight) { int topControlsHeight, int topControlsMinHeight) {
if (mMode == TabListCoordinator.TabListMode.CAROUSEL) return; if (mMode == TabListCoordinator.TabListMode.CAROUSEL) return;
updateTopControlsProperties(topControlsHeight); updateTopControlsProperties();
} }
@Override @Override
...@@ -355,7 +357,7 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView ...@@ -355,7 +357,7 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView
// Container view takes care of padding and margin in start surface. // Container view takes care of padding and margin in start surface.
if (mMode != TabListCoordinator.TabListMode.CAROUSEL) { if (mMode != TabListCoordinator.TabListMode.CAROUSEL) {
updateTopControlsProperties(browserControlsStateProvider.getTopControlsHeight()); updateTopControlsProperties();
mContainerViewModel.set( mContainerViewModel.set(
BOTTOM_CONTROLS_HEIGHT, browserControlsStateProvider.getBottomControlsHeight()); BOTTOM_CONTROLS_HEIGHT, browserControlsStateProvider.getBottomControlsHeight());
} }
...@@ -443,13 +445,32 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView ...@@ -443,13 +445,32 @@ class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView
mContainerViewModel.set(IS_VISIBLE, isVisible); mContainerViewModel.set(IS_VISIBLE, isVisible);
} }
private void updateTopControlsProperties(int topControlsHeight) { private void updateTopControlsProperties() {
// The start surface checks in this block are for top controls height and shadow // If the Start surface is enabled, it will handle the margins and positioning of the tab
// margin to be set correctly for displaying the omnibox above the tab switcher. // switcher. So, we shouldn't do it here.
topControlsHeight = if (StartSurfaceConfiguration.isStartSurfaceEnabled()) {
StartSurfaceConfiguration.isStartSurfaceEnabled() ? 0 : topControlsHeight; mContainerViewModel.set(TOP_MARGIN, 0);
mContainerViewModel.set(TOP_CONTROLS_HEIGHT, topControlsHeight); mContainerViewModel.set(SHADOW_TOP_OFFSET, 0);
mContainerViewModel.set(SHADOW_TOP_MARGIN, topControlsHeight); return;
}
final int controlsHeight = mBrowserControlsStateProvider.getTopControlsHeight();
final int contentOffset = mBrowserControlsStateProvider.getContentOffset();
// If the top controls are at the resting position or their height is decreasing, we want to
// update the margin. We don't do this if the controls height is increasing because changing
// the margin shrinks the view height to its final value, leaving a gap at the bottom until
// the animation finishes.
if (contentOffset >= controlsHeight) {
mContainerViewModel.set(TOP_MARGIN, controlsHeight);
}
// If the content offset is different from the margin, we use translationY to position the
// view in line with the content offset.
mContainerViewModel.set(TRANSLATION_Y, contentOffset - mContainerViewModel.get(TOP_MARGIN));
// Offsetting the shadow using the content offset will position it right below the top
// controls.
mContainerViewModel.set(SHADOW_TOP_OFFSET, contentOffset);
} }
/** /**
......
...@@ -28,6 +28,7 @@ import org.junit.runner.RunWith; ...@@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CommandLine; import org.chromium.base.CommandLine;
import org.chromium.base.MathUtils;
import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.DisableIf; import org.chromium.base.test.util.DisableIf;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
...@@ -235,12 +236,12 @@ public class TabListContainerViewBinderTest extends DummyUiActivityTestCase { ...@@ -235,12 +236,12 @@ public class TabListContainerViewBinderTest extends DummyUiActivityTestCase {
@Test @Test
@MediumTest @MediumTest
@UiThreadTest @UiThreadTest
public void testTopContainerHeightSetsTopMargin() { public void testTopMarginSetsTopMargin() {
assertThat(mRecyclerView.getLayoutParams(), instanceOf(FrameLayout.LayoutParams.class)); assertThat(mRecyclerView.getLayoutParams(), instanceOf(FrameLayout.LayoutParams.class));
assertThat( assertThat(
((FrameLayout.LayoutParams) mRecyclerView.getLayoutParams()).topMargin, equalTo(0)); ((FrameLayout.LayoutParams) mRecyclerView.getLayoutParams()).topMargin, equalTo(0));
mContainerModel.set(TabListContainerProperties.TOP_CONTROLS_HEIGHT, CONTAINER_HEIGHT); mContainerModel.set(TabListContainerProperties.TOP_MARGIN, CONTAINER_HEIGHT);
assertThat(((FrameLayout.LayoutParams) mRecyclerView.getLayoutParams()).topMargin, assertThat(((FrameLayout.LayoutParams) mRecyclerView.getLayoutParams()).topMargin,
equalTo(CONTAINER_HEIGHT)); equalTo(CONTAINER_HEIGHT));
} }
...@@ -270,13 +271,34 @@ public class TabListContainerViewBinderTest extends DummyUiActivityTestCase { ...@@ -270,13 +271,34 @@ public class TabListContainerViewBinderTest extends DummyUiActivityTestCase {
ImageView shadowImageView = mRecyclerView.getShadowImageViewForTesting(); ImageView shadowImageView = mRecyclerView.getShadowImageViewForTesting();
assertThat(mRecyclerView.getLayoutParams(), instanceOf(FrameLayout.LayoutParams.class)); assertEquals(0, shadowImageView.getTranslationY(), MathUtils.EPSILON);
assertEquals(0, ((FrameLayout.LayoutParams) shadowImageView.getLayoutParams()).topMargin);
mContainerModel.set(
TabListContainerProperties.SHADOW_TOP_OFFSET, INCREASED_CONTAINER_HEIGHT);
assertEquals(
INCREASED_CONTAINER_HEIGHT, shadowImageView.getTranslationY(), MathUtils.EPSILON);
}
@Test
@MediumTest
@UiThreadTest
public void testTranslationYSetsTranslation() {
mContainerModel.set( mContainerModel.set(
TabListContainerProperties.SHADOW_TOP_MARGIN, INCREASED_CONTAINER_HEIGHT); TabListContainerProperties.VISIBILITY_LISTENER, mMockVisibilityListener);
assertEquals(INCREASED_CONTAINER_HEIGHT,
((FrameLayout.LayoutParams) shadowImageView.getLayoutParams()).topMargin); mContainerModel.set(TabListContainerProperties.ANIMATE_VISIBILITY_CHANGES, false);
mContainerModel.set(TabListContainerProperties.IS_VISIBLE, true);
assertEquals("Wrong initial translationY.", 0, mRecyclerView.getTranslationY(),
MathUtils.EPSILON);
mContainerModel.set(TabListContainerProperties.TRANSLATION_Y, INCREASED_CONTAINER_HEIGHT);
assertEquals("translationY is not set to the correct value.", INCREASED_CONTAINER_HEIGHT,
mRecyclerView.getTranslationY(), MathUtils.EPSILON);
mContainerModel.set(TabListContainerProperties.TRANSLATION_Y, 0);
assertEquals("translationY is not set to the correct value.", 0,
mRecyclerView.getTranslationY(), MathUtils.EPSILON);
} }
@Override @Override
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment