diff --git a/api/current.txt b/api/current.txt index 62154b8d867f5..2282a33d33c3e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -825,6 +825,7 @@ package android { field public static final int persistent = 16842765; // 0x101000d field public static final int persistentDrawingCache = 16842990; // 0x10100ee field public static final deprecated int phoneNumber = 16843111; // 0x1010167 + field public static final int pinned = 16843776; // 0x1010400 field public static final int pivotX = 16843189; // 0x10101b5 field public static final int pivotY = 16843190; // 0x10101b6 field public static final int popupAnimationStyle = 16843465; // 0x10102c9 @@ -2196,6 +2197,8 @@ package android { field public static final int Widget_Quantum_Button_Borderless = 16974393; // 0x1030239 field public static final int Widget_Quantum_Button_Borderless_Small = 16974394; // 0x103023a field public static final int Widget_Quantum_Button_Inset = 16974395; // 0x103023b + field public static final int Widget_Quantum_Button_Paper = 16974503; // 0x10302a7 + field public static final int Widget_Quantum_Button_Paper_Color = 16974504; // 0x10302a8 field public static final int Widget_Quantum_Button_Small = 16974396; // 0x103023c field public static final int Widget_Quantum_Button_Toggle = 16974397; // 0x103023d field public static final int Widget_Quantum_CalendarView = 16974398; // 0x103023e @@ -2232,6 +2235,8 @@ package android { field public static final int Widget_Quantum_Light_Button = 16974452; // 0x1030274 field public static final int Widget_Quantum_Light_Button_Borderless_Small = 16974453; // 0x1030275 field public static final int Widget_Quantum_Light_Button_Inset = 16974454; // 0x1030276 + field public static final int Widget_Quantum_Light_Button_Paper = 16974505; // 0x10302a9 + field public static final int Widget_Quantum_Light_Button_Paper_Color = 16974506; // 0x10302aa field public static final int Widget_Quantum_Light_Button_Small = 16974455; // 0x1030277 field public static final int Widget_Quantum_Light_Button_Toggle = 16974456; // 0x1030278 field public static final int Widget_Quantum_Light_CalendarView = 16974457; // 0x1030279 diff --git a/core/res/res/drawable/btn_borderless_quantum_dark.xml b/core/res/res/drawable/btn_borderless_quantum_dark.xml index e1bff4fb71b67..2c0d3b7688f71 100644 --- a/core/res/res/drawable/btn_borderless_quantum_dark.xml +++ b/core/res/res/drawable/btn_borderless_quantum_dark.xml @@ -14,10 +14,6 @@ limitations under the License. --> - - - - - - + diff --git a/core/res/res/drawable/btn_borderless_quantum_light.xml b/core/res/res/drawable/btn_borderless_quantum_light.xml index e7a95b19b88e0..2faec207e0a75 100644 --- a/core/res/res/drawable/btn_borderless_quantum_light.xml +++ b/core/res/res/drawable/btn_borderless_quantum_light.xml @@ -14,10 +14,6 @@ limitations under the License. --> - - - - - - + diff --git a/core/res/res/drawable/btn_color_quantum_dark.xml b/core/res/res/drawable/btn_color_quantum_dark.xml index 5e44a784492a1..0507755a822fb 100644 --- a/core/res/res/drawable/btn_color_quantum_dark.xml +++ b/core/res/res/drawable/btn_color_quantum_dark.xml @@ -14,21 +14,16 @@ limitations under the License. --> - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/core/res/res/drawable/btn_color_quantum_light.xml b/core/res/res/drawable/btn_color_quantum_light.xml index d6be95892f163..9166b8d668998 100644 --- a/core/res/res/drawable/btn_color_quantum_light.xml +++ b/core/res/res/drawable/btn_color_quantum_light.xml @@ -14,21 +14,16 @@ limitations under the License. --> - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/core/res/res/drawable/btn_default_quantum_dark.xml b/core/res/res/drawable/btn_default_quantum_dark.xml index 7f0cca83b48e3..29c3e24c1fd7a 100644 --- a/core/res/res/drawable/btn_default_quantum_dark.xml +++ b/core/res/res/drawable/btn_default_quantum_dark.xml @@ -14,13 +14,8 @@ limitations under the License. --> - - - - - - - - + + + diff --git a/core/res/res/drawable/btn_default_quantum_light.xml b/core/res/res/drawable/btn_default_quantum_light.xml index e391a803e825e..c6e828c8c2792 100644 --- a/core/res/res/drawable/btn_default_quantum_light.xml +++ b/core/res/res/drawable/btn_default_quantum_light.xml @@ -14,13 +14,8 @@ limitations under the License. --> - - - - - - - - + + + diff --git a/core/res/res/drawable/item_background_borderless_quantum_dark.xml b/core/res/res/drawable/item_background_borderless_quantum_dark.xml index 1caee4e723f14..3f328501e6583 100644 --- a/core/res/res/drawable/item_background_borderless_quantum_dark.xml +++ b/core/res/res/drawable/item_background_borderless_quantum_dark.xml @@ -15,4 +15,4 @@ --> + android:tint="@color/lighter_gray" /> diff --git a/core/res/res/drawable/item_background_borderless_quantum_light.xml b/core/res/res/drawable/item_background_borderless_quantum_light.xml index ecf7dfb5d9721..09f6ae92d2ce0 100644 --- a/core/res/res/drawable/item_background_borderless_quantum_light.xml +++ b/core/res/res/drawable/item_background_borderless_quantum_light.xml @@ -15,4 +15,4 @@ --> + android:tint="@color/darker_gray" /> diff --git a/core/res/res/drawable/item_background_quantum_dark.xml b/core/res/res/drawable/item_background_quantum_dark.xml index 5ccaa8e6a2990..3f328501e6583 100644 --- a/core/res/res/drawable/item_background_quantum_dark.xml +++ b/core/res/res/drawable/item_background_quantum_dark.xml @@ -14,7 +14,5 @@ limitations under the License. --> - - - - + diff --git a/core/res/res/drawable/item_background_quantum_light.xml b/core/res/res/drawable/item_background_quantum_light.xml index f1453c5811b48..09f6ae92d2ce0 100644 --- a/core/res/res/drawable/item_background_quantum_light.xml +++ b/core/res/res/drawable/item_background_quantum_light.xml @@ -14,7 +14,5 @@ limitations under the License. --> - - - - + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index f85b19356b711..20439602f12ed 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4429,6 +4429,20 @@ + + + + + + + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 3ab3e5d84e347..02fff76b1a72d 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2106,6 +2106,7 @@ + @@ -2311,4 +2312,10 @@ + + + + + + diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java new file mode 100644 index 0000000000000..e6a755fd9a106 --- /dev/null +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.Xfermode; + +/** + * A Drawable that wraps another Drawable. + */ +class DrawableWrapper extends Drawable implements Drawable.Callback { + private WrapperState mWrapperState; + + /** Local drawable backed by its own constant state. */ + private Drawable mWrappedDrawable; + + private boolean mMutated; + + /** @hide */ + @Override + public boolean isProjected() { + return mWrappedDrawable.isProjected(); + } + + @Override + public void setAutoMirrored(boolean mirrored) { + mWrappedDrawable.setAutoMirrored(mirrored); + } + + @Override + public boolean isAutoMirrored() { + return mWrappedDrawable.isAutoMirrored(); + } + + @Override + public int getMinimumWidth() { + return mWrappedDrawable.getMinimumWidth(); + } + + @Override + public int getMinimumHeight() { + return mWrappedDrawable.getMinimumHeight(); + } + + @Override + public int getIntrinsicWidth() { + return mWrappedDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mWrappedDrawable.getIntrinsicHeight(); + } + + @Override + public Drawable getCurrent() { + return mWrappedDrawable.getCurrent(); + } + + @Override + public void invalidateDrawable(Drawable who) { + final Callback callback = getCallback(); + if (callback != null) { + callback.invalidateDrawable(this); + } + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + final Callback callback = getCallback(); + if (callback != null) { + callback.scheduleDrawable(this, what, when); + } + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + final Callback callback = getCallback(); + if (callback != null) { + callback.unscheduleDrawable(this, what); + } + } + + @Override + public void draw(Canvas canvas) { + mWrappedDrawable.draw(canvas); + } + + @Override + public int getChangingConfigurations() { + return mWrappedDrawable.getChangingConfigurations(); + } + + @Override + public boolean getPadding(Rect padding) { + return mWrappedDrawable.getPadding(padding); + } + + @Override + public Rect getDirtyBounds() { + return mWrappedDrawable.getDirtyBounds(); + } + + /** + * @hide + */ + @Override + public boolean supportsHotspots() { + return mWrappedDrawable.supportsHotspots(); + } + + /** + * @hide + */ + @Override + public void setHotspot(int id, float x, float y) { + mWrappedDrawable.setHotspot(id, x, y); + } + + /** + * @hide + */ + @Override + public void removeHotspot(int id) { + mWrappedDrawable.removeHotspot(id); + } + + /** + * @hide + */ + @Override + public void clearHotspots() { + mWrappedDrawable.clearHotspots(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + // Must call through to super(). + super.setVisible(visible, restart); + return mWrappedDrawable.setVisible(visible, restart); + } + + @Override + public void setAlpha(int alpha) { + mWrappedDrawable.setAlpha(alpha); + } + + @Override + public int getAlpha() { + return mWrappedDrawable.getAlpha(); + } + + /** {@hide} */ + @Override + public void setLayoutDirection(int layoutDirection) { + mWrappedDrawable.setLayoutDirection(layoutDirection); + } + + /** {@hide} */ + @Override + public int getLayoutDirection() { + return mWrappedDrawable.getLayoutDirection(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mWrappedDrawable.setColorFilter(cf); + } + + @Override + public ColorFilter getColorFilter() { + return mWrappedDrawable.getColorFilter(); + } + + @Override + public void setFilterBitmap(boolean filter) { + mWrappedDrawable.setFilterBitmap(filter); + } + + @Override + public void setXfermode(Xfermode mode) { + mWrappedDrawable.setXfermode(mode); + } + + @Override + public int getOpacity() { + return mWrappedDrawable.getOpacity(); + } + + @Override + public boolean isStateful() { + return mWrappedDrawable.isStateful(); + } + + @Override + public final boolean setState(int[] stateSet) { + return super.setState(stateSet); + } + + @Override + public final int[] getState() { + return super.getState(); + } + + @Override + protected boolean onStateChange(int[] state) { + // Don't override setState(), getState(). + return mWrappedDrawable.setState(state); + } + + @Override + protected boolean onLevelChange(int level) { + // Don't override setLevel(), getLevel(). + return mWrappedDrawable.setLevel(level); + } + + @Override + public final void setBounds(int left, int top, int right, int bottom) { + super.setBounds(left, top, right, bottom); + } + + @Override + public final void setBounds(Rect bounds) { + super.setBounds(bounds); + } + + @Override + protected void onBoundsChange(Rect bounds) { + // Don't override setBounds(), getBounds(). + mWrappedDrawable.setBounds(bounds); + } + + protected void setConstantState(WrapperState wrapperState, Resources res) { + mWrapperState = wrapperState; + + // Load a new drawable from the constant state. + if (wrapperState == null || wrapperState.mWrappedConstantState == null) { + mWrappedDrawable = null; + } else if (res != null) { + mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable(res); + } else { + mWrappedDrawable = wrapperState.mWrappedConstantState.newDrawable(); + } + } + + @Override + public ConstantState getConstantState() { + return mWrapperState; + } + + @Override + public Drawable mutate() { + if (!mMutated) { + mWrappedDrawable = mWrappedDrawable.mutate(); + mMutated = true; + } + return this; + } + + /** + * Sets the wrapped drawable and update the constant state. + * + * @param drawable + * @param res + */ + protected final void setDrawable(Drawable drawable, Resources res) { + if (mWrappedDrawable != null) { + mWrappedDrawable.setCallback(null); + } + + mWrappedDrawable = drawable; + + if (drawable != null) { + drawable.setCallback(this); + + mWrapperState.mWrappedConstantState = drawable.getConstantState(); + } else { + mWrapperState.mWrappedConstantState = null; + } + } + + protected final Drawable getDrawable() { + return mWrappedDrawable; + } + + static abstract class WrapperState extends ConstantState { + ConstantState mWrappedConstantState; + + WrapperState(WrapperState orig) { + if (orig != null) { + mWrappedConstantState = orig.mWrappedConstantState; + } + } + + @Override + public int getChangingConfigurations() { + return mWrappedConstantState.getChangingConfigurations(); + } + } +} diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index 6fbcb53c4592b..f4f545c4d1c87 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -21,15 +21,18 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.SystemClock; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.SparseArray; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -37,23 +40,9 @@ import java.io.IOException; import java.util.ArrayList; /** - * An extension of LayerDrawable that is intended to react to touch hotspots - * and reveal the second layer atop the first. - *

- * It can be defined in an XML file with the <reveal> element. - * Each Drawable in the transition is defined in a nested <item>. - * For more information, see the guide to Drawable Resources. - * - * @attr ref android.R.styleable#LayerDrawableItem_left - * @attr ref android.R.styleable#LayerDrawableItem_top - * @attr ref android.R.styleable#LayerDrawableItem_right - * @attr ref android.R.styleable#LayerDrawableItem_bottom - * @attr ref android.R.styleable#LayerDrawableItem_drawable - * @attr ref android.R.styleable#LayerDrawableItem_id * @hide */ -public class TouchFeedbackDrawable extends Drawable { +public class TouchFeedbackDrawable extends DrawableWrapper { private final Rect mTempRect = new Rect(); private final Rect mPaddingRect = new Rect(); @@ -77,32 +66,43 @@ public class TouchFeedbackDrawable extends Drawable { /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; + /** Paint used to control reveal layer masking. */ + private Paint mMaskingPaint; + /** Target density of the display into which ripples are drawn. */ private float mDensity = 1.0f; /** Whether the animation runnable has been posted. */ private boolean mAnimating; - TouchFeedbackDrawable() { - this(new TouchFeedbackState(null), null); + /** The drawable to use as the mask. */ + private Drawable mMask; + + /* package */TouchFeedbackDrawable() { + this(null, null); } TouchFeedbackDrawable(TouchFeedbackState state, Resources res) { + mState = new TouchFeedbackState(state); + + setConstantState(mState, res); + if (res != null) { mDensity = res.getDisplayMetrics().density; } - - mState = state; } + + private void setConstantState(TouchFeedbackState wrapperState, Resources res) { + super.setConstantState(wrapperState, res); - @Override - public void setColorFilter(ColorFilter cf) { - // Not supported. - } - - @Override - public void setAlpha(int alpha) { - // Not supported. + // Load a new mask drawable from the constant state. + if (wrapperState == null || wrapperState.mMaskState == null) { + mMask = null; + } else if (res != null) { + mMask = wrapperState.mMaskState.newDrawable(res); + } else { + mMask = wrapperState.mMaskState.newDrawable(); + } } @Override @@ -111,10 +111,21 @@ public class TouchFeedbackDrawable extends Drawable { PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; } + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + if (mMask != null) { + mMask.setBounds(bounds); + } + } + @Override protected boolean onStateChange(int[] stateSet) { - final ColorStateList stateList = mState.mColorStateList; - if (stateList != null && mRipplePaint != null) { + super.onStateChange(stateSet); + + if (mRipplePaint != null) { + final ColorStateList stateList = mState.mTint; final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mRipplePaint.getColor(); if (oldColor != newColor) { @@ -132,12 +143,12 @@ public class TouchFeedbackDrawable extends Drawable { */ @Override public boolean isProjected() { - return true; + return mState.mProjected; } @Override public boolean isStateful() { - return mState.mColorStateList != null && mState.mColorStateList.isStateful(); + return super.isStateful() || mState.mTint.isStateful(); } @Override @@ -145,15 +156,77 @@ public class TouchFeedbackDrawable extends Drawable { throws XmlPullParserException, IOException { super.inflate(r, parser, attrs); - final TypedArray a = r.obtainAttributes( - attrs, com.android.internal.R.styleable.ColorDrawable); - mState.mColorStateList = a.getColorStateList( - com.android.internal.R.styleable.ColorDrawable_color); + final TypedArray a = r.obtainAttributes(attrs, R.styleable.TouchFeedbackDrawable); + + mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + mState.mTintMode = Drawable.parseTintMode( + a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); + mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); + + if (mState.mTint == null) { + throw new XmlPullParserException(parser.getPositionDescription() + + ": tag requires a 'tint' attribute"); + } + + Drawable mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); + final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); a.recycle(); + final Drawable dr; + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } else { + int type; + while ((type = parser.next()) == XmlPullParser.TEXT) { + // Find the next non-text element. + } + + if (type == XmlPullParser.START_TAG) { + dr = Drawable.createFromXmlInner(r, parser, attrs); + } else { + dr = null; + } + } + + // If no mask is set, implicitly use the lower drawable. + if (mask == null) { + mask = dr; + } + + // If neither a mask not a bottom layer was specified, assume we're + // projecting onto a parent surface. + mState.mProjected = mask == null && dr == null; + + if (dr != null) { + setDrawable(dr, r); + } else { + // For now at least, we MUST have a wrapped drawable. + setDrawable(new ColorDrawable(Color.TRANSPARENT), r); + } + + setMaskDrawable(mask, r); setTargetDensity(r.getDisplayMetrics()); } + /** + * Sets the wrapped drawable and update the constant state. + * + * @param drawable + * @param res + */ + void setMaskDrawable(Drawable drawable, Resources res) { + mMask = drawable; + + if (drawable != null) { + // Nobody cares if the mask has a callback. + drawable.setCallback(null); + + mState.mMaskState = drawable.getConstantState(); + } else { + mState.mMaskState = null; + } + } + /** * Set the density at which this drawable will be rendered. * @@ -175,6 +248,9 @@ public class TouchFeedbackDrawable extends Drawable { } /** + * TODO: Maybe we should set hotspots for state/id combinations? So touch + * would be state_pressed and the pointer ID. + * * @hide until hotspot APIs are finalized */ @Override @@ -186,19 +262,22 @@ public class TouchFeedbackDrawable extends Drawable { final Ripple ripple = mTouchedRipples.get(id); if (ripple == null) { + final Rect bounds = getBounds(); final Rect padding = mPaddingRect; getPadding(padding); - final Rect bounds = getBounds(); - final Ripple newRipple = new Ripple(bounds, padding, bounds.exactCenterX(), - bounds.exactCenterY(), mDensity); + if (mState.mPinned) { + x = bounds.exactCenterX(); + y = bounds.exactCenterY(); + } + + final Ripple newRipple = new Ripple(bounds, padding, x, y, mDensity); newRipple.enter(); mActiveRipples.add(newRipple); mTouchedRipples.put(id, newRipple); - } else { - // TODO: How do we want to respond to movement? - //ripple.move(x, y); + } else if (!mState.mPinned) { + ripple.move(x, y); } scheduleAnimation(); @@ -254,7 +333,7 @@ public class TouchFeedbackDrawable extends Drawable { if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { - @Override + @Override public void run() { mAnimating = false; scheduleAnimation(); @@ -269,47 +348,77 @@ public class TouchFeedbackDrawable extends Drawable { @Override public void draw(Canvas canvas) { + // The lower layer always draws normally. + super.draw(canvas); + + if (mActiveRipples == null || mActiveRipples.size() == 0) { + // No ripples to draw. + return; + } + final ArrayList activeRipples = mActiveRipples; - if (activeRipples == null || activeRipples.isEmpty()) { - // Nothing to draw, we're done here. - return; - } + final Drawable mask = mMask; + final Rect bounds = mask == null ? null : mask.getBounds(); - final ColorStateList stateList = mState.mColorStateList; - if (stateList == null) { - // No color, we're done here. - return; - } - - final int color = stateList.getColorForState(getState(), Color.TRANSPARENT); - if (color == Color.TRANSPARENT) { - // No color, we're done here. - return; - } - - if (mRipplePaint == null) { - mRipplePaint = new Paint(); - mRipplePaint.setAntiAlias(true); - } - - mRipplePaint.setColor(color); - - final int restoreCount = canvas.save(); - - // Draw ripples directly onto the canvas. + // Draw ripples into a layer that merges using SRC_IN. + boolean hasRipples = false; + int rippleRestoreCount = -1; int n = activeRipples.size(); for (int i = 0; i < n; i++) { final Ripple ripple = activeRipples.get(i); if (!ripple.active()) { + // TODO: Mark and sweep is more efficient. activeRipples.remove(i); i--; n--; } else { - ripple.draw(canvas, mRipplePaint); + // If we're masking the ripple layer, make sure we have a layer first. + if (mask != null && rippleRestoreCount < 0) { + rippleRestoreCount = canvas.saveLayer(bounds.left, bounds.top, + bounds.right, bounds.bottom, getMaskingPaint(SRC_ATOP), 0); + canvas.clipRect(bounds); + } + + hasRipples |= ripple.draw(canvas, getRipplePaint()); } } - canvas.restoreToCount(restoreCount); + // If we have ripples, mask them. + if (mask != null && hasRipples) { + canvas.saveLayer(bounds.left, bounds.top, bounds.right, + bounds.bottom, getMaskingPaint(DST_IN), 0); + mask.draw(canvas); + } + + // Composite the layers if needed: + // 1. Mask DST_IN + // 2. Ripples SRC_ATOP + // 3. Lower n/a + if (rippleRestoreCount > 0) { + canvas.restoreToCount(rippleRestoreCount); + } + } + + private Paint getRipplePaint() { + if (mRipplePaint == null) { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + + final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT); + mRipplePaint.setColor(color); + } + return mRipplePaint; + } + + private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP); + private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN); + + private Paint getMaskingPaint(PorterDuffXfermode mode) { + if (mMaskingPaint == null) { + mMaskingPaint = new Paint(); + } + mMaskingPaint.setXfermode(mode); + return mMaskingPaint; } @Override @@ -322,39 +431,46 @@ public class TouchFeedbackDrawable extends Drawable { final Rect rippleBounds = mTempRect; final ArrayList activeRipples = mActiveRipples; if (activeRipples != null) { - final int N = activeRipples.size(); - for (int i = 0; i < N; i++) { - activeRipples.get(i).getBounds(rippleBounds); - drawingBounds.union(rippleBounds); - } + final int N = activeRipples.size(); + for (int i = 0; i < N; i++) { + activeRipples.get(i).getBounds(rippleBounds); + drawingBounds.union(rippleBounds); + } } dirtyBounds.union(drawingBounds); + dirtyBounds.union(super.getDirtyBounds()); return dirtyBounds; } @Override public ConstantState getConstantState() { + // TODO: Can we just rely on super.getConstantState()? return mState; } - static class TouchFeedbackState extends ConstantState { - ColorStateList mColorStateList; + static class TouchFeedbackState extends WrapperState { + ConstantState mMaskState; + ColorStateList mTint; + Mode mTintMode; + boolean mPinned; + boolean mProjected; public TouchFeedbackState(TouchFeedbackState orig) { + super(orig); + if (orig != null) { - mColorStateList = orig.mColorStateList; + mTint = orig.mTint; + mTintMode = orig.mTintMode; + mMaskState = orig.mMaskState; + mPinned = orig.mPinned; + mProjected = orig.mProjected; } } - @Override - public int getChangingConfigurations() { - return 0; - } - @Override public Drawable newDrawable() { - return newDrawable(null); + return new TouchFeedbackDrawable(this, null); } @Override