RippleDrawable.java revision a12962207155305da44b5a1b8fb9acaed358c14c
1ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu/*
2ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * Copyright (C) 2014 The Android Open Source Project
3ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *
4ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * Licensed under the Apache License, Version 2.0 (the "License");
5ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * you may not use this file except in compliance with the License.
6ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * You may obtain a copy of the License at
7ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *
8ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *      http://www.apache.org/licenses/LICENSE-2.0
9ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *
10ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * Unless required by applicable law or agreed to in writing, software
11ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * distributed under the License is distributed on an "AS IS" BASIS,
12ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * See the License for the specific language governing permissions and
14ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * limitations under the License.
15ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu */
16ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
17ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gupackage android.graphics.drawable;
18ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
19ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport com.android.internal.R;
20ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
21ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport org.xmlpull.v1.XmlPullParser;
22ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport org.xmlpull.v1.XmlPullParserException;
23ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
24ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.annotation.NonNull;
25ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.annotation.Nullable;
26ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.content.res.ColorStateList;
27ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.content.res.Resources;
28ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.content.res.Resources.Theme;
29ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.content.res.TypedArray;
30ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Bitmap;
31ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.BitmapShader;
32ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Canvas;
33ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Color;
34ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.ColorFilter;
35ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Matrix;
36ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Outline;
37ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Paint;
38ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.PixelFormat;
39ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.PorterDuff;
40ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.PorterDuffColorFilter;
41ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Rect;
42ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.graphics.Shader;
43ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.util.AttributeSet;
44ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport android.util.DisplayMetrics;
45fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette
46ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport java.io.IOException;
47ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Guimport java.util.Arrays;
48ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
49ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu/**
50ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * Drawable that shows a ripple effect in response to state changes. The
51ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * anchoring position of the ripple for a given state may be specified by
52fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * calling {@link #setHotspot(float, float)} with the corresponding state
53fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * attribute identifier.
54ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <p>
552337b3fa0ca11ddb9121974ca25211e4ae64392fAlan Viverette * A touch feedback drawable may contain multiple child layers, including a
56ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * special mask layer that is not drawn to the screen. A single layer may be set
57ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * as the mask by specifying its android:id value as {@link android.R.id#mask}.
58fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * <pre>
59fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * <code>&lt!-- A red ripple masked against an opaque rectangle. --/>
60ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &ltripple android:color="#ffff0000">
61fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette *   &ltitem android:id="@android:id/mask"
622337b3fa0ca11ddb9121974ca25211e4ae64392fAlan Viverette *         android:drawable="@android:color/white" />
63fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * &lt/ripple></code>
64fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * </pre>
65fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * <p>
66fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * If a mask layer is set, the ripple effect will be masked against that layer
67fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * before it is drawn over the composite of the remaining child layers.
68ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <p>
69fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * If no mask layer is set, the ripple effect is masked against the composite
70fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * of the child layers.
71fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * <pre>
72ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <code>&lt!-- A green ripple drawn atop a black rectangle. --/>
73fa0f278ff6c3ac316e12489a3cf0146766c168ceAlan Viverette * &ltripple android:color="#ff00ff00">
74ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *   &ltitem android:drawable="@android:color/black" />
75ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &lt/ripple>
76ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *
77ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &lt!-- A blue ripple drawn atop a drawable resource. --/>
78ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &ltripple android:color="#ff0000ff">
79ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *   &ltitem android:drawable="@drawable/my_drawable" />
80ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &lt/ripple></code>
81ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * </pre>
82ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <p>
83ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * If no child layers or mask is specified and the ripple is set as a View
84ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * background, the ripple will be drawn atop the first available parent
85ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * background within the View's hierarchy. In this case, the drawing region
86ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * may extend outside of the Drawable bounds.
87ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <pre>
88ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * <code>&lt!-- An unbounded red ripple. --/>
89ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * &ltripple android:color="#ffff0000" /></code>
90ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * </pre>
91ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu *
92ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu * @attr ref android.R.styleable#RippleDrawable_color
93ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu */
94ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gupublic class RippleDrawable extends LayerDrawable {
95ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /**
96ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * Radius value that specifies the ripple radius should be computed based
97ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * on the size of the ripple's container.
98ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     */
99ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    public static final int RADIUS_AUTO = -1;
100ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
101ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private static final int MASK_UNKNOWN = -1;
102ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private static final int MASK_NONE = 0;
103ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private static final int MASK_CONTENT = 1;
104ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private static final int MASK_EXPLICIT = 2;
1052182cce575fb80fe8d19ae4d2605c5f4b495305cNarayan Kamath
106ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** The maximum number of ripples supported. */
107ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private static final int MAX_RIPPLES = 10;
108ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
109ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private final Rect mTempRect = new Rect();
110ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
111ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Current ripple effect bounds, used to constrain ripple effects. */
112ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private final Rect mHotspotBounds = new Rect();
113ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
114ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Current drawing bounds, used to compute dirty region. */
115ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private final Rect mDrawingBounds = new Rect();
116ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
1172182cce575fb80fe8d19ae4d2605c5f4b495305cNarayan Kamath    /** Current dirty bounds, union of current and previous drawing bounds. */
118ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private final Rect mDirtyBounds = new Rect();
119ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
120ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Mirrors mLayerState with some extra information. */
121ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private RippleState mState;
122ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
123ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** The masking layer, e.g. the layer with id R.id.mask. */
124ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Drawable mMask;
125ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
126ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** The current background. May be actively animating or pending entry. */
127ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private RippleBackground mBackground;
128ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
129ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Bitmap mMaskBuffer;
130ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private BitmapShader mMaskShader;
131ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Canvas mMaskCanvas;
132ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Matrix mMaskMatrix;
133ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private PorterDuffColorFilter mMaskColorFilter;
134ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private boolean mHasValidMask;
135ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
136ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Whether we expect to draw a background when visible. */
137ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private boolean mBackgroundActive;
1382182cce575fb80fe8d19ae4d2605c5f4b495305cNarayan Kamath
139ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** The current ripple. May be actively animating or pending entry. */
140ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Ripple mRipple;
141ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
142ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Whether we expect to draw a ripple when visible. */
143ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private boolean mRippleActive;
144ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
145ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    // Hotspot coordinates that are awaiting activation.
146ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private float mPendingX;
147ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private float mPendingY;
148ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private boolean mHasPending;
149ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
150ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /**
151ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * Lazily-created array of actively animating ripples. Inactive ripples are
152ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * pruned during draw(). The locations of these will not change.
153ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     */
154ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Ripple[] mExitingRipples;
1552182cce575fb80fe8d19ae4d2605c5f4b495305cNarayan Kamath    private int mExitingRipplesCount = 0;
156ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
157ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Paint used to control appearance of ripples. */
158ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private Paint mRipplePaint;
159ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
160ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Target density of the display into which ripples are drawn. */
161ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private float mDensity = 1.0f;
162ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
163ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /** Whether bounds are being overridden. */
164ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    private boolean mOverrideBounds;
165ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
166ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /**
167ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * Constructor used for drawable inflation.
168ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     */
169ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    RippleDrawable() {
170ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        this(new RippleState(null, null, null), null);
1713b26e1a1412a8c3b6e7b520b4472d929e7e1e7f4Dake Gu    }
172ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
173ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    /**
174ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * Creates a new ripple drawable with the specified ripple color and
175ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * optional content and mask drawables.
176ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     *
177ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * @param color The ripple color
178ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * @param content The content drawable, may be {@code null}
179ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     * @param mask The mask drawable, may be {@code null}
180ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu     */
1813b26e1a1412a8c3b6e7b520b4472d929e7e1e7f4Dake Gu    public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
182ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            @Nullable Drawable mask) {
183ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        this(new RippleState(null, null, null), null);
184ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
185ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        if (color == null) {
186ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            throw new IllegalArgumentException("RippleDrawable requires a non-null color");
187ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        }
188ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
189ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        if (content != null) {
190ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            addLayer(content, null, 0, 0, 0, 0, 0);
191ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        }
1923b26e1a1412a8c3b6e7b520b4472d929e7e1e7f4Dake Gu
193ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        if (mask != null) {
194ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            addLayer(mask, null, android.R.id.mask, 0, 0, 0, 0);
195ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        }
196ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
197ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        setColor(color);
198ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        ensurePadding();
199ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        updateLocalState();
200ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    }
201ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
202ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    @Override
203ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu    public void jumpToCurrentState() {
204ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        super.jumpToCurrentState();
205ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
206ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        if (mRipple != null) {
207ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            mRipple.jump();
208ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        }
209ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
210ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        if (mBackground != null) {
211ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu            mBackground.jump();
212ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        }
213ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu
214ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        cancelExitingRipples();
215ae6b4cc8fd0872c64c4e1295e54e109222938d86Dake Gu        invalidateSelf();
216    }
217
218    private boolean cancelExitingRipples() {
219        boolean needsDraw = false;
220
221        final int count = mExitingRipplesCount;
222        final Ripple[] ripples = mExitingRipples;
223        for (int i = 0; i < count; i++) {
224            needsDraw |= ripples[i].isHardwareAnimating();
225            ripples[i].cancel();
226        }
227
228        if (ripples != null) {
229            Arrays.fill(ripples, 0, count, null);
230        }
231        mExitingRipplesCount = 0;
232
233        return needsDraw;
234    }
235
236    @Override
237    public void setAlpha(int alpha) {
238        super.setAlpha(alpha);
239
240        // TODO: Should we support this?
241    }
242
243    @Override
244    public void setColorFilter(ColorFilter cf) {
245        super.setColorFilter(cf);
246
247        // TODO: Should we support this?
248    }
249
250    @Override
251    public int getOpacity() {
252        // Worst-case scenario.
253        return PixelFormat.TRANSLUCENT;
254    }
255
256    @Override
257    protected boolean onStateChange(int[] stateSet) {
258        final boolean changed = super.onStateChange(stateSet);
259
260        boolean enabled = false;
261        boolean pressed = false;
262        boolean focused = false;
263
264        for (int state : stateSet) {
265            if (state == R.attr.state_enabled) {
266                enabled = true;
267            }
268            if (state == R.attr.state_focused) {
269                focused = true;
270            }
271            if (state == R.attr.state_pressed) {
272                pressed = true;
273            }
274        }
275
276        setRippleActive(enabled && pressed);
277        setBackgroundActive(focused || (enabled && pressed), focused);
278
279        return changed;
280    }
281
282    private void setRippleActive(boolean active) {
283        if (mRippleActive != active) {
284            mRippleActive = active;
285            if (active) {
286                tryRippleEnter();
287            } else {
288                tryRippleExit();
289            }
290        }
291    }
292
293    private void setBackgroundActive(boolean active, boolean focused) {
294        if (mBackgroundActive != active) {
295            mBackgroundActive = active;
296            if (active) {
297                tryBackgroundEnter(focused);
298            } else {
299                tryBackgroundExit();
300            }
301        }
302    }
303
304    @Override
305    protected void onBoundsChange(Rect bounds) {
306        super.onBoundsChange(bounds);
307
308        if (!mOverrideBounds) {
309            mHotspotBounds.set(bounds);
310            onHotspotBoundsChanged();
311        }
312
313        invalidateSelf();
314    }
315
316    @Override
317    public boolean setVisible(boolean visible, boolean restart) {
318        final boolean changed = super.setVisible(visible, restart);
319
320        if (!visible) {
321            clearHotspots();
322        } else if (changed) {
323            // If we just became visible, ensure the background and ripple
324            // visibilities are consistent with their internal states.
325            if (mRippleActive) {
326                tryRippleEnter();
327            }
328
329            if (mBackgroundActive) {
330                tryBackgroundEnter(false);
331            }
332
333            // Skip animations, just show the correct final states.
334            jumpToCurrentState();
335        }
336
337        return changed;
338    }
339
340    /**
341     * @hide
342     */
343    @Override
344    public boolean isProjected() {
345        return getNumberOfLayers() == 0;
346    }
347
348    @Override
349    public boolean isStateful() {
350        return true;
351    }
352
353    /**
354     * Sets the ripple color.
355     *
356     * @param color Ripple color as a color state list.
357     *
358     * @attr ref android.R.styleable#RippleDrawable_color
359     */
360    public void setColor(ColorStateList color) {
361        mState.mColor = color;
362        invalidateSelf();
363    }
364
365    /**
366     * Sets the radius in pixels of the fully expanded ripple.
367     *
368     * @param radius ripple radius in pixels, or {@link #RADIUS_AUTO} to
369     *               compute the radius based on the container size
370     * @attr ref android.R.styleable#RippleDrawable_radius
371     */
372    public void setRadius(int radius) {
373        mState.mMaxRadius = radius;
374        invalidateSelf();
375    }
376
377    /**
378     * @return the radius in pixels of the fully expanded ripple if an explicit
379     *         radius has been set, or {@link #RADIUS_AUTO} if the radius is
380     *         computed based on the container size
381     * @attr ref android.R.styleable#RippleDrawable_radius
382     */
383    public int getRadius() {
384        return mState.mMaxRadius;
385    }
386
387    @Override
388    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
389            throws XmlPullParserException, IOException {
390        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
391        updateStateFromTypedArray(a);
392        a.recycle();
393
394        // Force padding default to STACK before inflating.
395        setPaddingMode(PADDING_MODE_STACK);
396
397        super.inflate(r, parser, attrs, theme);
398
399        setTargetDensity(r.getDisplayMetrics());
400
401        updateLocalState();
402    }
403
404    @Override
405    public boolean setDrawableByLayerId(int id, Drawable drawable) {
406        if (super.setDrawableByLayerId(id, drawable)) {
407            if (id == R.id.mask) {
408                mMask = drawable;
409            }
410
411            return true;
412        }
413
414        return false;
415    }
416
417    /**
418     * Specifies how layer padding should affect the bounds of subsequent
419     * layers. The default and recommended value for RippleDrawable is
420     * {@link #PADDING_MODE_STACK}.
421     *
422     * @param mode padding mode, one of:
423     *            <ul>
424     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
425     *            padding of the previous layer
426     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
427     *            atop the previous layer
428     *            </ul>
429     * @see #getPaddingMode()
430     */
431    @Override
432    public void setPaddingMode(int mode) {
433        super.setPaddingMode(mode);
434    }
435
436    /**
437     * Initializes the constant state from the values in the typed array.
438     */
439    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
440        final RippleState state = mState;
441
442        // Account for any configuration changes.
443        state.mChangingConfigurations |= a.getChangingConfigurations();
444
445        // Extract the theme attributes, if any.
446        state.mTouchThemeAttrs = a.extractThemeAttrs();
447
448        final ColorStateList color = a.getColorStateList(R.styleable.RippleDrawable_color);
449        if (color != null) {
450            mState.mColor = color;
451        }
452
453        mState.mMaxRadius = a.getDimensionPixelSize(
454                R.styleable.RippleDrawable_radius, mState.mMaxRadius);
455
456        verifyRequiredAttributes(a);
457    }
458
459    private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
460        if (mState.mColor == null && (mState.mTouchThemeAttrs == null
461                || mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) {
462            throw new XmlPullParserException(a.getPositionDescription() +
463                    ": <ripple> requires a valid color attribute");
464        }
465    }
466
467    /**
468     * Set the density at which this drawable will be rendered.
469     *
470     * @param metrics The display metrics for this drawable.
471     */
472    private void setTargetDensity(DisplayMetrics metrics) {
473        if (mDensity != metrics.density) {
474            mDensity = metrics.density;
475            invalidateSelf();
476        }
477    }
478
479    @Override
480    public void applyTheme(Theme t) {
481        super.applyTheme(t);
482
483        final RippleState state = mState;
484        if (state == null) {
485            return;
486        }
487
488        if (state.mTouchThemeAttrs != null) {
489            final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
490                    R.styleable.RippleDrawable);
491            try {
492                updateStateFromTypedArray(a);
493            } catch (XmlPullParserException e) {
494                throw new RuntimeException(e);
495            } finally {
496                a.recycle();
497            }
498        }
499
500        if (state.mColor != null && state.mColor.canApplyTheme()) {
501            state.mColor.applyTheme(t);
502        }
503
504        updateLocalState();
505    }
506
507    @Override
508    public boolean canApplyTheme() {
509        return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
510    }
511
512    @Override
513    public void setHotspot(float x, float y) {
514        if (mRipple == null || mBackground == null) {
515            mPendingX = x;
516            mPendingY = y;
517            mHasPending = true;
518        }
519
520        if (mRipple != null) {
521            mRipple.move(x, y);
522        }
523    }
524
525    /**
526     * Creates an active hotspot at the specified location.
527     */
528    private void tryBackgroundEnter(boolean focused) {
529        if (mBackground == null) {
530            mBackground = new RippleBackground(this, mHotspotBounds);
531        }
532
533        mBackground.setup(mState.mMaxRadius, mDensity);
534        mBackground.enter(focused);
535    }
536
537    private void tryBackgroundExit() {
538        if (mBackground != null) {
539            // Don't null out the background, we need it to draw!
540            mBackground.exit();
541        }
542    }
543
544    /**
545     * Attempts to start an enter animation for the active hotspot. Fails if
546     * there are too many animating ripples.
547     */
548    private void tryRippleEnter() {
549        if (mExitingRipplesCount >= MAX_RIPPLES) {
550            // This should never happen unless the user is tapping like a maniac
551            // or there is a bug that's preventing ripples from being removed.
552            return;
553        }
554
555        if (mRipple == null) {
556            final float x;
557            final float y;
558            if (mHasPending) {
559                mHasPending = false;
560                x = mPendingX;
561                y = mPendingY;
562            } else {
563                x = mHotspotBounds.exactCenterX();
564                y = mHotspotBounds.exactCenterY();
565            }
566            mRipple = new Ripple(this, mHotspotBounds, x, y);
567        }
568
569        mRipple.setup(mState.mMaxRadius, mDensity);
570        mRipple.enter();
571    }
572
573    /**
574     * Attempts to start an exit animation for the active hotspot. Fails if
575     * there is no active hotspot.
576     */
577    private void tryRippleExit() {
578        if (mRipple != null) {
579            if (mExitingRipples == null) {
580                mExitingRipples = new Ripple[MAX_RIPPLES];
581            }
582            mExitingRipples[mExitingRipplesCount++] = mRipple;
583            mRipple.exit();
584            mRipple = null;
585        }
586    }
587
588    /**
589     * Cancels and removes the active ripple, all exiting ripples, and the
590     * background. Nothing will be drawn after this method is called.
591     */
592    private void clearHotspots() {
593        if (mRipple != null) {
594            mRipple.cancel();
595            mRipple = null;
596            mRippleActive = false;
597        }
598
599        if (mBackground != null) {
600            mBackground.cancel();
601            mBackground = null;
602            mBackgroundActive = false;
603        }
604
605        cancelExitingRipples();
606        invalidateSelf();
607    }
608
609    @Override
610    public void setHotspotBounds(int left, int top, int right, int bottom) {
611        mOverrideBounds = true;
612        mHotspotBounds.set(left, top, right, bottom);
613
614        onHotspotBoundsChanged();
615    }
616
617    @Override
618    public void getHotspotBounds(Rect outRect) {
619        outRect.set(mHotspotBounds);
620    }
621
622    /**
623     * Notifies all the animating ripples that the hotspot bounds have changed.
624     */
625    private void onHotspotBoundsChanged() {
626        final int count = mExitingRipplesCount;
627        final Ripple[] ripples = mExitingRipples;
628        for (int i = 0; i < count; i++) {
629            ripples[i].onHotspotBoundsChanged();
630        }
631
632        if (mRipple != null) {
633            mRipple.onHotspotBoundsChanged();
634        }
635
636        if (mBackground != null) {
637            mBackground.onHotspotBoundsChanged();
638        }
639    }
640
641    /**
642     * Populates <code>outline</code> with the first available layer outline,
643     * excluding the mask layer.
644     *
645     * @param outline Outline in which to place the first available layer outline
646     */
647    @Override
648    public void getOutline(@NonNull Outline outline) {
649        final LayerState state = mLayerState;
650        final ChildDrawable[] children = state.mChildren;
651        final int N = state.mNum;
652        for (int i = 0; i < N; i++) {
653            if (children[i].mId != R.id.mask) {
654                children[i].mDrawable.getOutline(outline);
655                if (!outline.isEmpty()) return;
656            }
657        }
658    }
659
660    /**
661     * Optimized for drawing ripples with a mask layer and optional content.
662     */
663    @Override
664    public void draw(@NonNull Canvas canvas) {
665        // Clip to the dirty bounds, which will be the drawable bounds if we
666        // have a mask or content and the ripple bounds if we're projecting.
667        final Rect bounds = getDirtyBounds();
668        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
669        canvas.clipRect(bounds);
670
671        drawContent(canvas);
672        drawBackgroundAndRipples(canvas);
673
674        canvas.restoreToCount(saveCount);
675    }
676
677    @Override
678    public void invalidateSelf() {
679        super.invalidateSelf();
680
681        // Force the mask to update on the next draw().
682        mHasValidMask = false;
683    }
684
685    /**
686     * @return whether we need to use a mask
687     */
688    private void updateMaskShaderIfNeeded() {
689        if (mHasValidMask) {
690            return;
691        }
692
693        final int maskType = getMaskType();
694        if (maskType == MASK_UNKNOWN) {
695            return;
696        }
697
698        mHasValidMask = true;
699
700        final Rect bounds = getBounds();
701        if (maskType == MASK_NONE || bounds.isEmpty()) {
702            if (mMaskBuffer != null) {
703                mMaskBuffer.recycle();
704                mMaskBuffer = null;
705                mMaskShader = null;
706                mMaskCanvas = null;
707            }
708            mMaskMatrix = null;
709            mMaskColorFilter = null;
710            return;
711        }
712
713        // Ensure we have a correctly-sized buffer.
714        if (mMaskBuffer == null
715                || mMaskBuffer.getWidth() != bounds.width()
716                || mMaskBuffer.getHeight() != bounds.height()) {
717            if (mMaskBuffer != null) {
718                mMaskBuffer.recycle();
719            }
720
721            mMaskBuffer = Bitmap.createBitmap(
722                    bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
723            mMaskShader = new BitmapShader(mMaskBuffer,
724                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
725            mMaskCanvas = new Canvas(mMaskBuffer);
726        } else {
727            mMaskBuffer.eraseColor(Color.TRANSPARENT);
728        }
729
730        if (mMaskMatrix == null) {
731            mMaskMatrix = new Matrix();
732        } else {
733            mMaskMatrix.reset();
734        }
735
736        if (mMaskColorFilter == null) {
737            mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
738        }
739
740        // Draw the appropriate mask.
741        if (maskType == MASK_EXPLICIT) {
742            drawMask(mMaskCanvas);
743        } else if (maskType == MASK_CONTENT) {
744            drawContent(mMaskCanvas);
745        }
746    }
747
748    private int getMaskType() {
749        if (mRipple == null && mExitingRipplesCount <= 0
750                && (mBackground == null || !mBackground.shouldDraw())) {
751            // We might need a mask later.
752            return MASK_UNKNOWN;
753        }
754
755        if (mMask != null) {
756            if (mMask.getOpacity() == PixelFormat.OPAQUE) {
757                // Clipping handles opaque explicit masks.
758                return MASK_NONE;
759            } else {
760                return MASK_EXPLICIT;
761            }
762        }
763
764        // Check for non-opaque, non-mask content.
765        final ChildDrawable[] array = mLayerState.mChildren;
766        final int count = mLayerState.mNum;
767        for (int i = 0; i < count; i++) {
768            if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
769                return MASK_CONTENT;
770            }
771        }
772
773        // Clipping handles opaque content.
774        return MASK_NONE;
775    }
776
777    /**
778     * Removes a ripple from the exiting ripple list.
779     *
780     * @param ripple the ripple to remove
781     */
782    void removeRipple(Ripple ripple) {
783        // Ripple ripple ripple ripple. Ripple ripple.
784        final Ripple[] ripples = mExitingRipples;
785        final int count = mExitingRipplesCount;
786        final int index = getRippleIndex(ripple);
787        if (index >= 0) {
788            System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
789            ripples[count - 1] = null;
790            mExitingRipplesCount--;
791
792            invalidateSelf();
793        }
794    }
795
796    private int getRippleIndex(Ripple ripple) {
797        final Ripple[] ripples = mExitingRipples;
798        final int count = mExitingRipplesCount;
799        for (int i = 0; i < count; i++) {
800            if (ripples[i] == ripple) {
801                return i;
802            }
803        }
804        return -1;
805    }
806
807    private void drawContent(Canvas canvas) {
808        // Draw everything except the mask.
809        final ChildDrawable[] array = mLayerState.mChildren;
810        final int count = mLayerState.mNum;
811        for (int i = 0; i < count; i++) {
812            if (array[i].mId != R.id.mask) {
813                array[i].mDrawable.draw(canvas);
814            }
815        }
816    }
817
818    private void drawBackgroundAndRipples(Canvas canvas) {
819        final Ripple active = mRipple;
820        final RippleBackground background = mBackground;
821        final int count = mExitingRipplesCount;
822        if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
823            // Move along, nothing to draw here.
824            return;
825        }
826
827        final float x = mHotspotBounds.exactCenterX();
828        final float y = mHotspotBounds.exactCenterY();
829        canvas.translate(x, y);
830
831        updateMaskShaderIfNeeded();
832
833        // Position the shader to account for canvas translation.
834        if (mMaskShader != null) {
835            mMaskMatrix.setTranslate(-x, -y);
836            mMaskShader.setLocalMatrix(mMaskMatrix);
837        }
838
839        // Grab the color for the current state and cut the alpha channel in
840        // half so that the ripple and background together yield full alpha.
841        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
842        final int halfAlpha = (Color.alpha(color) / 2) << 24;
843        final Paint p = getRipplePaint();
844
845        if (mMaskColorFilter != null) {
846            // The ripple timing depends on the paint's alpha value, so we need
847            // to push just the alpha channel into the paint and let the filter
848            // handle the full-alpha color.
849            final int fullAlphaColor = color | (0xFF << 24);
850            mMaskColorFilter.setColor(fullAlphaColor);
851
852            p.setColor(halfAlpha);
853            p.setColorFilter(mMaskColorFilter);
854            p.setShader(mMaskShader);
855        } else {
856            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
857            p.setColor(halfAlphaColor);
858            p.setColorFilter(null);
859            p.setShader(null);
860        }
861
862        if (background != null && background.shouldDraw()) {
863            background.draw(canvas, p);
864        }
865
866        if (count > 0) {
867            final Ripple[] ripples = mExitingRipples;
868            for (int i = 0; i < count; i++) {
869                ripples[i].draw(canvas, p);
870            }
871        }
872
873        if (active != null) {
874            active.draw(canvas, p);
875        }
876
877        canvas.translate(-x, -y);
878    }
879
880    private void drawMask(Canvas canvas) {
881        mMask.draw(canvas);
882    }
883
884    private Paint getRipplePaint() {
885        if (mRipplePaint == null) {
886            mRipplePaint = new Paint();
887            mRipplePaint.setAntiAlias(true);
888            mRipplePaint.setStyle(Paint.Style.FILL);
889        }
890        return mRipplePaint;
891    }
892
893    @Override
894    public Rect getDirtyBounds() {
895        if (isProjected()) {
896            final Rect drawingBounds = mDrawingBounds;
897            final Rect dirtyBounds = mDirtyBounds;
898            dirtyBounds.set(drawingBounds);
899            drawingBounds.setEmpty();
900
901            final int cX = (int) mHotspotBounds.exactCenterX();
902            final int cY = (int) mHotspotBounds.exactCenterY();
903            final Rect rippleBounds = mTempRect;
904
905            final Ripple[] activeRipples = mExitingRipples;
906            final int N = mExitingRipplesCount;
907            for (int i = 0; i < N; i++) {
908                activeRipples[i].getBounds(rippleBounds);
909                rippleBounds.offset(cX, cY);
910                drawingBounds.union(rippleBounds);
911            }
912
913            final RippleBackground background = mBackground;
914            if (background != null) {
915                background.getBounds(rippleBounds);
916                rippleBounds.offset(cX, cY);
917                drawingBounds.union(rippleBounds);
918            }
919
920            dirtyBounds.union(drawingBounds);
921            dirtyBounds.union(super.getDirtyBounds());
922            return dirtyBounds;
923        } else {
924            return getBounds();
925        }
926    }
927
928    @Override
929    public ConstantState getConstantState() {
930        return mState;
931    }
932
933    @Override
934    public Drawable mutate() {
935        super.mutate();
936
937        // LayerDrawable creates a new state using createConstantState, so
938        // this should always be a safe cast.
939        mState = (RippleState) mLayerState;
940
941        // The locally cached drawable may have changed.
942        mMask = findDrawableByLayerId(R.id.mask);
943
944        return this;
945    }
946
947    @Override
948    RippleState createConstantState(LayerState state, Resources res) {
949        return new RippleState(state, this, res);
950    }
951
952    static class RippleState extends LayerState {
953        int[] mTouchThemeAttrs;
954        ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
955        int mMaxRadius = RADIUS_AUTO;
956
957        public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
958            super(orig, owner, res);
959
960            if (orig != null && orig instanceof RippleState) {
961                final RippleState origs = (RippleState) orig;
962                mTouchThemeAttrs = origs.mTouchThemeAttrs;
963                mColor = origs.mColor;
964                mMaxRadius = origs.mMaxRadius;
965            }
966        }
967
968        @Override
969        public boolean canApplyTheme() {
970            return mTouchThemeAttrs != null
971                    || (mColor != null && mColor.canApplyTheme())
972                    || super.canApplyTheme();
973        }
974
975        @Override
976        public Drawable newDrawable() {
977            return new RippleDrawable(this, null);
978        }
979
980        @Override
981        public Drawable newDrawable(Resources res) {
982            return new RippleDrawable(this, res);
983        }
984    }
985
986    private RippleDrawable(RippleState state, Resources res) {
987        mState = new RippleState(state, this, res);
988        mLayerState = mState;
989
990        if (mState.mNum > 0) {
991            ensurePadding();
992        }
993
994        if (res != null) {
995            mDensity = res.getDisplayMetrics().density;
996        }
997
998        updateLocalState();
999    }
1000
1001    private void updateLocalState() {
1002        // Initialize from constant state.
1003        mMask = findDrawableByLayerId(R.id.mask);
1004    }
1005}
1006