diff options
author | James Lemieux <jplemieux@google.com> | 2014-07-29 16:12:17 -0700 |
---|---|---|
committer | James Lemieux <jplemieux@google.com> | 2014-07-30 11:27:54 -0700 |
commit | 29bc32547fcfb914f8f666f90343067f5d9b8e4d (patch) | |
tree | bcdce506e9f70b5fe3bfaaebfc8830d5bda8dbe4 /src | |
parent | 121084b946d54fa429b9e139b826d0e20cdb7015 (diff) | |
download | android_packages_apps_UnifiedEmail-29bc32547fcfb914f8f666f90343067f5d9b8e4d.tar.gz android_packages_apps_UnifiedEmail-29bc32547fcfb914f8f666f90343067f5d9b8e4d.tar.bz2 android_packages_apps_UnifiedEmail-29bc32547fcfb914f8f666f90343067f5d9b8e4d.zip |
Quantify Snackbars (aka Undo bars)
b/15991901
This CL addresses the major layout and styling changes introduced
by the Material theming. Note that layout is influenced by all of
the following factors:
1) RTL or LTR
2) Action text may exist or not
3) Description text may wrap or not
4) One-pane vs. Two-pane
Change-Id: Ie69bca2cef39666a90c9a43d6b4b030c64e664c8
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/mail/ui/AbstractActivityController.java | 2 | ||||
-rw-r--r-- | src/com/android/mail/ui/ActionableToastBar.java | 261 | ||||
-rw-r--r-- | src/com/android/mail/ui/OnePaneController.java | 4 | ||||
-rw-r--r-- | src/com/android/mail/ui/TwoPaneController.java | 51 |
4 files changed, 121 insertions, 197 deletions
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java index 0b291d11e..90772f36d 100644 --- a/src/com/android/mail/ui/AbstractActivityController.java +++ b/src/com/android/mail/ui/AbstractActivityController.java @@ -4062,9 +4062,7 @@ public abstract class AbstractActivityController implements ActivityController, return; } mToastBar.show(listener, - R.drawable.ic_alert_white, Utils.getSyncStatusText(mActivity.getActivityContext(), lastSyncResult), - false, /* showActionIcon */ actionTextResourceId, replaceVisibleToast, new ToastBarOperation(1, 0, ToastBarOperation.ERROR, false, folder)); diff --git a/src/com/android/mail/ui/ActionableToastBar.java b/src/com/android/mail/ui/ActionableToastBar.java index e940f1b68..69765e23e 100644 --- a/src/com/android/mail/ui/ActionableToastBar.java +++ b/src/com/android/mail/ui/ActionableToastBar.java @@ -17,30 +17,23 @@ package com.android.mail.ui; import android.animation.Animator; import android.animation.AnimatorInflater; -import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.ClipDrawable; -import android.graphics.drawable.Drawable; import android.os.Handler; +import android.support.annotation.StringRes; +import android.text.TextUtils; import android.util.AttributeSet; -import android.view.Gravity; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.FrameLayout; import android.widget.TextView; import com.android.mail.R; -import com.android.mail.utils.Utils; -import com.android.mail.utils.ViewUtils; /** * A custom {@link View} that exposes an action to the user. */ -public class ActionableToastBar extends LinearLayout { +public class ActionableToastBar extends FrameLayout { + private boolean mHidden = false; private Animator mShowAnimation; private Animator mHideAnimation; @@ -50,21 +43,21 @@ public class ActionableToastBar extends LinearLayout { /** How long toast will last in ms */ private static final long TOAST_LIFETIME = 15*1000L; - /** Icon for the description. */ - private ImageView mActionDescriptionIcon; - /** The clickable view */ - private View mActionButton; - /** The divider between the description and the action button. */ - private View mDivider; - /** Icon for the action button. */ - private View mActionIcon; - /** The view that contains the description. */ - private TextView mActionDescriptionView; - /** The view that contains the text for the action button. */ - private TextView mActionText; - private ToastBarOperation mOperation; + /** The view that contains the description when laid out as a single line. */ + private TextView mSingleLineDescriptionView; + + /** The view that contains the text for the action button when laid out as a single line. */ + private TextView mSingleLineActionView; - private ClipBoundsDrawable mButtonDrawable; + /** The view that contains the description when laid out as a multiple lines; + * always <tt>null</tt> in two-pane layouts. */ + private TextView mMultiLineDescriptionView; + + /** The view that contains the text for the action button when laid out as a multiple lines; + * always <tt>null</tt> in two-pane layouts. */ + private TextView mMultiLineActionView; + + private ToastBarOperation mOperation; public ActionableToastBar(Context context) { this(context, null); @@ -80,48 +73,40 @@ public class ActionableToastBar extends LinearLayout { mRunnable = new Runnable() { @Override public void run() { - if(!mHidden) { + if (!mHidden) { hide(true, false /* actionClicked */); } } }; - LayoutInflater.from(context).inflate(R.layout.actionable_toast_row, this, true); } @Override - @SuppressLint("NewApi") protected void onFinishInflate() { super.onFinishInflate(); - mActionDescriptionIcon = (ImageView) findViewById(R.id.description_icon); - mActionDescriptionView = (TextView) findViewById(R.id.description_text); - mActionButton = findViewById(R.id.action_button); - mDivider = findViewById(R.id.divider); - mActionIcon = findViewById(R.id.action_icon); - mActionText = (TextView) findViewById(R.id.action_text); - - if (Utils.isRunningKitkatOrLater()) { - // Wrap the drawable so we can clip the bounds (see explanation in onLayout). - final Drawable buttonToastBackground = mActionButton.getBackground(); - mActionButton.setBackground(null); - mButtonDrawable = new ClipBoundsDrawable(buttonToastBackground); - mActionButton.setBackground(mButtonDrawable); - } + mSingleLineDescriptionView = (TextView) findViewById(R.id.description_text); + mSingleLineActionView = (TextView) findViewById(R.id.action_text); + mMultiLineDescriptionView = (TextView) findViewById(R.id.multiline_description_text); + mMultiLineActionView = (TextView) findViewById(R.id.multiline_action_text); } @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - // The button has the same background on pressed state so it will have rounded corners - // on both the right edge. We clip the background before the divider to remove the - // rounded edge there, creating a split-pill button effect. - if (mButtonDrawable != null) { - final boolean isRtl = ViewUtils.isViewRtl(this); - mButtonDrawable.setClipBounds( - (isRtl ? 0 : mDivider.getLeft()), 0, - (isRtl ? mDivider.getRight() : mActionButton.getWidth()), - mActionButton.getHeight()); + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final boolean showAction = !TextUtils.isEmpty(mSingleLineActionView.getText()); + + // configure the UI assuming the description fits on a single line + setVisibility(false /* multiLine */, showAction); + + // measure the view and its content + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // if the description does not fit, switch to multi line display if one is present + final boolean descriptionIsMultiLine = mSingleLineDescriptionView.getLineCount() > 1; + final boolean haveMultiLineView = mMultiLineDescriptionView != null; + if (descriptionIsMultiLine && haveMultiLineView) { + setVisibility(true /* multiLine */, showAction); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @@ -134,28 +119,25 @@ public class ActionableToastBar extends LinearLayout { * to return <code>true</code>, the * {@link ToastBarOperation#onActionClicked(android.content.Context)} * will override this listener and be called instead. - * @param descriptionIconResourceId resource ID for the description icon or - * 0 if no icon should be shown * @param descriptionText a description text to show in the toast bar - * @param showActionIcon if true, the action button icon should be shown - * @param actionTextResource resource ID for the text to show in the action button + * @param actionTextResourceId resource ID for the text to show in the action button * @param replaceVisibleToast if true, this toast should replace any currently visible toast. * Otherwise, skip showing this toast. * @param op the operation that corresponds to the specific toast being shown */ - public void show(final ActionClickedListener listener, int descriptionIconResourceId, - CharSequence descriptionText, boolean showActionIcon, int actionTextResource, - boolean replaceVisibleToast, final ToastBarOperation op) { - + public void show(final ActionClickedListener listener, final CharSequence descriptionText, + @StringRes final int actionTextResourceId, final boolean replaceVisibleToast, + final ToastBarOperation op) { if (!mHidden && !replaceVisibleToast) { return; } + // Remove any running delayed animations first mFadeOutHandler.removeCallbacks(mRunnable); mOperation = op; - mActionButton.setOnClickListener(new OnClickListener() { + setActionClickListener(new OnClickListener() { @Override public void onClick(View widget) { if (op.shouldTakeOnActionClickedPrecedence()) { @@ -167,17 +149,8 @@ public class ActionableToastBar extends LinearLayout { } }); - // Set description icon. - if (descriptionIconResourceId == 0) { - mActionDescriptionIcon.setVisibility(GONE); - } else { - mActionDescriptionIcon.setVisibility(VISIBLE); - mActionDescriptionIcon.setImageResource(descriptionIconResourceId); - } - - mActionDescriptionView.setText(descriptionText); - mActionIcon.setVisibility(showActionIcon ? VISIBLE : GONE); - mActionText.setText(actionTextResource); + setDescriptionText(descriptionText); + setActionText(actionTextResourceId); mHidden = false; getShowAnimation().start(); @@ -197,8 +170,8 @@ public class ActionableToastBar extends LinearLayout { mHidden = true; mFadeOutHandler.removeCallbacks(mRunnable); if (getVisibility() == View.VISIBLE) { - mActionDescriptionView.setText(""); - mActionButton.setOnClickListener(null); + setDescriptionText(""); + setActionClickListener(null); // Hide view once it's clicked. if (animate) { getHideAnimation().start(); @@ -213,24 +186,41 @@ public class ActionableToastBar extends LinearLayout { } } + public boolean isAnimating() { + return mShowAnimation != null && mShowAnimation.isStarted(); + } + + @Override + public void onDetachedFromWindow() { + mFadeOutHandler.removeCallbacks(mRunnable); + super.onDetachedFromWindow(); + } + + public boolean isEventInToastBar(MotionEvent event) { + if (!isShown()) { + return false; + } + int[] xy = new int[2]; + float x = event.getX(); + float y = event.getY(); + getLocationOnScreen(xy); + return (x > xy[0] && x < (xy[0] + getWidth()) && y > xy[1] && y < xy[1] + getHeight()); + } + private Animator getShowAnimation() { if (mShowAnimation == null) { - mShowAnimation = AnimatorInflater.loadAnimator(getContext(), - R.anim.fade_in); + mShowAnimation = AnimatorInflater.loadAnimator(getContext(), R.anim.fade_in); mShowAnimation.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { setVisibility(View.VISIBLE); } @Override - public void onAnimationEnd(Animator animation) { - } + public void onAnimationEnd(Animator animation) { } @Override - public void onAnimationCancel(Animator animation) { - } + public void onAnimationCancel(Animator animation) { } @Override - public void onAnimationRepeat(Animator animation) { - } + public void onAnimationRepeat(Animator animation) { } }); mShowAnimation.setTarget(this); } @@ -239,47 +229,70 @@ public class ActionableToastBar extends LinearLayout { private Animator getHideAnimation() { if (mHideAnimation == null) { - mHideAnimation = AnimatorInflater.loadAnimator(getContext(), - R.anim.fade_out); + mHideAnimation = AnimatorInflater.loadAnimator(getContext(), R.anim.fade_out); mHideAnimation.addListener(new Animator.AnimatorListener() { @Override - public void onAnimationStart(Animator animation) { - } + public void onAnimationStart(Animator animation) { } @Override - public void onAnimationRepeat(Animator animation) { - } + public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { setVisibility(View.GONE); } @Override - public void onAnimationCancel(Animator animation) { - } + public void onAnimationCancel(Animator animation) { } }); mHideAnimation.setTarget(this); } return mHideAnimation; } - public boolean isEventInToastBar(MotionEvent event) { - if (!isShown()) { - return false; + /** + * If the View requires multiple lines to fully display the toast description then make the + * multi-line view visible and hide the single line view; otherwise vice versa. If the action + * text is present, display it, otherwise hide it. + * + * @param multiLine <tt>true</tt> if the View requires multiple lines to display the toast + * @param showAction <tt>true</tt> if the action text is present and should be shown + */ + private void setVisibility(boolean multiLine, boolean showAction) { + mSingleLineDescriptionView.setVisibility(!multiLine ? View.VISIBLE : View.GONE); + mSingleLineActionView.setVisibility(!multiLine && showAction ? View.VISIBLE : View.GONE); + if (mMultiLineDescriptionView != null) { + mMultiLineDescriptionView.setVisibility(multiLine ? View.VISIBLE : View.GONE); + } + if (mMultiLineActionView != null) { + mMultiLineActionView.setVisibility(multiLine && showAction ? View.VISIBLE : View.GONE); } - int[] xy = new int[2]; - float x = event.getX(); - float y = event.getY(); - getLocationOnScreen(xy); - return (x > xy[0] && x < (xy[0] + getWidth()) && y > xy[1] && y < xy[1] + getHeight()); } - public boolean isAnimating() { - return mShowAnimation != null && mShowAnimation.isStarted(); + private void setDescriptionText(CharSequence description) { + mSingleLineDescriptionView.setText(description); + if (mMultiLineDescriptionView != null) { + mMultiLineDescriptionView.setText(description); + } } - @Override - public void onDetachedFromWindow() { - mFadeOutHandler.removeCallbacks(mRunnable); - super.onDetachedFromWindow(); + private void setActionText(@StringRes int actionTextResourceId) { + if (actionTextResourceId == 0) { + mSingleLineActionView.setText(""); + if (mMultiLineActionView != null) { + mMultiLineActionView.setText(""); + } + } else { + mSingleLineActionView.setText(actionTextResourceId); + if (mMultiLineActionView != null) { + mMultiLineActionView.setText(actionTextResourceId); + } + } + } + + private void setActionClickListener(OnClickListener listener) { + mSingleLineActionView.setOnClickListener(listener); + + if (mMultiLineActionView != null) { + mMultiLineActionView.setOnClickListener(listener); + } } /** @@ -289,36 +302,4 @@ public class ActionableToastBar extends LinearLayout { public interface ActionClickedListener { public void onActionClicked(Context context); } - - /** - * A wrapper that allows a drawable to be clipped at specific bounds. {@link ClipDrawable} only - * supports clipping based on a relative level. This extends {@link ClipDrawable} since it is - * the simplest base class that will delegate the rest of the methods to the wrapped drawable. - * - * <br/><br/><b>Note: Only use on JBMR2 or later as clipRect is not supported until API 18.</b> - */ - private static class ClipBoundsDrawable extends ClipDrawable { - private final Drawable mDrawable; - private final Rect mClipRect = new Rect(); - - public ClipBoundsDrawable(Drawable drawable) { - super(drawable, Gravity.START, ClipDrawable.HORIZONTAL); - mDrawable = drawable; - } - - public void setClipBounds(int left, int top, int right, int bottom) { - mClipRect.left = left; - mClipRect.top = top; - mClipRect.right = right; - mClipRect.bottom = bottom; - } - - @Override - public void draw(Canvas canvas) { - canvas.save(); - canvas.clipRect(mClipRect); - mDrawable.draw(canvas); - canvas.restore(); - } - } -} +}
\ No newline at end of file diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java index 758c352d1..4a9a91611 100644 --- a/src/com/android/mail/ui/OnePaneController.java +++ b/src/com/android/mail/ui/OnePaneController.java @@ -426,10 +426,8 @@ public final class OnePaneController extends AbstractActivityController { case ViewMode.CONVERSATION: mToastBar.show(getUndoClickedListener( convList != null ? convList.getAnimatedAdapter() : null), - 0, Utils.convertHtmlToPlainText (op.getDescription(mActivity.getActivityContext())), - true, /* showActionIcon */ R.string.undo, true, /* replaceVisibleToast */ op); @@ -439,10 +437,8 @@ public final class OnePaneController extends AbstractActivityController { if (convList != null) { mToastBar.show( getUndoClickedListener(convList.getAnimatedAdapter()), - 0, Utils.convertHtmlToPlainText (op.getDescription(mActivity.getActivityContext())), - true, /* showActionIcon */ R.string.undo, true, /* replaceVisibleToast */ op); diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java index 6991a728d..bfe2aac67 100644 --- a/src/com/android/mail/ui/TwoPaneController.java +++ b/src/com/android/mail/ui/TwoPaneController.java @@ -26,7 +26,6 @@ import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.v4.widget.DrawerLayout; import android.view.Gravity; -import android.widget.FrameLayout; import android.widget.ListView; import com.android.mail.ConversationListContext; @@ -500,8 +499,6 @@ public final class TwoPaneController extends AbstractActivityController { final int mode = mViewMode.getMode(); final ConversationListFragment convList = getConversationListFragment(); - repositionToastBar(op); - switch (mode) { case ViewMode.SEARCH_RESULTS_LIST: case ViewMode.CONVERSATION_LIST: @@ -509,10 +506,8 @@ public final class TwoPaneController extends AbstractActivityController { case ViewMode.CONVERSATION: if (convList != null) { mToastBar.show(getUndoClickedListener(convList.getAnimatedAdapter()), - 0, Utils.convertHtmlToPlainText (op.getDescription(mActivity.getActivityContext())), - true, /* showActionIcon */ R.string.undo, true, /* replaceVisibleToast */ op); @@ -520,48 +515,6 @@ public final class TwoPaneController extends AbstractActivityController { } } - public void repositionToastBar(ToastBarOperation op) { - repositionToastBar(op.isBatchUndo()); - } - - /** - * Set the toast bar's layout params to position it in the right place - * depending the current view mode. - * - * @param convModeShowInList if we're in conversation mode, should the toast - * bar appear over the list? no effect when not in conversation mode. - */ - private void repositionToastBar(boolean convModeShowInList) { - final int mode = mViewMode.getMode(); - final FrameLayout.LayoutParams params = - (FrameLayout.LayoutParams) mToastBar.getLayoutParams(); - switch (mode) { - case ViewMode.SEARCH_RESULTS_LIST: - case ViewMode.CONVERSATION_LIST: - params.width = mLayout.computeConversationListWidth() - params.leftMargin - - params.rightMargin; - params.gravity = Gravity.BOTTOM | Gravity.END; - mToastBar.setLayoutParams(params); - break; - case ViewMode.SEARCH_RESULTS_CONVERSATION: - case ViewMode.CONVERSATION: - if (convModeShowInList && !mLayout.isConversationListCollapsed()) { - // Show undo bar in the conversation list. - params.gravity = Gravity.BOTTOM | Gravity.START; - params.width = mLayout.computeConversationListWidth() - params.leftMargin - - params.rightMargin; - mToastBar.setLayoutParams(params); - } else { - // Show undo bar in the conversation. - params.gravity = Gravity.BOTTOM | Gravity.END; - params.width = mLayout.computeConversationWidth() - params.leftMargin - - params.rightMargin; - mToastBar.setLayoutParams(params); - } - break; - } - } - @Override protected void hideOrRepositionToastBar(final boolean animated) { final int oldViewMode = mViewMode.getMode(); @@ -571,9 +524,6 @@ public final class TwoPaneController extends AbstractActivityController { if (/* the touch did not open a conversation */oldViewMode == mViewMode.getMode() || /* animation has ended */!mToastBar.isAnimating()) { mToastBar.hide(animated, false /* actionClicked */); - } else { - // the touch opened a conversation, reposition undo bar - repositionToastBar(mToastBar.getOperation()); } } }, @@ -583,7 +533,6 @@ public final class TwoPaneController extends AbstractActivityController { @Override public void onError(final Folder folder, boolean replaceVisibleToast) { - repositionToastBar(true /* convModeShowInList */); showErrorToast(folder, replaceVisibleToast); } |