Merge "Quantum ripple for ListView selector"
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6f51418..fdf31fa 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12761,6 +12761,10 @@
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ if (mBackground != null) {
+ mBackground.clearHotspots();
+ }
+
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 301317e..becda67 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -543,7 +543,7 @@
/**
* The last CheckForTap runnable we posted, if any
*/
- private Runnable mPendingCheckForTap;
+ private CheckForTap mPendingCheckForTap;
/**
* The last CheckForKeyLongPress runnable we posted, if any
@@ -2126,7 +2126,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+
mInLayout = true;
+
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
@@ -3185,7 +3187,10 @@
return INVALID_ROW_ID;
}
- final class CheckForTap implements Runnable {
+ private final class CheckForTap implements Runnable {
+ float x;
+ float y;
+
@Override
public void run() {
if (mTouchMode == TOUCH_MODE_DOWN) {
@@ -3205,7 +3210,7 @@
final boolean longClickable = isLongClickable();
if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
+ final Drawable d = mSelector.getCurrent();
if (d != null && d instanceof TransitionDrawable) {
if (longClickable) {
((TransitionDrawable) d).startTransition(longPressTimeout);
@@ -3213,6 +3218,9 @@
((TransitionDrawable) d).resetTransition();
}
}
+ if (d.supportsHotspots()) {
+ d.setHotspot(R.attr.state_pressed, x, y);
+ }
}
if (longClickable) {
@@ -3596,6 +3604,8 @@
mPendingCheckForTap = new CheckForTap();
}
+ mPendingCheckForTap.x = ev.getX();
+ mPendingCheckForTap.y = ev.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
}
}
@@ -3705,6 +3715,9 @@
if (d != null && d instanceof TransitionDrawable) {
((TransitionDrawable) d).resetTransition();
}
+ if (mSelector.supportsHotspots()) {
+ mSelector.setHotspot(R.attr.state_pressed, x, ev.getY());
+ }
}
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
@@ -3716,6 +3729,9 @@
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
+ if (mSelector != null && mSelector.supportsHotspots()) {
+ mSelector.removeHotspot(R.attr.state_pressed);
+ }
if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/list_selector_quantum.xml
new file mode 100644
index 0000000..c007117
--- /dev/null
+++ b/core/res/res/drawable/list_selector_quantum.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
+ android:tint="?attr/colorButtonPressed">
+ <item android:id="@id/mask">
+ <color android:color="@color/white" />
+ </item>
+</touch-feedback>
diff --git a/core/res/res/layout/simple_list_item_2_single_choice.xml b/core/res/res/layout/simple_list_item_2_single_choice.xml
index 65c5856..940c6b8 100644
--- a/core/res/res/layout/simple_list_item_2_single_choice.xml
+++ b/core/res/res/layout/simple_list_item_2_single_choice.xml
@@ -21,7 +21,8 @@
android:gravity="center_vertical"
android:paddingStart="16dip"
android:paddingEnd="12dip"
- android:minHeight="?android:attr/listPreferredItemHeightSmall">
+ android:minHeight="?attr/listPreferredItemHeightSmall"
+ android:background="@color/transparent">
<LinearLayout
android:layout_width="wrap_content"
@@ -33,8 +34,8 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceListItem"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="?attr/textAppearanceListItem"
+ android:textColor="?attr/textColorAlertDialogListItem"
android:gravity="center_vertical|start"
android:singleLine="true"
android:ellipsize="marquee" />
@@ -42,8 +43,8 @@
<TextView android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceListItemSecondary"
- android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ android:textColor="?attr/textColorAlertDialogListItem"
android:gravity="center_vertical|start"
android:singleLine="true"
android:ellipsize="marquee" />
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index d75e875..57f2443 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -480,7 +480,7 @@
<style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/>
<style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
</style>
@@ -654,7 +654,7 @@
<style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown">
<item name="background">@drawable/spinner_background_quantum</item>
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -713,7 +713,7 @@
<style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/>
<style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow">
- <item name="dropDownSelector">@drawable/list_selector_holo_dark</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -851,7 +851,7 @@
<style name="Widget.Quantum.Light.AbsListView" parent="Widget.Quantum.AbsListView"/>
<style name="Widget.Quantum.Light.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
</style>
@@ -938,7 +938,7 @@
<style name="Widget.Quantum.Light.Spinner" parent="Widget.Quantum.Spinner">
<item name="background">@drawable/spinner_background_quantum</item>
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -962,7 +962,7 @@
<style name="Widget.Quantum.Light.QuickContactBadgeSmall.WindowLarge" parent="Widget.Quantum.QuickContactBadgeSmall.WindowLarge"/>
<style name="Widget.Quantum.Light.ListPopupWindow" parent="Widget.ListPopupWindow">
- <item name="dropDownSelector">@drawable/list_selector_holo_light</item>
+ <item name="dropDownSelector">@drawable/list_selector_quantum</item>
<item name="popupBackground">?attr/colorBackground</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 5842380..9f76eae 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -125,7 +125,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_holo_dark</item>
+ <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
@@ -468,7 +468,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_holo_light</item>
+ <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 3b9d513..824e108 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -48,6 +48,7 @@
public class TouchFeedbackDrawable extends LayerDrawable {
private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName();
private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
+ private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
@@ -105,6 +106,26 @@
protected boolean onStateChange(int[] stateSet) {
super.onStateChange(stateSet);
+ // TODO: Implicitly tie states to ripple IDs. For now, just clear
+ // focused and pressed if they aren't in the state set.
+ boolean hasFocused = false;
+ boolean hasPressed = false;
+ for (int i = 0; i < stateSet.length; i++) {
+ if (stateSet[i] == R.attr.state_pressed) {
+ hasPressed = true;
+ } else if (stateSet[i] == R.attr.state_focused) {
+ hasFocused = true;
+ }
+ }
+
+ if (!hasPressed) {
+ removeHotspot(R.attr.state_pressed);
+ }
+
+ if (!hasFocused) {
+ removeHotspot(R.attr.state_focused);
+ }
+
if (mRipplePaint != null && mState.mTint != null) {
final ColorStateList stateList = mState.mTint;
final int newColor = stateList.getColorForState(stateSet, 0);
@@ -122,7 +143,7 @@
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
-
+
if (!mOverrideBounds) {
mHotspotBounds.set(bounds);
}
@@ -217,6 +238,27 @@
super.inflate(r, parser, attrs, theme);
setTargetDensity(r.getDisplayMetrics());
+
+ // Find the mask
+ final int N = getNumberOfLayers();
+ for (int i = 0; i < N; i++) {
+ if (mLayerState.mChildren[i].mId == R.id.mask) {
+ mState.mMask = mLayerState.mChildren[i].mDrawable;
+ }
+ }
+ }
+
+ @Override
+ public boolean setDrawableByLayerId(int id, Drawable drawable) {
+ if (super.setDrawableByLayerId(id, drawable)) {
+ if (id == R.id.mask) {
+ mState.mMask = drawable;
+ }
+
+ return true;
+ }
+
+ return false;
}
/**
@@ -310,7 +352,7 @@
mTouchedRipples = new SparseArray<Ripple>();
mActiveRipples = new Ripple[MAX_RIPPLES];
}
-
+
if (mActiveRipplesCount >= MAX_RIPPLES) {
Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
return;
@@ -415,99 +457,139 @@
@Override
public void draw(Canvas canvas) {
- final boolean projected = getNumberOfLayers() == 0;
+ final int N = mLayerState.mNum;
+ final Rect bounds = getBounds();
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final boolean maskOnly = mState.mMask != null && N == 1;
+
+ int restoreToCount = drawRippleLayer(canvas, bounds, maskOnly);
+
+ if (restoreToCount >= 0) {
+ // We have a ripple layer that contains ripples. If we also have an
+ // explicit mask drawable, apply it now using DST_IN blending.
+ if (mState.mMask != null) {
+ canvas.saveLayer(bounds.left, bounds.top, bounds.right,
+ bounds.bottom, getMaskingPaint(DST_IN));
+ mState.mMask.draw(canvas);
+ canvas.restoreToCount(restoreToCount);
+ restoreToCount = -1;
+ }
+
+ // If there's more content, we need an extra masking layer to merge
+ // the ripples over the content.
+ if (!maskOnly) {
+ final PorterDuffXfermode xfermode = mState.getTintXfermodeInverse();
+ final int count = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, getMaskingPaint(xfermode));
+ if (restoreToCount < 0) {
+ restoreToCount = count;
+ }
+ }
+ }
+
+ // Draw everything except the mask.
+ for (int i = 0; i < N; i++) {
+ if (array[i].mId != R.id.mask) {
+ array[i].mDrawable.draw(canvas);
+ }
+ }
+
+ // Composite the layers if needed.
+ if (restoreToCount >= 0) {
+ canvas.restoreToCount(restoreToCount);
+ }
+ }
+
+ private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) {
final Ripple[] activeRipples = mActiveRipples;
final int ripplesCount = mActiveRipplesCount;
- final Rect bounds = getBounds();
+
+ Paint ripplePaint = null;
+ boolean drewRipples = false;
+ int restoreToCount = -1;
+ int activeRipplesCount = 0;
// Draw ripples.
- boolean drewRipples = false;
- int rippleRestoreCount = -1;
- int activeRipplesCount = 0;
for (int i = 0; i < ripplesCount; i++) {
final Ripple ripple = activeRipples[i];
final RippleAnimator animator = ripple.animate();
animator.update();
+
+ // Mark and skip inactive ripples.
if (!animator.isRunning()) {
activeRipples[i] = null;
- } else {
- // If we're masking the ripple layer, make sure we have a layer
- // first. This will merge SRC_OVER (directly) onto the canvas.
- if (!projected && rippleRestoreCount < 0) {
- rippleRestoreCount = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, null);
+ continue;
+ }
+
+ // If we're masking the ripple layer, make sure we have a layer
+ // first. This will merge SRC_OVER (directly) onto the canvas.
+ if (restoreToCount < 0) {
+ // Separate the ripple color and alpha channel. The alpha will be
+ // applied when we merge the ripples down to the canvas.
+ final int rippleColor;
+ if (mState.mTint != null) {
+ rippleColor = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+ } else {
+ rippleColor = Color.TRANSPARENT;
+ }
+ final int rippleAlpha = Color.alpha(rippleColor);
+
+ // If we're projecting or we only have a mask, we want to treat the
+ // underlying canvas as our content and merge the ripple layer down
+ // using the tint xfermode.
+ final boolean projected = isProjected();
+ final PorterDuffXfermode xfermode;
+ if (projected || maskOnly) {
+ xfermode = mState.getTintXfermode();
+ } else {
+ xfermode = SRC_OVER;
}
- drewRipples |= ripple.draw(canvas, getRipplePaint());
-
- activeRipples[activeRipplesCount] = activeRipples[i];
- activeRipplesCount++;
+ final Paint layerPaint = getMaskingPaint(xfermode);
+ layerPaint.setAlpha(rippleAlpha);
+ final Rect layerBounds = projected ? getDirtyBounds() : bounds;
+ restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top,
+ layerBounds.right, layerBounds.bottom, layerPaint);
+ layerPaint.setAlpha(255);
}
+
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ }
+
+ drewRipples |= ripple.draw(canvas, mRipplePaint);
+
+ activeRipples[activeRipplesCount] = activeRipples[i];
+ activeRipplesCount++;
}
+
mActiveRipplesCount = activeRipplesCount;
- // TODO: Use the masking layer first, if there is one.
-
- // If we have ripples and content, we need a masking layer. This will
- // merge DST_ATOP onto (effectively under) the ripple layer.
- if (drewRipples && !projected && rippleRestoreCount >= 0) {
- final PorterDuffXfermode xfermode = mState.getTintXfermode();
- canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(xfermode));
+ // If we created a layer with no content, merge it immediately.
+ if (restoreToCount >= 0 && !drewRipples) {
+ canvas.restoreToCount(restoreToCount);
+ restoreToCount = -1;
}
- Drawable mask = null;
- final ChildDrawable[] array = mLayerState.mChildren;
- final int N = mLayerState.mNum;
- for (int i = 0; i < N; i++) {
- if (array[i].mId != R.id.mask) {
- array[i].mDrawable.draw(canvas);
- } else {
- mask = array[i].mDrawable;
- }
- }
-
- // If we have ripples, mask them.
- if (mask != null && drewRipples) {
- // TODO: This will also mask the lower layer, which is bad.
- canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(DST_IN));
- mask.draw(canvas);
- }
-
- // Composite the layers if needed.
- if (rippleRestoreCount >= 0) {
- canvas.restoreToCount(rippleRestoreCount);
- }
+ return restoreToCount;
}
- private Paint getRipplePaint() {
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
-
- if (mState.mTint != null) {
- final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
- mRipplePaint.setColor(color);
- }
- }
- return mRipplePaint;
- }
-
- private Paint getMaskingPaint(PorterDuffXfermode mode) {
+ private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
if (mMaskingPaint == null) {
mMaskingPaint = new Paint();
}
- mMaskingPaint.setXfermode(mode);
+ mMaskingPaint.setXfermode(xfermode);
return mMaskingPaint;
}
@Override
public Rect getDirtyBounds() {
- final Rect dirtyBounds = mDirtyBounds;
final Rect drawingBounds = mDrawingBounds;
+ final Rect dirtyBounds = mDirtyBounds;
dirtyBounds.set(drawingBounds);
drawingBounds.setEmpty();
+
final Rect rippleBounds = mTempRect;
final Ripple[] activeRipples = mActiveRipples;
final int N = mActiveRipplesCount;
@@ -530,6 +612,8 @@
int[] mTouchThemeAttrs;
ColorStateList mTint;
PorterDuffXfermode mTintXfermode;
+ PorterDuffXfermode mTintXfermodeInverse;
+ Drawable mMask;
boolean mPinned;
public TouchFeedbackState(
@@ -540,19 +624,26 @@
mTouchThemeAttrs = orig.mTouchThemeAttrs;
mTint = orig.mTint;
mTintXfermode = orig.mTintXfermode;
+ mTintXfermodeInverse = orig.mTintXfermodeInverse;
mPinned = orig.mPinned;
+ mMask = orig.mMask;
}
}
-
+
public void setTintMode(Mode mode) {
final Mode invertedMode = TouchFeedbackState.invertPorterDuffMode(mode);
- mTintXfermode = new PorterDuffXfermode(invertedMode);
+ mTintXfermodeInverse = new PorterDuffXfermode(invertedMode);
+ mTintXfermode = new PorterDuffXfermode(mode);
}
-
+
public PorterDuffXfermode getTintXfermode() {
return mTintXfermode;
}
+ public PorterDuffXfermode getTintXfermodeInverse() {
+ return mTintXfermodeInverse;
+ }
+
@Override
public boolean canApplyTheme() {
return mTouchThemeAttrs != null || super.canApplyTheme();