1 /*
2  * Copyright (C) 2013 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 
17 package android.graphics.drawable;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.TimeInterpolator;
23 import android.graphics.Canvas;
24 import android.graphics.CanvasProperty;
25 import android.graphics.Paint;
26 import android.graphics.Rect;
27 import android.util.FloatProperty;
28 import android.view.DisplayListCanvas;
29 import android.view.RenderNodeAnimator;
30 import android.view.animation.LinearInterpolator;
31 
32 /**
33  * Draws a ripple background.
34  */
35 class RippleBackground extends RippleComponent {
36 
37     private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
38 
39     private static final int OPACITY_ENTER_DURATION = 600;
40     private static final int OPACITY_ENTER_DURATION_FAST = 120;
41     private static final int OPACITY_EXIT_DURATION = 480;
42 
43     // Hardware rendering properties.
44     private CanvasProperty<Paint> mPropPaint;
45     private CanvasProperty<Float> mPropRadius;
46     private CanvasProperty<Float> mPropX;
47     private CanvasProperty<Float> mPropY;
48 
49     // Software rendering properties.
50     private float mOpacity = 0;
51 
52     /** Whether this ripple is bounded. */
53     private boolean mIsBounded;
54 
RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded, boolean forceSoftware)55     public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
56             boolean forceSoftware) {
57         super(owner, bounds, forceSoftware);
58 
59         mIsBounded = isBounded;
60     }
61 
isVisible()62     public boolean isVisible() {
63         return mOpacity > 0 || isHardwareAnimating();
64     }
65 
66     @Override
drawSoftware(Canvas c, Paint p)67     protected boolean drawSoftware(Canvas c, Paint p) {
68         boolean hasContent = false;
69 
70         final int origAlpha = p.getAlpha();
71         final int alpha = (int) (origAlpha * mOpacity + 0.5f);
72         if (alpha > 0) {
73             p.setAlpha(alpha);
74             c.drawCircle(0, 0, mTargetRadius, p);
75             p.setAlpha(origAlpha);
76             hasContent = true;
77         }
78 
79         return hasContent;
80     }
81 
82     @Override
drawHardware(DisplayListCanvas c)83     protected boolean drawHardware(DisplayListCanvas c) {
84         c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
85         return true;
86     }
87 
88     @Override
createSoftwareEnter(boolean fast)89     protected Animator createSoftwareEnter(boolean fast) {
90         // Linear enter based on current opacity.
91         final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
92         final int duration = (int) ((1 - mOpacity) * maxDuration);
93 
94         final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
95         opacity.setAutoCancel(true);
96         opacity.setDuration(duration);
97         opacity.setInterpolator(LINEAR_INTERPOLATOR);
98 
99         return opacity;
100     }
101 
102     @Override
createSoftwareExit()103     protected Animator createSoftwareExit() {
104         final AnimatorSet set = new AnimatorSet();
105 
106         // Linear exit after enter is completed.
107         final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
108         exit.setInterpolator(LINEAR_INTERPOLATOR);
109         exit.setDuration(OPACITY_EXIT_DURATION);
110         exit.setAutoCancel(true);
111 
112         final AnimatorSet.Builder builder = set.play(exit);
113 
114         // Linear "fast" enter based on current opacity.
115         final int fastEnterDuration = mIsBounded ?
116                 (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
117         if (fastEnterDuration > 0) {
118             final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
119             enter.setInterpolator(LINEAR_INTERPOLATOR);
120             enter.setDuration(fastEnterDuration);
121             enter.setAutoCancel(true);
122 
123             builder.after(enter);
124         }
125 
126         return set;
127     }
128 
129     @Override
createHardwareExit(Paint p)130     protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
131         final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
132 
133         final int targetAlpha = p.getAlpha();
134         final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
135         p.setAlpha(currentAlpha);
136 
137         mPropPaint = CanvasProperty.createPaint(p);
138         mPropRadius = CanvasProperty.createFloat(mTargetRadius);
139         mPropX = CanvasProperty.createFloat(0);
140         mPropY = CanvasProperty.createFloat(0);
141 
142         final int fastEnterDuration = mIsBounded ?
143                 (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
144 
145         // Linear exit after enter is completed.
146         final RenderNodeAnimator exit = new RenderNodeAnimator(
147                 mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
148         exit.setInterpolator(LINEAR_INTERPOLATOR);
149         exit.setDuration(OPACITY_EXIT_DURATION);
150         if (fastEnterDuration > 0) {
151             exit.setStartDelay(fastEnterDuration);
152             exit.setStartValue(targetAlpha);
153         }
154         set.add(exit);
155 
156         // Linear "fast" enter based on current opacity.
157         if (fastEnterDuration > 0) {
158             final RenderNodeAnimator enter = new RenderNodeAnimator(
159                     mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
160             enter.setInterpolator(LINEAR_INTERPOLATOR);
161             enter.setDuration(fastEnterDuration);
162             set.add(enter);
163         }
164 
165         return set;
166     }
167 
168     @Override
jumpValuesToExit()169     protected void jumpValuesToExit() {
170         mOpacity = 0;
171     }
172 
173     private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
BackgroundProperty(String name)174         public BackgroundProperty(String name) {
175             super(name);
176         }
177     }
178 
179     private static final BackgroundProperty OPACITY = new BackgroundProperty("opacity") {
180         @Override
181         public void setValue(RippleBackground object, float value) {
182             object.mOpacity = value;
183             object.invalidateSelf();
184         }
185 
186         @Override
187         public Float get(RippleBackground object) {
188             return object.mOpacity;
189         }
190     };
191 }
192