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}