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