FloatingActionButtonEclairMr1.java revision 39d1d2d33cdd490d8b9f68d3300725035c1e0c56
15bc087c573c70c84c6a39946457590b42d392a33Andreas Huber/* 25bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * Copyright (C) 2015 The Android Open Source Project 35bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * 45bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * Licensed under the Apache License, Version 2.0 (the "License"); 55bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * you may not use this file except in compliance with the License. 65bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * You may obtain a copy of the License at 75bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * 85bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * http://www.apache.org/licenses/LICENSE-2.0 95bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * 105bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * Unless required by applicable law or agreed to in writing, software 115bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * distributed under the License is distributed on an "AS IS" BASIS, 125bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * See the License for the specific language governing permissions and 145bc087c573c70c84c6a39946457590b42d392a33Andreas Huber * limitations under the License. 155bc087c573c70c84c6a39946457590b42d392a33Andreas Huber */ 165bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 175bc087c573c70c84c6a39946457590b42d392a33Andreas Huberpackage android.support.design.widget; 185bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 195bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.content.res.ColorStateList; 205bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.Color; 215bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.PorterDuff; 225bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.Rect; 235bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.drawable.Drawable; 245bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.drawable.GradientDrawable; 255bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.graphics.drawable.LayerDrawable; 265bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.support.design.R; 275bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.support.v4.graphics.drawable.DrawableCompat; 285bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.view.View; 295bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.view.animation.Animation; 305bc087c573c70c84c6a39946457590b42d392a33Andreas Huberimport android.view.animation.Transformation; 315bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 325bc087c573c70c84c6a39946457590b42d392a33Andreas Huberclass FloatingActionButtonEclairMr1 extends FloatingActionButtonImpl { 335bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 345bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private Drawable mShapeDrawable; 355bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private Drawable mRippleDrawable; 365bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private Drawable mBorderDrawable; 37eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber 385bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private float mElevation; 395bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private float mPressedTranslationZ; 405bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private int mAnimationDuration; 415bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 425bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private StateListAnimator mStateListAnimator; 435bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 445bc087c573c70c84c6a39946457590b42d392a33Andreas Huber ShadowDrawableWrapper mShadowDrawable; 45c4c17d47b674b425fb6c399822c0ab3258543c0aAndreas Huber 465bc087c573c70c84c6a39946457590b42d392a33Andreas Huber private boolean mIsHiding; 475bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 485bc087c573c70c84c6a39946457590b42d392a33Andreas Huber FloatingActionButtonEclairMr1(View view, ShadowViewDelegate shadowViewDelegate) { 495bc087c573c70c84c6a39946457590b42d392a33Andreas Huber super(view, shadowViewDelegate); 50eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber 51eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber mAnimationDuration = view.getResources().getInteger(android.R.integer.config_shortAnimTime); 52eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber 535bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mStateListAnimator = new StateListAnimator(); 545bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mStateListAnimator.setTarget(view); 55078cfcf7cce9185ec7559910d08b0bc02bfc88a3Andreas Huber 565bc087c573c70c84c6a39946457590b42d392a33Andreas Huber // Elevate with translationZ when pressed or focused 5732f3cefa373cd55e63deda36ca9d07c7fe22eaafAndreas Huber mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET, 5832f3cefa373cd55e63deda36ca9d07c7fe22eaafAndreas Huber setupAnimation(new ElevateToTranslationZAnimation())); 595bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET, 605bc087c573c70c84c6a39946457590b42d392a33Andreas Huber setupAnimation(new ElevateToTranslationZAnimation())); 61df64d15042bbd5e0e4933ac49bf3c177dd94752cSteve Block // Reset back to elevation by default 625bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mStateListAnimator.addState(EMPTY_STATE_SET, 63eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber setupAnimation(new ResetElevationAnimation())); 645bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 655bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 66bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber @Override 6742e549e4ab54802d788c43e3a04a85b7a1a95e97Andreas Huber void setBackgroundDrawable(Drawable originalBackground, ColorStateList backgroundTint, 68bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) { 6942e549e4ab54802d788c43e3a04a85b7a1a95e97Andreas Huber // Now we need to tint the original background with the tint, using 7042e549e4ab54802d788c43e3a04a85b7a1a95e97Andreas Huber // an InsetDrawable if we have a border width 71bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber mShapeDrawable = DrawableCompat.wrap(originalBackground); 72bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber DrawableCompat.setTintList(mShapeDrawable, backgroundTint); 73bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber if (backgroundTintMode != null) { 74bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode); 75bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber } 76bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber 77bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber // Now we created a mask Drawable which will be used for touch feedback. 7842e549e4ab54802d788c43e3a04a85b7a1a95e97Andreas Huber // As we don't know the actual outline of mShapeDrawable, we'll just guess that it's a 7942e549e4ab54802d788c43e3a04a85b7a1a95e97Andreas Huber // circle 80bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber GradientDrawable touchFeedbackShape = new GradientDrawable(); 81bfcc8d8ab7c56bc013bd221a29e1ecf3a6390813Andreas Huber touchFeedbackShape.setShape(GradientDrawable.OVAL); 825bc087c573c70c84c6a39946457590b42d392a33Andreas Huber touchFeedbackShape.setColor(Color.WHITE); 835bc087c573c70c84c6a39946457590b42d392a33Andreas Huber touchFeedbackShape.setCornerRadius(mShadowViewDelegate.getRadius()); 845bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 855bc087c573c70c84c6a39946457590b42d392a33Andreas Huber // We'll now wrap that touch feedback mask drawable with a ColorStateList. We do not need 865bc087c573c70c84c6a39946457590b42d392a33Andreas Huber // to inset for any border here as LayerDrawable will nest the padding for us 875bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape); 885bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); 895bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintMode(mRippleDrawable, PorterDuff.Mode.MULTIPLY); 905bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 9132f3cefa373cd55e63deda36ca9d07c7fe22eaafAndreas Huber final Drawable[] layers; 9232f3cefa373cd55e63deda36ca9d07c7fe22eaafAndreas Huber if (borderWidth > 0) { 935bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint); 9406528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber layers = new Drawable[] {mBorderDrawable, mShapeDrawable, mRippleDrawable}; 9506528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber } else { 9606528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber mBorderDrawable = null; 9706528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber layers = new Drawable[] {mShapeDrawable, mRippleDrawable}; 9806528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber } 9906528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber 100eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber mShadowDrawable = new ShadowDrawableWrapper( 10106528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber mView.getResources(), 10206528d7f18ad01377357d337eaa3e875a242bd2dAndreas Huber new LayerDrawable(layers), 1035bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mShadowViewDelegate.getRadius(), 1045bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mElevation, 1055bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mElevation + mPressedTranslationZ); 1065bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mShadowDrawable.setAddPaddingForCorners(false); 107eac68baf095aeef54865c28b6888924dc6295cbdAndreas Huber 1085bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable); 1095bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 1105bc087c573c70c84c6a39946457590b42d392a33Andreas Huber updatePadding(); 1115bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 112386d609dc513e838c7e7c4c46c604493ccd560beAndreas Huber 1135bc087c573c70c84c6a39946457590b42d392a33Andreas Huber @Override 1145bc087c573c70c84c6a39946457590b42d392a33Andreas Huber void setBackgroundTintList(ColorStateList tint) { 1155bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintList(mShapeDrawable, tint); 1165bc087c573c70c84c6a39946457590b42d392a33Andreas Huber if (mBorderDrawable != null) { 1175bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintList(mBorderDrawable, tint); 1185bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1195bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1205bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 1215bc087c573c70c84c6a39946457590b42d392a33Andreas Huber @Override 1225bc087c573c70c84c6a39946457590b42d392a33Andreas Huber void setBackgroundTintMode(PorterDuff.Mode tintMode) { 1235bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintMode(mShapeDrawable, tintMode); 1245bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1255bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 1265bc087c573c70c84c6a39946457590b42d392a33Andreas Huber @Override 127386d609dc513e838c7e7c4c46c604493ccd560beAndreas Huber void setRippleColor(int rippleColor) { 1285bc087c573c70c84c6a39946457590b42d392a33Andreas Huber DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor)); 1295bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1305bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 1315bc087c573c70c84c6a39946457590b42d392a33Andreas Huber @Override 1325bc087c573c70c84c6a39946457590b42d392a33Andreas Huber void setElevation(float elevation) { 1335bc087c573c70c84c6a39946457590b42d392a33Andreas Huber if (mElevation != elevation && mShadowDrawable != null) { 1345bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ); 1355bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mElevation = elevation; 1365bc087c573c70c84c6a39946457590b42d392a33Andreas Huber updatePadding(); 1375bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1385bc087c573c70c84c6a39946457590b42d392a33Andreas Huber } 1395bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 1405bc087c573c70c84c6a39946457590b42d392a33Andreas Huber @Override 1415bc087c573c70c84c6a39946457590b42d392a33Andreas Huber void setPressedTranslationZ(float translationZ) { 1425bc087c573c70c84c6a39946457590b42d392a33Andreas Huber if (mPressedTranslationZ != translationZ && mShadowDrawable != null) { 1435bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mPressedTranslationZ = translationZ; 1445bc087c573c70c84c6a39946457590b42d392a33Andreas Huber mShadowDrawable.setMaxShadowSize(mElevation + translationZ); 1455bc087c573c70c84c6a39946457590b42d392a33Andreas Huber 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 || mView.getVisibility() != View.VISIBLE) { 162 // A hide animation is in progress, or we're already hidden. Skip the call 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 if (mView.getVisibility() != View.VISIBLE || mIsHiding) { 188 // If the view is not visible, or is visible and currently being hidden, run 189 // the show animation 190 mView.clearAnimation(); 191 mView.setVisibility(View.VISIBLE); 192 Animation anim = android.view.animation.AnimationUtils.loadAnimation( 193 mView.getContext(), R.anim.design_fab_in); 194 anim.setDuration(SHOW_HIDE_ANIM_DURATION); 195 anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 196 mView.startAnimation(anim); 197 } 198 } 199 200 private void updatePadding() { 201 Rect rect = new Rect(); 202 mShadowDrawable.getPadding(rect); 203 mShadowViewDelegate.setShadowPadding(rect.left, rect.top, rect.right, rect.bottom); 204 } 205 206 private Animation setupAnimation(Animation animation) { 207 animation.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 208 animation.setDuration(mAnimationDuration); 209 return animation; 210 } 211 212 private abstract class BaseShadowAnimation extends Animation { 213 private float mShadowSizeStart; 214 private float mShadowSizeDiff; 215 216 @Override 217 public void reset() { 218 super.reset(); 219 220 mShadowSizeStart = mShadowDrawable.getShadowSize(); 221 mShadowSizeDiff = getTargetShadowSize() - mShadowSizeStart; 222 } 223 224 @Override 225 protected void applyTransformation(float interpolatedTime, Transformation t) { 226 mShadowDrawable.setShadowSize(mShadowSizeStart + (mShadowSizeDiff * interpolatedTime)); 227 } 228 229 /** 230 * @return the shadow size we want to animate to. 231 */ 232 protected abstract float getTargetShadowSize(); 233 } 234 235 private class ResetElevationAnimation extends BaseShadowAnimation { 236 @Override 237 protected float getTargetShadowSize() { 238 return mElevation; 239 } 240 } 241 242 private class ElevateToTranslationZAnimation extends BaseShadowAnimation { 243 @Override 244 protected float getTargetShadowSize() { 245 return mElevation + mPressedTranslationZ; 246 } 247 } 248 249 private static ColorStateList createColorStateList(int selectedColor) { 250 final int[][] states = new int[3][]; 251 final int[] colors = new int[3]; 252 int i = 0; 253 254 states[i] = FOCUSED_ENABLED_STATE_SET; 255 colors[i] = selectedColor; 256 i++; 257 258 states[i] = PRESSED_ENABLED_STATE_SET; 259 colors[i] = selectedColor; 260 i++; 261 262 // Default enabled state 263 states[i] = new int[0]; 264 colors[i] = Color.TRANSPARENT; 265 i++; 266 267 return new ColorStateList(states, colors); 268 } 269}