1 /** 2 * Copyright (c) 2011, Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.mail.ui; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorInflater; 20 import android.content.Context; 21 import android.os.Handler; 22 import android.util.AttributeSet; 23 import android.view.LayoutInflater; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.widget.ImageView; 27 import android.widget.LinearLayout; 28 import android.widget.TextView; 29 30 import com.android.mail.R; 31 32 /** 33 * A custom {@link View} that exposes an action to the user. 34 */ 35 public class ActionableToastBar extends LinearLayout { 36 private boolean mHidden = false; 37 private Animator mShowAnimation; 38 private Animator mHideAnimation; 39 private final Runnable mRunnable; 40 private final Handler mFadeOutHandler; 41 42 /** How long toast will last in ms */ 43 private static final long TOAST_LIFETIME = 15*1000L; 44 45 /** Icon for the description. */ 46 private ImageView mActionDescriptionIcon; 47 /** The clickable view */ 48 private View mActionButton; 49 /** Icon for the action button. */ 50 private View mActionIcon; 51 /** The view that contains the description. */ 52 private TextView mActionDescriptionView; 53 /** The view that contains the text for the action button. */ 54 private TextView mActionText; 55 private ToastBarOperation mOperation; 56 ActionableToastBar(Context context)57 public ActionableToastBar(Context context) { 58 this(context, null); 59 } 60 ActionableToastBar(Context context, AttributeSet attrs)61 public ActionableToastBar(Context context, AttributeSet attrs) { 62 this(context, attrs, 0); 63 } 64 ActionableToastBar(Context context, AttributeSet attrs, int defStyle)65 public ActionableToastBar(Context context, AttributeSet attrs, int defStyle) { 66 super(context, attrs, defStyle); 67 mFadeOutHandler = new Handler(); 68 mRunnable = new Runnable() { 69 @Override 70 public void run() { 71 if(!mHidden) { 72 hide(true, false /* actionClicked */); 73 } 74 } 75 }; 76 LayoutInflater.from(context).inflate(R.layout.actionable_toast_row, this, true); 77 } 78 79 @Override onFinishInflate()80 protected void onFinishInflate() { 81 super.onFinishInflate(); 82 83 mActionDescriptionIcon = (ImageView) findViewById(R.id.description_icon); 84 mActionDescriptionView = (TextView) findViewById(R.id.description_text); 85 mActionButton = findViewById(R.id.action_button); 86 mActionIcon = findViewById(R.id.action_icon); 87 mActionText = (TextView) findViewById(R.id.action_text); 88 } 89 90 /** 91 * Displays the toast bar and makes it visible. Allows the setting of 92 * parameters to customize the display. 93 * @param listener Performs some action when the action button is clicked. 94 * If the {@link ToastBarOperation} overrides 95 * {@link ToastBarOperation#shouldTakeOnActionClickedPrecedence()} 96 * to return <code>true</code>, the 97 * {@link ToastBarOperation#onActionClicked(android.content.Context)} 98 * will override this listener and be called instead. 99 * @param descriptionIconResourceId resource ID for the description icon or 100 * 0 if no icon should be shown 101 * @param descriptionText a description text to show in the toast bar 102 * @param showActionIcon if true, the action button icon should be shown 103 * @param actionTextResource resource ID for the text to show in the action button 104 * @param replaceVisibleToast if true, this toast should replace any currently visible toast. 105 * Otherwise, skip showing this toast. 106 * @param op the operation that corresponds to the specific toast being shown 107 */ show(final ActionClickedListener listener, int descriptionIconResourceId, CharSequence descriptionText, boolean showActionIcon, int actionTextResource, boolean replaceVisibleToast, final ToastBarOperation op)108 public void show(final ActionClickedListener listener, int descriptionIconResourceId, 109 CharSequence descriptionText, boolean showActionIcon, int actionTextResource, 110 boolean replaceVisibleToast, final ToastBarOperation op) { 111 112 if (!mHidden && !replaceVisibleToast) { 113 return; 114 } 115 // Remove any running delayed animations first 116 mFadeOutHandler.removeCallbacks(mRunnable); 117 118 mOperation = op; 119 120 mActionButton.setOnClickListener(new OnClickListener() { 121 @Override 122 public void onClick(View widget) { 123 if (op.shouldTakeOnActionClickedPrecedence()) { 124 op.onActionClicked(getContext()); 125 } else { 126 listener.onActionClicked(getContext()); 127 } 128 hide(true /* animate */, true /* actionClicked */); 129 } 130 }); 131 132 // Set description icon. 133 if (descriptionIconResourceId == 0) { 134 mActionDescriptionIcon.setVisibility(GONE); 135 } else { 136 mActionDescriptionIcon.setVisibility(VISIBLE); 137 mActionDescriptionIcon.setImageResource(descriptionIconResourceId); 138 } 139 140 mActionDescriptionView.setText(descriptionText); 141 mActionIcon.setVisibility(showActionIcon ? VISIBLE : GONE); 142 mActionText.setText(actionTextResource); 143 144 mHidden = false; 145 getShowAnimation().start(); 146 147 // Set up runnable to execute hide toast once delay is completed 148 mFadeOutHandler.postDelayed(mRunnable, TOAST_LIFETIME); 149 } 150 getOperation()151 public ToastBarOperation getOperation() { 152 return mOperation; 153 } 154 155 /** 156 * Hides the view and resets the state. 157 */ hide(boolean animate, boolean actionClicked)158 public void hide(boolean animate, boolean actionClicked) { 159 mHidden = true; 160 mFadeOutHandler.removeCallbacks(mRunnable); 161 if (getVisibility() == View.VISIBLE) { 162 mActionDescriptionView.setText(""); 163 mActionButton.setOnClickListener(null); 164 // Hide view once it's clicked. 165 if (animate) { 166 getHideAnimation().start(); 167 } else { 168 setAlpha(0); 169 setVisibility(View.GONE); 170 } 171 172 if (!actionClicked && mOperation != null) { 173 mOperation.onToastBarTimeout(getContext()); 174 } 175 } 176 } 177 getShowAnimation()178 private Animator getShowAnimation() { 179 if (mShowAnimation == null) { 180 mShowAnimation = AnimatorInflater.loadAnimator(getContext(), 181 R.anim.fade_in); 182 mShowAnimation.addListener(new Animator.AnimatorListener() { 183 @Override 184 public void onAnimationStart(Animator animation) { 185 setVisibility(View.VISIBLE); 186 } 187 @Override 188 public void onAnimationEnd(Animator animation) { 189 } 190 @Override 191 public void onAnimationCancel(Animator animation) { 192 } 193 @Override 194 public void onAnimationRepeat(Animator animation) { 195 } 196 }); 197 mShowAnimation.setTarget(this); 198 } 199 return mShowAnimation; 200 } 201 getHideAnimation()202 private Animator getHideAnimation() { 203 if (mHideAnimation == null) { 204 mHideAnimation = AnimatorInflater.loadAnimator(getContext(), 205 R.anim.fade_out); 206 mHideAnimation.addListener(new Animator.AnimatorListener() { 207 @Override 208 public void onAnimationStart(Animator animation) { 209 } 210 @Override 211 public void onAnimationRepeat(Animator animation) { 212 } 213 @Override 214 public void onAnimationEnd(Animator animation) { 215 setVisibility(View.GONE); 216 } 217 @Override 218 public void onAnimationCancel(Animator animation) { 219 } 220 }); 221 mHideAnimation.setTarget(this); 222 } 223 return mHideAnimation; 224 } 225 isEventInToastBar(MotionEvent event)226 public boolean isEventInToastBar(MotionEvent event) { 227 if (!isShown()) { 228 return false; 229 } 230 int[] xy = new int[2]; 231 float x = event.getX(); 232 float y = event.getY(); 233 getLocationOnScreen(xy); 234 return (x > xy[0] && x < (xy[0] + getWidth()) && y > xy[1] && y < xy[1] + getHeight()); 235 } 236 isAnimating()237 public boolean isAnimating() { 238 return mShowAnimation != null && mShowAnimation.isStarted(); 239 } 240 241 @Override onDetachedFromWindow()242 public void onDetachedFromWindow() { 243 mFadeOutHandler.removeCallbacks(mRunnable); 244 super.onDetachedFromWindow(); 245 } 246 247 /** 248 * Classes that wish to perform some action when the action button is clicked 249 * should implement this interface. 250 */ 251 public interface ActionClickedListener { onActionClicked(Context context)252 public void onActionClicked(Context context); 253 } 254 } 255