Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1089)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java

Issue 1587403002: Max-width, floating infobars. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: suppress lint warning about using left/right instead of start/end for shadows Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java
index 3dd20bf46d3121aaf6d83826987f453c9efa90a8..f4638c38711f444bf7f2b00cefa7cad6f4e9a8c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarContainerLayout.java
@@ -8,12 +8,13 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Resources;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.chrome.R;
@@ -58,10 +59,7 @@ import java.util.Collection;
* Or can we just call setEnabled() false on the infobar wrapper? Will this cause the buttons
* visual state to change (i.e. to turn gray)?
*
- * TODO(newt): finalize animation timings.
- *
- * TODO: investigate major lag on Nexus 7 runing KK when tapping e.g. "French" on the translation
- * infobar to trigger the Change Languages panel.
+ * TODO(newt): finalize animation timings and interpolators.
*/
class InfoBarContainerLayout extends FrameLayout {
@@ -99,9 +97,9 @@ class InfoBarContainerLayout extends FrameLayout {
*/
InfoBarContainerLayout(Context context) {
super(context);
- setChildrenDrawingOrderEnabled(true);
- mBackInfobarHeight = context.getResources().getDimensionPixelSize(
- R.dimen.infobar_peeking_height);
+ Resources res = context.getResources();
+ mBackInfobarHeight = res.getDimensionPixelSize(R.dimen.infobar_peeking_height);
+ mFloatingBehavior = new FloatingBehavior(this);
}
/**
@@ -200,6 +198,22 @@ class InfoBarContainerLayout extends FrameLayout {
}
/**
+ * Returns an animator that animates an InfoBarWrapper's y-translation from its current
+ * value to endValue and updates the side shadow positions on each frame.
+ */
+ ValueAnimator createTranslationYAnimator(final InfoBarWrapper wrapper, float endValue) {
+ ValueAnimator animator = ValueAnimator.ofFloat(wrapper.getTranslationY(), endValue);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ wrapper.setTranslationY((float) animation.getAnimatedValue());
+ mFloatingBehavior.updateShadowPosition();
+ }
+ });
+ return animator;
+ }
+
+ /**
* Called before the animation begins. This is the time to add views to the hierarchy and
* adjust layout parameters.
*/
@@ -229,32 +243,30 @@ class InfoBarContainerLayout extends FrameLayout {
* content fades in.
*/
private class FrontInfoBarAppearingAnimation extends InfoBarAnimation {
- private ViewGroup mFrontView;
- private View mFrontInnerView;
+ private InfoBarWrapper mFrontWrapper;
+ private View mFrontContents;
- FrontInfoBarAppearingAnimation(View frontInnerView) {
- mFrontInnerView = frontInnerView;
+ FrontInfoBarAppearingAnimation(View frontContents) {
+ mFrontContents = frontContents;
}
@Override
void prepareAnimation() {
- mFrontView = (ViewGroup) LayoutInflater.from(getContext()).inflate(
- R.layout.infobar_wrapper, InfoBarContainerLayout.this, false);
- addInnerView(mFrontView, mFrontInnerView);
- addView(mFrontView);
- updateLayoutParams();
+ mFrontWrapper = new InfoBarWrapper(getContext());
+ mFrontWrapper.addView(mFrontContents);
+ addWrapper(mFrontWrapper);
}
@Override
Animator createAnimator() {
- mFrontView.setTranslationY(mFrontView.getHeight());
- mFrontInnerView.setAlpha(0f);
+ mFrontWrapper.setTranslationY(mFrontWrapper.getHeight());
+ mFrontContents.setAlpha(0f);
AnimatorSet animator = new AnimatorSet();
animator.playSequentially(
- ObjectAnimator.ofFloat(mFrontView, View.TRANSLATION_Y, 0f)
+ createTranslationYAnimator(mFrontWrapper, 0f)
.setDuration(DURATION_SLIDE_UP_MS),
- ObjectAnimator.ofFloat(mFrontInnerView, View.ALPHA, 1f)
+ ObjectAnimator.ofFloat(mFrontContents, View.ALPHA, 1f)
.setDuration(DURATION_FADE_MS));
return animator;
}
@@ -275,20 +287,18 @@ class InfoBarContainerLayout extends FrameLayout {
* its top edge peeks out just a bit.
*/
private class BackInfoBarAppearingAnimation extends InfoBarAnimation {
- private View mAppearingView;
+ private InfoBarWrapper mAppearingWrapper;
@Override
void prepareAnimation() {
- mAppearingView = LayoutInflater.from(getContext()).inflate(R.layout.infobar_wrapper,
- InfoBarContainerLayout.this, false);
- addView(mAppearingView);
- updateLayoutParams();
+ mAppearingWrapper = new InfoBarWrapper(getContext());
+ addWrapper(mAppearingWrapper);
}
@Override
Animator createAnimator() {
- mAppearingView.setTranslationY(mAppearingView.getHeight());
- return ObjectAnimator.ofFloat(mAppearingView, View.TRANSLATION_Y, 0f)
+ mAppearingWrapper.setTranslationY(mAppearingWrapper.getHeight());
+ return createTranslationYAnimator(mAppearingWrapper, 0f)
.setDuration(DURATION_SLIDE_UP_MS);
}
@@ -304,46 +314,46 @@ class InfoBarContainerLayout extends FrameLayout {
* new front infobar, and then the new front infobar's contents will fade in.
*/
private class FrontInfoBarDisappearingAndRevealingAnimation extends InfoBarAnimation {
- private ViewGroup mOldFrontView;
- private ViewGroup mNewFrontView;
- private View mNewFrontInnerView;
+ private InfoBarWrapper mOldFrontWrapper;
+ private InfoBarWrapper mNewFrontWrapper;
+ private View mNewFrontContents;
- FrontInfoBarDisappearingAndRevealingAnimation(View newFrontInnerView) {
- mNewFrontInnerView = newFrontInnerView;
+ FrontInfoBarDisappearingAndRevealingAnimation(View newFrontContents) {
+ mNewFrontContents = newFrontContents;
}
@Override
void prepareAnimation() {
- mOldFrontView = (ViewGroup) getChildAt(0);
- mNewFrontView = (ViewGroup) getChildAt(1);
- addInnerView(mNewFrontView, mNewFrontInnerView);
+ mOldFrontWrapper = mInfoBarWrappers.get(0);
+ mNewFrontWrapper = mInfoBarWrappers.get(1);
+ mNewFrontWrapper.addView(mNewFrontContents);
}
@Override
Animator createAnimator() {
- // The amount by which mNewFrontView will grow (a negative value indicates shrinking).
- int deltaHeight = (mNewFrontView.getHeight() - mBackInfobarHeight)
- - mOldFrontView.getHeight();
+ // The amount by which mNewFrontWrapper will grow (negative value indicates shrinking).
+ int deltaHeight = (mNewFrontWrapper.getHeight() - mBackInfobarHeight)
+ - mOldFrontWrapper.getHeight();
int startTranslationY = Math.max(deltaHeight, 0);
int endTranslationY = Math.max(-deltaHeight, 0);
// Slide the front infobar down and away.
AnimatorSet animator = new AnimatorSet();
- mOldFrontView.setTranslationY(startTranslationY);
- animator.play(ObjectAnimator.ofFloat(mOldFrontView, View.TRANSLATION_Y,
- startTranslationY + mOldFrontView.getHeight())
+ mOldFrontWrapper.setTranslationY(startTranslationY);
+ animator.play(createTranslationYAnimator(mOldFrontWrapper,
+ startTranslationY + mOldFrontWrapper.getHeight())
.setDuration(DURATION_SLIDE_UP_MS));
// Slide the other infobars to their new positions.
// Note: animator.play() causes these animations to run simultaneously.
- for (int i = 1; i < getChildCount(); i++) {
- getChildAt(i).setTranslationY(startTranslationY);
- animator.play(ObjectAnimator.ofFloat(getChildAt(i), View.TRANSLATION_Y,
+ for (int i = 1; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(startTranslationY);
+ animator.play(createTranslationYAnimator(mInfoBarWrappers.get(i),
endTranslationY).setDuration(DURATION_SLIDE_UP_MS));
}
- mNewFrontInnerView.setAlpha(0f);
- animator.play(ObjectAnimator.ofFloat(mNewFrontInnerView, View.ALPHA, 1f)
+ mNewFrontContents.setAlpha(0f);
+ animator.play(ObjectAnimator.ofFloat(mNewFrontContents, View.ALPHA, 1f)
.setDuration(DURATION_FADE_MS)).after(DURATION_SLIDE_UP_MS);
return animator;
@@ -351,12 +361,11 @@ class InfoBarContainerLayout extends FrameLayout {
@Override
void onAnimationEnd() {
- mOldFrontView.removeAllViews();
- removeView(mOldFrontView);
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).setTranslationY(0);
+ mOldFrontWrapper.removeAllViews();
+ removeWrapper(mOldFrontWrapper);
+ for (int i = 0; i < mInfoBarWrappers.size(); i++) {
+ mInfoBarWrappers.get(i).setTranslationY(0);
}
- updateLayoutParams();
announceForAccessibility(mFrontItem.getAccessibilityText());
}
@@ -371,25 +380,24 @@ class InfoBarContainerLayout extends FrameLayout {
* The infobar simply slides down out of the container.
*/
private class InfoBarDisappearingAnimation extends InfoBarAnimation {
- private ViewGroup mDisappearingView;
+ private InfoBarWrapper mDisappearingWrapper;
@Override
void prepareAnimation() {
- mDisappearingView = (ViewGroup) getChildAt(getChildCount() - 1);
+ mDisappearingWrapper = mInfoBarWrappers.get(mInfoBarWrappers.size() - 1);
}
@Override
Animator createAnimator() {
- return ObjectAnimator.ofFloat(mDisappearingView, View.TRANSLATION_Y,
- mDisappearingView.getHeight())
+ return createTranslationYAnimator(mDisappearingWrapper,
+ mDisappearingWrapper.getHeight())
.setDuration(DURATION_SLIDE_DOWN_MS);
}
@Override
void onAnimationEnd() {
- mDisappearingView.removeAllViews();
- removeView(mDisappearingView);
- updateLayoutParams();
+ mDisappearingWrapper.removeAllViews();
+ removeWrapper(mDisappearingWrapper);
}
@Override
@@ -403,41 +411,41 @@ class InfoBarContainerLayout extends FrameLayout {
* then the infobar resizes to fit the new contents, then the new contents fade in.
*/
private class FrontInfoBarSwapContentsAnimation extends InfoBarAnimation {
- private ViewGroup mFrontView;
- private View mOldInnerView;
- private View mNewInnerView;
+ private InfoBarWrapper mFrontWrapper;
+ private View mOldContents;
+ private View mNewContents;
- FrontInfoBarSwapContentsAnimation(View newInnerView) {
- mNewInnerView = newInnerView;
+ FrontInfoBarSwapContentsAnimation(View newContents) {
+ mNewContents = newContents;
}
@Override
void prepareAnimation() {
- mFrontView = (ViewGroup) getChildAt(0);
- mOldInnerView = mFrontView.getChildAt(0);
- addInnerView(mFrontView, mNewInnerView);
+ mFrontWrapper = mInfoBarWrappers.get(0);
+ mOldContents = mFrontWrapper.getChildAt(0);
+ mFrontWrapper.addView(mNewContents);
}
@Override
Animator createAnimator() {
- int deltaHeight = mNewInnerView.getHeight() - mOldInnerView.getHeight();
+ int deltaHeight = mNewContents.getHeight() - mOldContents.getHeight();
InfoBarContainerLayout.this.setTranslationY(Math.max(0, deltaHeight));
- mNewInnerView.setAlpha(0f);
+ mNewContents.setAlpha(0f);
AnimatorSet animator = new AnimatorSet();
animator.playSequentially(
- ObjectAnimator.ofFloat(mOldInnerView, View.ALPHA, 0f)
+ ObjectAnimator.ofFloat(mOldContents, View.ALPHA, 0f)
.setDuration(DURATION_FADE_OUT_MS),
ObjectAnimator.ofFloat(InfoBarContainerLayout.this, View.TRANSLATION_Y,
Math.max(0, -deltaHeight)).setDuration(DURATION_SLIDE_UP_MS),
- ObjectAnimator.ofFloat(mNewInnerView, View.ALPHA, 1f)
+ ObjectAnimator.ofFloat(mNewContents, View.ALPHA, 1f)
.setDuration(DURATION_FADE_OUT_MS));
return animator;
}
@Override
void onAnimationEnd() {
- mFrontView.removeViewAt(0);
+ mFrontWrapper.removeViewAt(0);
InfoBarContainerLayout.this.setTranslationY(0f);
mFrontItem.setControlsEnabled(true);
announceForAccessibility(mFrontItem.getAccessibilityText());
@@ -450,6 +458,130 @@ class InfoBarContainerLayout extends FrameLayout {
}
/**
+ * Controls whether infobars fill the full available width, or whether they "float" in the
+ * middle of the available space. The latter case happens if the available space is wider than
+ * the max width allowed for infobars.
+ *
+ * Also handles the shadows on the sides of the infobars in floating mode. The side shadows are
+ * separate views -- rather than being part of each InfoBarWrapper -- to avoid a double-shadow
+ * effect, which would happen during animations when two InfoBarWrappers overlap each other.
+ */
+ private static class FloatingBehavior {
+ /** The InfoBarContainerLayout. */
+ private FrameLayout mLayout;
+
+ /**
+ * The max width of the infobars. If the available space is wider than this, the infobars
+ * will switch to floating mode.
+ */
+ private final int mMaxWidth;
+
+ /** The width of the left and right shadows. */
+ private final int mShadowWidth;
+
+ /** Whether the layout is currently floating. */
+ private boolean mIsFloating;
+
+ /** The shadows that appear on the sides of the infobars in floating mode. */
+ private View mLeftShadowView;
+ private View mRightShadowView;
+
+ FloatingBehavior(FrameLayout layout) {
+ mLayout = layout;
+ Resources res = mLayout.getContext().getResources();
+ mMaxWidth = res.getDimensionPixelSize(R.dimen.infobar_max_width);
+ mShadowWidth = res.getDimensionPixelSize(R.dimen.infobar_shadow_width);
+ }
+
+ /**
+ * This should be called in onMeasure() before super.onMeasure(). The return value is a new
+ * widthMeasureSpec that should be passed to super.onMeasure().
+ */
+ int beforeOnMeasure(int widthMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ boolean isFloating = width > mMaxWidth;
+ if (isFloating != mIsFloating) {
+ mIsFloating = isFloating;
+ onIsFloatingChanged();
+ }
+
+ if (isFloating) {
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ width = Math.min(width, mMaxWidth + 2 * mShadowWidth);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, mode);
+ }
+ return widthMeasureSpec;
+ }
+
+ /**
+ * This should be called in onMeasure() after super.onMeasure().
+ */
+ void afterOnMeasure(int measuredHeight) {
+ if (!mIsFloating) return;
+ // Measure side shadows to match the parent view's height.
+ int widthSpec = MeasureSpec.makeMeasureSpec(mShadowWidth, MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY);
+ mLeftShadowView.measure(widthSpec, heightSpec);
+ mRightShadowView.measure(widthSpec, heightSpec);
+ }
+
+ /**
+ * This should be called whenever the Y-position of an infobar changes.
+ */
+ void updateShadowPosition() {
+ if (!mIsFloating) return;
+ float minY = mLayout.getHeight();
+ int childCount = mLayout.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mLayout.getChildAt(i);
+ if (child != mLeftShadowView && child != mRightShadowView) {
+ minY = Math.min(minY, child.getY());
+ }
+ }
+ mLeftShadowView.setY(minY);
+ mRightShadowView.setY(minY);
+ }
+
+ private void onIsFloatingChanged() {
+ if (mIsFloating) {
+ initShadowViews();
+ mLayout.setPadding(mShadowWidth, 0, mShadowWidth, 0);
+ mLayout.setClipToPadding(false);
+ mLayout.addView(mLeftShadowView);
+ mLayout.addView(mRightShadowView);
+ } else {
+ mLayout.setPadding(0, 0, 0, 0);
+ mLayout.removeView(mLeftShadowView);
+ mLayout.removeView(mRightShadowView);
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void initShadowViews() {
+ if (mLeftShadowView != null) return;
+
+ mLeftShadowView = new View(mLayout.getContext());
+ mLeftShadowView.setBackgroundResource(R.drawable.infobar_shadow_left);
+ LayoutParams leftLp = new FrameLayout.LayoutParams(0, 0, Gravity.LEFT);
+ leftLp.leftMargin = -mShadowWidth;
+ mLeftShadowView.setLayoutParams(leftLp);
+
+ mRightShadowView = new View(mLayout.getContext());
+ mRightShadowView.setBackgroundResource(R.drawable.infobar_shadow_left);
+ LayoutParams rightLp = new FrameLayout.LayoutParams(0, 0, Gravity.RIGHT);
+ rightLp.rightMargin = -mShadowWidth;
+ mRightShadowView.setScaleX(-1f);
+ mRightShadowView.setLayoutParams(rightLp);
+ }
+ }
+
+ /**
+ * The height of back infobars, i.e. the distance between the top of the front infobar and the
+ * top of the next infobar back.
+ */
+ private final int mBackInfobarHeight;
+
+ /**
* All the Items, in front to back order.
* This list is updated immediately when addInfoBar(), removeInfoBar(), and swapInfoBar() are
* called; so during animations, it does *not* match the currently visible views.
@@ -462,16 +594,17 @@ class InfoBarContainerLayout extends FrameLayout {
*/
private Item mFrontItem;
+ /**
+ * The list of InfoBarWrapper views that are currently visible.
+ */
+ private final ArrayList<InfoBarWrapper> mInfoBarWrappers = new ArrayList<>();
+
/** The current animation, or null if no animation is happening currently. */
private InfoBarAnimation mAnimation;
private InfoBarAnimationListener mAnimationListener;
- /**
- * The height of back infobars, i.e. the distance between the top of the front infobar and the
- * top of the next infobar back.
- */
- private int mBackInfobarHeight;
+ private FloatingBehavior mFloatingBehavior;
/**
* Determines whether any animations need to run in order to make the visible views match the
@@ -485,7 +618,7 @@ class InfoBarContainerLayout extends FrameLayout {
// removals happen before additions or swaps, and changes are made to back infobars before
// front infobars.
- int childCount = getChildCount();
+ int childCount = mInfoBarWrappers.size();
int desiredChildCount = Math.min(mItems.size(), MAX_STACK_DEPTH);
boolean shouldRemoveFrontView = mFrontItem != null && !mItems.contains(mFrontItem);
@@ -513,8 +646,8 @@ class InfoBarContainerLayout extends FrameLayout {
// Third, run swap animation on front infobar if needed.
if (mFrontItem != null && mItems.contains(mFrontItem)) {
- View frontInnerView = ((ViewGroup) getChildAt(0)).getChildAt(0);
- if (frontInnerView != mFrontItem.getView()) {
+ View frontContents = mInfoBarWrappers.get(0).getChildAt(0);
+ if (frontContents != mFrontItem.getView()) {
runAnimation(new FrontInfoBarSwapContentsAnimation(mFrontItem.getView()));
return;
}
@@ -543,19 +676,23 @@ class InfoBarContainerLayout extends FrameLayout {
}
}
- /**
- * Adds an infobar view to a wrapper view, with suitable LayoutParams.
- */
- private void addInnerView(ViewGroup wrapperView, View innerView) {
- wrapperView.addView(innerView, new LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.TOP));
+ private void addWrapper(InfoBarWrapper wrapper) {
+ addView(wrapper, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ mInfoBarWrappers.add(wrapper);
+ updateLayoutParams();
+ }
+
+ private void removeWrapper(InfoBarWrapper wrapper) {
+ removeView(wrapper);
+ mInfoBarWrappers.remove(wrapper);
+ updateLayoutParams();
}
private void updateLayoutParams() {
// Stagger the top margins so the back infobars peek out a bit.
- int childCount = getChildCount();
+ int childCount = mInfoBarWrappers.size();
for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
+ View child = mInfoBarWrappers.get(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.topMargin = (childCount - 1 - i) * mBackInfobarHeight;
child.setLayoutParams(lp);
@@ -563,8 +700,16 @@ class InfoBarContainerLayout extends FrameLayout {
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ widthMeasureSpec = mFloatingBehavior.beforeOnMeasure(widthMeasureSpec);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mFloatingBehavior.afterOnMeasure(getMeasuredHeight());
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ mFloatingBehavior.updateShadowPosition();
// Animations start after a layout has completed, at which point all views are guaranteed
// to have valid sizes and positions.
@@ -574,13 +719,6 @@ class InfoBarContainerLayout extends FrameLayout {
}
@Override
- protected int getChildDrawingOrder(int childCount, int i) {
- // Draw children from last to first. This allows us to order the children from front to
- // back, which simplifies the logic elsewhere in this class.
- return childCount - i - 1;
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Trap any attempts to fiddle with the infobars while we're animating.
return super.onInterceptTouchEvent(ev) || mAnimation != null

Powered by Google App Engine
This is Rietveld 408576698