Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarView.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarPopupWindow.java b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarView.java |
| similarity index 39% |
| rename from chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarPopupWindow.java |
| rename to chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarView.java |
| index 957a6f0cb51d7fadcaa9e41b8b98c0ba13c5b351..32f9a310c20a555b9ce202f7d8b8d3114e47cb8a 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarPopupWindow.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/SnackbarView.java |
| @@ -4,14 +4,20 @@ |
| package org.chromium.chrome.browser.snackbar; |
| +import android.content.Context; |
| import android.graphics.Bitmap; |
| +import android.graphics.Rect; |
| import android.graphics.drawable.GradientDrawable; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| +import android.view.ViewGroup.MarginLayoutParams; |
| +import android.view.ViewTreeObserver.OnGlobalLayoutListener; |
| +import android.view.animation.Animation; |
| +import android.view.animation.AnimationUtils; |
| +import android.view.animation.DecelerateInterpolator; |
| import android.widget.ImageView; |
| -import android.widget.PopupWindow; |
| import android.widget.TextView; |
| import org.chromium.base.ApiCompatibilityUtils; |
| @@ -19,61 +25,136 @@ import org.chromium.chrome.R; |
| import org.chromium.ui.base.DeviceFormFactor; |
| /** |
| - * Visual representation of a snackbar. On phone it fills the width of the activity; on tablet it |
| + * Visual representation of a snackbar. On phone it matches the width of the activity; on tablet it |
| * has a fixed width and is anchored at the start-bottom corner of the current window. |
| */ |
| -class SnackbarPopupWindow extends PopupWindow { |
| +class SnackbarView { |
| + private final ViewGroup mView; |
| + private final ViewGroup mParent; |
| private final TemplatePreservingTextView mMessageView; |
| private final TextView mActionButtonView; |
| private final ImageView mProfileImageView; |
| private final int mAnimationDuration; |
| + private final boolean mIsTablet; |
| private Snackbar mSnackbar; |
| + // Variables used to calculate the virtual keyboard's height. |
| + private int[] mTempDecorPosition = new int[2]; |
| + private Rect mCurrentVisibleRect = new Rect(); |
| + private Rect mPreviousVisibleRect = new Rect(); |
| + |
| + private OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() { |
| + @Override |
| + public void onGlobalLayout() { |
| + adjustViewPosition(); |
|
newt (away)
2016/03/08 17:38:16
Could you add a comment explaining why we need to
Ian Wen
2016/03/09 00:20:39
Done.
|
| + } |
| + }; |
| + |
| /** |
| - * Creates an instance of the {@link SnackbarPopupWindow}. |
| - * @param parent Parent View the popup window anchors to |
| + * Creates an instance of the {@link SnackbarView}. |
| + * @param parent The main view of the embedding Activity. |
| * @param listener An {@link OnClickListener} that will be called when the action button is |
| * clicked. |
| * @param snackbar The snackbar to be displayed. |
| */ |
| - SnackbarPopupWindow(View parent, OnClickListener listener, Snackbar snackbar) { |
| - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.snackbar, null); |
| - setContentView(view); |
| - mMessageView = (TemplatePreservingTextView) view.findViewById(R.id.snackbar_message); |
| - mActionButtonView = (TextView) view.findViewById(R.id.snackbar_button); |
| - mAnimationDuration = view.getResources().getInteger( |
| - android.R.integer.config_mediumAnimTime); |
| + SnackbarView(ViewGroup parent, OnClickListener listener, Snackbar snackbar) { |
| + Context context = parent.getContext(); |
| + mIsTablet = DeviceFormFactor.isTablet(context); |
| + mParent = parent; |
| + mView = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.snackbar, mParent, false); |
| + mAnimationDuration = mView.getResources() |
| + .getInteger(android.R.integer.config_mediumAnimTime); |
| + mMessageView = (TemplatePreservingTextView) mView.findViewById(R.id.snackbar_message); |
| + mActionButtonView = (TextView) mView.findViewById(R.id.snackbar_button); |
| mActionButtonView.setOnClickListener(listener); |
| - mProfileImageView = (ImageView) view.findViewById(R.id.snackbar_profile_image); |
| + mProfileImageView = (ImageView) mView.findViewById(R.id.snackbar_profile_image); |
| - // Set width and height of popup window |
| - boolean isTablet = DeviceFormFactor.isTablet(parent.getContext()); |
| - setWidth(isTablet |
| - ? parent.getResources().getDimensionPixelSize(R.dimen.snackbar_tablet_width) |
| - : parent.getWidth()); |
| + updateInternal(snackbar, false); |
| + } |
| - setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); |
| - update(snackbar, false); |
| + void show() { |
| + mParent.addView(mView); |
| + adjustViewPosition(); |
| + mView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); |
| + |
| + // Animation, instead of Animator or ViewPropertyAnimator is prefered here because: |
| + // 1. Animation xml allows specifying 100% as translation, which is much more convenient |
| + // than waiting for mView to be laid out to get measured height. |
| + // 2. Animation avoids SurfaceView's clipping issue when doing translating animation. |
| + Animation fadeIn = AnimationUtils.loadAnimation(mParent.getContext(), R.anim.snackbar_in); |
| + mView.startAnimation(fadeIn); |
| } |
| - @Override |
| - public void dismiss() { |
| + void dismiss() { |
| // Disable action button during animation. |
| mActionButtonView.setEnabled(false); |
| - super.dismiss(); |
| + mView.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener); |
| + // ViewPropertyAnimator is prefered here because Animation is not canceled when the activity |
| + // is in backbround. |
| + mView.animate() |
| + .alpha(0f) |
| + .setDuration(mAnimationDuration) |
| + .setInterpolator(new DecelerateInterpolator()) |
| + .withEndAction(new Runnable() { |
| + @Override |
| + public void run() { |
| + mParent.removeView(mView); |
|
newt (away)
2016/03/08 17:38:16
Does this always get called, even if, say, the act
Ian Wen
2016/03/09 00:20:39
Yes. If the activity is stopped, the runnable is c
|
| + } |
| + }).start(); |
| + } |
| + |
| + void adjustViewPosition() { |
| + mParent.getWindowVisibleDisplayFrame(mCurrentVisibleRect); |
| + // Only update the visible frame has changed, otherwise there will be a layout loop. |
|
newt (away)
2016/03/08 17:38:16
s/the/if the/
Ian Wen
2016/03/09 00:20:39
Done.
|
| + if (!mCurrentVisibleRect.equals(mPreviousVisibleRect)) { |
| + mPreviousVisibleRect.set(mCurrentVisibleRect); |
| + |
| + mParent.getLocationInWindow(mTempDecorPosition); |
| + int activityHeight = mTempDecorPosition[1] + mParent.getHeight(); |
| + int visibleHeight = Math.min(mCurrentVisibleRect.bottom, activityHeight); |
| + int keyboardHeight = activityHeight - visibleHeight; |
| + |
| + int margin = mParent.getResources().getDimensionPixelSize(R.dimen.snackbar_margin); |
| + MarginLayoutParams lp = (MarginLayoutParams) mView.getLayoutParams(); |
| + lp.bottomMargin = keyboardHeight; |
| + if (mIsTablet) { |
|
newt (away)
2016/03/08 17:38:16
It'd be cleaner if we didn't check "mIsTablet" at
Ian Wen
2016/03/09 00:20:39
Discussed offline. mIsTablet is still needed becau
|
| + int width = mParent.getResources().getDimensionPixelSize(R.dimen.snackbar_width); |
| + // In multiwindow mode on tablet, we might still force the snackbar to be at bottom |
| + // if the activity width is too small. |
| + if (width + 2 * margin > mParent.getWidth()) { |
| + lp.width = mParent.getWidth(); |
| + } else { |
| + lp.bottomMargin += margin; |
| + lp.width = width; |
| + } |
| + } |
| + mView.setLayoutParams(lp); |
| + } |
| + } |
| + |
| + boolean isShowing() { |
| + return mView.isShown(); |
| + } |
| + |
| + /** |
| + * Sends an accessibility event to mMessageView announcing that this window was added so that |
| + * the mMessageView content description is read aloud if accessibility is enabled. |
| + */ |
| + void announceforAccessibility() { |
| + mMessageView.announceForAccessibility(mMessageView.getContentDescription()); |
| } |
| /** |
| - * Updates the view to display data from the given snackbar. No-op if the popup is already |
| + * Updates the view to display data from the given snackbar. No-op if the view is already |
| * showing the given snackbar. |
| * @param snackbar The snackbar to display |
| * @return Whether update has actually been executed. |
| */ |
| boolean update(Snackbar snackbar) { |
| - return update(snackbar, true); |
| + return updateInternal(snackbar, true); |
| } |
| - private boolean update(Snackbar snackbar, boolean animate) { |
| + private boolean updateInternal(Snackbar snackbar, boolean animate) { |
| if (mSnackbar == snackbar) return false; |
| mSnackbar = snackbar; |
| mMessageView.setMaxLines(snackbar.getSingleLine() ? 1 : Integer.MAX_VALUE); |
| @@ -81,23 +162,22 @@ class SnackbarPopupWindow extends PopupWindow { |
| setViewText(mMessageView, snackbar.getText(), animate); |
| String actionText = snackbar.getActionText(); |
| - View view = getContentView(); |
| int backgroundColor = snackbar.getBackgroundColor(); |
| if (backgroundColor == 0) { |
| - backgroundColor = ApiCompatibilityUtils.getColor(view.getResources(), |
| + backgroundColor = ApiCompatibilityUtils.getColor(mView.getResources(), |
| R.color.snackbar_background_color); |
| } |
| - if (DeviceFormFactor.isTablet(view.getContext())) { |
| - // On tablet, snackbar popups have rounded corners. |
| - view.setBackgroundResource(R.drawable.snackbar_background); |
| - ((GradientDrawable) view.getBackground()).setColor(backgroundColor); |
| + if (mIsTablet) { |
| + // On tablet, snackbars have rounded corners. |
| + mView.setBackgroundResource(R.drawable.snackbar_background); |
| + ((GradientDrawable) mView.getBackground()).setColor(backgroundColor); |
| } else { |
| - view.setBackgroundColor(backgroundColor); |
| + mView.setBackgroundColor(backgroundColor); |
| } |
| if (snackbar.getBackgroundColor() != 0) { |
| - view.setBackgroundColor(snackbar.getBackgroundColor()); |
| + mView.setBackgroundColor(snackbar.getBackgroundColor()); |
| } |
| if (actionText != null) { |
| mActionButtonView.setVisibility(View.VISIBLE); |
| @@ -109,7 +189,7 @@ class SnackbarPopupWindow extends PopupWindow { |
| if (profileImage != null) { |
| mProfileImageView.setImageBitmap(profileImage); |
| } else { |
| - ((ViewGroup) view).removeView(mProfileImageView); |
| + mView.removeView(mProfileImageView); |
| } |
| return true; |
| } |
| @@ -125,12 +205,4 @@ class SnackbarPopupWindow extends PopupWindow { |
| view.setText(text); |
| } |
| } |
| - |
| - /** |
| - * Sends an accessibility event to mMessageView announcing that this window was added so that |
| - * the mMessageView content description is read aloud if accessibility is enabled. |
| - */ |
| - void announceforAccessibility() { |
| - mMessageView.announceForAccessibility(mMessageView.getContentDescription()); |
| - } |
| } |