RippleDrawable.java revision a4eab42fe437bff3f8ee9dde264579067ea5cdbd
1d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette/*
2d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * Copyright (C) 2013 The Android Open Source Project
3d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
4d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
5d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * you may not use this file except in compliance with the License.
6d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * You may obtain a copy of the License at
7d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
8d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
9d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette *
10d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * Unless required by applicable law or agreed to in writing, software
11d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
12d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * See the License for the specific language governing permissions and
14d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette * limitations under the License.
15d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette */
16d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
17d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverettepackage android.graphics.drawable;
18d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
19d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.ColorStateList;
20d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.Resources;
2152b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viveretteimport android.content.res.Resources.Theme;
22d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.content.res.TypedArray;
23d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Canvas;
24d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Color;
25c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viveretteimport android.graphics.ColorFilter;
26d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Paint;
27d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.PixelFormat;
28ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viveretteimport android.graphics.PorterDuff.Mode;
29ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viveretteimport android.graphics.PorterDuffXfermode;
30d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.graphics.Rect;
31d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.util.AttributeSet;
32d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport android.util.DisplayMetrics;
3370ad9bc03341cc0a5b8d6c9c1f43afac6bcc9c62Alan Viveretteimport android.util.Log;
34d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
35ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viveretteimport com.android.internal.R;
36ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
37d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport org.xmlpull.v1.XmlPullParser;
38d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport org.xmlpull.v1.XmlPullParserException;
39d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
40d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viveretteimport java.io.IOException;
41d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
42d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette/**
43ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * Drawable that shows a ripple effect in response to state changes. The
44ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * anchoring position of the ripple for a given state may be specified by
45c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette * calling {@link #setHotspot(float, float)} with the corresponding state
46ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * attribute identifier.
47ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
48ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * A touch feedback drawable may contain multiple child layers, including a
49ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * special mask layer that is not drawn to the screen. A single layer may be set
50ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * as the mask by specifying its android:id value as {@link android.R.id#mask}.
51a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
52a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <code>&lt!-- A red ripple masked against an opaque rectangle. --/>
53a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ffff0000">
54a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *   &ltitem android:id="@android:id/mask"
55a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *         android:drawable="#ffffffff" />
56a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple /></code>
57a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
58ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
59ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * If a mask layer is set, the ripple effect will be masked against that layer
60a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * before it is drawn over the composite of the remaining child layers.
61ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
62a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * If no mask layer is set, the ripple effect is masked against the composite
63a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * of the child layers.
64a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
65a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <code>&lt!-- A blue ripple drawn atop a green rectangle. --/>
66a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ff00ff00">
67a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *   &ltitem android:drawable="#ff0000ff" />
68a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple />
69a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *
70a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &lt!-- A red ripple drawn atop a drawable resource. --/>
71a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ff00ff00">
72a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette *   &ltitem android:drawable="@drawable/my_drawable" />
73a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple /></code>
74a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
75ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * <p>
76ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette * If no child layers or mask is specified and the ripple is set as a View
77a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * background, the ripple will be drawn atop the first available parent
78a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * background within the View's hierarchy. In this case, the drawing region
79a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * may extend outside of the Drawable bounds.
80a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <pre>
81a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <code>&lt!-- An unbounded green ripple. --/>
82a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * &ltripple android:color="#ff0000ff" /></code>
83a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * </pre>
84ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette *
85a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * @attr ref android.R.styleable#RippleDrawable_color
86d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette */
87c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverettepublic class RippleDrawable extends LayerDrawable {
88c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    private static final String LOG_TAG = RippleDrawable.class.getSimpleName();
89dccbe8b02a34a3c78028a31ee158d4d2818c72baAlan Viverette    private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
90ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
91d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
92dccbe8b02a34a3c78028a31ee158d4d2818c72baAlan Viverette
934d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
944d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * Constant for automatically determining the maximum ripple radius.
954d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *
964d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @see #setMaxRadius(int)
974d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @hide
984d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
994d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    public static final int RADIUS_AUTO = -1;
1004d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
10147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette    /** The maximum number of ripples supported. */
10247bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette    private static final int MAX_RIPPLES = 10;
10347bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
104d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mTempRect = new Rect();
105d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
10661956606818918194a38e045a8e35e7108480e5eAlan Viverette    /** Current ripple effect bounds, used to constrain ripple effects. */
10761956606818918194a38e045a8e35e7108480e5eAlan Viverette    private final Rect mHotspotBounds = new Rect();
10861956606818918194a38e045a8e35e7108480e5eAlan Viverette
109d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Current drawing bounds, used to compute dirty region. */
110d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mDrawingBounds = new Rect();
111d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
112d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Current dirty bounds, union of current and previous drawing bounds. */
113d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private final Rect mDirtyBounds = new Rect();
114d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
115c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    private final RippleState mState;
116d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
117b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    /** The masking layer, e.g. the layer with id R.id.mask. */
118b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    private Drawable mMask;
119b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
1204d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /** The current hotspot. May be actively animating or pending entry. */
1214d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private Ripple mHotspot;
122d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
123ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    /**
124ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     * Lazily-created array of actively animating ripples. Inactive ripples are
125ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     * pruned during draw(). The locations of these will not change.
126ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     */
12753d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette    private Ripple[] mAnimatingRipples;
12853d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette    private int mAnimatingRipplesCount = 0;
129d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
130d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Paint used to control appearance of ripples. */
131d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    private Paint mRipplePaint;
132d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
133ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    /** Paint used to control reveal layer masking. */
134ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    private Paint mMaskingPaint;
135ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
136d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /** Target density of the display into which ripples are drawn. */
137cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette    private float mDensity = 1.0f;
138d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
13961956606818918194a38e045a8e35e7108480e5eAlan Viverette    /** Whether bounds are being overridden. */
14061956606818918194a38e045a8e35e7108480e5eAlan Viverette    private boolean mOverrideBounds;
14161956606818918194a38e045a8e35e7108480e5eAlan Viverette
1424d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /** Whether the hotspot is currently active (e.g. focused or pressed). */
1434d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private boolean mActive;
1444d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
145c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    RippleDrawable() {
1464d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        this(null, null);
1474d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
1484d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1494d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
1504d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * Creates a new ripple drawable with the specified content and mask
1514d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * drawables.
1524d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *
1534d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param content The content drawable, may be {@code null}
1544d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param mask The mask drawable, may be {@code null}
1554d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
1564d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    public RippleDrawable(Drawable content, Drawable mask) {
157c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        this(new RippleState(null, null, null), null, null);
1584d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1594d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (content != null) {
1604d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            addLayer(content, null, 0, 0, 0, 0, 0);
1614d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
1624d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1634d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mask != null) {
1644d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
1654d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
1664d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1674d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        ensurePadding();
168c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    }
169c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette
170c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    @Override
171c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    public void setAlpha(int alpha) {
1724d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        super.setAlpha(alpha);
1734d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1744d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // TODO: Should we support this?
175c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    }
176c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette
177c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    @Override
178c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    public void setColorFilter(ColorFilter cf) {
1794d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        super.setColorFilter(cf);
1804d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
1814d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // TODO: Should we support this?
182d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
183d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
184d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
185d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public int getOpacity() {
18647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        // Worst-case scenario.
18747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        return PixelFormat.TRANSLUCENT;
188ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
189ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
190ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    @Override
19112b97f5d2b15194ed6673c9838b13c8312157709Alan Viverette    protected boolean onStateChange(int[] stateSet) {
192ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        super.onStateChange(stateSet);
193ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
1941b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        // TODO: This would make more sense in a StateListDrawable.
1954d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        boolean active = false;
1961b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        boolean enabled = false;
1974d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int N = stateSet.length;
1984d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        for (int i = 0; i < N; i++) {
1991b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette            if (stateSet[i] == R.attr.state_enabled) {
2001b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette                enabled = true;
2011b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette            }
2024d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            if (stateSet[i] == R.attr.state_focused
2034d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                    || stateSet[i] == R.attr.state_pressed) {
2044d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                active = true;
2054d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            }
206d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
2071b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        setActive(active && enabled);
208d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
2094d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // Update the paint color. Only applicable when animated in software.
210a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (mRipplePaint != null && mState.mColor != null) {
211a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            final ColorStateList stateList = mState.mColor;
212d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            final int newColor = stateList.getColorForState(stateSet, 0);
213d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            final int oldColor = mRipplePaint.getColor();
214d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            if (oldColor != newColor) {
215d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette                mRipplePaint.setColor(newColor);
216d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette                invalidateSelf();
217d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette                return true;
218d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            }
219d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
220d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
221d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        return false;
222d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
223d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
2244d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private void setActive(boolean active) {
2254d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mActive != active) {
2264d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mActive = active;
2274d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
2284d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            if (active) {
2294d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                activateHotspot();
2304d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            } else {
2314d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                removeHotspot();
2324d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            }
2334d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
2344d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
2354d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
236307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    @Override
237c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    protected void onBoundsChange(Rect bounds) {
238c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette        super.onBoundsChange(bounds);
239d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
24061956606818918194a38e045a8e35e7108480e5eAlan Viverette        if (!mOverrideBounds) {
24161956606818918194a38e045a8e35e7108480e5eAlan Viverette            mHotspotBounds.set(bounds);
242dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette            onHotspotBoundsChanged();
24361956606818918194a38e045a8e35e7108480e5eAlan Viverette        }
244c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette
245ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        invalidateSelf();
246c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    }
247c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette
248c3f35b01b5a21e110ca4eedf09c8c6164ab85dfbAlan Viverette    @Override
249307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    public boolean setVisible(boolean visible, boolean restart) {
250307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette        if (!visible) {
25153d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette            clearHotspots();
252307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette        }
253307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette
254307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette        return super.setVisible(visible, restart);
255307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette    }
256307ad09228ebf70f1b456f5f00540c0126277850Alan Viverette
257d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    /**
258d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette     * @hide
259d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette     */
260d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
261d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public boolean isProjected() {
26247bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        return getNumberOfLayers() == 0;
263d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
264d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
265d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
266d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public boolean isStateful() {
267ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        return true;
268d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
269d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
270a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette    public void setColor(ColorStateList color) {
271a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        mState.mColor = color;
272d30688f0a0a75743df247831688efcbe2fa62cc4Alan Viverette        invalidateSelf();
2732f8ba8f7fad2b608102a9282219aaea2223e94f5Alan Viverette    }
2742f8ba8f7fad2b608102a9282219aaea2223e94f5Alan Viverette
275d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
27652b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
277d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            throws XmlPullParserException, IOException {
278e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable);
279ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        updateStateFromTypedArray(a);
280d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        a.recycle();
281cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette
282e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        // Force padding default to STACK before inflating.
283e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        setPaddingMode(PADDING_MODE_STACK);
284e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette
28547bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        super.inflate(r, parser, attrs, theme);
286ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
28747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        setTargetDensity(r.getDisplayMetrics());
288b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        initializeFromState();
289d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    }
290d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
291d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    @Override
292d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    public boolean setDrawableByLayerId(int id, Drawable drawable) {
293d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        if (super.setDrawableByLayerId(id, drawable)) {
294d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            if (id == R.id.mask) {
295b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette                mMask = drawable;
296d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
297d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
298d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            return true;
299d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
300d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
301d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        return false;
302ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
303ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
304ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    /**
305e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * Specifies how layer padding should affect the bounds of subsequent
306e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * layers. The default and recommended value for RippleDrawable is
307e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * {@link #PADDING_MODE_STACK}.
308e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *
309e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * @param mode padding mode, one of:
310e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <ul>
311e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
312e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            padding of the previous layer
313e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
314e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            atop the previous layer
315e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     *            </ul>
316e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     * @see #getPaddingMode()
317e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette     */
318e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    @Override
319e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    public void setPaddingMode(int mode) {
320e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        super.setPaddingMode(mode);
321e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    }
322e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette
323e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette    /**
32452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette     * Initializes the constant state from the values in the typed array.
32552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette     */
3264d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
327c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        final RippleState state = mState;
32852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
32952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        // Extract the theme attributes, if any.
330ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        state.mTouchThemeAttrs = a.extractThemeAttrs();
33152b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
332a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final ColorStateList color = a.getColorStateList(R.styleable.RippleDrawable_color);
333a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (color != null) {
334a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            mState.mColor = color;
33552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
33652b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
3374d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // If we're not waiting on a theme, verify required attributes.
338a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (state.mTouchThemeAttrs == null && mState.mColor == null) {
3394d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            throw new XmlPullParserException(a.getPositionDescription() +
340a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                    ": <ripple> requires a valid color attribute");
3414d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
34252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
34352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
34452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    /**
345cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette     * Set the density at which this drawable will be rendered.
346cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette     *
347cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette     * @param metrics The display metrics for this drawable.
348cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette     */
349cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette    private void setTargetDensity(DisplayMetrics metrics) {
350cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette        if (mDensity != metrics.density) {
351cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette            mDensity = metrics.density;
352cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette            invalidateSelf();
353cb29189c29a553a8005044f32de37a610f2857dbAlan Viverette        }
354d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
355d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
35652b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    @Override
35752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    public void applyTheme(Theme t) {
35852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        super.applyTheme(t);
35952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
360c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        final RippleState state = mState;
361ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        if (state == null || state.mTouchThemeAttrs == null) {
362ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette            return;
36352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
36452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
365ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
366c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette                R.styleable.RippleDrawable);
3674d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        try {
3684d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            updateStateFromTypedArray(a);
3694d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        } catch (XmlPullParserException e) {
3704d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            throw new RuntimeException(e);
3714d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        } finally {
3724d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            a.recycle();
3734d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
374b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
375b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        initializeFromState();
37652b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
37752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
37852b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    @Override
37952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    public boolean canApplyTheme() {
38047bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        return super.canApplyTheme() || mState != null && mState.mTouchThemeAttrs != null;
38152b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
38252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
383d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
384c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    public void setHotspot(float x, float y) {
3854d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mHotspot == null) {
3864d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot = new Ripple(this, mHotspotBounds, x, y);
387ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
3884d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            if (mActive) {
3894d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                activateHotspot();
3904d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            }
3914d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        } else {
3924d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot.move(x, y);
393ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
394ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    }
395ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
396ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    /**
3974d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * Creates an active hotspot at the specified location.
398ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette     */
3994d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private void activateHotspot() {
4004d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mAnimatingRipplesCount >= MAX_RIPPLES) {
4014d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            // This should never happen unless the user is tapping like a maniac
4024d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            // or there is a bug that's preventing ripples from being removed.
4034d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
4044d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            return;
4054d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
4064d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
4074d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mHotspot == null) {
4084d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            final float x = mHotspotBounds.exactCenterX();
4094d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            final float y = mHotspotBounds.exactCenterY();
4104d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot = new Ripple(this, mHotspotBounds, x, y);
411ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
412ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
413a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
4144d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        mHotspot.setup(mState.mMaxRadius, color, mDensity);
4154d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        mHotspot.enter();
416ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
417ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        if (mAnimatingRipples == null) {
418ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette            mAnimatingRipples = new Ripple[MAX_RIPPLES];
419ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
4204d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
421ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette    }
422ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
4234d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private void removeHotspot() {
4244d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mHotspot != null) {
4254d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot.exit();
4264d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot = null;
427d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
428d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
429d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
430c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    private void clearHotspots() {
4314d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (mHotspot != null) {
4324d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot.cancel();
4334d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mHotspot = null;
434de399397947c5379c61a1003c017b96accbbf545Alan Viverette        }
435de399397947c5379c61a1003c017b96accbbf545Alan Viverette
436de399397947c5379c61a1003c017b96accbbf545Alan Viverette        final int count = mAnimatingRipplesCount;
437de399397947c5379c61a1003c017b96accbbf545Alan Viverette        final Ripple[] ripples = mAnimatingRipples;
438de399397947c5379c61a1003c017b96accbbf545Alan Viverette        for (int i = 0; i < count; i++) {
4397a98f74438ac8da8bed5ebdb54c70ce24557a9d8Alan Viverette            // Calling cancel may remove the ripple from the animating ripple
4407a98f74438ac8da8bed5ebdb54c70ce24557a9d8Alan Viverette            // array, so cache the reference before nulling it out.
4417a98f74438ac8da8bed5ebdb54c70ce24557a9d8Alan Viverette            final Ripple ripple = ripples[i];
442de399397947c5379c61a1003c017b96accbbf545Alan Viverette            ripples[i] = null;
4437a98f74438ac8da8bed5ebdb54c70ce24557a9d8Alan Viverette            ripple.cancel();
444d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
445d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
446de399397947c5379c61a1003c017b96accbbf545Alan Viverette        mAnimatingRipplesCount = 0;
44753d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        invalidateSelf();
448d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
449d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
45061956606818918194a38e045a8e35e7108480e5eAlan Viverette    @Override
45161956606818918194a38e045a8e35e7108480e5eAlan Viverette    public void setHotspotBounds(int left, int top, int right, int bottom) {
45261956606818918194a38e045a8e35e7108480e5eAlan Viverette        mOverrideBounds = true;
45361956606818918194a38e045a8e35e7108480e5eAlan Viverette        mHotspotBounds.set(left, top, right, bottom);
454dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette
455dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        onHotspotBoundsChanged();
456dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    }
457dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette
458dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    /**
459dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette     * Notifies all the animating ripples that the hotspot bounds have changed.
460dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette     */
461dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette    private void onHotspotBoundsChanged() {
462dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        final int count = mAnimatingRipplesCount;
463dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        final Ripple[] ripples = mAnimatingRipples;
464dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        for (int i = 0; i < count; i++) {
465dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette            ripples[i].onHotspotBoundsChanged();
466dc6046fca37264b48e9c959f38d08cfb78f436edAlan Viverette        }
46761956606818918194a38e045a8e35e7108480e5eAlan Viverette    }
46861956606818918194a38e045a8e35e7108480e5eAlan Viverette
469d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
470d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public void draw(Canvas canvas) {
4711b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final boolean isProjected = isProjected();
4721b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final boolean hasMask = mMask != null;
4731b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
4741b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
4751b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final Rect bounds = isProjected ? getDirtyBounds() : getBounds();
4764d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
4771b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        // If we have content, draw it into a layer first.
4781b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final int contentLayer = drawNonMaskContent ?
4791b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette                drawContentLayer(canvas, bounds, SRC_OVER) : -1;
4804d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
481a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        // Next, try to draw the ripples (into a layer if necessary). If we need
482a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        // to mask against the underlying content, set the xfermode to SRC_ATOP.
483a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;
484a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
4854d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
4861b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        // If we have ripples and a non-opaque mask, draw the masking layer.
4871b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        if (rippleLayer >= 0 && drawMask) {
4884d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            drawMaskingLayer(canvas, bounds, DST_IN);
4894d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
4904d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
4914d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // Composite the layers if needed.
4924d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (contentLayer >= 0) {
4934d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            canvas.restoreToCount(contentLayer);
4944d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        } else if (rippleLayer >= 0) {
4954d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            canvas.restoreToCount(rippleLayer);
4964d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
4974d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
4984d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
4994d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
5004d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * Removes a ripple from the animating ripple list.
5014d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *
5024d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param ripple the ripple to remove
5034d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
5044d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    void removeRipple(Ripple ripple) {
5054d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        // Ripple ripple ripple ripple. Ripple ripple.
5064d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final Ripple[] ripples = mAnimatingRipples;
5074d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int count = mAnimatingRipplesCount;
5084d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int index = getRippleIndex(ripple);
5094d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (index >= 0) {
5104d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            for (int i = index + 1; i < count; i++) {
5114d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                ripples[i - 1] = ripples[i];
512d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
5134d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            ripples[count - 1] = null;
5144d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            mAnimatingRipplesCount--;
5154d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            invalidateSelf();
5164d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        }
5174d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
518d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
5194d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private int getRippleIndex(Ripple ripple) {
5204d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final Ripple[] ripples = mAnimatingRipples;
5214d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int count = mAnimatingRipplesCount;
5224d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        for (int i = 0; i < count; i++) {
5234d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            if (ripples[i] == ripple) {
5244d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                return i;
525d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
526d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
5274d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        return -1;
5284d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
5294d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
5304d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
531a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final ChildDrawable[] array = mLayerState.mChildren;
532a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final int count = mLayerState.mNum;
533a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette
534a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        // We don't need a layer if we don't expect to draw any ripples, we have
535a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        // an explicit mask, or if the non-mask content is all opaque.
536a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        boolean needsLayer = false;
537a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (mAnimatingRipplesCount > 0 && mMask == null) {
538a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            for (int i = 0; i < count; i++) {
539a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                if (array[i].mId != R.id.mask
540a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                        && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
541a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                    needsLayer = true;
542a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                    break;
543a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                }
544a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            }
545a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        }
546a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette
5474d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final Paint maskingPaint = getMaskingPaint(mode);
548a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top,
549a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                bounds.right, bounds.bottom, maskingPaint) : -1;
550d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
551d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        // Draw everything except the mask.
5524d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        for (int i = 0; i < count; i++) {
553d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            if (array[i].mId != R.id.mask) {
554d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette                array[i].mDrawable.draw(canvas);
555d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
556d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        }
557d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
5584d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        return restoreToCount;
559d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    }
560d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
5614d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
56253d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        final int count = mAnimatingRipplesCount;
56353d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        if (count == 0) {
564323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette            return -1;
565323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        }
566323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette
567323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        // Separate the ripple color and alpha channel. The alpha will be
568323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        // applied when we merge the ripples down to the canvas.
5694d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int rippleARGB;
570a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        if (mState.mColor != null) {
571a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
572323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        } else {
5734d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            rippleARGB = Color.TRANSPARENT;
574323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        }
575323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette
576323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        if (mRipplePaint == null) {
577323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette            mRipplePaint = new Paint();
578323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette            mRipplePaint.setAntiAlias(true);
579323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        }
5801b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette
5811b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        final int rippleAlpha = Color.alpha(rippleARGB);
582323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette        final Paint ripplePaint = mRipplePaint;
5831b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        ripplePaint.setColor(rippleARGB);
5841b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        ripplePaint.setAlpha(0xFF);
585d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
58647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        boolean drewRipples = false;
587d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        int restoreToCount = -1;
588ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        int restoreTranslate = -1;
589d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
59053d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        // Draw ripples and update the animating ripples array.
5914d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final Ripple[] ripples = mAnimatingRipples;
59253d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        for (int i = 0; i < count; i++) {
59353d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette            final Ripple ripple = ripples[i];
594d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
595d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            // If we're masking the ripple layer, make sure we have a layer
596d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            // first. This will merge SRC_OVER (directly) onto the canvas.
597d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            if (restoreToCount < 0) {
5984d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                final Paint maskingPaint = getMaskingPaint(mode);
5994d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                maskingPaint.setAlpha(rippleAlpha);
6004d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
6014d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                        bounds.right, bounds.bottom, maskingPaint);
602ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
603ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette                restoreTranslate = canvas.save();
604ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette                // Translate the canvas to the current hotspot bounds.
605ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette                canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
606d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            }
60747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
608323596de4efc46149719b41de5a9f668d7f3f784Alan Viverette            drewRipples |= ripple.draw(canvas, ripplePaint);
609ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        }
610ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
611ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        // Always restore the translation.
612ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        if (restoreTranslate >= 0) {
613ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette            canvas.restoreToCount(restoreTranslate);
614ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        }
615ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette
616d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        // If we created a layer with no content, merge it immediately.
617d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        if (restoreToCount >= 0 && !drewRipples) {
618d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            canvas.restoreToCount(restoreToCount);
619d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette            restoreToCount = -1;
620ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        }
621d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
622d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        return restoreToCount;
623ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette    }
624ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
6254d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
6264d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
6274d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                bounds.right, bounds.bottom, getMaskingPaint(mode));
6284d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
629dcf7b59f845297c6e96950615e439d96ce3fb739Alan Viverette        // Ensure that DST_IN blends using the entire layer.
630dcf7b59f845297c6e96950615e439d96ce3fb739Alan Viverette        canvas.drawColor(Color.TRANSPARENT);
631dcf7b59f845297c6e96950615e439d96ce3fb739Alan Viverette
6321b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        mMask.draw(canvas);
6334d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
6344d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        return restoreToCount;
6354d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
6364d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
637d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette    private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
638ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        if (mMaskingPaint == null) {
639ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette            mMaskingPaint = new Paint();
640ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        }
641d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        mMaskingPaint.setXfermode(xfermode);
6421b6e856e6f9dab4464e3c556b2f68527439fc329Alan Viverette        mMaskingPaint.setAlpha(0xFF);
643ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        return mMaskingPaint;
644d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
645d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
646d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    @Override
647d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    public Rect getDirtyBounds() {
648d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        final Rect drawingBounds = mDrawingBounds;
649d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette        final Rect dirtyBounds = mDirtyBounds;
650d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        dirtyBounds.set(drawingBounds);
651d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        drawingBounds.setEmpty();
652d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
653ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        final int cX = (int) mHotspotBounds.exactCenterX();
654ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette        final int cY = (int) mHotspotBounds.exactCenterY();
655d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        final Rect rippleBounds = mTempRect;
65653d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        final Ripple[] activeRipples = mAnimatingRipples;
65753d1cfe2d073bff7c7771d5f7dd9108062ddc706Alan Viverette        final int N = mAnimatingRipplesCount;
65847bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        for (int i = 0; i < N; i++) {
65947bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            activeRipples[i].getBounds(rippleBounds);
660ad2f8e334f3ef22d3e412b0660a2e1f996f94116Alan Viverette            rippleBounds.offset(cX, cY);
66147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            drawingBounds.union(rippleBounds);
662d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
663d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
664d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        dirtyBounds.union(drawingBounds);
665ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette        dirtyBounds.union(super.getDirtyBounds());
666d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        return dirtyBounds;
667d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
668d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
6697f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    @Override
6707f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    public ConstantState getConstantState() {
6717f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette        return mState;
6727f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette    }
6737f610fed107b158c144dca1b20a44ee91eb8c934Alan Viverette
674c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    static class RippleState extends LayerState {
67547bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        int[] mTouchThemeAttrs;
676a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette        ColorStateList mColor = null;
6774d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        int mMaxRadius = RADIUS_AUTO;
678d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
679e7772d313b88e9fd3366670a9c8d1b145edef344Alan Viverette        public RippleState(RippleState orig, RippleDrawable owner, Resources res) {
68047bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            super(orig, owner, res);
681ba346f9d8d681c3c8166609382eb882e538b9b05Alan Viverette
682d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            if (orig != null) {
68347bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette                mTouchThemeAttrs = orig.mTouchThemeAttrs;
684a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette                mColor = orig.mColor;
6854d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette                mMaxRadius = orig.mMaxRadius;
686d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette            }
687d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
688d1ca75bffef070f62ab70ed514f7f91824f73cbcAlan Viverette
689d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        @Override
69052b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        public boolean canApplyTheme() {
69147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            return mTouchThemeAttrs != null || super.canApplyTheme();
69252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
69352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
69452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        @Override
695d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        public Drawable newDrawable() {
696c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            return new RippleDrawable(this, null, null);
697d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
698d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette
699d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        @Override
700d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        public Drawable newDrawable(Resources res) {
701c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            return new RippleDrawable(this, res, null);
70252b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
70352b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
70452b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        @Override
70552b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        public Drawable newDrawable(Resources res, Theme theme) {
706c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            return new RippleDrawable(this, res, theme);
707d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette        }
7084d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
709d30688f0a0a75743df247831688efcbe2fa62cc4Alan Viverette
7104d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
7114d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * Sets the maximum ripple radius in pixels. The default value of
7124d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * {@link #RADIUS_AUTO} defines the radius as the distance from the center
7134d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * of the drawable bounds (or hotspot bounds, if specified) to a corner.
7144d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *
7154d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @param maxRadius the maximum ripple radius in pixels or
7164d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *            {@link #RADIUS_AUTO} to automatically determine the maximum
7174d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *            radius based on the bounds
7184d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @see #getMaxRadius()
7194d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @see #setHotspotBounds(int, int, int, int)
7204d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @hide
7214d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
7224d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    public void setMaxRadius(int maxRadius) {
7234d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        if (maxRadius != RADIUS_AUTO && maxRadius < 0) {
7244d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette            throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0");
725d30688f0a0a75743df247831688efcbe2fa62cc4Alan Viverette        }
7264d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
7274d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        mState.mMaxRadius = maxRadius;
7284d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    }
7294d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette
7304d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    /**
7314d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if
7324d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     *         the radius is determined automatically
7334d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @see #setMaxRadius(int)
7344d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     * @hide
7354d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette     */
7364d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette    public int getMaxRadius() {
7374d2f2483f6d9e2eb25d843d676981f4ebc9c79e5Alan Viverette        return mState.mMaxRadius;
738d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette    }
73952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette
740c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette    private RippleDrawable(RippleState state, Resources res, Theme theme) {
74147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        boolean needsTheme = false;
74247bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
743c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette        final RippleState ns;
74447bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        if (theme != null && state != null && state.canApplyTheme()) {
745c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            ns = new RippleState(state, this, res);
74647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            needsTheme = true;
74747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        } else if (state == null) {
748c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            ns = new RippleState(null, this, res);
74952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        } else {
750e106ac7f7c28cd8711e533810e4b3218adadc40fAlan Viverette            // We always need a new state since child drawables contain local
751e106ac7f7c28cd8711e533810e4b3218adadc40fAlan Viverette            // state but live within the parent's constant state.
752e106ac7f7c28cd8711e533810e4b3218adadc40fAlan Viverette            // TODO: Move child drawables into local state.
753c80ad99a33ee49d0bac994c1749ff24d243c3862Alan Viverette            ns = new RippleState(state, this, res);
75447bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        }
75547bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
75647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        if (res != null) {
75747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            mDensity = res.getDisplayMetrics().density;
75847bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        }
75947bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
76047bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        mState = ns;
76147bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        mLayerState = ns;
76247bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
76347bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        if (ns.mNum > 0) {
76447bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            ensurePadding();
76547bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        }
76647bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette
76747bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette        if (needsTheme) {
76847bf0d95ef6c9ac68773567d503749c874a07f2fAlan Viverette            applyTheme(theme);
76952b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette        }
770b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
771b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        initializeFromState();
772b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    }
773b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette
774b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette    private void initializeFromState() {
775b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        // Initialize from constant state.
776b996d809bf10a9ad1f6f6be790d0261fe1565f3bAlan Viverette        mMask = findDrawableByLayerId(R.id.mask);
77752b999f0721b53e9c6e18a4bd664e89aeb65b2d5Alan Viverette    }
778d5154ec2bc7e7c0bdfd14fc784912d390afe43ccAlan Viverette}
779