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}