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 55 public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded, 56 boolean forceSoftware) { 57 super(owner, bounds, forceSoftware); 58 59 mIsBounded = isBounded; 60 } 61 62 public boolean isVisible() { 63 return mOpacity > 0 || isHardwareAnimating(); 64 } 65 66 @Override 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 83 protected boolean drawHardware(DisplayListCanvas c) { 84 c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint); 85 return true; 86 } 87 88 @Override 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 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 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 169 protected void jumpValuesToExit() { 170 mOpacity = 0; 171 } 172 173 private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> { 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