Commit 997afa6d authored by Yue Zhang's avatar Yue Zhang Committed by Commit Bot

Polish TabGridDialog show/hide animation

* This change makes dialog show/hide a two-phase animation. For showing
  dialog, card zooms out and fades out first and then dialog zooms out
  and fades in; vice versa for hiding dialog.
* Add DummyUiActivity tests for the animation to make sure the animated
  views behave correctly during animation.

Bug: 977717
Change-Id: Ic62cb1e6abd45e22909d35cb0eb915f5ef4c416b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1710724
Commit-Queue: Yue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#688635}
parent 9b32c079
......@@ -28,8 +28,13 @@
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/dialog_block_view"
android:clickable="true"
android:focusable="true">
android:id="@+id/dialog_frame"
android:background="@drawable/tab_grid_card_background">
</View>
<FrameLayout
android:id="@+id/dialog_animation_card_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include layout="@layout/tab_grid_card_item"/>
</FrameLayout>
</FrameLayout>
\ No newline at end of file
......@@ -36,13 +36,13 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.ResetHand
TabContentManager tabContentManager, TabCreatorManager tabCreatorManager,
ViewGroup containerView, TabSwitcherMediator.ResetHandler resetHandler,
TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
TabGridDialogMediator.AnimationOriginProvider animationOriginProvider) {
TabGridDialogMediator.AnimationParamsProvider animationParamsProvider) {
mContext = context;
mToolbarPropertyModel = new PropertyModel(TabGridSheetProperties.ALL_KEYS);
mMediator = new TabGridDialogMediator(context, this, mToolbarPropertyModel,
tabModelSelector, tabCreatorManager, resetHandler, animationOriginProvider);
tabModelSelector, tabCreatorManager, resetHandler, animationParamsProvider);
mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
......
......@@ -5,7 +5,6 @@
package org.chromium.chrome.browser.tasks.tab_management;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.view.View;
......@@ -20,7 +19,6 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabSelectionType;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.tab_ui.R;
......@@ -55,18 +53,17 @@ public class TabGridDialogMediator {
}
/**
* Defines an interface for a {@link TabGridDialogMediator} to get the position on the screen
* of the tab grid card related to TabGridDialog in order to prepare animation.
* Defines an interface for a {@link TabGridDialogMediator} to get the {@link
* TabGridDialogParent.AnimationParams} in order to prepare show/hide animation.
*/
interface AnimationOriginProvider {
interface AnimationParamsProvider {
/**
* Provide position of a tab card in GridTabSwitcher as the originate position of a
* animation.
* Provide a {@link TabGridDialogParent.AnimationParams} to setup the animation.
*
* @param index Index in GridTabSwitcher of the tab whose position is requested.
* @return A {@link Rect} that contains position information of the tab card.
* @return A {@link TabGridDialogParent.AnimationParams} used to setup the animation.
*/
Rect getAnimationOriginRect(int index);
TabGridDialogParent.AnimationParams getAnimationParamsForIndex(int index);
}
private final Context mContext;
......@@ -76,21 +73,21 @@ public class TabGridDialogMediator {
private final TabCreatorManager mTabCreatorManager;
private final ResetHandler mDialogResetHandler;
private final TabSwitcherMediator.ResetHandler mTabSwitcherResetHandler;
private final AnimationOriginProvider mAnimationOriginProvider;
private final AnimationParamsProvider mAnimationParamsProvider;
private final DialogHandler mTabGridDialogHandler;
private int mCurrentTabId = Tab.INVALID_TAB_ID;
TabGridDialogMediator(Context context, ResetHandler dialogResetHandler, PropertyModel model,
TabModelSelector tabModelSelector, TabCreatorManager tabCreatorManager,
TabSwitcherMediator.ResetHandler tabSwitcherResetHandler,
AnimationOriginProvider animationOriginProvider) {
AnimationParamsProvider animationParamsProvider) {
mContext = context;
mModel = model;
mTabModelSelector = tabModelSelector;
mTabCreatorManager = tabCreatorManager;
mDialogResetHandler = dialogResetHandler;
mTabSwitcherResetHandler = tabSwitcherResetHandler;
mAnimationOriginProvider = animationOriginProvider;
mAnimationParamsProvider = animationParamsProvider;
mTabGridDialogHandler = new DialogHandler();
// Register for tab model.
......@@ -142,7 +139,19 @@ public class TabGridDialogMediator {
}
void hideDialog(boolean showAnimation) {
if (!showAnimation) mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, null);
if (!showAnimation) {
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, null);
} else {
TabGroupModelFilter filter =
(TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter();
int index = filter.indexOf(
TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), mCurrentTabId));
if (mAnimationParamsProvider != null && index != TabModel.INVALID_TAB_INDEX) {
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS,
mAnimationParamsProvider.getAnimationParamsForIndex(index));
}
}
mDialogResetHandler.resetWithListOfTabs(null);
}
......@@ -154,21 +163,14 @@ public class TabGridDialogMediator {
mCurrentTabId = tabId;
int index = filter.indexOf(
TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId));
if (mAnimationOriginProvider != null) {
Rect rect = mAnimationOriginProvider.getAnimationOriginRect(index);
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, rect);
if (mAnimationParamsProvider != null) {
TabGridDialogParent.AnimationParams params =
mAnimationParamsProvider.getAnimationParamsForIndex(index);
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, params);
}
updateDialog();
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, true);
} else {
if (!FeatureUtilities.isTabToGtsAnimationEnabled()) {
int index = filter.indexOf(TabModelUtils.getTabById(
mTabModelSelector.getCurrentModel(), mCurrentTabId));
if (mAnimationOriginProvider != null && index != TabModel.INVALID_TAB_INDEX) {
Rect rect = mAnimationOriginProvider.getAnimationOriginRect(index);
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, rect);
}
}
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, false);
}
}
......
......@@ -5,7 +5,6 @@
package org.chromium.chrome.browser.tasks.tab_management;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.view.View.OnClickListener;
import org.chromium.chrome.browser.widget.ScrimView;
......@@ -32,11 +31,12 @@ class TabGridSheetProperties {
public static final PropertyModel
.WritableObjectPropertyKey<ScrimView.ScrimObserver> SCRIMVIEW_OBSERVER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<Rect> ANIMATION_SOURCE_RECT =
public static final PropertyModel
.WritableObjectPropertyKey<TabGridDialogParent.AnimationParams> ANIMATION_PARAMS =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableIntPropertyKey UNGROUP_BAR_STATUS =
new PropertyModel.WritableIntPropertyKey();
public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {COLLAPSE_CLICK_LISTENER,
ADD_CLICK_LISTENER, HEADER_TITLE, CONTENT_TOP_MARGIN, PRIMARY_COLOR, TINT,
IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_SOURCE_RECT, UNGROUP_BAR_STATUS};
IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_PARAMS, UNGROUP_BAR_STATUS};
}
......@@ -5,7 +5,7 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.ADD_CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.ANIMATION_SOURCE_RECT;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.ANIMATION_PARAMS;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.COLLAPSE_CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.CONTENT_TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.HEADER_TITLE;
......@@ -74,8 +74,8 @@ class TabGridSheetViewBinder {
} else {
viewHolder.dialogView.hideDialog();
}
} else if (ANIMATION_SOURCE_RECT == propertyKey) {
viewHolder.dialogView.setupDialogAnimation(model.get(ANIMATION_SOURCE_RECT));
} else if (ANIMATION_PARAMS == propertyKey) {
viewHolder.dialogView.setupDialogAnimation(model.get(ANIMATION_PARAMS));
} else if (UNGROUP_BAR_STATUS == propertyKey) {
viewHolder.dialogView.updateUngroupBar(model.get(UNGROUP_BAR_STATUS));
}
......
......@@ -9,6 +9,7 @@ import android.graphics.Bitmap;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.base.Callback;
......@@ -86,7 +87,7 @@ public class TabSwitcherCoordinator implements Destroyable, TabSwitcher,
if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
mTabGridDialogCoordinator = new TabGridDialogCoordinator(context, tabModelSelector,
tabContentManager, tabCreatorManager, container, this, mMediator,
this::getTabGridCardPosition);
this::getTabGridDialogAnimationParams);
mUndoGroupSnackbarController =
new UndoGroupSnackbarController(context, tabModelSelector, snackbarManageable);
......@@ -227,8 +228,12 @@ public class TabSwitcherCoordinator implements Destroyable, TabSwitcher,
return mTabListCoordinator.resetWithListOfTabs(tabs, quickMode, mruMode);
}
private Rect getTabGridCardPosition(int index) {
return mTabListCoordinator.getContainerView().getRectOfCurrentTabGridCard(index);
private TabGridDialogParent.AnimationParams getTabGridDialogAnimationParams(int index) {
View itemView = mTabListCoordinator.getContainerView()
.findViewHolderForAdapterPosition(index)
.itemView;
Rect rect = mTabListCoordinator.getContainerView().getRectOfCurrentTabGridCard(index);
return new TabGridDialogParent.AnimationParams(rect, itemView);
}
@Override
......
......@@ -45,7 +45,6 @@ import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.tabmodel.TabSelectionType;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.util.browser.Features;
......@@ -82,6 +81,8 @@ public class TabGridDialogMediatorUnitTest {
@Mock
Resources mResources;
@Mock
TabGridDialogParent.AnimationParams mAnimationParams;
@Mock
Rect mRect;
@Mock
View mView;
......@@ -96,7 +97,7 @@ public class TabGridDialogMediatorUnitTest {
@Mock
TabSwitcherMediator.ResetHandler mTabSwitcherResetHandler;
@Mock
TabGridDialogMediator.AnimationOriginProvider mAnimationOriginProvider;
TabGridDialogMediator.AnimationParamsProvider mAnimationParamsProvider;
@Mock
TabModelFilterProvider mTabModelFilterProvider;
@Mock
......@@ -153,13 +154,15 @@ public class TabGridDialogMediatorUnitTest {
doReturn(DIALOG_TITLE2)
.when(mResources)
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 2, 2);
doReturn(mRect).when(mAnimationOriginProvider).getAnimationOriginRect(anyInt());
doReturn(mAnimationParams)
.when(mAnimationParamsProvider)
.getAnimationParamsForIndex(anyInt());
doReturn(mTabCreator).when(mTabCreatorManager).getTabCreator(anyBoolean());
mModel = new PropertyModel(TabGridSheetProperties.ALL_KEYS);
mMediator =
new TabGridDialogMediator(mContext, mDialogResetHandler, mModel, mTabModelSelector,
mTabCreatorManager, mTabSwitcherResetHandler, mAnimationOriginProvider);
mTabCreatorManager, mTabSwitcherResetHandler, mAnimationParamsProvider);
}
@After
......@@ -182,13 +185,13 @@ public class TabGridDialogMediatorUnitTest {
@Test
public void onClickAdd_HasCurrentTab() {
// Mock that the animation source Rect is not null.
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, mRect);
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, mAnimationParams);
mMediator.setCurrentTabIdForTest(TAB1_ID);
View.OnClickListener listener = mModel.get(TabGridSheetProperties.ADD_CLICK_LISTENER);
listener.onClick(mView);
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(null));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
verify(mDialogResetHandler).resetWithListOfTabs(null);
verify(mTabCreator)
.createNewTab(
......@@ -224,12 +227,12 @@ public class TabGridDialogMediatorUnitTest {
@Test
public void tabAddition() {
Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
// Mock that the animation source Rect is not null.
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, mRect);
// Mock that the animation params is not null.
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, mAnimationParams);
mTabModelObserverCaptor.getValue().didAddTab(newTab, TabLaunchType.FROM_CHROME_UI);
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(null));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
verify(mDialogResetHandler).resetWithListOfTabs(null);
}
......@@ -279,14 +282,14 @@ public class TabGridDialogMediatorUnitTest {
doReturn(new ArrayList<>()).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
// As last tab in the group, tab1 is definitely the current tab for the dialog.
mMediator.setCurrentTabIdForTest(TAB1_ID);
// Assume the dialog is showing and the source Rect is not null.
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, mRect);
// Assume the dialog is showing and the source animation params is not null.
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, mAnimationParams);
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, true);
mTabModelObserverCaptor.getValue().willCloseTab(mTab1, false);
assertThat(mMediator.getCurrentTabIdForTest(), equalTo(Tab.INVALID_TAB_ID));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(null));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
verify(mDialogResetHandler).resetWithListOfTabs(null);
verify(mTabSwitcherResetHandler, never())
.resetWithTabList(mTabGroupModelFilter, false, false);
......@@ -346,59 +349,80 @@ public class TabGridDialogMediatorUnitTest {
@Test
public void tabSelection() {
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, mAnimationParams);
mTabModelObserverCaptor.getValue().didSelectTab(
mTab1, TabSelectionType.FROM_USER, Tab.INVALID_TAB_ID);
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(null));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
verify(mDialogResetHandler).resetWithListOfTabs(null);
}
@Test
public void hideDialog_Animation() {
FeatureUtilities.setIsTabToGtsAnimationEnabledForTesting(true);
public void hideDialog_FadeOutAnimation() {
// Mock that the animation source Rect is null.
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, null);
// Mock that the dialog is showing and animation source Rect is null.
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, true);
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, null);
mMediator.hideDialog(false);
mMediator.onReset(null);
// Animation params should not be specified.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
verify(mDialogResetHandler).resetWithListOfTabs(eq(null));
}
assertThat(mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE), equalTo(false));
// Animation source Rect should be updated with specific Rect.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(null));
@Test
public void hideDialog_ZoomOutAnimation() {
// Mock that the animation source Rect is null.
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, null);
mMediator.hideDialog(true);
FeatureUtilities.setIsTabToGtsAnimationEnabledForTesting(null);
// Animation params should be specified.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(mAnimationParams));
verify(mDialogResetHandler).resetWithListOfTabs(eq(null));
}
@Test
public void hideDialog_NoAnimation() {
FeatureUtilities.setIsTabToGtsAnimationEnabledForTesting(false);
// Mock that the dialog is showing and animation source Rect is null.
public void hideDialog_onReset() {
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, true);
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, null);
mMediator.onReset(null);
assertThat(mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE), equalTo(false));
// Animation source Rect should be updated with specific Rect.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(mRect));
FeatureUtilities.setIsTabToGtsAnimationEnabledForTesting(null);
}
@Test
public void showDialog() {
public void showDialog_FromGTS() {
// Mock that the dialog is hidden and animation source Rect and header title are all null.
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, null);
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, null);
mModel.set(TabGridSheetProperties.HEADER_TITLE, null);
mMediator.onReset(TAB1_ID);
assertThat(mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Animation source Rect should be updated with specific Rect.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_SOURCE_RECT), equalTo(mRect));
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(mAnimationParams));
// Dialog title should be updated.
assertThat(mModel.get(TabGridSheetProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
}
@Test
public void showDialog_FromStrip() {
// For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
// the animationParamsProvider is null.
mMediator = new TabGridDialogMediator(mContext, mDialogResetHandler, mModel,
mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler, null);
// Mock that the dialog is hidden and animation source Rect and header title are all null.
mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridSheetProperties.ANIMATION_PARAMS, null);
mModel.set(TabGridSheetProperties.HEADER_TITLE, null);
mMediator.onReset(TAB1_ID);
assertThat(mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE), equalTo(true));
// Animation params should not be specified.
assertThat(mModel.get(TabGridSheetProperties.ANIMATION_PARAMS), equalTo(null));
// Dialog title should be updated.
assertThat(mModel.get(TabGridSheetProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
}
......@@ -419,4 +443,4 @@ public class TabGridDialogMediatorUnitTest {
doReturn(true).when(tab).isIncognito();
return tab;
}
}
}
\ No newline at end of file
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