Merge "Add new touch effect to player" into rvc-dev

This commit is contained in:
Lucas Dupin
2020-05-06 16:37:19 +00:00
committed by Android (Google) Code Review
4 changed files with 215 additions and 11 deletions

View File

@@ -14,13 +14,9 @@
~ See the License for the specific language governing permissions and ~ See the License for the specific language governing permissions and
~ limitations under the License ~ limitations under the License
--> -->
<shape xmlns:android="http://schemas.android.com/apk/res/android" <com.android.systemui.media.IlluminationDrawable
android:shape="rectangle"> xmlns:systemui="http://schemas.android.com/apk/res-auto"
<solid android:color="?android:attr/colorBackgroundFloating" /> systemui:rippleMinSize="30dp"
<corners systemui:rippleMaxSize="135dp"
android:bottomLeftRadius="@dimen/qs_media_corner_radius" systemui:highlight="15"
android:topLeftRadius="@dimen/qs_media_corner_radius" systemui:cornerRadius="@dimen/qs_media_corner_radius" />
android:bottomRightRadius="@dimen/qs_media_corner_radius"
android:topRightRadius="@dimen/qs_media_corner_radius"
/>
</shape>

View File

@@ -154,5 +154,12 @@
<declare-styleable name="CaptionsToggleImageButton"> <declare-styleable name="CaptionsToggleImageButton">
<attr name="optedOut" format="boolean" /> <attr name="optedOut" format="boolean" />
</declare-styleable> </declare-styleable>
<declare-styleable name="IlluminationDrawable">
<attr name="highlight" format="integer" />
<attr name="cornerRadius" format="dimension" />
<attr name="rippleMinSize" format="dimension" />
<attr name="rippleMaxSize" format="dimension" />
</declare-styleable>
</resources> </resources>

View File

@@ -0,0 +1,190 @@
package com.android.systemui.media
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RadialGradient
import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.MathUtils
import android.util.MathUtils.lerp
import androidx.annotation.Keep
import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.ColorUtils.blendARGB
import com.android.systemui.Interpolators
import com.android.systemui.R
import org.xmlpull.v1.XmlPullParser
private const val BACKGROUND_ANIM_DURATION = 370L
private const val RIPPLE_ANIM_DURATION = 800L
private val GRADIENT_STOPS = floatArrayOf(0.2f, 1f)
private data class RippleData(
var x: Float,
var y: Float,
var alpha: Float,
var progress: Float,
var minSize: Float,
var maxSize: Float,
var highlight: Float
)
/**
* Drawable that can draw an animated gradient when tapped.
*/
@Keep
class IlluminationDrawable : Drawable() {
private var cornerRadius = 0f
private var highlightColor = Color.TRANSPARENT
private val rippleData = RippleData(0f, 0f, 0f, 0f, 0f, 0f, 0f)
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
private var paint = Paint()
var backgroundColor = Color.TRANSPARENT
set(value) {
if (value == field) {
return
}
field = value
animateBackground()
}
private var rippleAnimation: AnimatorSet? = null
private var backgroundAnimation: ValueAnimator? = null
/**
* Draw background and gradient.
*/
override fun draw(canvas: Canvas) {
paint.shader = if (rippleData.progress > 0) {
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
val centerColor = blendARGB(paint.color, highlightColor, rippleData.alpha)
RadialGradient(rippleData.x, rippleData.y, radius, intArrayOf(centerColor, paint.color),
GRADIENT_STOPS, Shader.TileMode.CLAMP)
} else {
null
}
canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
cornerRadius, cornerRadius, paint)
}
override fun getOpacity(): Int {
return PixelFormat.TRANSPARENT
}
override fun inflate(
r: Resources,
parser: XmlPullParser,
attrs: AttributeSet,
theme: Resources.Theme?
) {
val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
a.recycle()
}
override fun setColorFilter(p0: ColorFilter?) {
throw UnsupportedOperationException("Color filters are not supported")
}
override fun setAlpha(value: Int) {
throw UnsupportedOperationException("Alpha is not supported")
}
/**
* Cross fade background.
* @see setTintList
* @see backgroundColor
*/
private fun animateBackground() {
ColorUtils.colorToHSL(backgroundColor, tmpHsl)
val L = tmpHsl[2]
tmpHsl[2] = MathUtils.constrain(if (L < 1f - rippleData.highlight) {
L + rippleData.highlight
} else {
L - rippleData.highlight
}, 0f, 1f)
val initialBackground = paint.color
val initialHighlight = highlightColor
val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
backgroundAnimation?.cancel()
backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
duration = BACKGROUND_ANIM_DURATION
interpolator = Interpolators.FAST_OUT_LINEAR_IN
addUpdateListener {
val progress = it.animatedValue as Float
paint.color = blendARGB(initialBackground, backgroundColor, progress)
highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
invalidateSelf()
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
backgroundAnimation = null
}
})
start()
}
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
backgroundColor = tint!!.defaultColor
}
/**
* Draws an animated ripple that expands from {@param x} and {@param y} fading away.
*
* @param x X position of gradient centroid.
* @param y Y position of gradient centroid.
*/
fun illuminate(x: Int, y: Int) {
rippleData.x = x.toFloat()
rippleData.y = y.toFloat()
rippleData.alpha = 1f
rippleData.progress = 0f
invalidateSelf()
rippleAnimation?.cancel()
rippleAnimation = AnimatorSet().apply {
playTogether(ValueAnimator.ofFloat(1f, 0f).apply {
startDelay = 133
duration = RIPPLE_ANIM_DURATION - startDelay
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
addUpdateListener {
rippleData.alpha = it.animatedValue as Float
invalidateSelf()
}
}, ValueAnimator.ofFloat(0f, 1f).apply {
duration = RIPPLE_ANIM_DURATION
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
addUpdateListener {
rippleData.progress = it.animatedValue as Float
invalidateSelf()
}
})
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
rippleData.progress = 0f
rippleAnimation = null
invalidateSelf()
}
})
start()
}
}
}

View File

@@ -22,6 +22,7 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.media.MediaDescription; import android.media.MediaDescription;
@@ -38,6 +39,7 @@ import android.widget.TextView;
import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.media.IlluminationDrawable;
import com.android.systemui.media.MediaControlPanel; import com.android.systemui.media.MediaControlPanel;
import com.android.systemui.media.SeekBarObserver; import com.android.systemui.media.SeekBarObserver;
import com.android.systemui.media.SeekBarViewModel; import com.android.systemui.media.SeekBarViewModel;
@@ -173,7 +175,7 @@ public class QSMediaPlayer extends MediaControlPanel {
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
int i = 0; int i = 0;
for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) { for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]); final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]); ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
if (thatBtn == null || thatBtn.getDrawable() == null if (thatBtn == null || thatBtn.getDrawable() == null
|| thatBtn.getVisibility() != View.VISIBLE) { || thatBtn.getVisibility() != View.VISIBLE) {
@@ -187,6 +189,15 @@ public class QSMediaPlayer extends MediaControlPanel {
thisBtn.setOnClickListener(v -> { thisBtn.setOnClickListener(v -> {
Log.d(TAG, "clicking on other button"); Log.d(TAG, "clicking on other button");
thatBtn.performClick(); thatBtn.performClick();
if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
Rect mediaRect = new Rect();
Rect buttonRect = new Rect();
mMediaNotifView.getGlobalVisibleRect(mediaRect);
thisBtn.getGlobalVisibleRect(buttonRect);
((IlluminationDrawable) mMediaNotifView.getBackground()).illuminate(
buttonRect.centerX() - mediaRect.left,
buttonRect.centerY() - mediaRect.top);
}
}); });
} }