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