blob: 73f9b31d334ddd7c1587b6052f884742aa2aa698 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.widget.ImageViewCompat;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.IntDef;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ui.widget.animation.Interpolators;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Parent for TabGridDialog component.
*/
public class TabGridDialogParent
implements TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider {
private static final int DIALOG_ANIMATION_DURATION = 300;
private static final int DIALOG_ALPHA_ANIMATION_DURATION = 150;
private static final int CARD_FADE_ANIMATION_DURATION = 50;
@IntDef({UngroupBarStatus.SHOW, UngroupBarStatus.HIDE, UngroupBarStatus.HOVERED})
@Retention(RetentionPolicy.SOURCE)
public @interface UngroupBarStatus {
int SHOW = 0;
int HIDE = 1;
int HOVERED = 2;
int NUM_ENTRIES = 3;
}
/** Params that define information about the origin tab grid card of the show/hide animation. */
public static class AnimationParams {
/**
* The {@link Rect} of origin tab grid card of the current tab, relative to the
* {@link TabListRecyclerView} coordinates.
*/
public final Rect sourceRect;
/**
* The {@link View} that is the item view of origin tab grid card in the {@link
* TabListRecyclerView}.
*/
public final View sourceView;
/**
* Build a new set of params to setup the animation.
* @param sourceRect The {@link Rect} that is the origin rect relative to the {@link
* TabListRecyclerView} coordinates.
* @param sourceView The {@link View} that is the item view of origin tab grid card in the
* {@link TabListRecyclerView}.
*/
AnimationParams(Rect sourceRect, View sourceView) {
this.sourceRect = sourceRect;
this.sourceView = sourceView;
}
}
private final ComponentCallbacks mComponentCallbacks;
private final FrameLayout.LayoutParams mContainerParams;
private final ViewGroup mParent;
private final int mToolbarHeight;
private final int mScreenHeight;
private final int mScreenWidth;
private final int mUngroupBarHeight;
private final float mTabGridCardPadding;
private PopupWindow mPopupWindow;
private FrameLayout mTabGridDialogParentView;
private RelativeLayout mDialogContainerView;
private ScrimView mScrimView;
private ScrimView.ScrimParams mScrimParams;
private View mContentView;
private View mBackgroundFrame;
private View mAnimationCardView;
private View mItemView;
private View mUngroupBar;
private TextView mUngroupBarTextView;
private Animator mCurrentDialogAnimator;
private Animator mCurrentUngroupBarAnimator;
private AnimatorSet mBasicFadeInAnimation;
private AnimatorSet mBasicFadeOutAnimation;
private ObjectAnimator mUngroupBarShow;
private ObjectAnimator mUngroupBarHide;
private AnimatorSet mShowDialogAnimation;
private AnimatorSet mHideDialogAnimation;
private AnimatorListenerAdapter mShowDialogAnimationListener;
private AnimatorListenerAdapter mHideDialogAnimationListener;
private int mSideMargin;
private int mTopMargin;
private int mCurrentScreenHeight;
private int mCurrentScreenWidth;
private int mOrientation;
private int mUngroupBarStatus = UngroupBarStatus.HIDE;
private int mUngroupBarBackgroundColorResourceId = R.color.tab_grid_dialog_background_color;
private int mUngroupBarHoveredBackgroundColorResourceId = R.color.tab_grid_card_selected_color;
private int mUngroupBarTextAppearance = R.style.TextAppearance_BlueTitle2;
private int mBackgroundDrawableResourceId = R.drawable.tab_grid_dialog_background;
TabGridDialogParent(Context context, ViewGroup parent) {
mParent = parent;
mShowDialogAnimation = new AnimatorSet();
mHideDialogAnimation = new AnimatorSet();
mTabGridCardPadding = context.getResources().getDimension(R.dimen.tab_list_card_padding);
mToolbarHeight =
(int) context.getResources().getDimension(R.dimen.tab_group_toolbar_height);
mUngroupBarHeight =
(int) context.getResources().getDimension(R.dimen.bottom_sheet_peek_height);
mContainerParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getMetrics(displayMetrics);
// Screen height and width when in portrait mode.
mScreenHeight = Math.max(displayMetrics.heightPixels, displayMetrics.widthPixels);
mScreenWidth = Math.min(displayMetrics.heightPixels, displayMetrics.widthPixels);
mComponentCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
updateDialogWithOrientation(context, newConfig.orientation);
}
@Override
public void onLowMemory() {}
};
ContextUtils.getApplicationContext().registerComponentCallbacks(mComponentCallbacks);
setupDialogContent(context);
prepareAnimation();
}
private void setupDialogContent(Context context) {
mTabGridDialogParentView = (FrameLayout) LayoutInflater.from(context).inflate(
R.layout.tab_grid_dialog_layout, mParent, false);
mDialogContainerView = mTabGridDialogParentView.findViewById(R.id.dialog_container_view);
mDialogContainerView.setLayoutParams(mContainerParams);
Drawable backgroundDrawable = mDialogContainerView.getBackground();
DrawableCompat.setTint(backgroundDrawable,
ContextCompat.getColor(context, R.color.default_bg_color_elev_1));
mUngroupBar = mTabGridDialogParentView.findViewById(R.id.dialog_ungroup_bar);
mUngroupBarTextView = mUngroupBar.findViewById(R.id.dialog_ungroup_bar_text);
mBackgroundFrame = mTabGridDialogParentView.findViewById(R.id.dialog_frame);
mBackgroundFrame.setLayoutParams(mContainerParams);
mAnimationCardView = mTabGridDialogParentView.findViewById(R.id.dialog_animation_card_view);
mScrimView = new ScrimView(context, null, mTabGridDialogParentView);
mPopupWindow = new PopupWindow(mTabGridDialogParentView,
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
updateDialogWithOrientation(context, context.getResources().getConfiguration().orientation);
}
private void prepareAnimation() {
mBasicFadeInAnimation = new AnimatorSet();
ObjectAnimator dialogFadeInAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 0f, 1f);
mBasicFadeInAnimation.play(dialogFadeInAnimator);
mBasicFadeInAnimation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
mBasicFadeInAnimation.setDuration(DIALOG_ANIMATION_DURATION);
mBasicFadeInAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// Hide the dummy views for zoom-in and zoom-out animation.
mBackgroundFrame.setAlpha(0f);
mAnimationCardView.setAlpha(0f);
}
});
mBasicFadeOutAnimation = new AnimatorSet();
ObjectAnimator dialogFadeOutAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 1f, 0f);
mBasicFadeOutAnimation.play(dialogFadeOutAnimator);
mBasicFadeOutAnimation.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
mBasicFadeOutAnimation.setDuration(DIALOG_ANIMATION_DURATION);
mBasicFadeOutAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// Hide the dummy views for zoom-in and zoom-out animation.
mBackgroundFrame.setAlpha(0f);
mAnimationCardView.setAlpha(0f);
}
@Override
public void onAnimationEnd(Animator animation) {
// Restore the dummy views for zoom-in and zoom-out animation.
mBackgroundFrame.setAlpha(1f);
mAnimationCardView.setAlpha(1f);
// Restore the original card.
if (mItemView == null) return;
mItemView.setAlpha(1f);
}
});
mShowDialogAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCurrentDialogAnimator = null;
}
};
mHideDialogAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mPopupWindow.dismiss();
mCurrentDialogAnimator = null;
}
};
mUngroupBarShow =
ObjectAnimator.ofFloat(mUngroupBar, View.TRANSLATION_Y, mUngroupBarHeight, 0);
mUngroupBarShow.setDuration(DIALOG_ANIMATION_DURATION);
mUngroupBarShow.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
mUngroupBarShow.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (mCurrentUngroupBarAnimator != null) {
mCurrentUngroupBarAnimator.end();
}
mCurrentUngroupBarAnimator = mUngroupBarShow;
mUngroupBar.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentUngroupBarAnimator = null;
}
});
mUngroupBarHide =
ObjectAnimator.ofFloat(mUngroupBar, View.TRANSLATION_Y, 0, mUngroupBarHeight);
mUngroupBarHide.setDuration(DIALOG_ANIMATION_DURATION);
mUngroupBarHide.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
mUngroupBarHide.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (mCurrentUngroupBarAnimator != null) {
mCurrentUngroupBarAnimator.end();
}
mCurrentUngroupBarAnimator = mUngroupBarHide;
}
@Override
public void onAnimationEnd(Animator animation) {
mUngroupBar.setVisibility(View.INVISIBLE);
mCurrentUngroupBarAnimator = null;
}
});
}
void setupDialogAnimation(AnimationParams animationParams) {
// In case where user jumps to a new page from dialog, clean existing animations in
// mHideDialogAnimation and play basic fade out instead of zooming back to corresponding tab
// grid card.
if (animationParams == null) {
mShowDialogAnimation = new AnimatorSet();
mShowDialogAnimation.play(mBasicFadeInAnimation);
mShowDialogAnimation.removeAllListeners();
mShowDialogAnimation.addListener(mShowDialogAnimationListener);
mHideDialogAnimation = new AnimatorSet();
mHideDialogAnimation.play(mBasicFadeOutAnimation);
mHideDialogAnimation.removeAllListeners();
mHideDialogAnimation.addListener(mHideDialogAnimationListener);
return;
}
Rect rect = animationParams.sourceRect;
mItemView = animationParams.sourceView;
// Setup a dummy animation card that looks the same as the original tab grid card for
// animation.
updateAnimationCardView(mItemView);
// Calculate dialog size.
int statusBarHeight = mCurrentScreenHeight - mParent.getHeight();
int dialogHeight = mCurrentScreenHeight - 2 * mTopMargin - statusBarHeight;
int dialogWidth = mCurrentScreenWidth - 2 * mSideMargin;
// Calculate position and size info about the original tab grid card.
float sourceLeft = rect.left + mTabGridCardPadding;
float sourceTop = rect.top + mToolbarHeight + mTabGridCardPadding;
float sourceHeight = rect.height() - 2 * mTabGridCardPadding;
float sourceWidth = rect.width() - 2 * mTabGridCardPadding;
// Setup animation position info and scale ratio of the background frame.
float frameInitYPosition = -(dialogHeight / 2 + mTopMargin - sourceHeight / 2 - sourceTop);
float frameInitXPosition = -(dialogWidth / 2 + mSideMargin - sourceWidth / 2 - sourceLeft);
float frameScaleY = sourceHeight / dialogHeight;
float frameScaleX = sourceWidth / dialogWidth;
// Setup scale ratio of card and dialog. Height and Width for both dialog and card scale at
// the same rate during scaling animations.
float cardScale = mOrientation == Configuration.ORIENTATION_PORTRAIT
? (float) dialogWidth / rect.width()
: (float) dialogHeight / rect.height();
float dialogScale = frameScaleX;
// Setup animation position info of the animation card.
float cardScaledYPosition = mTopMargin + ((cardScale - 1f) / 2) * sourceHeight;
float cardScaledXPosition = mSideMargin + ((cardScale - 1f) / 2) * sourceWidth;
float cardInitYPosition = sourceTop - mTabGridCardPadding;
float cardInitXPosition = sourceLeft - mTabGridCardPadding;
// Setup animation position info of the dialog.
float dialogInitYPosition =
frameInitYPosition - (sourceHeight - (dialogHeight * dialogScale)) / 2f;
float dialogInitXPosition = frameInitXPosition;
// In the first half of the dialog showing animation, the animation card scales up and moves
// towards where the dialog should be.
final ObjectAnimator cardZoomOutMoveYAnimator = ObjectAnimator.ofFloat(
mAnimationCardView, View.TRANSLATION_Y, cardInitYPosition, cardScaledYPosition);
final ObjectAnimator cardZoomOutMoveXAnimator = ObjectAnimator.ofFloat(
mAnimationCardView, View.TRANSLATION_X, cardInitXPosition, cardScaledXPosition);
final ObjectAnimator cardZoomOutScaleXAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.SCALE_X, 1f, cardScale);
final ObjectAnimator cardZoomOutScaleYAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.SCALE_Y, 1f, cardScale);
AnimatorSet cardZoomOutAnimatorSet = new AnimatorSet();
cardZoomOutAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
cardZoomOutAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
cardZoomOutAnimatorSet.play(cardZoomOutMoveYAnimator)
.with(cardZoomOutMoveXAnimator)
.with(cardZoomOutScaleYAnimator)
.with(cardZoomOutScaleXAnimator);
// In the first half of the dialog showing animation, the animation card fades out as it
// moves and scales up.
final ObjectAnimator cardZoomOutAlphaAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.ALPHA, 1f, 0f);
cardZoomOutAlphaAnimator.setDuration(DIALOG_ALPHA_ANIMATION_DURATION);
cardZoomOutAlphaAnimator.setInterpolator(Interpolators.LINEAR_INTERPOLATOR);
// In the second half of the dialog showing animation, the dialog zooms out from where the
// card stops at the end of the first half and moves towards where the dialog should be.
final ObjectAnimator dialogZoomOutMoveYAnimator = ObjectAnimator.ofFloat(
mDialogContainerView, View.TRANSLATION_Y, dialogInitYPosition, 0f);
final ObjectAnimator dialogZoomOutMoveXAnimator = ObjectAnimator.ofFloat(
mDialogContainerView, View.TRANSLATION_X, dialogInitXPosition, 0f);
final ObjectAnimator dialogZoomOutScaleYAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.SCALE_Y, dialogScale, 1f);
final ObjectAnimator dialogZoomOutScaleXAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.SCALE_X, dialogScale, 1f);
AnimatorSet dialogZoomOutAnimatorSet = new AnimatorSet();
dialogZoomOutAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
dialogZoomOutAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
dialogZoomOutAnimatorSet.play(dialogZoomOutMoveYAnimator)
.with(dialogZoomOutMoveXAnimator)
.with(dialogZoomOutScaleYAnimator)
.with(dialogZoomOutScaleXAnimator);
// In the second half of the dialog showing animation, the dialog fades in while it moves
// and scales up.
final ObjectAnimator dialogZoomOutAlphaAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 0f, 1f);
dialogZoomOutAlphaAnimator.setDuration(DIALOG_ALPHA_ANIMATION_DURATION);
dialogZoomOutAlphaAnimator.setStartDelay(DIALOG_ALPHA_ANIMATION_DURATION);
dialogZoomOutAlphaAnimator.setInterpolator(Interpolators.LINEAR_INTERPOLATOR);
// During the whole dialog showing animation, the frame background scales up and moves so
// that it looks like the card zooms out and becomes the dialog.
final ObjectAnimator frameZoomOutMoveYAnimator = ObjectAnimator.ofFloat(
mBackgroundFrame, View.TRANSLATION_Y, frameInitYPosition, 0f);
final ObjectAnimator frameZoomOutMoveXAnimator = ObjectAnimator.ofFloat(
mBackgroundFrame, View.TRANSLATION_X, frameInitXPosition, 0f);
final ObjectAnimator frameZoomOutScaleYAnimator =
ObjectAnimator.ofFloat(mBackgroundFrame, View.SCALE_Y, frameScaleY, 1f);
final ObjectAnimator frameZoomOutScaleXAnimator =
ObjectAnimator.ofFloat(mBackgroundFrame, View.SCALE_X, frameScaleX, 1f);
AnimatorSet frameZoomOutAnimatorSet = new AnimatorSet();
frameZoomOutAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
frameZoomOutAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
frameZoomOutAnimatorSet.play(frameZoomOutMoveYAnimator)
.with(frameZoomOutMoveXAnimator)
.with(frameZoomOutScaleYAnimator)
.with(frameZoomOutScaleXAnimator);
// After the dialog showing animation starts, the original card in grid tab switcher fades
// out.
final ObjectAnimator tabFadeOutAnimator =
ObjectAnimator.ofFloat(mItemView, View.ALPHA, 1f, 0f);
tabFadeOutAnimator.setDuration(CARD_FADE_ANIMATION_DURATION);
dialogZoomOutAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// At the beginning of the first half of the showing animation, the white frame and
// the animation card should be above the the dialog view.
mBackgroundFrame.bringToFront();
mAnimationCardView.bringToFront();
mDialogContainerView.setAlpha(0f);
}
});
dialogZoomOutAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// At the beginning of the second half of the showing animation, the dialog should
// be above the white frame and the animation card.
mDialogContainerView.bringToFront();
}
});
// Setup the dialog showing animation.
mShowDialogAnimation = new AnimatorSet();
mShowDialogAnimation.play(cardZoomOutAnimatorSet)
.with(cardZoomOutAlphaAnimator)
.with(frameZoomOutAnimatorSet)
.with(dialogZoomOutAnimatorSet)
.with(dialogZoomOutAlphaAnimator)
.with(tabFadeOutAnimator);
mShowDialogAnimation.addListener(mShowDialogAnimationListener);
// In the first half of the dialog hiding animation, the dialog scales down and moves
// towards where the tab grid card should be.
final ObjectAnimator dialogZoomInMoveYAnimator = ObjectAnimator.ofFloat(
mDialogContainerView, View.TRANSLATION_Y, 0f, dialogInitYPosition);
final ObjectAnimator dialogZoomInMoveXAnimator = ObjectAnimator.ofFloat(
mDialogContainerView, View.TRANSLATION_X, 0f, dialogInitXPosition);
final ObjectAnimator dialogZoomInScaleYAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.SCALE_Y, 1f, dialogScale);
final ObjectAnimator dialogZoomInScaleXAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.SCALE_X, 1f, dialogScale);
AnimatorSet dialogZoomInAnimatorSet = new AnimatorSet();
dialogZoomInAnimatorSet.play(dialogZoomInMoveYAnimator)
.with(dialogZoomInMoveXAnimator)
.with(dialogZoomInScaleYAnimator)
.with(dialogZoomInScaleXAnimator);
dialogZoomInAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
dialogZoomInAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
// In the first half of the dialog hiding animation, the dialog fades out while it moves and
// scales down.
final ObjectAnimator dialogZoomInAlphaAnimator =
ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 1f, 0f);
dialogZoomInAlphaAnimator.setDuration(DIALOG_ALPHA_ANIMATION_DURATION);
dialogZoomInAlphaAnimator.setInterpolator(Interpolators.LINEAR_INTERPOLATOR);
// In the second half of the dialog hiding animation, the animation card zooms in from where
// the dialog stops at the end of the first half and moves towards where the card should be.
final ObjectAnimator cardZoomInMoveYAnimator = ObjectAnimator.ofFloat(
mAnimationCardView, View.TRANSLATION_Y, cardScaledYPosition, cardInitYPosition);
final ObjectAnimator cardZoomInMoveXAnimator = ObjectAnimator.ofFloat(
mAnimationCardView, View.TRANSLATION_X, cardScaledXPosition, cardInitXPosition);
final ObjectAnimator cardZoomInScaleXAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.SCALE_X, cardScale, 1f);
final ObjectAnimator cardZoomInScaleYAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.SCALE_Y, cardScale, 1f);
AnimatorSet cardZoomInAnimatorSet = new AnimatorSet();
cardZoomInAnimatorSet.play(cardZoomInMoveYAnimator)
.with(cardZoomInMoveXAnimator)
.with(cardZoomInScaleXAnimator)
.with(cardZoomInScaleYAnimator);
cardZoomInAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
cardZoomInAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
// In the second half of the dialog hiding animation, the tab grid card fades in while it
// scales down and moves.
final ObjectAnimator cardZoomInAlphaAnimator =
ObjectAnimator.ofFloat(mAnimationCardView, View.ALPHA, 0f, 1f);
cardZoomInAlphaAnimator.setDuration(DIALOG_ALPHA_ANIMATION_DURATION);
cardZoomInAlphaAnimator.setStartDelay(DIALOG_ALPHA_ANIMATION_DURATION);
cardZoomInAlphaAnimator.setInterpolator(Interpolators.LINEAR_INTERPOLATOR);
// During the whole dialog hiding animation, the frame background scales down and moves so
// that it looks like the dialog zooms in and becomes the card.
final ObjectAnimator frameZoomInMoveYAnimator = ObjectAnimator.ofFloat(
mBackgroundFrame, View.TRANSLATION_Y, 0f, frameInitYPosition);
final ObjectAnimator frameZoomInMoveXAnimator = ObjectAnimator.ofFloat(
mBackgroundFrame, View.TRANSLATION_X, 0f, frameInitXPosition);
final ObjectAnimator frameZoomInScaleYAnimator =
ObjectAnimator.ofFloat(mBackgroundFrame, View.SCALE_Y, 1f, frameScaleY);
final ObjectAnimator frameZoomInScaleXAnimator =
ObjectAnimator.ofFloat(mBackgroundFrame, View.SCALE_X, 1f, frameScaleX);
AnimatorSet frameZoomInAnimatorSet = new AnimatorSet();
frameZoomInAnimatorSet.play(frameZoomInMoveYAnimator)
.with(frameZoomInMoveXAnimator)
.with(frameZoomInScaleYAnimator)
.with(frameZoomInScaleXAnimator);
frameZoomInAnimatorSet.setDuration(DIALOG_ANIMATION_DURATION);
frameZoomInAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
// At the end of the dialog hiding animation, the original tab grid card fades in.
final ObjectAnimator tabFadeInAnimator =
ObjectAnimator.ofFloat(mItemView, View.ALPHA, 0f, 1f);
tabFadeInAnimator.setDuration(CARD_FADE_ANIMATION_DURATION);
tabFadeInAnimator.setStartDelay(DIALOG_ANIMATION_DURATION - CARD_FADE_ANIMATION_DURATION);
cardZoomInAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// At the beginning of the second half of the hiding animation, the white frame and
// the animation card should be above the the dialog view.
mBackgroundFrame.bringToFront();
mAnimationCardView.bringToFront();
}
});
// Setup the dialog hiding animation.
mHideDialogAnimation = new AnimatorSet();
mHideDialogAnimation.play(dialogZoomInAnimatorSet)
.with(dialogZoomInAlphaAnimator)
.with(frameZoomInAnimatorSet)
.with(cardZoomInAnimatorSet)
.with(cardZoomInAlphaAnimator)
.with(tabFadeInAnimator);
mHideDialogAnimation.addListener(mHideDialogAnimationListener);
}
@VisibleForTesting
void updateDialogWithOrientation(Context context, int orientation) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
mSideMargin =
(int) context.getResources().getDimension(R.dimen.tab_grid_dialog_side_margin);
mTopMargin =
(int) context.getResources().getDimension(R.dimen.tab_grid_dialog_top_margin);
mCurrentScreenHeight = mScreenHeight;
mCurrentScreenWidth = mScreenWidth;
} else {
mSideMargin =
(int) context.getResources().getDimension(R.dimen.tab_grid_dialog_top_margin);
mTopMargin =
(int) context.getResources().getDimension(R.dimen.tab_grid_dialog_side_margin);
mCurrentScreenWidth = mScreenHeight;
mCurrentScreenHeight = mScreenWidth;
}
mContainerParams.setMargins(mSideMargin, mTopMargin, mSideMargin, mTopMargin);
mOrientation = orientation;
}
private void updateAnimationCardView(View view) {
// Update the dummy animation card view with the actual item view from grid tab switcher
// recyclerView.
FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mAnimationCardView.getLayoutParams();
params.width = view.getWidth();
params.height = view.getHeight();
if (view.findViewById(R.id.tab_title) == null) return;
mAnimationCardView.findViewById(R.id.card_view)
.setBackground(view.findViewById(R.id.card_view).getBackground());
((ImageView) (mAnimationCardView.findViewById(R.id.tab_favicon)))
.setImageDrawable(
((ImageView) (view.findViewById(R.id.tab_favicon))).getDrawable());
((TextView) (mAnimationCardView.findViewById(R.id.tab_title)))
.setText(((TextView) (view.findViewById(R.id.tab_title))).getText());
((TextView) (mAnimationCardView.findViewById(R.id.tab_title)))
.setTextColor(((TextView) (view.findViewById(R.id.tab_title))).getTextColors());
((ImageView) (mAnimationCardView.findViewById(R.id.tab_thumbnail)))
.setImageDrawable(
((ImageView) (view.findViewById(R.id.tab_thumbnail))).getDrawable());
ImageView actionButton = mAnimationCardView.findViewById(R.id.action_button);
actionButton.setImageDrawable(
((ImageView) (view.findViewById(R.id.action_button))).getDrawable());
ApiCompatibilityUtils.setImageTintList(actionButton,
ImageViewCompat.getImageTintList((view.findViewById(R.id.action_button))));
View dividerView = mAnimationCardView.findViewById(R.id.divider_view);
dividerView.setBackgroundColor(
((ColorDrawable) view.findViewById(R.id.divider_view).getBackground()).getColor());
mAnimationCardView.findViewById(R.id.background_view).setBackground(null);
}
/**
* Setup mScrimParams with the {@code scrimViewObserver}.
*
* @param scrimViewObserver The ScrimObserver to be used to setup mScrimParams.
*/
void setScrimViewObserver(ScrimView.ScrimObserver scrimViewObserver) {
mScrimParams =
new ScrimView.ScrimParams(mDialogContainerView, false, true, 0, scrimViewObserver);
}
/**
* Reset the dialog content with {@code toolbarView} and {@code recyclerView}.
*
* @param toolbarView The toolbarview to be added to dialog.
* @param recyclerView The recyclerview to be added to dialog.
*/
void resetDialog(View toolbarView, View recyclerView) {
mContentView = recyclerView;
mDialogContainerView.removeAllViews();
mDialogContainerView.addView(toolbarView);
mDialogContainerView.addView(mContentView);
mDialogContainerView.addView(mUngroupBar);
RelativeLayout.LayoutParams params =
(RelativeLayout.LayoutParams) recyclerView.getLayoutParams();
params.setMargins(0, mToolbarHeight, 0, 0);
recyclerView.setVisibility(View.VISIBLE);
}
/**
* Show {@link PopupWindow} for dialog with animation.
*/
void showDialog() {
if (mCurrentDialogAnimator != null && mCurrentDialogAnimator != mShowDialogAnimation) {
mCurrentDialogAnimator.end();
}
mCurrentDialogAnimator = mShowDialogAnimation;
if (mScrimParams != null) {
mScrimView.showScrim(mScrimParams);
}
mPopupWindow.showAtLocation(mParent, Gravity.CENTER, 0, 0);
mShowDialogAnimation.start();
}
/**
* Hide {@link PopupWindow} for dialog with animation.
*/
void hideDialog() {
if (mCurrentDialogAnimator != null && mCurrentDialogAnimator != mHideDialogAnimation) {
mCurrentDialogAnimator.end();
}
mCurrentDialogAnimator = mHideDialogAnimation;
mScrimView.hideScrim(true);
mHideDialogAnimation.start();
}
/**
* {@link TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider} implementation.
* Returns a {@link Rect} that indicates the current position of dialog.
*/
@Override
public Rect getSelectionEditorPositionRect() {
Rect positionRect = new Rect();
mDialogContainerView.getGlobalVisibleRect(positionRect);
Rect parentRect = new Rect();
mParent.getGlobalVisibleRect(parentRect);
// Offset by the status bar height.
positionRect.offset(0, parentRect.top);
return positionRect;
}
/**
* Destroy any members that needs clean up.
*/
public void destroy() {
ContextUtils.getApplicationContext().unregisterComponentCallbacks(mComponentCallbacks);
}
/**
* Update the ungroup bar based on {@code status}.
*
* @param status The status in {@link TabGridDialogParent.UngroupBarStatus} that the ungroup bar
* should be updated to.
*/
void updateUngroupBar(int status) {
if (status == mUngroupBarStatus) return;
switch (status) {
case UngroupBarStatus.SHOW:
updateUngroupBarTextView(false);
if (mUngroupBarStatus == UngroupBarStatus.HIDE) {
mUngroupBarShow.start();
}
break;
case UngroupBarStatus.HIDE:
mUngroupBarHide.start();
break;
case UngroupBarStatus.HOVERED:
updateUngroupBarTextView(true);
if (mUngroupBarStatus == UngroupBarStatus.HIDE) {
mUngroupBarShow.start();
}
break;
default:
assert false;
}
mUngroupBarStatus = status;
}
private void updateUngroupBarTextView(boolean isHovered) {
assert mUngroupBarTextView.getBackground() instanceof GradientDrawable;
mUngroupBar.bringToFront();
Context context = mParent.getContext();
GradientDrawable background = (GradientDrawable) mUngroupBarTextView.getBackground();
background.setColor(ContextCompat.getColor(context,
isHovered ? mUngroupBarHoveredBackgroundColorResourceId
: mUngroupBarBackgroundColorResourceId));
mUngroupBarTextView.setTextAppearance(context,
isHovered ? R.style.TextAppearance_WhiteTitle2 : mUngroupBarTextAppearance);
}
/**
* Update the dialog container background.
*
* @param backgroundResourceId The new background resource id to use.
*/
void updateDialogContainerBackgroundResource(int backgroundResourceId) {
mBackgroundDrawableResourceId = backgroundResourceId;
mDialogContainerView.setBackgroundResource(backgroundResourceId);
mBackgroundFrame.setBackgroundResource(backgroundResourceId);
}
/**
* Update the ungroup bar background color.
*
* @param colorResource The new background color resource to use when ungroup bar is visible.
*/
void updateUngroupBarBackgroundColor(int colorResource) {
mUngroupBarBackgroundColorResourceId = colorResource;
}
/**
* Update the ungroup bar background color when the ungroup bar is hovered.
*
* @param colorResource The new background color resource to use when ungroup bar is visible
* and hovered.
*/
void updateUngroupBarHoveredBackgroundColor(int colorResource) {
mUngroupBarHoveredBackgroundColorResourceId = colorResource;
}
/**
* Update the ungroup bar text appearance when the ungroup bar is visible but not hovered.
*
* @param textAppearance The new text appearance to use.
*/
void updateUngroupBarTextAppearance(int textAppearance) {
mUngroupBarTextAppearance = textAppearance;
}
@VisibleForTesting
PopupWindow getPopupWindowForTesting() {
return mPopupWindow;
}
@VisibleForTesting
FrameLayout getTabGridDialogParentViewForTesting() {
return mTabGridDialogParentView;
}
@VisibleForTesting
Animator getCurrentDialogAnimatorForTesting() {
return mCurrentDialogAnimator;
}
@VisibleForTesting
View getAnimationCardViewForTesting() {
return mAnimationCardView;
}
@VisibleForTesting
Animator getCurrentUngroupBarAnimatorForTesting() {
return mCurrentUngroupBarAnimator;
}
@VisibleForTesting
int getUngroupBarStatusForTesting() {
return mUngroupBarStatus;
}
@VisibleForTesting
AnimatorSet getShowDialogAnimationForTesting() {
return mShowDialogAnimation;
}
@VisibleForTesting
int getBackgroundDrawableResourceIdForTesting() {
return mBackgroundDrawableResourceId;
}
@VisibleForTesting
int getUngroupBarBackgroundColorResourceIdForTesting() {
return mUngroupBarBackgroundColorResourceId;
}
@VisibleForTesting
int getUngroupBarHoveredBackgroundColorResourceIdForTesting() {
return mUngroupBarHoveredBackgroundColorResourceId;
}
@VisibleForTesting
int getUngroupBarTextAppearanceForTesting() {
return mUngroupBarTextAppearance;
}
}