Loading core/java/android/view/View.java +2 −2 Original line number Diff line number Diff line Loading @@ -14983,7 +14983,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param displayList Valid display list for the background drawable */ private void setBackgroundDisplayListProperties(DisplayList displayList) { displayList.setProjectBackwards((mPrivateFlags3 & PFLAG3_PROJECT_BACKGROUND) != 0); displayList.setTranslationX(mScrollX); displayList.setTranslationY(mScrollY); } Loading Loading @@ -15014,6 +15013,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Set up drawable properties that are view-independent. displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom); displayList.setProjectBackwards(drawable.isProjected()); displayList.setClipToBounds(false); return displayList; } Loading Loading @@ -15367,7 +15367,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mBackgroundDisplayList.clear(); } final Rect dirty = drawable.getBounds(); final Rect dirty = drawable.getDirtyBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY; graphics/java/android/graphics/drawable/Drawable.java +25 −0 Original line number Diff line number Diff line Loading @@ -227,6 +227,20 @@ public abstract class Drawable { return mBounds; } /** * Return the drawable's dirty bounds Rect. Note: for efficiency, the * returned object may be the same object stored in the drawable (though * this is not guaranteed). * <p> * By default, this returns the full drawable bounds. Custom drawables may * override this method to perform more precise invalidation. * * @hide */ public Rect getDirtyBounds() { return getBounds(); } /** * Set a mask of the configuration parameters for which this drawable * may change, requiring that it be re-created. Loading Loading @@ -507,6 +521,15 @@ public abstract class Drawable { */ public void clearHotspots() {} /** * Whether this drawable requests projection. * * @hide */ public boolean isProjected() { return false; } /** * Indicates whether this view will change its appearance based on state. * Clients can use this to determine whether it is necessary to calculate Loading Loading @@ -962,6 +985,8 @@ public abstract class Drawable { drawable = new TransitionDrawable(); } else if (name.equals("reveal")) { drawable = new RevealDrawable(); } else if (name.equals("touch-feedback")) { drawable = new TouchFeedbackDrawable(); } else if (name.equals("color")) { drawable = new ColorDrawable(); } else if (name.equals("shape")) { Loading graphics/java/android/graphics/drawable/Ripple.java +9 −0 Original line number Diff line number Diff line Loading @@ -251,4 +251,13 @@ class Ripple { return false; } public void getBounds(Rect bounds) { final int x = (int) mX; final int y = (int) mY; final int dX = Math.max(x, mBounds.right - x); final int dY = Math.max(x, mBounds.bottom - y); final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius); } } graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java 0 → 100644 +371 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 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.ColorStateList; 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.Rect; import android.graphics.Xfermode; import android.os.SystemClock; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.SparseArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; 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. * <p> * It can be defined in an XML file with the <code><reveal></code> element. * Each Drawable in the transition is defined in a nested <code><item></code>. * For more information, see the guide to <a href="{@docRoot} * guide/topics/resources/drawable-resource.html">Drawable Resources</a>. * * @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 { private final Rect mTempRect = new Rect(); private final Rect mPaddingRect = new Rect(); /** Current drawing bounds, used to compute dirty region. */ private final Rect mDrawingBounds = new Rect(); /** Current dirty bounds, union of current and previous drawing bounds. */ private final Rect mDirtyBounds = new Rect(); private final TouchFeedbackState mState; /** Lazily-created map of touch hotspot IDs to ripples. */ private SparseArray<Ripple> mTouchedRipples; /** Lazily-created list of actively animating ripples. */ private ArrayList<Ripple> mActiveRipples; /** Lazily-created runnable for scheduling invalidation. */ private Runnable mAnimationRunnable; /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; /** Target density of the display into which ripples are drawn. */ private int mTargetDensity; /** Whether the animation runnable has been posted. */ private boolean mAnimating; TouchFeedbackDrawable() { this(new TouchFeedbackState(null), null); } TouchFeedbackDrawable(TouchFeedbackState state, Resources res) { if (res != null) { mTargetDensity = res.getDisplayMetrics().densityDpi; } else if (state != null) { mTargetDensity = state.mTargetDensity; } mState = state; } @Override public void setColorFilter(ColorFilter cf) { // Not supported. } @Override public void setAlpha(int alpha) { // Not supported. } @Override public int getOpacity() { return mActiveRipples != null && !mActiveRipples.isEmpty() ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; } @Override public boolean onStateChange(int[] stateSet) { final ColorStateList stateList = mState.mColorStateList; if (stateList != null && mRipplePaint != null) { final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mRipplePaint.getColor(); if (oldColor != newColor) { mRipplePaint.setColor(newColor); invalidateSelf(); return true; } } return false; } /** * @hide */ @Override public boolean isProjected() { return true; } @Override public boolean isStateful() { return mState.mColorStateList != null && mState.mColorStateList.isStateful(); } /** * Set the density at which this drawable will be rendered. * * @param density The density scale for this drawable. */ public void setTargetDensity(int density) { if (mTargetDensity != density) { mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; // TODO: Update density in ripples? invalidateSelf(); } } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 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); a.recycle(); mState.mXfermode = null; //new PorterDuffXfermode(Mode.SRC_ATOP); mState.mProjected = false; } /** * @hide until hotspot APIs are finalized */ @Override public boolean supportsHotspots() { return true; } /** * @hide until hotspot APIs are finalized */ @Override public void setHotspot(int id, float x, float y) { if (mTouchedRipples == null) { mTouchedRipples = new SparseArray<Ripple>(); mActiveRipples = new ArrayList<Ripple>(); } final Ripple ripple = mTouchedRipples.get(id); if (ripple == null) { final Rect padding = mPaddingRect; getPadding(padding); final Rect bounds = getBounds(); final Ripple newRipple = new Ripple(bounds, padding, bounds.exactCenterX(), bounds.exactCenterY(), mTargetDensity); newRipple.enter(); mActiveRipples.add(newRipple); mTouchedRipples.put(id, newRipple); } else { //ripple.move(x, y); } scheduleAnimation(); } /** * @hide until hotspot APIs are finalized */ @Override public void removeHotspot(int id) { if (mTouchedRipples == null) { return; } final Ripple ripple = mTouchedRipples.get(id); if (ripple != null) { ripple.exit(); mTouchedRipples.remove(id); scheduleAnimation(); } } /** * @hide until hotspot APIs are finalized */ @Override public void clearHotspots() { if (mTouchedRipples == null) { return; } final int n = mTouchedRipples.size(); for (int i = 0; i < n; i++) { final Ripple ripple = mTouchedRipples.valueAt(i); ripple.exit(); } if (n > 0) { mTouchedRipples.clear(); scheduleAnimation(); } } /** * Schedules the next animation, if necessary. */ private void scheduleAnimation() { if (mActiveRipples == null || mActiveRipples.isEmpty()) { mAnimating = false; } else if (!mAnimating) { mAnimating = true; if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { @Override public void run() { mAnimating = false; scheduleAnimation(); invalidateSelf(); } }; } scheduleSelf(mAnimationRunnable, SystemClock.uptimeMillis() + 1000 / 60); } } @Override public void draw(Canvas canvas) { final ArrayList<Ripple> activeRipples = mActiveRipples; if (activeRipples == null || activeRipples.isEmpty()) { // Nothing to draw, we're done here. return; } 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.setXfermode(mState.mXfermode); mRipplePaint.setColor(color); final int restoreCount = canvas.save(); // Draw ripples directly onto the canvas. int n = activeRipples.size(); for (int i = 0; i < n; i++) { final Ripple ripple = activeRipples.get(i); if (!ripple.active()) { activeRipples.remove(i); i--; n--; } else { ripple.draw(canvas, mRipplePaint); } } canvas.restoreToCount(restoreCount); } @Override public Rect getDirtyBounds() { final Rect dirtyBounds = mDirtyBounds; final Rect drawingBounds = mDrawingBounds; dirtyBounds.set(drawingBounds); drawingBounds.setEmpty(); final Rect rippleBounds = mTempRect; final ArrayList<Ripple> 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); } } dirtyBounds.union(drawingBounds); return dirtyBounds; } private static class TouchFeedbackState extends ConstantState { private ColorStateList mColorStateList; private Xfermode mXfermode; private int mTargetDensity; private boolean mProjected; public TouchFeedbackState(TouchFeedbackState orig) { if (orig != null) { mColorStateList = orig.mColorStateList; mXfermode = orig.mXfermode; mTargetDensity = orig.mTargetDensity; mProjected = orig.mProjected; } } @Override public int getChangingConfigurations() { return 0; } @Override public Drawable newDrawable() { return newDrawable(null); } @Override public Drawable newDrawable(Resources res) { return new TouchFeedbackDrawable(this, res); } } } Loading
core/java/android/view/View.java +2 −2 Original line number Diff line number Diff line Loading @@ -14983,7 +14983,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param displayList Valid display list for the background drawable */ private void setBackgroundDisplayListProperties(DisplayList displayList) { displayList.setProjectBackwards((mPrivateFlags3 & PFLAG3_PROJECT_BACKGROUND) != 0); displayList.setTranslationX(mScrollX); displayList.setTranslationY(mScrollY); } Loading Loading @@ -15014,6 +15013,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Set up drawable properties that are view-independent. displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom); displayList.setProjectBackwards(drawable.isProjected()); displayList.setClipToBounds(false); return displayList; } Loading Loading @@ -15367,7 +15367,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mBackgroundDisplayList.clear(); } final Rect dirty = drawable.getBounds(); final Rect dirty = drawable.getDirtyBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY;
graphics/java/android/graphics/drawable/Drawable.java +25 −0 Original line number Diff line number Diff line Loading @@ -227,6 +227,20 @@ public abstract class Drawable { return mBounds; } /** * Return the drawable's dirty bounds Rect. Note: for efficiency, the * returned object may be the same object stored in the drawable (though * this is not guaranteed). * <p> * By default, this returns the full drawable bounds. Custom drawables may * override this method to perform more precise invalidation. * * @hide */ public Rect getDirtyBounds() { return getBounds(); } /** * Set a mask of the configuration parameters for which this drawable * may change, requiring that it be re-created. Loading Loading @@ -507,6 +521,15 @@ public abstract class Drawable { */ public void clearHotspots() {} /** * Whether this drawable requests projection. * * @hide */ public boolean isProjected() { return false; } /** * Indicates whether this view will change its appearance based on state. * Clients can use this to determine whether it is necessary to calculate Loading Loading @@ -962,6 +985,8 @@ public abstract class Drawable { drawable = new TransitionDrawable(); } else if (name.equals("reveal")) { drawable = new RevealDrawable(); } else if (name.equals("touch-feedback")) { drawable = new TouchFeedbackDrawable(); } else if (name.equals("color")) { drawable = new ColorDrawable(); } else if (name.equals("shape")) { Loading
graphics/java/android/graphics/drawable/Ripple.java +9 −0 Original line number Diff line number Diff line Loading @@ -251,4 +251,13 @@ class Ripple { return false; } public void getBounds(Rect bounds) { final int x = (int) mX; final int y = (int) mY; final int dX = Math.max(x, mBounds.right - x); final int dY = Math.max(x, mBounds.bottom - y); final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY)); bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius); } }
graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java 0 → 100644 +371 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 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.ColorStateList; 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.Rect; import android.graphics.Xfermode; import android.os.SystemClock; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.SparseArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; 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. * <p> * It can be defined in an XML file with the <code><reveal></code> element. * Each Drawable in the transition is defined in a nested <code><item></code>. * For more information, see the guide to <a href="{@docRoot} * guide/topics/resources/drawable-resource.html">Drawable Resources</a>. * * @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 { private final Rect mTempRect = new Rect(); private final Rect mPaddingRect = new Rect(); /** Current drawing bounds, used to compute dirty region. */ private final Rect mDrawingBounds = new Rect(); /** Current dirty bounds, union of current and previous drawing bounds. */ private final Rect mDirtyBounds = new Rect(); private final TouchFeedbackState mState; /** Lazily-created map of touch hotspot IDs to ripples. */ private SparseArray<Ripple> mTouchedRipples; /** Lazily-created list of actively animating ripples. */ private ArrayList<Ripple> mActiveRipples; /** Lazily-created runnable for scheduling invalidation. */ private Runnable mAnimationRunnable; /** Paint used to control appearance of ripples. */ private Paint mRipplePaint; /** Target density of the display into which ripples are drawn. */ private int mTargetDensity; /** Whether the animation runnable has been posted. */ private boolean mAnimating; TouchFeedbackDrawable() { this(new TouchFeedbackState(null), null); } TouchFeedbackDrawable(TouchFeedbackState state, Resources res) { if (res != null) { mTargetDensity = res.getDisplayMetrics().densityDpi; } else if (state != null) { mTargetDensity = state.mTargetDensity; } mState = state; } @Override public void setColorFilter(ColorFilter cf) { // Not supported. } @Override public void setAlpha(int alpha) { // Not supported. } @Override public int getOpacity() { return mActiveRipples != null && !mActiveRipples.isEmpty() ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; } @Override public boolean onStateChange(int[] stateSet) { final ColorStateList stateList = mState.mColorStateList; if (stateList != null && mRipplePaint != null) { final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mRipplePaint.getColor(); if (oldColor != newColor) { mRipplePaint.setColor(newColor); invalidateSelf(); return true; } } return false; } /** * @hide */ @Override public boolean isProjected() { return true; } @Override public boolean isStateful() { return mState.mColorStateList != null && mState.mColorStateList.isStateful(); } /** * Set the density at which this drawable will be rendered. * * @param density The density scale for this drawable. */ public void setTargetDensity(int density) { if (mTargetDensity != density) { mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; // TODO: Update density in ripples? invalidateSelf(); } } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 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); a.recycle(); mState.mXfermode = null; //new PorterDuffXfermode(Mode.SRC_ATOP); mState.mProjected = false; } /** * @hide until hotspot APIs are finalized */ @Override public boolean supportsHotspots() { return true; } /** * @hide until hotspot APIs are finalized */ @Override public void setHotspot(int id, float x, float y) { if (mTouchedRipples == null) { mTouchedRipples = new SparseArray<Ripple>(); mActiveRipples = new ArrayList<Ripple>(); } final Ripple ripple = mTouchedRipples.get(id); if (ripple == null) { final Rect padding = mPaddingRect; getPadding(padding); final Rect bounds = getBounds(); final Ripple newRipple = new Ripple(bounds, padding, bounds.exactCenterX(), bounds.exactCenterY(), mTargetDensity); newRipple.enter(); mActiveRipples.add(newRipple); mTouchedRipples.put(id, newRipple); } else { //ripple.move(x, y); } scheduleAnimation(); } /** * @hide until hotspot APIs are finalized */ @Override public void removeHotspot(int id) { if (mTouchedRipples == null) { return; } final Ripple ripple = mTouchedRipples.get(id); if (ripple != null) { ripple.exit(); mTouchedRipples.remove(id); scheduleAnimation(); } } /** * @hide until hotspot APIs are finalized */ @Override public void clearHotspots() { if (mTouchedRipples == null) { return; } final int n = mTouchedRipples.size(); for (int i = 0; i < n; i++) { final Ripple ripple = mTouchedRipples.valueAt(i); ripple.exit(); } if (n > 0) { mTouchedRipples.clear(); scheduleAnimation(); } } /** * Schedules the next animation, if necessary. */ private void scheduleAnimation() { if (mActiveRipples == null || mActiveRipples.isEmpty()) { mAnimating = false; } else if (!mAnimating) { mAnimating = true; if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { @Override public void run() { mAnimating = false; scheduleAnimation(); invalidateSelf(); } }; } scheduleSelf(mAnimationRunnable, SystemClock.uptimeMillis() + 1000 / 60); } } @Override public void draw(Canvas canvas) { final ArrayList<Ripple> activeRipples = mActiveRipples; if (activeRipples == null || activeRipples.isEmpty()) { // Nothing to draw, we're done here. return; } 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.setXfermode(mState.mXfermode); mRipplePaint.setColor(color); final int restoreCount = canvas.save(); // Draw ripples directly onto the canvas. int n = activeRipples.size(); for (int i = 0; i < n; i++) { final Ripple ripple = activeRipples.get(i); if (!ripple.active()) { activeRipples.remove(i); i--; n--; } else { ripple.draw(canvas, mRipplePaint); } } canvas.restoreToCount(restoreCount); } @Override public Rect getDirtyBounds() { final Rect dirtyBounds = mDirtyBounds; final Rect drawingBounds = mDrawingBounds; dirtyBounds.set(drawingBounds); drawingBounds.setEmpty(); final Rect rippleBounds = mTempRect; final ArrayList<Ripple> 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); } } dirtyBounds.union(drawingBounds); return dirtyBounds; } private static class TouchFeedbackState extends ConstantState { private ColorStateList mColorStateList; private Xfermode mXfermode; private int mTargetDensity; private boolean mProjected; public TouchFeedbackState(TouchFeedbackState orig) { if (orig != null) { mColorStateList = orig.mColorStateList; mXfermode = orig.mXfermode; mTargetDensity = orig.mTargetDensity; mProjected = orig.mProjected; } } @Override public int getChangingConfigurations() { return 0; } @Override public Drawable newDrawable() { return newDrawable(null); } @Override public Drawable newDrawable(Resources res) { return new TouchFeedbackDrawable(this, res); } } }