summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/customization/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt (renamed from packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt)27
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt54
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt12
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt224
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt38
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt272
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt342
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt37
9 files changed, 683 insertions, 324 deletions
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 2bb5541f4b0a..723f9bff3152 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -34,6 +34,7 @@
<dimen name="small_clock_padding_top">28dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
<dimen name="weather_date_icon_padding">28dp</dimen>
+ <dimen name="clock_vertical_digit_buffer">8dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt
index ef8bee0875d2..dd1599e5259d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 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.
@@ -16,20 +16,17 @@
package com.android.systemui.shared.clocks
-import android.graphics.Rect
-import android.view.View
+import android.graphics.Canvas
-fun computeLayoutDiff(
- view: View,
- targetRegion: Rect,
- isLargeClock: Boolean,
-): Pair<Float, Float> {
- val parent = view.parent
- if (parent is View && parent.isLaidOut() && isLargeClock) {
- return Pair(
- targetRegion.centerX() - parent.width / 2f,
- targetRegion.centerY() - parent.height / 2f
- )
+object CanvasUtil {
+ fun Canvas.translate(pt: VPointF) = this.translate(pt.x, pt.y)
+
+ fun Canvas.translate(pt: VPoint) = this.translate(pt.x.toFloat(), pt.y.toFloat())
+
+ fun <T> Canvas.use(func: (Canvas) -> T): T {
+ val saveNum = save()
+ val result = func(this)
+ restoreToCount(saveNum)
+ return result
}
- return Pair(0f, 0f)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
index 38697063bea5..f5ccc52c8c6b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt
@@ -20,57 +20,42 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
-import android.graphics.Point
+import com.android.systemui.shared.clocks.VPointF.Companion.times
-class DigitTranslateAnimator(val updateCallback: () -> Unit) {
- val DEFAULT_ANIMATION_DURATION = 500L
- val updatedTranslate = Point(0, 0)
+class DigitTranslateAnimator(private val updateCallback: (VPointF) -> Unit) {
+ var currentTranslation = VPointF.ZERO
+ var baseTranslation = VPointF.ZERO
+ var targetTranslation = VPointF.ZERO
- val baseTranslation = Point(0, 0)
- var targetTranslation: Point? = null
- val bounceAnimator: ValueAnimator =
+ private val bounceAnimator: ValueAnimator =
ValueAnimator.ofFloat(1f).apply {
- duration = DEFAULT_ANIMATION_DURATION
- addUpdateListener {
- updateTranslation(it.animatedFraction, updatedTranslate)
- updateCallback()
- }
+ addUpdateListener { updateCallback(getInterpolatedTranslation(it.animatedFraction)) }
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- rebase()
+ baseTranslation = currentTranslation
}
override fun onAnimationCancel(animation: Animator) {
- rebase()
+ baseTranslation = currentTranslation
}
}
)
}
- fun rebase() {
- baseTranslation.x = updatedTranslate.x
- baseTranslation.y = updatedTranslate.y
- }
-
fun animatePosition(
animate: Boolean = true,
delay: Long = 0,
- duration: Long = -1L,
+ duration: Long,
interpolator: TimeInterpolator? = null,
- targetTranslation: Point? = null,
+ targetTranslation: VPointF,
onAnimationEnd: Runnable? = null,
) {
- this.targetTranslation = targetTranslation ?: Point(0, 0)
+ this.targetTranslation = targetTranslation
if (animate) {
bounceAnimator.cancel()
bounceAnimator.startDelay = delay
- bounceAnimator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
+ bounceAnimator.duration = duration
interpolator?.let { bounceAnimator.interpolator = it }
if (onAnimationEnd != null) {
val listener =
@@ -89,16 +74,13 @@ class DigitTranslateAnimator(val updateCallback: () -> Unit) {
bounceAnimator.start()
} else {
// No animation is requested, thus set base and target state to the same state.
- updateTranslation(1F, updatedTranslate)
- rebase()
- updateCallback()
+ currentTranslation = targetTranslation
+ baseTranslation = targetTranslation
+ updateCallback(targetTranslation)
}
}
- fun updateTranslation(progress: Float, outPoint: Point) {
- outPoint.x =
- (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt()
- outPoint.y =
- (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt()
+ fun getInterpolatedTranslation(progress: Float): VPointF {
+ return baseTranslation + progress * (targetTranslation - baseTranslation)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index b4c2f5de290f..2282863b4017 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -35,12 +35,14 @@ import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff
import com.android.systemui.shared.clocks.view.FlexClockView
import com.android.systemui.shared.clocks.view.HorizontalAlignment
import com.android.systemui.shared.clocks.view.VerticalAlignment
import java.util.Locale
import java.util.TimeZone
import kotlin.math.max
+import kotlin.math.roundToInt
// TODO(b/364680879): Merge w/ ComposedDigitalLayerController
class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: Boolean) :
@@ -168,17 +170,17 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
else targetRegion.height() / maxHeight
FrameLayout.LayoutParams(
- (maxWidth * scale).toInt(),
- (maxHeight * scale).toInt(),
+ (maxWidth * scale).roundToInt(),
+ (maxHeight * scale).roundToInt(),
)
}
lp.gravity = Gravity.CENTER
view.layoutParams = lp
targetRegion?.let {
- val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock)
- view.translationX = xDiff
- view.translationY = yDiff
+ val diff = view.computeLayoutDiff(it, isLargeClock)
+ view.translationX = diff.x
+ view.translationY = diff.y
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt
new file mode 100644
index 000000000000..3dae5305542b
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2025 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 com.android.systemui.shared.clocks
+
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sqrt
+
+private val X_MASK: ULong = 0xFFFFFFFF00000000U
+private val Y_MASK: ULong = 0x00000000FFFFFFFFU
+
+private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt()
+
+private fun unpackY(data: ULong): Int = (data and Y_MASK).toInt()
+
+private fun pack(x: Int, y: Int): ULong {
+ return ((x.toULong() shl 32) and X_MASK) or (y.toULong() and Y_MASK)
+}
+
+@JvmInline
+value class VPointF(private val data: ULong) {
+ val x: Float
+ get() = Float.fromBits(unpackX(data))
+
+ val y: Float
+ get() = Float.fromBits(unpackY(data))
+
+ constructor() : this(0f, 0f)
+
+ constructor(pt: PointF) : this(pt.x, pt.y)
+
+ constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat())
+
+ constructor(x: Int, y: Float) : this(x.toFloat(), y)
+
+ constructor(x: Float, y: Int) : this(x, y.toFloat())
+
+ constructor(x: Float, y: Float) : this(pack(x.toBits(), y.toBits()))
+
+ fun toPointF() = PointF(x, y)
+
+ fun lengthSq(): Float = x * x + y * y
+
+ fun length(): Float = sqrt(lengthSq())
+
+ fun abs() = VPointF(abs(x), abs(y))
+
+ fun dot(pt: VPointF): Float = x * pt.x + y * pt.y
+
+ fun normalize(): VPointF {
+ val length = this.length()
+ return VPointF(x / length, y / length)
+ }
+
+ operator fun component1(): Float = x
+
+ operator fun component2(): Float = y
+
+ override fun toString() = "($x, $y)"
+
+ operator fun plus(pt: VPoint) = VPointF(x + pt.x, y + pt.y)
+
+ operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y)
+
+ operator fun plus(value: Int) = VPointF(x + value, y + value)
+
+ operator fun plus(value: Float) = VPointF(x + value, y + value)
+
+ operator fun minus(pt: VPoint) = VPointF(x - pt.x, y - pt.y)
+
+ operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y)
+
+ operator fun minus(value: Int) = VPointF(x - value, y - value)
+
+ operator fun minus(value: Float) = VPointF(x - value, y - value)
+
+ operator fun times(pt: VPoint) = VPointF(x * pt.x, y * pt.y)
+
+ operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y)
+
+ operator fun times(value: Int) = VPointF(x * value, y * value)
+
+ operator fun times(value: Float) = VPointF(x * value, y * value)
+
+ operator fun div(pt: VPoint) = VPointF(x / pt.x, y / pt.y)
+
+ operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y)
+
+ operator fun div(value: Int) = VPointF(x / value, y / value)
+
+ operator fun div(value: Float) = VPointF(x / value, y / value)
+
+ companion object {
+ val ZERO = VPointF(0, 0)
+
+ fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
+
+ fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
+
+ operator fun Float.plus(value: VPointF) = VPointF(this + value.x, this + value.y)
+
+ operator fun Int.minus(value: VPointF) = VPointF(this - value.x, this - value.y)
+
+ operator fun Float.minus(value: VPointF) = VPointF(this - value.x, this - value.y)
+
+ operator fun Int.times(value: VPointF) = VPointF(this * value.x, this * value.y)
+
+ operator fun Float.times(value: VPointF) = VPointF(this * value.x, this * value.y)
+
+ operator fun Int.div(value: VPointF) = VPointF(this / value.x, this / value.y)
+
+ operator fun Float.div(value: VPointF) = VPointF(this / value.x, this / value.y)
+
+ val RectF.center: VPointF
+ get() = VPointF(centerX(), centerY())
+
+ val RectF.size: VPointF
+ get() = VPointF(width(), height())
+ }
+}
+
+@JvmInline
+value class VPoint(private val data: ULong) {
+ val x: Int
+ get() = unpackX(data)
+
+ val y: Int
+ get() = unpackY(data)
+
+ constructor() : this(0, 0)
+
+ constructor(x: Int, y: Int) : this(pack(x, y))
+
+ fun toPoint() = Point(x, y)
+
+ fun abs() = VPoint(abs(x), abs(y))
+
+ operator fun component1(): Int = x
+
+ operator fun component2(): Int = y
+
+ override fun toString() = "($x, $y)"
+
+ operator fun plus(pt: VPoint) = VPoint(x + pt.x, y + pt.y)
+
+ operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y)
+
+ operator fun plus(value: Int) = VPoint(x + value, y + value)
+
+ operator fun plus(value: Float) = VPointF(x + value, y + value)
+
+ operator fun minus(pt: VPoint) = VPoint(x - pt.x, y - pt.y)
+
+ operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y)
+
+ operator fun minus(value: Int) = VPoint(x - value, y - value)
+
+ operator fun minus(value: Float) = VPointF(x - value, y - value)
+
+ operator fun times(pt: VPoint) = VPoint(x * pt.x, y * pt.y)
+
+ operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y)
+
+ operator fun times(value: Int) = VPoint(x * value, y * value)
+
+ operator fun times(value: Float) = VPointF(x * value, y * value)
+
+ operator fun div(pt: VPoint) = VPoint(x / pt.x, y / pt.y)
+
+ operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y)
+
+ operator fun div(value: Int) = VPoint(x / value, y / value)
+
+ operator fun div(value: Float) = VPointF(x / value, y / value)
+
+ companion object {
+ val ZERO = VPoint(0, 0)
+
+ fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
+
+ fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
+
+ operator fun Int.plus(value: VPoint) = VPoint(this + value.x, this + value.y)
+
+ operator fun Float.plus(value: VPoint) = VPointF(this + value.x, this + value.y)
+
+ operator fun Int.minus(value: VPoint) = VPoint(this - value.x, this - value.y)
+
+ operator fun Float.minus(value: VPoint) = VPointF(this - value.x, this - value.y)
+
+ operator fun Int.times(value: VPoint) = VPoint(this * value.x, this * value.y)
+
+ operator fun Float.times(value: VPoint) = VPointF(this * value.x, this * value.y)
+
+ operator fun Int.div(value: VPoint) = VPoint(this / value.x, this / value.y)
+
+ operator fun Float.div(value: VPoint) = VPointF(this / value.x, this / value.y)
+
+ val Rect.center: VPoint
+ get() = VPoint(centerX(), centerY())
+
+ val Rect.size: VPoint
+ get() = VPoint(width(), height())
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt
new file mode 100644
index 000000000000..1e90a2370786
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.shared.clocks
+
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.shared.clocks.VPoint.Companion.center
+import com.android.systemui.shared.clocks.VPointF.Companion.center
+
+object ViewUtils {
+ fun View.computeLayoutDiff(targetRegion: Rect, isLargeClock: Boolean): VPointF {
+ val parent = this.parent
+ if (parent is View && parent.isLaidOut() && isLargeClock) {
+ return targetRegion.center - parent.size / 2f
+ }
+ return VPointF.ZERO
+ }
+
+ val View.size: VPointF
+ get() = VPointF(width, height)
+
+ val View.measuredSize: VPointF
+ get() = VPointF(measuredWidth, measuredHeight)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index a9f91e077651..ec99af17bae2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -17,28 +17,37 @@
package com.android.systemui.shared.clocks.view
import android.graphics.Canvas
-import android.graphics.Point
+import android.graphics.RectF
import android.icu.text.NumberFormat
import android.util.MathUtils.constrainedMap
import android.view.View
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.RelativeLayout
import androidx.annotation.VisibleForTesting
+import androidx.core.view.children
import com.android.app.animation.Interpolators
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockLogger
+import com.android.systemui.shared.clocks.CanvasUtil.translate
+import com.android.systemui.shared.clocks.CanvasUtil.use
import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.VPointF
+import com.android.systemui.shared.clocks.VPointF.Companion.max
+import com.android.systemui.shared.clocks.VPointF.Companion.times
+import com.android.systemui.shared.clocks.ViewUtils.measuredSize
import java.util.Locale
+import kotlin.collections.filterNotNull
+import kotlin.collections.map
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
+import kotlin.math.roundToInt
fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal)
-class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
+class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: ClockLogger.INIT_LOGGER
@@ -46,13 +55,13 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
var isAnimationEnabled = true
set(value) {
field = value
- digitalClockTextViewMap.forEach { _, view -> view.isAnimationEnabled = value }
+ childViews.forEach { view -> view.isAnimationEnabled = value }
}
var dozeFraction: Float = 0F
set(value) {
field = value
- digitalClockTextViewMap.forEach { _, view -> view.dozeFraction = field }
+ childViews.forEach { view -> view.dozeFraction = field }
}
var isReactiveTouchInteractionEnabled = false
@@ -60,12 +69,20 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
field = value
}
- var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
- private val digitLeftTopMap = mutableMapOf<Int, Point>()
+ var _childViews: List<SimpleDigitalClockTextView>? = null
+ val childViews: List<SimpleDigitalClockTextView>
+ get() {
+ return _childViews
+ ?: this.children
+ .map { child -> child as? SimpleDigitalClockTextView }
+ .filterNotNull()
+ .toList()
+ .also { _childViews = it }
+ }
- private var maxSingleDigitSize = Point(-1, -1)
- private val lockscreenTranslate = Point(0, 0)
- private var aodTranslate = Point(0, 0)
+ private var maxChildSize = VPointF(-1, -1)
+ private val lockscreenTranslate = VPointF.ZERO
+ private var aodTranslate = VPointF.ZERO
private var onAnimateDoze: (() -> Unit)? = null
private var isDozeReadyToAnimate = false
@@ -85,63 +102,51 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
private val digitOffsets = mutableMapOf<Int, Float>()
- protected fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point? {
- maxSingleDigitSize = Point(-1, -1)
- val viewHeight: (textView: SimpleDigitalClockTextView) -> Int = { textView ->
- if (isMonoVerticalNumericLineSpacing) {
- maxSingleDigitSize.y
- } else {
- (textView.paint.fontMetrics.descent - textView.paint.fontMetrics.ascent).toInt()
+ protected fun calculateSize(
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
+ shouldMeasureChildren: Boolean,
+ ): VPointF {
+ maxChildSize = VPointF(-1, -1)
+ fun SimpleDigitalClockTextView.getSize() = VPointF(measuredWidth, measuredHeight)
+
+ childViews.forEach { textView ->
+ if (shouldMeasureChildren) {
+ textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
}
+ maxChildSize = max(maxChildSize, textView.getSize())
}
-
- digitalClockTextViewMap.forEach { (_, textView) ->
- textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
- maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth)
- maxSingleDigitSize.y = max(viewHeight(textView), textView.measuredHeight)
- }
- aodTranslate = Point(0, 0)
+ aodTranslate = VPointF.ZERO
// TODO(b/364680879): Cleanup
/*
- aodTranslate = Point(
- (maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt(),
- (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt())
- */
- return Point(
- ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
- ((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2),
+ aodTranslate = VPointF(
+ maxChildSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO,
+ maxChildSize.y * AOD_VERTICAL_TRANSLATE_RATIO
)
+ */
+ val yBuffer = context.resources.getDimensionPixelSize(R.dimen.clock_vertical_digit_buffer)
+ return (maxChildSize + aodTranslate.abs()) * VPointF(1f, 2f) + VPointF(0f, yBuffer)
}
- protected fun calculateLeftTopPosition() {
- digitLeftTopMap[R.id.HOUR_FIRST_DIGIT] = Point(0, 0)
- digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0)
- digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y)
- digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize)
- digitLeftTopMap[R.id.HOUR_DIGIT_PAIR] = Point(maxSingleDigitSize.x / 2, 0)
- // Add a small vertical buffer for the second digit pair
- digitLeftTopMap[R.id.MINUTE_DIGIT_PAIR] =
- Point(maxSingleDigitSize.x / 2, (maxSingleDigitSize.y * 1.05f).toInt())
- digitLeftTopMap.forEach { (_, point) ->
- point.x += abs(aodTranslate.x)
- point.y += abs(aodTranslate.y)
- }
- }
-
- override fun addView(child: View?) {
+ override fun onViewAdded(child: View?) {
if (child == null) return
- logger.addView(child)
- super.addView(child)
+ logger.onViewAdded(child)
+ super.onViewAdded(child)
(child as? SimpleDigitalClockTextView)?.let {
- it.digitTranslateAnimator = DigitTranslateAnimator(::invalidate)
- digitalClockTextViewMap[child.id] = child
+ it.digitTranslateAnimator = DigitTranslateAnimator { invalidate() }
}
child.setWillNotDraw(true)
+ _childViews = null
+ }
+
+ override fun onViewRemoved(child: View?) {
+ super.onViewRemoved(child)
+ _childViews = null
}
fun refreshTime() {
logger.refreshTime()
- digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() }
+ childViews.forEach { textView -> textView.refreshText() }
}
override fun setVisibility(visibility: Int) {
@@ -164,38 +169,94 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
super.requestLayout()
}
+ fun updateMeasuredSize() =
+ updateMeasuredSize(
+ measuredWidthAndState,
+ measuredHeightAndState,
+ shouldMeasureChildren = false,
+ )
+
+ private fun updateMeasuredSize(
+ widthMeasureSpec: Int = measuredWidthAndState,
+ heightMeasureSpec: Int = measuredHeightAndState,
+ shouldMeasureChildren: Boolean,
+ ) {
+ val size = calculateSize(widthMeasureSpec, heightMeasureSpec, shouldMeasureChildren)
+ setMeasuredDimension(size.x.roundToInt(), size.y.roundToInt())
+ }
+
+ fun updateLocation() {
+ val layoutBounds = this.layoutBounds ?: return
+ setFrame(
+ (layoutBounds.centerX() - measuredWidth / 2f).roundToInt(),
+ (layoutBounds.centerY() - measuredHeight / 2f).roundToInt(),
+ (layoutBounds.centerX() + measuredWidth / 2f).roundToInt(),
+ (layoutBounds.centerY() + measuredHeight / 2f).roundToInt(),
+ )
+ updateChildFrames(isLayout = false)
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- logger.onMeasure()
- calculateSize(widthMeasureSpec, heightMeasureSpec)?.let { size ->
- setMeasuredDimension(size.x, size.y)
- } ?: run { super.onMeasure(widthMeasureSpec, heightMeasureSpec) }
- calculateLeftTopPosition()
+ logger.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ updateMeasuredSize(widthMeasureSpec, heightMeasureSpec, shouldMeasureChildren = true)
isDozeReadyToAnimate = true
onAnimateDoze?.invoke()
onAnimateDoze = null
}
+ private val layoutBounds = RectF()
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- logger.onLayout()
- super.onLayout(changed, left, top, right, bottom)
+ logger.onLayout(changed, left, top, right, bottom)
+
+ layoutBounds.left = left.toFloat()
+ layoutBounds.top = top.toFloat()
+ layoutBounds.right = right.toFloat()
+ layoutBounds.bottom = bottom.toFloat()
+
+ updateChildFrames(isLayout = true)
+ }
+
+ private fun updateChildFrames(isLayout: Boolean) {
+ val yBuffer = context.resources.getDimensionPixelSize(R.dimen.clock_vertical_digit_buffer)
+ childViews.forEach { child ->
+ var offset =
+ maxChildSize.run {
+ when (child.id) {
+ R.id.HOUR_FIRST_DIGIT -> VPointF.ZERO
+ R.id.HOUR_SECOND_DIGIT -> VPointF(x, 0f)
+ R.id.MINUTE_FIRST_DIGIT -> VPointF(0f, y + yBuffer)
+ R.id.MINUTE_SECOND_DIGIT -> this
+ R.id.HOUR_DIGIT_PAIR -> VPointF.ZERO
+ // Add a small vertical buffer for the second digit pair
+ R.id.MINUTE_DIGIT_PAIR -> VPointF(0f, y + yBuffer)
+ else -> VPointF.ZERO
+ }
+ }
+
+ val childSize = child.measuredSize
+ offset += VPointF((measuredWidth - childSize.x) / 2f, 0f)
+ offset += aodTranslate.abs()
+
+ val setPos = if (isLayout) child::layout else child::setLeftTopRightBottom
+ setPos(
+ offset.x.roundToInt(),
+ offset.y.roundToInt(),
+ (offset.x + childSize.x).roundToInt(),
+ (offset.y + childSize.y).roundToInt(),
+ )
+ }
}
override fun onDraw(canvas: Canvas) {
logger.onDraw()
- super.onDraw(canvas)
-
- digitalClockTextViewMap.forEach { (id, textView) ->
- // save canvas location in anticipation of restoration later
- canvas.save()
- val xTranslateAmount =
- digitOffsets.getOrDefault(id, 0f) + (digitLeftTopMap[id]?.x?.toFloat() ?: 0f)
- // move canvas to location that the textView would like
- canvas.translate(xTranslateAmount, digitLeftTopMap[id]?.y?.toFloat() ?: 0f)
- // draw the textView at the location of the canvas above
- textView.draw(canvas)
- // reset the canvas location back to 0 without drawing
- canvas.restore()
+ childViews.forEach { child ->
+ canvas.use { canvas ->
+ canvas.translate(digitOffsets.getOrDefault(child.id, 0f), 0f)
+ canvas.translate(child.left.toFloat(), child.top.toFloat())
+ child.draw(canvas)
+ }
}
}
@@ -205,26 +266,26 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
}
fun updateColor(color: Int) {
- digitalClockTextViewMap.forEach { _, view -> view.updateColor(color) }
+ childViews.forEach { view -> view.updateColor(color) }
invalidate()
}
fun updateAxes(axes: List<ClockFontAxisSetting>) {
- digitalClockTextViewMap.forEach { _, view -> view.updateAxes(axes) }
+ childViews.forEach { view -> view.updateAxes(axes) }
requestLayout()
}
fun onFontSettingChanged(fontSizePx: Float) {
- digitalClockTextViewMap.forEach { _, view -> view.applyTextSize(fontSizePx) }
+ childViews.forEach { view -> view.applyTextSize(fontSizePx) }
}
fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
fun executeDozeAnimation() {
- digitalClockTextViewMap.forEach { _, view -> view.animateDoze(isDozing, isAnimated) }
- if (maxSingleDigitSize.x < 0 || maxSingleDigitSize.y < 0) {
+ childViews.forEach { view -> view.animateDoze(isDozing, isAnimated) }
+ if (maxChildSize.x < 0 || maxChildSize.y < 0) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
}
- digitalClockTextViewMap.forEach { (id, textView) ->
+ childViews.forEach { textView ->
textView.digitTranslateAnimator?.let {
if (!isDozing) {
it.animatePosition(
@@ -252,8 +313,8 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
}
fun animateCharge() {
- digitalClockTextViewMap.forEach { _, view -> view.animateCharge() }
- digitalClockTextViewMap.forEach { (id, textView) ->
+ childViews.forEach { view -> view.animateCharge() }
+ childViews.forEach { textView ->
textView.digitTranslateAnimator?.let {
it.animatePosition(
animate = isAnimationEnabled,
@@ -266,14 +327,14 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
duration = CHARGING_TRANSITION_DURATION,
targetTranslation =
updateDirectionalTargetTranslate(
- id,
+ textView.id,
if (dozeFraction == 1F) aodTranslate else lockscreenTranslate,
),
)
},
targetTranslation =
updateDirectionalTargetTranslate(
- id,
+ textView.id,
if (dozeFraction == 1F) lockscreenTranslate else aodTranslate,
),
)
@@ -282,7 +343,7 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
}
fun animateFidget(x: Float, y: Float) {
- digitalClockTextViewMap.forEach { _, view -> view.animateFidget(x, y) }
+ childViews.forEach { view -> view.animateFidget(x, y) }
}
private fun updateLocale(locale: Locale) {
@@ -321,7 +382,7 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
// so we no longer need to multiply direct sign to moveAmountDeltaForDigit
val currentMoveAmount = left - clockStartLeft
var index = 0
- digitalClockTextViewMap.forEach { id, _ ->
+ childViews.forEach { child ->
val digitFraction =
getDigitFraction(
digit = index++,
@@ -332,7 +393,7 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
val moveAmountForDigit = currentMoveAmount * digitFraction
var moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
if (isMovingToCenter && moveAmountForDigit < 0) moveAmountDeltaForDigit *= -1
- digitOffsets[id] = moveAmountDeltaForDigit
+ digitOffsets[child.id] = moveAmountDeltaForDigit
invalidate()
}
}
@@ -353,8 +414,7 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
/* rangeMin= */ 0.0f,
/* rangeMax= */ 1.0f,
/* valueMin= */ digitInitialDelay,
- /* valueMax= */ digitInitialDelay +
- availableAnimationTime(digitalClockTextViewMap.size),
+ /* valueMax= */ digitInitialDelay + availableAnimationTime(childViews.size),
/* value= */ fraction,
)
)
@@ -401,35 +461,17 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) {
)
// Use the sign of targetTranslation to control the direction of digit translation
- fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
- val outPoint = Point(targetTranslation)
- when (id) {
- R.id.HOUR_FIRST_DIGIT -> {
- outPoint.x *= -1
- outPoint.y *= -1
- }
- R.id.HOUR_SECOND_DIGIT -> {
- outPoint.x *= 1
- outPoint.y *= -1
- }
- R.id.MINUTE_FIRST_DIGIT -> {
- outPoint.x *= -1
- outPoint.y *= 1
+ fun updateDirectionalTargetTranslate(id: Int, targetTranslation: VPointF): VPointF {
+ return targetTranslation *
+ when (id) {
+ R.id.HOUR_FIRST_DIGIT -> VPointF(-1, -1)
+ R.id.HOUR_SECOND_DIGIT -> VPointF(1, -1)
+ R.id.MINUTE_FIRST_DIGIT -> VPointF(-1, 1)
+ R.id.MINUTE_SECOND_DIGIT -> VPointF(1, 1)
+ R.id.HOUR_DIGIT_PAIR -> VPointF(-1, -1)
+ R.id.MINUTE_DIGIT_PAIR -> VPointF(-1, 1)
+ else -> VPointF(1, 1)
}
- R.id.MINUTE_SECOND_DIGIT -> {
- outPoint.x *= 1
- outPoint.y *= 1
- }
- R.id.HOUR_DIGIT_PAIR -> {
- outPoint.x *= -1
- outPoint.y *= -1
- }
- R.id.MINUTE_DIGIT_PAIR -> {
- outPoint.x *= -1
- outPoint.y *= 1
- }
- }
- return outPoint
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index b7ce20ec32d1..479e1464c8a1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -20,16 +20,16 @@ import android.annotation.SuppressLint
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
-import android.graphics.Point
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
+import android.graphics.RectF
import android.os.VibrationEffect
import android.text.Layout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
-import android.util.MathUtils
+import android.util.MathUtils.lerp
import android.util.TypedValue
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
@@ -44,18 +44,33 @@ import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace
import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.toFVar
import com.android.systemui.plugins.clocks.ClockLogger
+import com.android.systemui.shared.clocks.CanvasUtil.translate
+import com.android.systemui.shared.clocks.CanvasUtil.use
import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
import com.android.systemui.shared.clocks.DimensionParser
import com.android.systemui.shared.clocks.FLEX_CLOCK_ID
import com.android.systemui.shared.clocks.FontTextStyle
+import com.android.systemui.shared.clocks.VPoint
+import com.android.systemui.shared.clocks.VPointF
+import com.android.systemui.shared.clocks.VPointF.Companion.size
+import com.android.systemui.shared.clocks.ViewUtils.measuredSize
+import com.android.systemui.shared.clocks.ViewUtils.size
import com.android.systemui.shared.clocks.toClockAxisSetting
import java.lang.Thread
import kotlin.math.max
import kotlin.math.min
+import kotlin.math.roundToInt
private val TAG = SimpleDigitalClockTextView::class.simpleName!!
+private fun Paint.getTextBounds(text: CharSequence, result: RectF = RectF()): RectF {
+ val rect = Rect()
+ this.getTextBounds(text, 0, text.length, rect)
+ result.set(rect)
+ return result
+}
+
enum class VerticalAlignment {
TOP,
BOTTOM,
@@ -103,27 +118,27 @@ open class SimpleDigitalClockTextView(
}
private val parser = DimensionParser(clockCtx.context)
- var maxSingleDigitHeight = -1
- var maxSingleDigitWidth = -1
+ var maxSingleDigitHeight = -1f
+ var maxSingleDigitWidth = -1f
var digitTranslateAnimator: DigitTranslateAnimator? = null
- var aodFontSizePx: Float = -1F
+ var aodFontSizePx = -1f
// Store the font size when there's no height constraint as a reference when adjusting font size
- private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
+ private var lastUnconstrainedTextSize = Float.MAX_VALUE
// Calculated by height of styled text view / text size
// Used as a factor to calculate a smaller font size when text height is constrained
- @VisibleForTesting var fontSizeAdjustFactor = 1F
+ @VisibleForTesting var fontSizeAdjustFactor = 1f
private val initThread = Thread.currentThread()
// textBounds is the size of text in LS, which only measures current text in lockscreen style
- var textBounds = Rect()
+ var textBounds = RectF()
// prevTextBounds and targetTextBounds are to deal with dozing animation between LS and AOD
// especially for the textView which has different bounds during the animation
// prevTextBounds holds the state we are transitioning from
- private val prevTextBounds = Rect()
+ private val prevTextBounds = RectF()
// targetTextBounds holds the state we are interpolating to
- private val targetTextBounds = Rect()
+ private val targetTextBounds = RectF()
protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: ClockLogger.INIT_LOGGER
@@ -141,14 +156,14 @@ open class SimpleDigitalClockTextView(
var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
var isAnimationEnabled = true
- var dozeFraction: Float = 0F
+ var dozeFraction: Float = 0f
set(value) {
field = value
invalidate()
}
- var textBorderWidth = 0F
- var baselineFromMeasure = 0
+ var textBorderWidth = 0f
+ var measuredBaseline = 0
var lockscreenColor = Color.WHITE
fun updateColor(color: Int) {
@@ -169,7 +184,7 @@ open class SimpleDigitalClockTextView(
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
- lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ lockScreenPaint.getTextBounds(text, textBounds)
targetTextBounds.set(textBounds)
textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation))
@@ -195,7 +210,7 @@ open class SimpleDigitalClockTextView(
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- logger.onMeasure()
+ logger.onMeasure(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val layout = this.layout
@@ -206,7 +221,7 @@ open class SimpleDigitalClockTextView(
} else {
textAnimator.updateLayout(layout)
}
- baselineFromMeasure = layout.getLineBaseline(0)
+ measuredBaseline = layout.getLineBaseline(0)
} else {
val currentThread = Thread.currentThread()
Log.wtf(
@@ -216,24 +231,33 @@ open class SimpleDigitalClockTextView(
)
}
- setInterpolatedViewBounds(getInterpolatedTextBounds(), widthMeasureSpec, heightMeasureSpec)
+ val bounds = getInterpolatedTextBounds()
+ val size = computeMeasuredSize(bounds, widthMeasureSpec, heightMeasureSpec)
+ setInterpolatedSize(size, widthMeasureSpec, heightMeasureSpec)
}
+ private var drawnProgress: Float? = null
+
override fun onDraw(canvas: Canvas) {
logger.onDraw(textAnimator.textInterpolator.shapedText)
- val translation = getLocalTranslation()
- canvas.translate(translation.x.toFloat(), translation.y.toFloat())
- digitTranslateAnimator?.let {
- canvas.translate(it.updatedTranslate.x.toFloat(), it.updatedTranslate.y.toFloat())
+ val interpProgress = getInterpolatedProgress()
+ val interpBounds = getInterpolatedTextBounds(interpProgress)
+ if (interpProgress != drawnProgress) {
+ drawnProgress = interpProgress
+ val measureSize = computeMeasuredSize(interpBounds)
+ setInterpolatedSize(measureSize)
+ (parent as? FlexClockView)?.run {
+ updateMeasuredSize()
+ updateLocation()
+ } ?: setInterpolatedLocation(measureSize)
}
- textAnimator.draw(canvas)
-
- digitTranslateAnimator?.let {
- canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat())
+ canvas.use {
+ digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
+ canvas.translate(getDrawTranslation(interpBounds))
+ textAnimator.draw(canvas)
}
- canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
}
override fun setVisibility(visibility: Int) {
@@ -246,6 +270,18 @@ open class SimpleDigitalClockTextView(
super.setAlpha(alpha)
}
+ private val layoutBounds = RectF()
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ logger.onLayout(changed, left, top, right, bottom)
+
+ layoutBounds.left = left.toFloat()
+ layoutBounds.top = top.toFloat()
+ layoutBounds.right = right.toFloat()
+ layoutBounds.bottom = bottom.toFloat()
+ }
+
override fun invalidate() {
logger.invalidate()
super.invalidate()
@@ -338,14 +374,9 @@ open class SimpleDigitalClockTextView(
}
fun refreshText() {
- lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ lockScreenPaint.getTextBounds(text, textBounds)
if (this::textAnimator.isInitialized) {
- textAnimator.textInterpolator.targetPaint.getTextBounds(
- text,
- 0,
- text.length,
- targetTextBounds,
- )
+ textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds)
}
if (layout == null) {
@@ -362,113 +393,137 @@ open class SimpleDigitalClockTextView(
id == R.id.MINUTE_SECOND_DIGIT
}
- private fun getInterpolatedTextBounds(): Rect {
- val progress = textAnimator.animator?.let { it.animatedValue as Float } ?: 1f
+ private fun getInterpolatedProgress(): Float {
+ return textAnimator.animator?.let { it.animatedValue as Float } ?: 1f
+ }
+
+ /** Returns the interpolated text bounding rect based on interpolation progress */
+ private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): RectF {
if (!textAnimator.isRunning || progress >= 1f) {
- return Rect(targetTextBounds)
+ return RectF(targetTextBounds)
}
- val interpolatedTextBounds = Rect()
- interpolatedTextBounds.left =
- MathUtils.lerp(prevTextBounds.left, targetTextBounds.left, progress).toInt()
- interpolatedTextBounds.right =
- MathUtils.lerp(prevTextBounds.right, targetTextBounds.right, progress).toInt()
- interpolatedTextBounds.top =
- MathUtils.lerp(prevTextBounds.top, targetTextBounds.top, progress).toInt()
- interpolatedTextBounds.bottom =
- MathUtils.lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress).toInt()
- return interpolatedTextBounds
+ return RectF().apply {
+ left = lerp(prevTextBounds.left, targetTextBounds.left, progress)
+ right = lerp(prevTextBounds.right, targetTextBounds.right, progress)
+ top = lerp(prevTextBounds.top, targetTextBounds.top, progress)
+ bottom = lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress)
+ }
}
- private fun setInterpolatedViewBounds(
- interpBounds: Rect,
+ private fun computeMeasuredSize(
+ interpBounds: RectF,
widthMeasureSpec: Int = measuredWidthAndState,
heightMeasureSpec: Int = measuredHeightAndState,
- ) {
- val heightMode = MeasureSpec.getMode(heightMeasureSpec)
- val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+ ): VPointF {
+ val mode =
+ VPoint(
+ x = MeasureSpec.getMode(widthMeasureSpec),
+ y = MeasureSpec.getMode(heightMeasureSpec),
+ )
- val heightSpec =
- if (heightMode == EXACTLY) {
- heightMeasureSpec
- } else {
- MeasureSpec.makeMeasureSpec(
- if (isSingleDigit()) maxSingleDigitHeight
- else interpBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt(),
- heightMode,
- )
- }
+ return VPointF(
+ when {
+ mode.x == EXACTLY -> MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ isSingleDigit() -> maxSingleDigitWidth
+ else -> interpBounds.width() + 2 * lockScreenPaint.strokeWidth
+ },
+ when {
+ mode.y == EXACTLY -> MeasureSpec.getSize(heightMeasureSpec).toFloat()
+ isSingleDigit() -> maxSingleDigitHeight
+ else -> interpBounds.height() + 2 * lockScreenPaint.strokeWidth
+ },
+ )
+ }
- val widthSpec =
- if (widthMode == EXACTLY) {
- widthMeasureSpec
- } else {
- MeasureSpec.makeMeasureSpec(
- if (isSingleDigit()) maxSingleDigitWidth
- else interpBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
- widthMode,
- )
- }
+ /** Set the measured size of the view to match the interpolated text bounds */
+ private fun setInterpolatedSize(
+ measureBounds: VPointF,
+ widthMeasureSpec: Int = measuredWidthAndState,
+ heightMeasureSpec: Int = measuredHeightAndState,
+ ) {
+ val mode =
+ VPoint(
+ x = MeasureSpec.getMode(widthMeasureSpec),
+ y = MeasureSpec.getMode(heightMeasureSpec),
+ )
- setMeasuredDimension(widthSpec, heightSpec)
+ setMeasuredDimension(
+ MeasureSpec.makeMeasureSpec(measureBounds.x.roundToInt(), mode.x),
+ MeasureSpec.makeMeasureSpec(measureBounds.y.roundToInt(), mode.y),
+ )
}
- private fun updateXTranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
- when (horizontalAlignment) {
- HorizontalAlignment.LEFT -> {
- inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left
+ /** Set the location of the view to match the interpolated text bounds */
+ private fun setInterpolatedLocation(measureSize: VPointF): RectF {
+ val targetRect = RectF()
+ targetRect.apply {
+ when (horizontalAlignment) {
+ HorizontalAlignment.LEFT -> {
+ left = layoutBounds.left
+ right = layoutBounds.left + measureSize.x
+ }
+ HorizontalAlignment.CENTER -> {
+ left = layoutBounds.centerX() - measureSize.x / 2f
+ right = layoutBounds.centerX() + measureSize.x / 2f
+ }
+ HorizontalAlignment.RIGHT -> {
+ left = layoutBounds.right - measureSize.x
+ right = layoutBounds.right
+ }
}
- HorizontalAlignment.RIGHT -> {
- inPoint.x =
- measuredWidth -
- interpolatedTextBounds.right -
- lockScreenPaint.strokeWidth.toInt()
- }
- HorizontalAlignment.CENTER -> {
- inPoint.x =
- (measuredWidth - interpolatedTextBounds.width()) / 2 -
- interpolatedTextBounds.left
- }
- }
- return inPoint
- }
- // translation of reference point of text
- // used for translation when calling textInterpolator
- private fun getLocalTranslation(): Point {
- val interpolatedTextBounds = getInterpolatedTextBounds()
- setInterpolatedViewBounds(interpolatedTextBounds)
-
- val localTranslation = Point(0, 0)
- val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure
- // get the change from current baseline to expected baseline
- when (verticalAlignment) {
- VerticalAlignment.CENTER -> {
- localTranslation.y =
- ((measuredHeight - interpolatedTextBounds.height()) / 2 -
- interpolatedTextBounds.top -
- correctedBaseline)
- }
- VerticalAlignment.TOP -> {
- localTranslation.y =
- (-interpolatedTextBounds.top + lockScreenPaint.strokeWidth - correctedBaseline)
- .toInt()
- }
- VerticalAlignment.BOTTOM -> {
- localTranslation.y =
- measuredHeight -
- interpolatedTextBounds.bottom -
- lockScreenPaint.strokeWidth.toInt() -
- correctedBaseline
- }
- VerticalAlignment.BASELINE -> {
- // account for max bottom distance of font, so clock doesn't collide with elements
- localTranslation.y =
- -lockScreenPaint.strokeWidth.toInt() - paint.fontMetrics.descent.toInt()
+ when (verticalAlignment) {
+ VerticalAlignment.TOP -> {
+ top = layoutBounds.top
+ bottom = layoutBounds.top + measureSize.y
+ }
+ VerticalAlignment.CENTER -> {
+ top = layoutBounds.centerY() - measureSize.y / 2f
+ bottom = layoutBounds.centerY() + measureSize.y / 2f
+ }
+ VerticalAlignment.BOTTOM -> {
+ top = layoutBounds.bottom - measureSize.y
+ bottom = layoutBounds.bottom
+ }
+ VerticalAlignment.BASELINE -> {
+ top = layoutBounds.centerY() - measureSize.y / 2f
+ bottom = layoutBounds.centerY() + measureSize.y / 2f
+ }
}
}
- return updateXTranslation(localTranslation, interpolatedTextBounds)
+ setFrame(
+ targetRect.left.roundToInt(),
+ targetRect.top.roundToInt(),
+ targetRect.right.roundToInt(),
+ targetRect.bottom.roundToInt(),
+ )
+ return targetRect
+ }
+
+ private fun getDrawTranslation(interpBounds: RectF): VPointF {
+ val sizeDiff = this.measuredSize - interpBounds.size
+ val alignment =
+ VPointF(
+ when (horizontalAlignment) {
+ HorizontalAlignment.LEFT -> 0f
+ HorizontalAlignment.CENTER -> 0.5f
+ HorizontalAlignment.RIGHT -> 1f
+ },
+ when (verticalAlignment) {
+ VerticalAlignment.TOP -> 0f
+ VerticalAlignment.CENTER -> 0.5f
+ VerticalAlignment.BASELINE -> 0.5f
+ VerticalAlignment.BOTTOM -> 1f
+ },
+ )
+ val renderCorrection =
+ VPointF(
+ x = -interpBounds.left,
+ y = -interpBounds.top - (if (baseline != -1) baseline else measuredBaseline),
+ )
+ return sizeDiff * alignment + renderCorrection
}
fun applyStyles(textStyle: FontTextStyle, aodStyle: FontTextStyle?) {
@@ -476,7 +531,7 @@ open class SimpleDigitalClockTextView(
lockScreenPaint.strokeJoin = Paint.Join.ROUND
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
- textStyle.lineHeight?.let { lineHeight = it.toInt() }
+ textStyle.lineHeight?.let { lineHeight = it.roundToInt() }
this.aodStyle = aodStyle ?: textStyle.copy()
aodDozingInterpolator = this.aodStyle.transitionInterpolator ?: Interpolators.LINEAR
@@ -487,7 +542,7 @@ open class SimpleDigitalClockTextView(
invalidate()
}
- // When constrainedByHeight is on, targetFontSizePx is the constrained height of textView
+ /** When constrainedByHeight is on, targetFontSizePx is the constrained height of textView */
fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean = false) {
val adjustedFontSizePx = adjustFontSize(targetFontSizePx, constrainedByHeight)
val fontSizePx = adjustedFontSizePx * (textStyle.fontSizeScale ?: 1f)
@@ -496,7 +551,7 @@ open class SimpleDigitalClockTextView(
if (fontSizePx > 0) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
lockScreenPaint.textSize = textSize
- lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ lockScreenPaint.getTextBounds(text, textBounds)
targetTextBounds.set(textBounds)
}
if (!constrainedByHeight) {
@@ -513,20 +568,19 @@ open class SimpleDigitalClockTextView(
}
private fun recomputeMaxSingleDigitSizes() {
- val rectForCalculate = Rect()
- maxSingleDigitHeight = 0
- maxSingleDigitWidth = 0
+ maxSingleDigitHeight = 0f
+ maxSingleDigitWidth = 0f
for (i in 0..9) {
- lockScreenPaint.getTextBounds("$i", 0, 1, rectForCalculate)
+ val rectForCalculate = lockScreenPaint.getTextBounds("$i")
maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height())
maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width())
}
- maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth.toInt()
- maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth.toInt()
+ maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth
+ maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth
}
- // called without animation, can be used to set the initial state of animator
+ /** Called without animation, can be used to set the initial state of animator */
private fun setInterpolatorPaint() {
if (this::textAnimator.isInitialized) {
// set initial style
@@ -542,25 +596,19 @@ open class SimpleDigitalClockTextView(
}
}
- /* Called after textAnimator.setTextStyle
- * textAnimator.setTextStyle will update targetPaint,
- * and rebase if previous animator is canceled
- * so basePaint will store the state we transition from
+ /**
+ * Called after textAnimator.setTextStyle textAnimator.setTextStyle will update targetPaint, and
+ * rebase if previous animator is canceled so basePaint will store the state we transition from
* and targetPaint will store the state we transition to
*/
private fun updateTextBoundsForTextAnimator() {
- textAnimator.textInterpolator.basePaint.getTextBounds(text, 0, text.length, prevTextBounds)
- textAnimator.textInterpolator.targetPaint.getTextBounds(
- text,
- 0,
- text.length,
- targetTextBounds,
- )
+ textAnimator.textInterpolator.basePaint.getTextBounds(text, prevTextBounds)
+ textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds)
}
- /*
- * Adjust text size to adapt to large display / font size
- * where the text view will be constrained by height
+ /**
+ * Adjust text size to adapt to large display / font size where the text view will be
+ * constrained by height
*/
private fun adjustFontSize(targetFontSizePx: Float?, constrainedByHeight: Boolean): Float {
return if (constrainedByHeight) {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
index 3ed321e48cd3..02a3902a042c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt
@@ -16,7 +16,9 @@
package com.android.systemui.plugins.clocks
+import android.graphics.Rect
import android.view.View
+import android.view.View.MeasureSpec
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
@@ -46,12 +48,21 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
- fun onMeasure() {
- d("onMeasure()")
+ fun onMeasure(widthSpec: Int, heightSpec: Int) {
+ d({ "onMeasure(${getSpecText(int1)}, ${getSpecText(int2)})" }) {
+ int1 = widthSpec
+ int2 = heightSpec
+ }
}
- fun onLayout() {
- d("onLayout()")
+ fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ d({ "onLayout($bool1, ${Rect(int1, int2, long1.toInt(), long2.toInt())})" }) {
+ bool1 = changed
+ int1 = left
+ int2 = top
+ long1 = right.toLong()
+ long2 = bottom.toLong()
+ }
}
fun onDraw() {
@@ -90,8 +101,8 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
- fun addView(child: View) {
- d({ "addView($str1 @$int1)" }) {
+ fun onViewAdded(child: View) {
+ d({ "onViewAdded($str1 @$int1)" }) {
str1 = child::class.simpleName!!
int1 = child.id
}
@@ -133,6 +144,20 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
@JvmStatic
+ fun getSpecText(spec: Int): String {
+ val size = MeasureSpec.getSize(spec)
+ val mode = MeasureSpec.getMode(spec)
+ val modeText =
+ when (mode) {
+ MeasureSpec.EXACTLY -> "EXACTLY"
+ MeasureSpec.AT_MOST -> "AT MOST"
+ MeasureSpec.UNSPECIFIED -> "UNSPECIFIED"
+ else -> "$mode"
+ }
+ return "($size, $modeText)"
+ }
+
+ @JvmStatic
fun escapeTime(timeStr: String?): String? {
return timeStr?.replace("\n", "\\n")
}