diff options
| -rw-r--r-- | packages/SystemUI/customization/res/values/dimens.xml | 1 | ||||
| -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.kt | 54 | ||||
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt | 12 | ||||
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt | 224 | ||||
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt | 38 | ||||
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt | 272 | ||||
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt | 342 | ||||
| -rw-r--r-- | packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt | 37 |
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") } |