Merge "Animate ranges and toggles" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-04-14 19:56:46 +00:00
committed by Android (Google) Code Review
6 changed files with 106 additions and 51 deletions

View File

@@ -54,6 +54,11 @@ public class Interpolators {
public static final Interpolator PANEL_CLOSE_ACCELERATED public static final Interpolator PANEL_CLOSE_ACCELERATED
= new PathInterpolator(0.3f, 0, 0.5f, 1); = new PathInterpolator(0.3f, 0, 0.5f, 1);
public static final Interpolator BOUNCE = new BounceInterpolator(); public static final Interpolator BOUNCE = new BounceInterpolator();
/**
* For state transitions on the control panel that lives in GlobalActions.
*/
public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
1.0f);
/** /**
* Interpolator to be used when animating a move based on a click. Pair with enough duration. * Interpolator to be used when animating a move based on a click. Pair with enough duration.

View File

@@ -24,12 +24,11 @@ import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction import android.service.controls.actions.CommandAction
import android.util.Log import android.util.Log
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import com.android.systemui.R import com.android.systemui.R
object ControlActionCoordinator { object ControlActionCoordinator {
public const val MIN_LEVEL = 0 const val MIN_LEVEL = 0
public const val MAX_LEVEL = 10000 const val MAX_LEVEL = 10000
private var dialog: Dialog? = null private var dialog: Dialog? = null
@@ -40,9 +39,6 @@ object ControlActionCoordinator {
fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
cvh.action(BooleanAction(templateId, !isChecked)) cvh.action(BooleanAction(templateId, !isChecked))
val nextLevel = if (isChecked) MIN_LEVEL else MAX_LEVEL
cvh.clipLayer.setLevel(nextLevel)
} }
fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {

View File

@@ -16,6 +16,9 @@
package com.android.systemui.controls.ui package com.android.systemui.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.drawable.ClipDrawable import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
@@ -32,11 +35,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.android.internal.graphics.ColorUtils
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.R
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@@ -53,15 +56,17 @@ class ControlViewHolder(
) { ) {
companion object { companion object {
const val STATE_ANIMATION_DURATION = 700L
private const val UPDATE_DELAY_IN_MILLIS = 3000L private const val UPDATE_DELAY_IN_MILLIS = 3000L
private const val ALPHA_ENABLED = (255.0 * 0.2).toInt() private const val ALPHA_ENABLED = (255.0 * 0.2).toInt()
private const val ALPHA_DISABLED = 255 private const val ALPHA_DISABLED = 0
private val FORCE_PANEL_DEVICES = setOf( private val FORCE_PANEL_DEVICES = setOf(
DeviceTypes.TYPE_THERMOSTAT, DeviceTypes.TYPE_THERMOSTAT,
DeviceTypes.TYPE_CAMERA DeviceTypes.TYPE_CAMERA
) )
} }
private var stateAnimator: ValueAnimator? = null
val icon: ImageView = layout.requireViewById(R.id.icon) val icon: ImageView = layout.requireViewById(R.id.icon)
val status: TextView = layout.requireViewById(R.id.status) val status: TextView = layout.requireViewById(R.id.status)
val title: TextView = layout.requireViewById(R.id.title) val title: TextView = layout.requireViewById(R.id.title)
@@ -79,6 +84,7 @@ class ControlViewHolder(
val ld = layout.getBackground() as LayerDrawable val ld = layout.getBackground() as LayerDrawable
ld.mutate() ld.mutate()
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
clipLayer.alpha = ALPHA_DISABLED
// needed for marquee to start // needed for marquee to start
status.setSelected(true) status.setSelected(true)
} }
@@ -160,30 +166,49 @@ class ControlViewHolder(
} }
} }
internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) { internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0, animated: Boolean = true) {
setEnabled(enabled) setEnabled(enabled)
val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset) val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
val (bg, alpha) = if (enabled) { val (bg, newAlpha) = if (enabled) {
Pair(ri.enabledBackground, ALPHA_ENABLED) Pair(ri.enabledBackground, ALPHA_ENABLED)
} else { } else {
Pair(R.color.control_default_background, ALPHA_DISABLED) Pair(R.color.control_default_background, ALPHA_DISABLED)
} }
status.setTextColor(fg) status.setTextColor(fg)
icon.setImageDrawable(ri.icon) icon.setImageDrawable(ri.icon)
// do not color app icons // do not color app icons
if (deviceType != DeviceTypes.TYPE_ROUTINE) { if (deviceType != DeviceTypes.TYPE_ROUTINE) {
icon.setImageTintList(fg) icon.imageTintList = fg
} }
(clipLayer.getDrawable() as GradientDrawable).apply { (clipLayer.getDrawable() as GradientDrawable).apply {
setColor(context.getResources().getColor(bg, context.getTheme())) val newColor = context.resources.getColor(bg, context.theme)
setAlpha(alpha) stateAnimator?.cancel()
if (animated) {
val oldColor = color?.defaultColor ?: newColor
stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply {
addUpdateListener {
alpha = it.animatedValue as Int
setColor(ColorUtils.blendARGB(oldColor, newColor, it.animatedFraction))
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
stateAnimator = null
}
})
duration = STATE_ANIMATION_DURATION
interpolator = Interpolators.CONTROL_STATE
start()
}
} else {
alpha = newAlpha
setColor(newColor)
}
} }
} }

View File

@@ -18,12 +18,10 @@ package com.android.systemui.controls.ui
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.view.View
import android.service.controls.Control import android.service.controls.Control
import android.service.controls.templates.ToggleTemplate import android.service.controls.templates.ToggleTemplate
import android.view.View
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
class ToggleBehavior : Behavior { class ToggleBehavior : Behavior {
@@ -34,7 +32,7 @@ class ToggleBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) { override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh this.cvh = cvh
cvh.applyRenderInfo(false) cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() { cvh.layout.setOnClickListener(View.OnClickListener() {
ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked()) ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
@@ -49,9 +47,9 @@ class ToggleBehavior : Behavior {
val ld = cvh.layout.getBackground() as LayerDrawable val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.level = MAX_LEVEL
val checked = template.isChecked() val checked = template.isChecked()
clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL)
cvh.applyRenderInfo(checked) cvh.applyRenderInfo(checked)
} }
} }

View File

@@ -16,11 +16,20 @@
package com.android.systemui.controls.ui package com.android.systemui.controls.ui
import android.os.Bundle import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.service.controls.Control
import android.service.controls.actions.FloatAction
import android.service.controls.templates.RangeTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.util.Log import android.util.Log
import android.util.MathUtils
import android.util.TypedValue
import android.view.GestureDetector import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent import android.view.MotionEvent
@@ -29,19 +38,14 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView import android.widget.TextView
import android.service.controls.Control import com.android.systemui.Interpolators
import android.service.controls.actions.FloatAction
import android.service.controls.templates.RangeTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.util.TypedValue
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
import java.util.IllegalFormatException import java.util.IllegalFormatException
class ToggleRangeBehavior : Behavior { class ToggleRangeBehavior : Behavior {
private var rangeAnimator: ValueAnimator? = null
lateinit var clipLayer: Drawable lateinit var clipLayer: Drawable
lateinit var template: ToggleRangeTemplate lateinit var template: ToggleRangeTemplate
lateinit var control: Control lateinit var control: Control
@@ -61,20 +65,21 @@ class ToggleRangeBehavior : Behavior {
status = cvh.status status = cvh.status
context = status.getContext() context = status.getContext()
cvh.applyRenderInfo(false) cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
val gestureListener = ToggleRangeGestureListener(cvh.layout) val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener) val gestureDetector = GestureDetector(context, gestureListener)
cvh.layout.setOnTouchListener { v: View, e: MotionEvent -> cvh.layout.setOnTouchListener { v: View, e: MotionEvent ->
if (gestureDetector.onTouchEvent(e)) { if (gestureDetector.onTouchEvent(e)) {
return@setOnTouchListener true // Don't return true to let the state list change to "pressed"
return@setOnTouchListener false
} }
if (e.getAction() == MotionEvent.ACTION_UP && gestureListener.isDragging) { if (e.getAction() == MotionEvent.ACTION_UP && gestureListener.isDragging) {
v.getParent().requestDisallowInterceptTouchEvent(false) v.getParent().requestDisallowInterceptTouchEvent(false)
gestureListener.isDragging = false gestureListener.isDragging = false
endUpdateRange() endUpdateRange()
return@setOnTouchListener true return@setOnTouchListener false
} }
return@setOnTouchListener false return@setOnTouchListener false
@@ -87,17 +92,18 @@ class ToggleRangeBehavior : Behavior {
currentStatusText = control.getStatusText() currentStatusText = control.getStatusText()
status.setText(currentStatusText) status.setText(currentStatusText)
// ControlViewHolder sets a long click listener, but we want to handle touch in
// here instead, otherwise we'll have state conflicts.
cvh.layout.setOnLongClickListener(null)
val ld = cvh.layout.getBackground() as LayerDrawable val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
clipLayer.setLevel(MIN_LEVEL)
template = control.getControlTemplate() as ToggleRangeTemplate template = control.getControlTemplate() as ToggleRangeTemplate
rangeTemplate = template.getRange() rangeTemplate = template.getRange()
val checked = template.isChecked() val checked = template.isChecked()
val currentRatio = rangeTemplate.getCurrentValue() / updateRange(rangeToLevelValue(rangeTemplate.currentValue), checked, /* isDragging */ false)
(rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())
updateRange(currentRatio, checked, /* isDragging */ false)
cvh.applyRenderInfo(checked) cvh.applyRenderInfo(checked)
@@ -146,9 +152,8 @@ class ToggleRangeBehavior : Behavior {
} else { } else {
val value = arguments.getFloat( val value = arguments.getFloat(
AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE) AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)
val ratioDiff = (value - rangeTemplate.getCurrentValue()) / val level = rangeToLevelValue(value - rangeTemplate.getCurrentValue())
(rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()) updateRange(level, template.isChecked(), /* isDragging */ false)
updateRange(ratioDiff, template.isChecked(), /* isDragging */ false)
endUpdateRange() endUpdateRange()
true true
} }
@@ -172,13 +177,30 @@ class ToggleRangeBehavior : Behavior {
.getDimensionPixelSize(R.dimen.control_status_expanded).toFloat()) .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
} }
fun updateRange(ratioDiff: Float, checked: Boolean, isDragging: Boolean) { fun updateRange(level: Int, checked: Boolean, isDragging: Boolean) {
val changeAmount = if (checked) (MAX_LEVEL * ratioDiff).toInt() else MIN_LEVEL val newLevel = if (checked) Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level)) else MIN_LEVEL
val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, clipLayer.getLevel() + changeAmount))
clipLayer.setLevel(newLevel) rangeAnimator?.cancel()
if (isDragging) {
clipLayer.level = newLevel
} else {
rangeAnimator = ValueAnimator.ofInt(cvh.clipLayer.level, newLevel).apply {
addUpdateListener {
cvh.clipLayer.level = it.animatedValue as Int
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
rangeAnimator = null
}
})
duration = ControlViewHolder.STATE_ANIMATION_DURATION
interpolator = Interpolators.CONTROL_STATE
start()
}
}
if (checked) { if (checked) {
val newValue = levelToRangeValue(clipLayer.getLevel()) val newValue = levelToRangeValue(newLevel)
currentRangeValue = format(rangeTemplate.getFormatString().toString(), currentRangeValue = format(rangeTemplate.getFormatString().toString(),
DEFAULT_FORMAT, newValue) DEFAULT_FORMAT, newValue)
val text = if (isDragging) { val text = if (isDragging) {
@@ -206,9 +228,13 @@ class ToggleRangeBehavior : Behavior {
} }
private fun levelToRangeValue(i: Int): Float { private fun levelToRangeValue(i: Int): Float {
val ratio = i.toFloat() / MAX_LEVEL return MathUtils.constrainedMap(rangeTemplate.minValue, rangeTemplate.maxValue,
return rangeTemplate.getMinValue() + MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(), i.toFloat())
(ratio * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())) }
private fun rangeToLevelValue(i: Float): Int {
return MathUtils.constrainedMap(MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(),
rangeTemplate.minValue, rangeTemplate.maxValue, i).toInt()
} }
fun endUpdateRange() { fun endUpdateRange() {
@@ -247,6 +273,9 @@ class ToggleRangeBehavior : Behavior {
} }
override fun onLongPress(e: MotionEvent) { override fun onLongPress(e: MotionEvent) {
if (isDragging) {
return
}
ControlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh) ControlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
} }
@@ -265,8 +294,10 @@ class ToggleRangeBehavior : Behavior {
isDragging = true isDragging = true
} }
this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(), val ratioDiff = -xDiff / v.width
/* checked */ true, /* isDragging */ true) val changeAmount = ((MAX_LEVEL - MIN_LEVEL) * ratioDiff).toInt()
this@ToggleRangeBehavior.updateRange(clipLayer.level + changeAmount,
checked = true, isDragging = true)
return true return true
} }

View File

@@ -37,7 +37,7 @@ class TouchBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) { override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh this.cvh = cvh
cvh.applyRenderInfo(false) cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() { cvh.layout.setOnClickListener(View.OnClickListener() {
ControlActionCoordinator.touch(cvh, template.getTemplateId(), control) ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)