FloatingActionButtonEclairMr1.java revision acbf0dbe11f4d0594dc2949ca18d410b3c3a20b8
1/* 2 * Copyright (C) 2015 The Android Open Source Project 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 17package android.support.design.widget; 18 19import android.content.res.ColorStateList; 20import android.graphics.Color; 21import android.graphics.PorterDuff; 22import android.graphics.Rect; 23import android.graphics.drawable.Drawable; 24import android.graphics.drawable.GradientDrawable; 25import android.graphics.drawable.LayerDrawable; 26import android.os.Build; 27import android.support.design.R; 28import android.support.v4.graphics.drawable.DrawableCompat; 29import android.view.View; 30import android.view.animation.Animation; 31import android.view.animation.Transformation; 32 33class FloatingActionButtonEclairMr1 extends FloatingActionButtonImpl { 34 35 private Drawable mShapeDrawable; 36 private Drawable mRippleDrawable; 37 private Drawable mBorderDrawable; 38 39 private float mElevation; 40 private float mPressedTranslationZ; 41 private int mAnimationDuration; 42 43 private StateListAnimator mStateListAnimator; 44 45 ShadowDrawableWrapper mShadowDrawable; 46 47 private boolean mIsHiding; 48 49 FloatingActionButtonEclairMr1(View view, ShadowViewDelegate shadowViewDelegate) { 50 super(view, shadowViewDelegate); 51 52 mAnimationDuration = view.getResources().getInteger(android.R.integer.config_shortAnimTime); 53 54 mStateListAnimator = new StateListAnimator(); 55 mStateListAnimator.setTarget(view); 56 57 // Elevate with translationZ when pressed or focused 58 mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET, 59 setupAnimation(new ElevateToTranslationZAnimation())); 60 mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET, 61 setupAnimation(new ElevateToTranslationZAnimation())); 62 // Reset back to elevation by default 63 mStateListAnimator.addState(EMPTY_STATE_SET, 64 setupAnimation(new ResetElevationAnimation())); 65 } 66 67 @Override 68 void setBackgroundDrawable(Drawable originalBackground, ColorStateList backgroundTint, 69 PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) { 70 // Now we need to tint the original background with the tint, using 71 // an InsetDrawable if we have a border width 72 mShapeDrawable = DrawableCompat.wrap(mutateDrawable(originalBackground)); 73 DrawableCompat.setTintList(mShapeDrawable, backgroundTint); 74 if (backgroundTintMode != null) { 75 DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode); 76 } 77 78 // Now we created a mask Drawable which will be used for touch feedback. 79 // As we don't know the actual outline of mShapeDrawable, we'll just guess that it's a 80 // circle 81 GradientDrawable touchFeedbackShape = new GradientDrawable(); 82 touchFeedbackShape.setShape(GradientDrawable.OVAL); 83 touchFeedbackShape.setColor(Color.WHITE); 84 touchFeedbackShape.setCornerRadius(mShadowViewDelegate.getRadius()); 85 86 // We'll now wrap that touch feedback mask drawable with a ColorStateList. We do not need 87 // to inset for any border here as LayerDrawable will nest the padding for us 88 mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape); 89 DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); 90 DrawableCompat.setTintMode(mRippleDrawable, PorterDuff.Mode.MULTIPLY); 91 92 final Drawable[] layers; 93 if (borderWidth > 0) { 94 mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint); 95 layers = new Drawable[] {mBorderDrawable, mShapeDrawable, mRippleDrawable}; 96 } else { 97 mBorderDrawable = null; 98 layers = new Drawable[] {mShapeDrawable, mRippleDrawable}; 99 } 100 101 mShadowDrawable = new ShadowDrawableWrapper( 102 mView.getResources(), 103 new LayerDrawable(layers), 104 mShadowViewDelegate.getRadius(), 105 mElevation, 106 mElevation + mPressedTranslationZ); 107 mShadowDrawable.setAddPaddingForCorners(false); 108 109 mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable); 110 111 updatePadding(); 112 } 113 114 private static Drawable mutateDrawable(Drawable drawable) { 115 if (Build.VERSION.SDK_INT < 14 && drawable instanceof GradientDrawable) { 116 // GradientDrawable pre-ICS does not copy over it's color when mutated. We just skip 117 // the mutate and hope for the best. 118 return drawable; 119 } 120 return drawable.mutate(); 121 } 122 123 @Override 124 void setBackgroundTintList(ColorStateList tint) { 125 DrawableCompat.setTintList(mShapeDrawable, tint); 126 if (mBorderDrawable != null) { 127 DrawableCompat.setTintList(mBorderDrawable, tint); 128 } 129 } 130 131 @Override 132 void setBackgroundTintMode(PorterDuff.Mode tintMode) { 133 DrawableCompat.setTintMode(mShapeDrawable, tintMode); 134 } 135 136 @Override 137 void setRippleColor(int rippleColor) { 138 DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); 139 } 140 141 @Override 142 void setElevation(float elevation) { 143 if (mElevation != elevation && mShadowDrawable != null) { 144 mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ); 145 mElevation = elevation; 146 updatePadding(); 147 } 148 } 149 150 @Override 151 void setPressedTranslationZ(float translationZ) { 152 if (mPressedTranslationZ != translationZ && mShadowDrawable != null) { 153 mPressedTranslationZ = translationZ; 154 mShadowDrawable.setMaxShadowSize(mElevation + translationZ); 155 updatePadding(); 156 } 157 } 158 159 @Override 160 void onDrawableStateChanged(int[] state) { 161 mStateListAnimator.setState(state); 162 } 163 164 @Override 165 void jumpDrawableToCurrentState() { 166 mStateListAnimator.jumpToCurrentState(); 167 } 168 169 @Override 170 void hide() { 171 if (mIsHiding || mView.getVisibility() != View.VISIBLE) { 172 // A hide animation is in progress, or we're already hidden. Skip the call 173 return; 174 } 175 176 Animation anim = android.view.animation.AnimationUtils.loadAnimation( 177 mView.getContext(), R.anim.design_fab_out); 178 anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 179 anim.setDuration(SHOW_HIDE_ANIM_DURATION); 180 anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() { 181 @Override 182 public void onAnimationStart(Animation animation) { 183 mIsHiding = true; 184 } 185 186 @Override 187 public void onAnimationEnd(Animation animation) { 188 mIsHiding = false; 189 mView.setVisibility(View.GONE); 190 } 191 }); 192 mView.startAnimation(anim); 193 } 194 195 @Override 196 void show() { 197 if (mView.getVisibility() != View.VISIBLE || mIsHiding) { 198 // If the view is not visible, or is visible and currently being hidden, run 199 // the show animation 200 mView.clearAnimation(); 201 mView.setVisibility(View.VISIBLE); 202 Animation anim = android.view.animation.AnimationUtils.loadAnimation( 203 mView.getContext(), R.anim.design_fab_in); 204 anim.setDuration(SHOW_HIDE_ANIM_DURATION); 205 anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 206 mView.startAnimation(anim); 207 } 208 } 209 210 private void updatePadding() { 211 Rect rect = new Rect(); 212 mShadowDrawable.getPadding(rect); 213 mShadowViewDelegate.setShadowPadding(rect.left, rect.top, rect.right, rect.bottom); 214 } 215 216 private Animation setupAnimation(Animation animation) { 217 animation.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 218 animation.setDuration(mAnimationDuration); 219 return animation; 220 } 221 222 private abstract class BaseShadowAnimation extends Animation { 223 private float mShadowSizeStart; 224 private float mShadowSizeDiff; 225 226 @Override 227 public void reset() { 228 super.reset(); 229 230 mShadowSizeStart = mShadowDrawable.getShadowSize(); 231 mShadowSizeDiff = getTargetShadowSize() - mShadowSizeStart; 232 } 233 234 @Override 235 protected void applyTransformation(float interpolatedTime, Transformation t) { 236 mShadowDrawable.setShadowSize(mShadowSizeStart + (mShadowSizeDiff * interpolatedTime)); 237 } 238 239 /** 240 * @return the shadow size we want to animate to. 241 */ 242 protected abstract float getTargetShadowSize(); 243 } 244 245 private class ResetElevationAnimation extends BaseShadowAnimation { 246 @Override 247 protected float getTargetShadowSize() { 248 return mElevation; 249 } 250 } 251 252 private class ElevateToTranslationZAnimation extends BaseShadowAnimation { 253 @Override 254 protected float getTargetShadowSize() { 255 return mElevation + mPressedTranslationZ; 256 } 257 } 258 259 private static ColorStateList createColorStateList(int selectedColor) { 260 final int[][] states = new int[3][]; 261 final int[] colors = new int[3]; 262 int i = 0; 263 264 states[i] = FOCUSED_ENABLED_STATE_SET; 265 colors[i] = selectedColor; 266 i++; 267 268 states[i] = PRESSED_ENABLED_STATE_SET; 269 colors[i] = selectedColor; 270 i++; 271 272 // Default enabled state 273 states[i] = new int[0]; 274 colors[i] = Color.TRANSPARENT; 275 i++; 276 277 return new ColorStateList(states, colors); 278 } 279}