summaryrefslogtreecommitdiffstats
path: root/graphics/java
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2014-02-05 17:52:02 -0800
committerAlan Viverette <alanv@google.com>2014-02-05 17:53:33 -0800
commitd5154ec2bc7e7c0bdfd14fc784912d390afe43cc (patch)
tree949021561aff16d06757dcded552aa31bd0bc11a /graphics/java
parent0e29ad02f9ed46f2f2a9de59249969f9d1bdd7f6 (diff)
downloadframeworks_base-d5154ec2bc7e7c0bdfd14fc784912d390afe43cc.zip
frameworks_base-d5154ec2bc7e7c0bdfd14fc784912d390afe43cc.tar.gz
frameworks_base-d5154ec2bc7e7c0bdfd14fc784912d390afe43cc.tar.bz2
Add prototype for borderless touch feedback drawable
Change-Id: I6366855b1fb838aa077bc6bdb62adc2134c51dca
Diffstat (limited to 'graphics/java')
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java25
-rw-r--r--graphics/java/android/graphics/drawable/Ripple.java9
-rw-r--r--graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java371
3 files changed, 405 insertions, 0 deletions
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b8365aa..b81e1c0 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -228,6 +228,20 @@ public abstract class Drawable {
}
/**
+ * 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.
*
@@ -508,6 +522,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
* their state and call setState.
@@ -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")) {
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 543d2a6..cbe20dc 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -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);
+ }
}
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
new file mode 100644
index 0000000..1bfdc4d
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -0,0 +1,371 @@
+/*
+ * 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>&lt;reveal&gt;</code> element.
+ * Each Drawable in the transition is defined in a nested <code>&lt;item&gt;</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);
+ }
+ }
+}