| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.graphics.drawable; |
| |
| import android.animation.Animator; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.view.DisplayListCanvas; |
| import android.view.RenderNodeAnimator; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Abstract class that handles hardware/software hand-off and lifecycle for |
| * animated ripple foreground and background components. |
| */ |
| abstract class RippleComponent { |
| private final RippleDrawable mOwner; |
| |
| /** Bounds used for computing max radius. May be modified by the owner. */ |
| protected final Rect mBounds; |
| |
| /** Whether we can use hardware acceleration for the exit animation. */ |
| private boolean mHasDisplayListCanvas; |
| |
| private boolean mHasPendingHardwareAnimator; |
| private RenderNodeAnimatorSet mHardwareAnimator; |
| |
| private Animator mSoftwareAnimator; |
| |
| /** Whether we have an explicit maximum radius. */ |
| private boolean mHasMaxRadius; |
| |
| /** How big this ripple should be when fully entered. */ |
| protected float mTargetRadius; |
| |
| /** Screen density used to adjust pixel-based constants. */ |
| protected float mDensity; |
| |
| public RippleComponent(RippleDrawable owner, Rect bounds) { |
| mOwner = owner; |
| mBounds = bounds; |
| } |
| |
| public void onBoundsChange() { |
| if (!mHasMaxRadius) { |
| mTargetRadius = getTargetRadius(mBounds); |
| onTargetRadiusChanged(mTargetRadius); |
| } |
| } |
| |
| public final void setup(float maxRadius, float density) { |
| if (maxRadius >= 0) { |
| mHasMaxRadius = true; |
| mTargetRadius = maxRadius; |
| } else { |
| mTargetRadius = getTargetRadius(mBounds); |
| } |
| |
| mDensity = density; |
| |
| onTargetRadiusChanged(mTargetRadius); |
| } |
| |
| private static float getTargetRadius(Rect bounds) { |
| final float halfWidth = bounds.width() / 2.0f; |
| final float halfHeight = bounds.height() / 2.0f; |
| return (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); |
| } |
| |
| /** |
| * Starts a ripple enter animation. |
| * |
| * @param fast whether the ripple should enter quickly |
| */ |
| public final void enter(boolean fast) { |
| cancel(); |
| |
| mSoftwareAnimator = createSoftwareEnter(fast); |
| |
| if (mSoftwareAnimator != null) { |
| mSoftwareAnimator.start(); |
| } |
| } |
| |
| /** |
| * Starts a ripple exit animation. |
| */ |
| public final void exit() { |
| cancel(); |
| |
| if (mHasDisplayListCanvas) { |
| // We don't have access to a canvas here, but we expect one on the |
| // next frame. We'll start the render thread animation then. |
| mHasPendingHardwareAnimator = true; |
| |
| // Request another frame. |
| invalidateSelf(); |
| } else { |
| mSoftwareAnimator = createSoftwareExit(); |
| mSoftwareAnimator.start(); |
| } |
| } |
| |
| /** |
| * Cancels all animations. Software animation values are left in the |
| * current state, while hardware animation values jump to the end state. |
| */ |
| public void cancel() { |
| cancelSoftwareAnimations(); |
| endHardwareAnimations(); |
| } |
| |
| /** |
| * Ends all animations, jumping values to the end state. |
| */ |
| public void end() { |
| endSoftwareAnimations(); |
| endHardwareAnimations(); |
| } |
| |
| /** |
| * Draws the ripple to the canvas, inheriting the paint's color and alpha |
| * properties. |
| * |
| * @param c the canvas to which the ripple should be drawn |
| * @param p the paint used to draw the ripple |
| * @return {@code true} if something was drawn, {@code false} otherwise |
| */ |
| public boolean draw(Canvas c, Paint p) { |
| final boolean hasDisplayListCanvas = c.isHardwareAccelerated() |
| && c instanceof DisplayListCanvas; |
| if (mHasDisplayListCanvas != hasDisplayListCanvas) { |
| mHasDisplayListCanvas = hasDisplayListCanvas; |
| |
| if (!hasDisplayListCanvas) { |
| // We've switched from hardware to non-hardware mode. Panic. |
| endHardwareAnimations(); |
| } |
| } |
| |
| if (hasDisplayListCanvas) { |
| final DisplayListCanvas hw = (DisplayListCanvas) c; |
| startPendingAnimation(hw, p); |
| |
| if (mHardwareAnimator != null) { |
| return drawHardware(hw); |
| } |
| } |
| |
| return drawSoftware(c, p); |
| } |
| |
| /** |
| * Populates {@code bounds} with the maximum drawing bounds of the ripple |
| * relative to its center. The resulting bounds should be translated into |
| * parent drawable coordinates before use. |
| * |
| * @param bounds the rect to populate with drawing bounds |
| */ |
| public void getBounds(Rect bounds) { |
| final int r = (int) Math.ceil(mTargetRadius); |
| bounds.set(-r, -r, r, r); |
| } |
| |
| /** |
| * Starts the pending hardware animation, if available. |
| * |
| * @param hw hardware canvas on which the animation should draw |
| * @param p paint whose properties the hardware canvas should use |
| */ |
| private void startPendingAnimation(DisplayListCanvas hw, Paint p) { |
| if (mHasPendingHardwareAnimator) { |
| mHasPendingHardwareAnimator = false; |
| |
| mHardwareAnimator = createHardwareExit(new Paint(p)); |
| mHardwareAnimator.start(hw); |
| |
| // Preemptively jump the software values to the end state now that |
| // the hardware exit has read whatever values it needs. |
| jumpValuesToExit(); |
| } |
| } |
| |
| /** |
| * Cancels any current software animations, leaving the values in their |
| * current state. |
| */ |
| private void cancelSoftwareAnimations() { |
| if (mSoftwareAnimator != null) { |
| mSoftwareAnimator.cancel(); |
| mSoftwareAnimator = null; |
| } |
| } |
| |
| /** |
| * Ends any current software animations, jumping the values to their end |
| * state. |
| */ |
| private void endSoftwareAnimations() { |
| if (mSoftwareAnimator != null) { |
| mSoftwareAnimator.end(); |
| mSoftwareAnimator = null; |
| } |
| } |
| |
| /** |
| * Ends any pending or current hardware animations. |
| * <p> |
| * Hardware animations can't synchronize values back to the software |
| * thread, so there is no "cancel" equivalent. |
| */ |
| private void endHardwareAnimations() { |
| if (mHardwareAnimator != null) { |
| mHardwareAnimator.end(); |
| mHardwareAnimator = null; |
| } |
| |
| if (mHasPendingHardwareAnimator) { |
| mHasPendingHardwareAnimator = false; |
| |
| // Manually jump values to their exited state. Normally we'd do that |
| // later when starting the hardware exit, but we're aborting early. |
| jumpValuesToExit(); |
| } |
| } |
| |
| protected final void invalidateSelf() { |
| mOwner.invalidateSelf(false); |
| } |
| |
| protected final boolean isHardwareAnimating() { |
| return mHardwareAnimator != null && mHardwareAnimator.isRunning() |
| || mHasPendingHardwareAnimator; |
| } |
| |
| protected final void onHotspotBoundsChanged() { |
| if (!mHasMaxRadius) { |
| final float halfWidth = mBounds.width() / 2.0f; |
| final float halfHeight = mBounds.height() / 2.0f; |
| final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth |
| + halfHeight * halfHeight); |
| |
| onTargetRadiusChanged(targetRadius); |
| } |
| } |
| |
| /** |
| * Called when the target radius changes. |
| * |
| * @param targetRadius the new target radius |
| */ |
| protected void onTargetRadiusChanged(float targetRadius) { |
| // Stub. |
| } |
| |
| protected abstract Animator createSoftwareEnter(boolean fast); |
| |
| protected abstract Animator createSoftwareExit(); |
| |
| protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p); |
| |
| protected abstract boolean drawHardware(DisplayListCanvas c); |
| |
| protected abstract boolean drawSoftware(Canvas c, Paint p); |
| |
| /** |
| * Called when the hardware exit is cancelled. Jumps software values to end |
| * state to ensure that software and hardware values are synchronized. |
| */ |
| protected abstract void jumpValuesToExit(); |
| |
| public static class RenderNodeAnimatorSet { |
| private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>(); |
| |
| public void add(RenderNodeAnimator anim) { |
| mAnimators.add(anim); |
| } |
| |
| public void clear() { |
| mAnimators.clear(); |
| } |
| |
| public void start(DisplayListCanvas target) { |
| if (target == null) { |
| throw new IllegalArgumentException("Hardware canvas must be non-null"); |
| } |
| |
| final ArrayList<RenderNodeAnimator> animators = mAnimators; |
| final int N = animators.size(); |
| for (int i = 0; i < N; i++) { |
| final RenderNodeAnimator anim = animators.get(i); |
| anim.setTarget(target); |
| anim.start(); |
| } |
| } |
| |
| public void cancel() { |
| final ArrayList<RenderNodeAnimator> animators = mAnimators; |
| final int N = animators.size(); |
| for (int i = 0; i < N; i++) { |
| final RenderNodeAnimator anim = animators.get(i); |
| anim.cancel(); |
| } |
| } |
| |
| public void end() { |
| final ArrayList<RenderNodeAnimator> animators = mAnimators; |
| final int N = animators.size(); |
| for (int i = 0; i < N; i++) { |
| final RenderNodeAnimator anim = animators.get(i); |
| anim.end(); |
| } |
| } |
| |
| public boolean isRunning() { |
| final ArrayList<RenderNodeAnimator> animators = mAnimators; |
| final int N = animators.size(); |
| for (int i = 0; i < N; i++) { |
| final RenderNodeAnimator anim = animators.get(i); |
| if (anim.isRunning()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| } |