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