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><!-- A red ripple masked against an opaque rectangle. --/> 53a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple android:color="#ffff0000"> 54a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <item android:id="@android:id/mask" 55a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * android:drawable="#ffffffff" /> 56a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple /></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><!-- A blue ripple drawn atop a green rectangle. --/> 66a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple android:color="#ff00ff00"> 67a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <item android:drawable="#ff0000ff" /> 68a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple /> 69a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * 70a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <!-- A red ripple drawn atop a drawable resource. --/> 71a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple android:color="#ff00ff00"> 72a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <item android:drawable="@drawable/my_drawable" /> 73a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple /></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><!-- An unbounded green ripple. --/> 82a4eab42fe437bff3f8ee9dde264579067ea5cdbdAlan Viverette * <ripple 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