FloatingActionButtonEclairMr1.java revision 6d7a9a02765e4cb497081e66dafb5d9fa76f4312
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 mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable); 100 } 101 102 @Override 103 void setBackgroundTintList(ColorStateList tint) { 104 DrawableCompat.setTintList(mShapeDrawable, tint); 105 if (mBorderDrawable != null) { 106 mBorderDrawable.setBorderTint(tint); 107 } 108 } 109 110 @Override 111 void setBackgroundTintMode(PorterDuff.Mode tintMode) { 112 DrawableCompat.setTintMode(mShapeDrawable, tintMode); 113 } 114 115 @Override 116 void setRippleColor(int rippleColor) { 117 DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); 118 } 119 120 @Override 121 float getElevation() { 122 return mElevation; 123 } 124 125 @Override 126 void onElevationChanged(float elevation) { 127 if (mShadowDrawable != null) { 128 mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ); 129 updatePadding(); 130 } 131 } 132 133 @Override 134 void onTranslationZChanged(float translationZ) { 135 if (mShadowDrawable != null) { 136 mShadowDrawable.setMaxShadowSize(mElevation + translationZ); 137 updatePadding(); 138 } 139 } 140 141 @Override 142 void onDrawableStateChanged(int[] state) { 143 mStateListAnimator.setState(state); 144 } 145 146 @Override 147 void jumpDrawableToCurrentState() { 148 mStateListAnimator.jumpToCurrentState(); 149 } 150 151 @Override 152 void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { 153 if (mIsHiding || mView.getVisibility() != View.VISIBLE) { 154 // A hide animation is in progress, or we're already hidden. Skip the call 155 if (listener != null) { 156 listener.onHidden(); 157 } 158 return; 159 } 160 161 Animation anim = android.view.animation.AnimationUtils.loadAnimation( 162 mView.getContext(), R.anim.design_fab_out); 163 anim.setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR); 164 anim.setDuration(SHOW_HIDE_ANIM_DURATION); 165 anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() { 166 @Override 167 public void onAnimationStart(Animation animation) { 168 mIsHiding = true; 169 } 170 171 @Override 172 public void onAnimationEnd(Animation animation) { 173 mIsHiding = false; 174 mView.internalSetVisibility(View.GONE, fromUser); 175 if (listener != null) { 176 listener.onHidden(); 177 } 178 } 179 }); 180 mView.startAnimation(anim); 181 } 182 183 @Override 184 void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) { 185 if (mView.getVisibility() != View.VISIBLE || mIsHiding) { 186 // If the view is not visible, or is visible and currently being hidden, run 187 // the show animation 188 mView.clearAnimation(); 189 mView.internalSetVisibility(View.VISIBLE, fromUser); 190 Animation anim = android.view.animation.AnimationUtils.loadAnimation( 191 mView.getContext(), R.anim.design_fab_in); 192 anim.setDuration(SHOW_HIDE_ANIM_DURATION); 193 anim.setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); 194 anim.setAnimationListener(new AnimationListenerAdapter() { 195 @Override 196 public void onAnimationEnd(Animation animation) { 197 if (listener != null) { 198 listener.onShown(); 199 } 200 } 201 }); 202 mView.startAnimation(anim); 203 } else { 204 if (listener != null) { 205 listener.onShown(); 206 } 207 } 208 } 209 210 @Override 211 void onCompatShadowChanged() { 212 // Ignore pre-v21 213 } 214 215 void getPadding(Rect rect) { 216 mShadowDrawable.getPadding(rect); 217 } 218 219 private Animation setupAnimation(Animation animation) { 220 animation.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 221 animation.setDuration(mAnimationDuration); 222 return animation; 223 } 224 225 private abstract class BaseShadowAnimation extends Animation { 226 private float mShadowSizeStart; 227 private float mShadowSizeDiff; 228 229 @Override 230 public void reset() { 231 super.reset(); 232 233 mShadowSizeStart = mShadowDrawable.getShadowSize(); 234 mShadowSizeDiff = getTargetShadowSize() - mShadowSizeStart; 235 } 236 237 @Override 238 protected void applyTransformation(float interpolatedTime, Transformation t) { 239 mShadowDrawable.setShadowSize(mShadowSizeStart + (mShadowSizeDiff * interpolatedTime)); 240 } 241 242 /** 243 * @return the shadow size we want to animate to. 244 */ 245 protected abstract float getTargetShadowSize(); 246 } 247 248 private class ResetElevationAnimation extends BaseShadowAnimation { 249 @Override 250 protected float getTargetShadowSize() { 251 return mElevation; 252 } 253 } 254 255 private class ElevateToTranslationZAnimation extends BaseShadowAnimation { 256 @Override 257 protected float getTargetShadowSize() { 258 return mElevation + mPressedTranslationZ; 259 } 260 } 261 262 private static ColorStateList createColorStateList(int selectedColor) { 263 final int[][] states = new int[3][]; 264 final int[] colors = new int[3]; 265 int i = 0; 266 267 states[i] = FOCUSED_ENABLED_STATE_SET; 268 colors[i] = selectedColor; 269 i++; 270 271 states[i] = PRESSED_ENABLED_STATE_SET; 272 colors[i] = selectedColor; 273 i++; 274 275 // Default enabled state 276 states[i] = new int[0]; 277 colors[i] = Color.TRANSPARENT; 278 i++; 279 280 return new ColorStateList(states, colors); 281 } 282}