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 57 public ActionableToastBar(Context context) { 58 this(context, null); 59 } 60 61 public ActionableToastBar(Context context, AttributeSet attrs) { 62 this(context, attrs, 0); 63 } 64 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 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 */ 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 151 public ToastBarOperation getOperation() { 152 return mOperation; 153 } 154 155 /** 156 * Hides the view and resets the state. 157 */ 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 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 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 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 237 public boolean isAnimating() { 238 return mShowAnimation != null && mShowAnimation.isStarted(); 239 } 240 241 @Override 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 { 252 public void onActionClicked(Context context); 253 } 254 } 255