Add support for LS Clock to use > 1 Paint object
Updated TextInterpolator and TextAnimator to support a Paint object for each line of text. Now we can interpolate between the colors when animating. Clock colors are now based on the theme (for now, wallpaper text color). ie: first line (of vertical clock) is one color, second line is another color. Also adjusted Keyguard clock logic to animate on doze changes instead of animating incrementally on setDarkAmount. Test: atest TextInterpolatorTest TextAnimatorTest Bug: 170228350 Change-Id: Ia62f2de48534be2007afc5a95eac46b3c44ec880
This commit is contained in:
@@ -69,8 +69,8 @@
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:visibility="gone">
|
||||
<com.android.keyguard.GradientTextClock
|
||||
android:id="@+id/gradient_clock_view"
|
||||
<com.android.keyguard.AnimatableClockView
|
||||
android:id="@+id/animatable_clock_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="80dp"
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
/**
|
||||
* Controls the color of a GradientTextClock.
|
||||
*/
|
||||
public class AnimatableClockController extends ViewController<AnimatableClockView> {
|
||||
|
||||
private final StatusBarStateController mStatusBarStateController;
|
||||
private final int[] mDozingColors = new int[] {Color.WHITE, Color.WHITE};
|
||||
private int[] mLockScreenColors = new int[2];
|
||||
|
||||
private boolean mIsDozing;
|
||||
|
||||
public AnimatableClockController(
|
||||
AnimatableClockView view,
|
||||
StatusBarStateController statusBarStateController) {
|
||||
super(view);
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
mIsDozing = mStatusBarStateController.isDozing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
mStatusBarStateController.addCallback(mStatusBarStateListener);
|
||||
mIsDozing = mStatusBarStateController.isDozing();
|
||||
refreshTime();
|
||||
initColors();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
mStatusBarStateController.removeCallback(mStatusBarStateListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the time for this view.
|
||||
*/
|
||||
public void refreshTime() {
|
||||
mView.refreshTime();
|
||||
}
|
||||
|
||||
private void initColors() {
|
||||
mLockScreenColors[0] = Utils.getColorAttrDefaultColor(getContext(),
|
||||
com.android.systemui.R.attr.wallpaperTextColor);
|
||||
mLockScreenColors[1] = Utils.getColorAttrDefaultColor(getContext(),
|
||||
com.android.systemui.R.attr.wallpaperTextColorSecondary);
|
||||
mView.setColors(mDozingColors, mLockScreenColors);
|
||||
mView.animateDoze(mIsDozing, false);
|
||||
}
|
||||
|
||||
private final StatusBarStateController.StateListener mStatusBarStateListener =
|
||||
new StatusBarStateController.StateListener() {
|
||||
@Override
|
||||
public void onDozingChanged(boolean isDozing) {
|
||||
mIsDozing = isDozing;
|
||||
mView.animateDoze(mIsDozing, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import android.annotation.FloatRange;
|
||||
import android.annotation.IntRange;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.icu.text.DateTimePatternGenerator;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
||||
* The time's text color is a gradient that changes its colors based on its controller.
|
||||
*/
|
||||
public class AnimatableClockView extends TextView {
|
||||
private static final CharSequence FORMAT_12_HOUR = "hh\nmm";
|
||||
private static final CharSequence FORMAT_24_HOUR = "HH\nmm";
|
||||
private static final long ANIM_DURATION = 300;
|
||||
|
||||
private CharSequence mFormat;
|
||||
private CharSequence mDescFormat;
|
||||
private Calendar mTime = Calendar.getInstance();
|
||||
private int[] mDozingColors;
|
||||
private int[] mLockScreenColors;
|
||||
|
||||
private TextAnimator mTextAnimator = null;
|
||||
private Runnable mOnTextAnimatorInitialized;
|
||||
|
||||
public AnimatableClockView(Context context) {
|
||||
this(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
updateTimeFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
updateTimeFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
void refreshTime() {
|
||||
mTime.setTimeInMillis(System.currentTimeMillis());
|
||||
setText(DateFormat.format(mFormat, mTime));
|
||||
setContentDescription(DateFormat.format(mDescFormat, mTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mTextAnimator == null) {
|
||||
mTextAnimator = new TextAnimator(
|
||||
getLayout(),
|
||||
() -> {
|
||||
invalidate();
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
2 /* number of lines (each can have a unique Paint) */);
|
||||
if (mOnTextAnimatorInitialized != null) {
|
||||
mOnTextAnimatorInitialized.run();
|
||||
mOnTextAnimatorInitialized = null;
|
||||
}
|
||||
} else {
|
||||
mTextAnimator.updateLayout(getLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
mTextAnimator.draw(canvas);
|
||||
}
|
||||
|
||||
void setColors(int[] dozingColors, int[] lockScreenColors) {
|
||||
mDozingColors = dozingColors;
|
||||
mLockScreenColors = lockScreenColors;
|
||||
}
|
||||
|
||||
void animateDoze(boolean isDozing, boolean animate) {
|
||||
setTextStyle(isDozing ? 100 : 300 /* weight */,
|
||||
-1,
|
||||
isDozing ? mDozingColors : mLockScreenColors,
|
||||
animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text style with an optional animation.
|
||||
*
|
||||
* By passing -1 to weight, the view preserves its current weight.
|
||||
* By passing -1 to textSize, the view preserves its current text size.
|
||||
*
|
||||
* @param weight text weight.
|
||||
* @param textSize font size.
|
||||
* @param animate true to animate the text style change, otherwise false.
|
||||
*/
|
||||
private void setTextStyle(
|
||||
@IntRange(from = 0, to = 1000) int weight,
|
||||
@FloatRange(from = 0) float textSize,
|
||||
int[] colors,
|
||||
boolean animate) {
|
||||
if (mTextAnimator != null) {
|
||||
mTextAnimator.setTextStyle(weight, textSize, colors, animate, ANIM_DURATION, null);
|
||||
} else {
|
||||
// when the text animator is set, update its start values
|
||||
mOnTextAnimatorInitialized =
|
||||
() -> mTextAnimator.setTextStyle(
|
||||
weight, textSize, colors, false, ANIM_DURATION, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTimeFormat() {
|
||||
final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
|
||||
mFormat = use24HourFormat ? FORMAT_24_HOUR : FORMAT_12_HOUR;
|
||||
mDescFormat = getBestDateTimePattern(getContext(), use24HourFormat ? "Hm" : "hm");
|
||||
}
|
||||
|
||||
private static String getBestDateTimePattern(Context context, String skeleton) {
|
||||
DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
|
||||
context.getResources().getConfiguration().locale);
|
||||
return dtpg.getBestPattern(skeleton);
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import android.annotation.FloatRange;
|
||||
import android.annotation.IntRange;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextClock;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
|
||||
* The time's text color is a gradient that changes its colors based on its controller.
|
||||
*/
|
||||
public class GradientTextClock extends TextClock {
|
||||
private int[] mGradientColors;
|
||||
private float[] mPositions;
|
||||
|
||||
private TextAnimator mTextAnimator = null;
|
||||
|
||||
public GradientTextClock(Context context) {
|
||||
this(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public GradientTextClock(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
addOnLayoutChangeListener(mOnLayoutChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
removeOnLayoutChangeListener(mOnLayoutChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshTime() {
|
||||
super.refreshTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFormat12Hour(CharSequence format) {
|
||||
super.setFormat12Hour(FORMAT_12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFormat24Hour(CharSequence format) {
|
||||
super.setFormat24Hour(FORMAT_24);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mTextAnimator == null) {
|
||||
mTextAnimator = new TextAnimator(getLayout(), () -> {
|
||||
invalidate();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
} else {
|
||||
mTextAnimator.updateLayout(getLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
mTextAnimator.draw(canvas);
|
||||
}
|
||||
|
||||
public void setGradientColors(int[] colors) {
|
||||
mGradientColors = colors;
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
public void setColorPositions(float[] positions) {
|
||||
mPositions = positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text style with animation.
|
||||
*
|
||||
* By passing -1 to weight, the view preserve the current weight.
|
||||
* By passing -1 to textSize, the view preserve the current text size.
|
||||
*
|
||||
* @param weight text weight.
|
||||
* @param textSize font size.
|
||||
* @param animate true for changing text style with animation, otherwise false.
|
||||
*/
|
||||
public void setTextStyle(
|
||||
@IntRange(from = 0, to = 1000) int weight,
|
||||
@FloatRange(from = 0) float textSize,
|
||||
boolean animate) {
|
||||
if (mTextAnimator != null) {
|
||||
mTextAnimator.setTextStyle(weight, textSize, animate, -1, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePaint() {
|
||||
Shader shader = new LinearGradient(
|
||||
getX(), getY(), getX(), getMeasuredHeight() + getY(), mGradientColors, mPositions,
|
||||
Shader.TileMode.REPEAT);
|
||||
getPaint().setShader(shader);
|
||||
if (mTextAnimator != null) {
|
||||
mTextAnimator.setShader(shader);
|
||||
}
|
||||
}
|
||||
|
||||
private final OnLayoutChangeListener mOnLayoutChangeListener =
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
if (bottom != oldBottom || top != oldTop) {
|
||||
updatePaint();
|
||||
}
|
||||
};
|
||||
|
||||
public static final CharSequence FORMAT_12 = "hh\nmm";
|
||||
public static final CharSequence FORMAT_24 = "HH\nmm";
|
||||
}
|
||||
@@ -72,11 +72,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
*/
|
||||
private TextClock mClockViewBold;
|
||||
|
||||
/**
|
||||
* Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
|
||||
*/
|
||||
private TimeBasedColorsClockController mNewLockscreenClockViewController;
|
||||
|
||||
/**
|
||||
* Frame for clock when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
|
||||
*/
|
||||
@@ -157,7 +152,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
|
||||
mSmallClockFrame.setVisibility(GONE);
|
||||
mNewLockscreenClockFrame.setVisibility(VISIBLE);
|
||||
mNewLockscreenClockViewController.init();
|
||||
|
||||
statusAreaLP.removeRule(RelativeLayout.BELOW);
|
||||
statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.new_lockscreen_clock_view);
|
||||
@@ -181,8 +175,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
mClockView = findViewById(R.id.default_clock_view);
|
||||
mClockViewBold = findViewById(R.id.default_clock_view_bold);
|
||||
mNewLockscreenClockFrame = findViewById(R.id.new_lockscreen_clock_view);
|
||||
mNewLockscreenClockViewController =
|
||||
new TimeBasedColorsClockController(findViewById(R.id.gradient_clock_view));
|
||||
mSmallClockFrame = findViewById(R.id.clock_view);
|
||||
mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
|
||||
}
|
||||
@@ -305,7 +297,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
if (mClockPlugin != null) {
|
||||
mClockPlugin.setDarkAmount(darkAmount);
|
||||
}
|
||||
mNewLockscreenClockViewController.setDarkAmount(darkAmount);
|
||||
updateBigClockAlpha();
|
||||
}
|
||||
|
||||
@@ -356,7 +347,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
|
||||
*/
|
||||
public void refresh() {
|
||||
mNewLockscreenClockViewController.refreshTime(System.currentTimeMillis());
|
||||
mClockView.refreshTime();
|
||||
mClockViewBold.refreshTime();
|
||||
if (mClockPlugin != null) {
|
||||
|
||||
@@ -55,7 +55,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
private final ClockManager mClockManager;
|
||||
private final KeyguardSliceViewController mKeyguardSliceViewController;
|
||||
private final NotificationIconAreaController mNotificationIconAreaController;
|
||||
private FrameLayout mNewLockscreenClockFrame;
|
||||
|
||||
/**
|
||||
* Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
|
||||
*/
|
||||
private AnimatableClockController mNewLockScreenClockViewController;
|
||||
private FrameLayout mNewLockScreenClockFrame;
|
||||
|
||||
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
|
||||
|
||||
@@ -119,7 +124,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
mColorExtractor.addOnColorsChangedListener(mColorsListener);
|
||||
mView.updateColors(getGradientColors());
|
||||
updateAodIcons();
|
||||
mNewLockscreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
|
||||
mNewLockScreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,6 +187,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
* Refresh clock. Called in response to TIME_TICK broadcasts.
|
||||
*/
|
||||
void refresh() {
|
||||
if (mNewLockScreenClockViewController != null) {
|
||||
mNewLockScreenClockViewController.refreshTime();
|
||||
}
|
||||
|
||||
mView.refresh();
|
||||
}
|
||||
|
||||
@@ -192,8 +201,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
*/
|
||||
void updatePosition(int x, AnimationProperties props, boolean animate) {
|
||||
x = Math.abs(x);
|
||||
if (mNewLockscreenClockFrame != null) {
|
||||
PropertyAnimator.setProperty(mNewLockscreenClockFrame, AnimatableProperty.TRANSLATION_X,
|
||||
if (mNewLockScreenClockFrame != null) {
|
||||
PropertyAnimator.setProperty(mNewLockScreenClockFrame, AnimatableProperty.TRANSLATION_X,
|
||||
-x, props, animate);
|
||||
}
|
||||
mKeyguardSliceViewController.updatePosition(x, props, animate);
|
||||
@@ -205,6 +214,17 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
*/
|
||||
void updateLockScreenMode(int mode) {
|
||||
mLockScreenMode = mode;
|
||||
if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
|
||||
if (mNewLockScreenClockViewController == null) {
|
||||
mNewLockScreenClockViewController =
|
||||
new AnimatableClockController(
|
||||
mView.findViewById(R.id.animatable_clock_view),
|
||||
mStatusBarStateController);
|
||||
mNewLockScreenClockViewController.init();
|
||||
}
|
||||
} else {
|
||||
mNewLockScreenClockViewController = null;
|
||||
}
|
||||
mView.updateLockScreenMode(mLockScreenMode);
|
||||
updateAodIcons();
|
||||
}
|
||||
|
||||
@@ -21,11 +21,10 @@ import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.TimeInterpolator
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Shader
|
||||
import android.text.Layout
|
||||
|
||||
private const val TAG_WGHT = "wght"
|
||||
private const val DEFAULT_ANIMATION_DURATION: Long = 1000
|
||||
private const val DEFAULT_ANIMATION_DURATION: Long = 300
|
||||
|
||||
/**
|
||||
* This class provides text animation between two styles.
|
||||
@@ -53,9 +52,13 @@ private const val DEFAULT_ANIMATION_DURATION: Long = 1000
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
class TextAnimator(
|
||||
layout: Layout,
|
||||
private val invalidateCallback: () -> Unit,
|
||||
private val numLines: Int = 1
|
||||
) {
|
||||
// Following two members are for mutable for testing purposes.
|
||||
internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
|
||||
internal var textInterpolator: TextInterpolator = TextInterpolator(layout, numLines)
|
||||
internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
|
||||
duration = DEFAULT_ANIMATION_DURATION
|
||||
addUpdateListener {
|
||||
@@ -72,19 +75,6 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
textInterpolator.layout = layout
|
||||
}
|
||||
|
||||
var shader: Shader
|
||||
get() = textInterpolator.basePaint.shader.also {
|
||||
require(it === textInterpolator.targetPaint.shader) {
|
||||
"base and target paint has different shader. Usually shader is not interpolatable."
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
textInterpolator.basePaint.shader = value
|
||||
textInterpolator.targetPaint.shader = value
|
||||
// Shader doesn't change the text layout, so no need to call onTargetPaintModified or
|
||||
// onBasePaintModified
|
||||
}
|
||||
|
||||
fun draw(c: Canvas) = textInterpolator.draw(c)
|
||||
|
||||
/**
|
||||
@@ -97,6 +87,8 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
*
|
||||
* @param weight an optional text weight.
|
||||
* @param textSize an optional font size.
|
||||
* @param colors an optional colors array that must be the same size as numLines passed to
|
||||
* the TextInterpolator
|
||||
* @param animate an optional boolean indicating true for showing style transition as animation,
|
||||
* false for immediate style transition. True by default.
|
||||
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
|
||||
@@ -107,6 +99,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
fun setTextStyle(
|
||||
weight: Int = -1,
|
||||
textSize: Float = -1f,
|
||||
colors: IntArray? = null,
|
||||
animate: Boolean = true,
|
||||
duration: Long = -1L,
|
||||
interpolator: TimeInterpolator? = null
|
||||
@@ -117,10 +110,21 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
}
|
||||
|
||||
if (textSize >= 0) {
|
||||
textInterpolator.targetPaint.textSize = textSize
|
||||
for (targetPaint in textInterpolator.targetPaint)
|
||||
targetPaint.textSize = textSize
|
||||
}
|
||||
if (weight >= 0) {
|
||||
textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
|
||||
for (targetPaint in textInterpolator.targetPaint)
|
||||
targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
|
||||
}
|
||||
if (colors != null) {
|
||||
require(colors.size == textInterpolator.targetPaint.size) {
|
||||
"colors size (${colors.size}) must be the same size as" +
|
||||
" targetPaints size (${textInterpolator.targetPaint.size})," +
|
||||
" which was initialized as numLines ($numLines)"
|
||||
}
|
||||
for ((index, targetPaint) in textInterpolator.targetPaint.withIndex())
|
||||
targetPaint.color = colors[index]
|
||||
}
|
||||
textInterpolator.onTargetPaintModified()
|
||||
|
||||
@@ -138,4 +142,4 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||
textInterpolator.rebase()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,21 +22,28 @@ import android.graphics.text.PositionedGlyphs
|
||||
import android.graphics.text.TextRunShaper
|
||||
import android.text.Layout
|
||||
import android.util.MathUtils
|
||||
import com.android.internal.graphics.ColorUtils
|
||||
import java.lang.Math.max
|
||||
|
||||
/**
|
||||
* Provide text style linear interpolation for plain text.
|
||||
*/
|
||||
class TextInterpolator(layout: Layout) {
|
||||
class TextInterpolator(
|
||||
layout: Layout,
|
||||
lines: Int = 1
|
||||
) {
|
||||
|
||||
/**
|
||||
* Returns base paint used for interpolation.
|
||||
*
|
||||
* Once you modified the style parameters, you have to call reshapeText to recalculate base text
|
||||
* layout.
|
||||
*
|
||||
* @return a paint object.
|
||||
* @return an array list of paint objects representing one paint per line of text. If this
|
||||
* list has a smaller size than the number of lines, all extra lines will use the Paint an
|
||||
* index 0.
|
||||
*/
|
||||
val basePaint = Paint(layout.paint)
|
||||
val basePaint = createDefaultPaint(layout.paint, lines)
|
||||
|
||||
/**
|
||||
* Returns target paint used for interpolation.
|
||||
@@ -44,9 +51,18 @@ class TextInterpolator(layout: Layout) {
|
||||
* Once you modified the style parameters, you have to call reshapeText to recalculate target
|
||||
* text layout.
|
||||
*
|
||||
* @return a paint object
|
||||
* @return an array list of paint objects representing one paint per line of text. If this
|
||||
* list has a smaller size than the number of lines, all extra lines will use the Paint an
|
||||
* index 0.
|
||||
*/
|
||||
val targetPaint = Paint(layout.paint)
|
||||
val targetPaint = createDefaultPaint(layout.paint, lines)
|
||||
|
||||
private fun createDefaultPaint(paint: Paint, lines: Int): ArrayList<Paint> {
|
||||
val paintList = ArrayList<Paint>()
|
||||
for (i in 0 until lines)
|
||||
paintList.add(Paint(paint))
|
||||
return paintList
|
||||
}
|
||||
|
||||
/**
|
||||
* A class represents a single font run.
|
||||
@@ -78,7 +94,7 @@ class TextInterpolator(layout: Layout) {
|
||||
private val fontInterpolator = FontInterpolator()
|
||||
|
||||
// Recycling object for glyph drawing. Will be extended for the longest font run if needed.
|
||||
private val tmpDrawPaint = Paint()
|
||||
private val tmpDrawPaints = ArrayList<Paint>()
|
||||
private var tmpPositionArray = FloatArray(20)
|
||||
|
||||
/**
|
||||
@@ -115,8 +131,8 @@ class TextInterpolator(layout: Layout) {
|
||||
/**
|
||||
* Recalculate internal text layout for interpolation.
|
||||
*
|
||||
* Whenever you modifies target paint, you have to call this method to recalculate internal text
|
||||
* layout used for interpolation.
|
||||
* Whenever the target paint is modified, call this method to recalculate internal
|
||||
* text layout used for interpolation.
|
||||
*/
|
||||
fun onTargetPaintModified() {
|
||||
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
|
||||
@@ -125,8 +141,8 @@ class TextInterpolator(layout: Layout) {
|
||||
/**
|
||||
* Recalculate internal text layout for interpolation.
|
||||
*
|
||||
* Whenever you modifies base paint, you have to call this method to recalculate internal text
|
||||
* layout used for interpolation.
|
||||
* Whenever the base paint is modified, call this method to recalculate internal
|
||||
* text layout used for interpolation.
|
||||
*/
|
||||
fun onBasePaintModified() {
|
||||
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
|
||||
@@ -192,10 +208,10 @@ class TextInterpolator(layout: Layout) {
|
||||
if (progress == 0f) {
|
||||
return
|
||||
} else if (progress == 1f) {
|
||||
basePaint.set(targetPaint)
|
||||
updatePaint(basePaint, targetPaint)
|
||||
} else {
|
||||
lerp(basePaint, targetPaint, progress, tmpDrawPaint)
|
||||
basePaint.set(tmpDrawPaint)
|
||||
lerp(basePaint, targetPaint, progress, tmpDrawPaints)
|
||||
updatePaint(basePaint, tmpDrawPaints)
|
||||
}
|
||||
|
||||
lines.forEach { line ->
|
||||
@@ -211,13 +227,21 @@ class TextInterpolator(layout: Layout) {
|
||||
progress = 0f
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun updatePaint(toUpdate: ArrayList<Paint>, newValues: ArrayList<Paint>) {
|
||||
toUpdate.clear()
|
||||
for (paint in newValues)
|
||||
toUpdate.add(Paint(paint))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws interpolated text at the given progress.
|
||||
*
|
||||
* @param canvas a canvas.
|
||||
*/
|
||||
fun draw(canvas: Canvas) {
|
||||
lerp(basePaint, targetPaint, progress, tmpDrawPaint)
|
||||
lerp(basePaint, targetPaint, progress, tmpDrawPaints)
|
||||
lines.forEachIndexed { lineNo, line ->
|
||||
canvas.save()
|
||||
try {
|
||||
@@ -226,7 +250,10 @@ class TextInterpolator(layout: Layout) {
|
||||
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
|
||||
|
||||
line.fontRuns.forEach { run ->
|
||||
drawFontRun(canvas, line, run, tmpDrawPaint)
|
||||
if (lineNo >= tmpDrawPaints.size)
|
||||
drawFontRun(canvas, line, run, tmpDrawPaints[0])
|
||||
else
|
||||
drawFontRun(canvas, line, run, tmpDrawPaints[lineNo])
|
||||
}
|
||||
} finally {
|
||||
canvas.restore()
|
||||
@@ -384,15 +411,25 @@ class TextInterpolator(layout: Layout) {
|
||||
}
|
||||
|
||||
// Linear interpolate the paint.
|
||||
private fun lerp(from: Paint, to: Paint, t: Float, out: Paint) {
|
||||
// Currently only font size is interpolated.
|
||||
private fun lerp(
|
||||
from: ArrayList<Paint>,
|
||||
to: ArrayList<Paint>,
|
||||
progress: Float,
|
||||
out: ArrayList<Paint>
|
||||
) {
|
||||
out.clear()
|
||||
// Currently only font size & colors are interpolated.
|
||||
// TODO(172943390): Add other interpolation or support custom interpolator.
|
||||
out.set(from)
|
||||
out.textSize = MathUtils.lerp(from.textSize, to.textSize, t)
|
||||
for (index in from.indices) {
|
||||
val paint = Paint(from[index])
|
||||
paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress)
|
||||
paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress)
|
||||
out.add(paint)
|
||||
}
|
||||
}
|
||||
|
||||
// Shape the text and stores the result to out argument.
|
||||
private fun shapeText(layout: Layout, paint: Paint): List<PositionedGlyphs> {
|
||||
private fun shapeText(layout: Layout, paints: ArrayList<Paint>): List<PositionedGlyphs> {
|
||||
val out = mutableListOf<PositionedGlyphs>()
|
||||
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
|
||||
val lineStart = layout.getLineStart(lineNo)
|
||||
@@ -403,7 +440,7 @@ class TextInterpolator(layout: Layout) {
|
||||
lineStart, count, // shape context = shape range.
|
||||
0f, 0f, // the layout offset. Not changed.
|
||||
layout.getParagraphDirection(lineNo) == Layout.DIR_RIGHT_TO_LEFT,
|
||||
paint)) // Use given paint instead of layout's paint for style interpolation.
|
||||
paints[lineNo])) // Use given paint instead of layout's for style interpolation.
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -414,4 +451,4 @@ private fun Layout.getDrawOrigin(lineNo: Int) =
|
||||
getLineLeft(lineNo)
|
||||
} else {
|
||||
getLineRight(lineNo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import android.util.MathUtils;
|
||||
|
||||
import com.android.internal.graphics.ColorUtils;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* Changes the color of the text clock based on the time of day.
|
||||
*/
|
||||
public class TimeBasedColorsClockController extends ViewController<GradientTextClock> {
|
||||
private final int[] mGradientColors = new int[3];
|
||||
private final float[] mPositions = new float[3];
|
||||
|
||||
/**
|
||||
* 0 = fully awake
|
||||
* between 0 and 1 = transitioning between awake and doze
|
||||
* 1 = fully in doze
|
||||
*/
|
||||
private float mDarkAmount = 0f;
|
||||
|
||||
public TimeBasedColorsClockController(GradientTextClock view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
refreshTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the time for this view. Also updates any color changes.
|
||||
*/
|
||||
public void refreshTime(long timeInMillis) {
|
||||
updateColors(timeInMillis);
|
||||
updatePositions(timeInMillis);
|
||||
mView.refreshTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount (ratio) that the device has transitioned to doze.
|
||||
*
|
||||
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
|
||||
*/
|
||||
public void setDarkAmount(float darkAmount) {
|
||||
mDarkAmount = darkAmount;
|
||||
|
||||
refreshTime(System.currentTimeMillis());
|
||||
|
||||
int weight = (int) MathUtils.lerp(200, 400, 1f - darkAmount);
|
||||
mView.setTextStyle(weight, -1 /* unchange text size */, true);
|
||||
}
|
||||
|
||||
private int getTimeIndex(long timeInMillis) {
|
||||
Calendar now = getCalendar(timeInMillis);
|
||||
int hour = now.get(Calendar.HOUR_OF_DAY); // 0 - 23
|
||||
if (hour < mTimes[0]) {
|
||||
return mTimes.length - 1;
|
||||
}
|
||||
|
||||
for (int i = 1; i < mTimes.length; i++) {
|
||||
if (hour < mTimes[i]) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return mTimes.length - 1;
|
||||
}
|
||||
|
||||
private void updateColors(long timeInMillis) {
|
||||
final int index = getTimeIndex(timeInMillis);
|
||||
final int wallpaperTextColor =
|
||||
Utils.getColorAttrDefaultColor(mView.getContext(), R.attr.wallpaperTextColor);
|
||||
for (int i = 0; i < mGradientColors.length; i++) {
|
||||
// wallpaperTextColor on LS when mDarkAmount = 0f
|
||||
// full color on AOD when mDarkAmount = 1f
|
||||
mGradientColors[i] =
|
||||
ColorUtils.blendARGB(wallpaperTextColor, COLORS[index][i], mDarkAmount);
|
||||
}
|
||||
mView.setGradientColors(mGradientColors);
|
||||
}
|
||||
|
||||
private void updatePositions(long timeInMillis) {
|
||||
Calendar now = getCalendar(timeInMillis);
|
||||
final int index = getTimeIndex(timeInMillis);
|
||||
|
||||
final Calendar startTime = new GregorianCalendar();
|
||||
startTime.setTimeInMillis(now.getTimeInMillis());
|
||||
startTime.set(Calendar.HOUR_OF_DAY, mTimes[index]);
|
||||
if (startTime.getTimeInMillis() > now.getTimeInMillis()) {
|
||||
// start should be earlier than 'now'
|
||||
startTime.add(Calendar.DATE, -1);
|
||||
}
|
||||
|
||||
final Calendar endTime = new GregorianCalendar();
|
||||
endTime.setTimeInMillis(now.getTimeInMillis());
|
||||
if (index == mTimes.length - 1) {
|
||||
endTime.set(Calendar.HOUR_OF_DAY, mTimes[0]);
|
||||
endTime.add(Calendar.DATE, 1); // end time is tomorrow
|
||||
} else {
|
||||
endTime.set(Calendar.HOUR_OF_DAY, mTimes[index + 1]);
|
||||
}
|
||||
|
||||
long totalTimeInThisColorGradient = endTime.getTimeInMillis() - startTime.getTimeInMillis();
|
||||
long timeIntoThisColorGradient = now.getTimeInMillis() - startTime.getTimeInMillis();
|
||||
float percentageWithinGradient =
|
||||
(float) timeIntoThisColorGradient / (float) totalTimeInThisColorGradient;
|
||||
|
||||
for (int i = 0; i < mPositions.length; i++) {
|
||||
// currently hard-coded .3 movement of gradient
|
||||
mPositions[i] = POSITIONS[index][i] - (.3f * percentageWithinGradient);
|
||||
}
|
||||
mView.setColorPositions(mPositions);
|
||||
}
|
||||
|
||||
private Calendar getCalendar(long timeInMillis) {
|
||||
Calendar now = new GregorianCalendar();
|
||||
now.setTimeInMillis(timeInMillis);
|
||||
return now;
|
||||
}
|
||||
|
||||
private static final int[] SUNRISE = new int[] {0xFF6F75AA, 0xFFAFF0FF, 0xFFFFDEBF};
|
||||
private static final int[] DAY = new int[] {0xFF9BD8FB, 0xFFD7F5FF, 0xFFFFF278};
|
||||
private static final int[] NIGHT = new int[] {0xFF333D5E, 0xFFC5A1D6, 0xFF907359};
|
||||
|
||||
private static final float[] SUNRISE_START_POSITIONS = new float[] {.3f, .5f, .8f};
|
||||
private static final float[] DAY_START_POSITIONS = new float[] {.4f, .8f, 1f};
|
||||
private static final float[] NIGHT_START_POSITIONS = new float[] {.25f, .5f, .8f};
|
||||
|
||||
// TODO (b/170228350): use TwilightManager to set sunrise/sunset times
|
||||
private final int mSunriseTime = 6; // 6am
|
||||
private final int mDaytime = 9; // 9 am
|
||||
private final int mNightTime = 19; // 7pm
|
||||
|
||||
private int[] mTimes = new int[] {
|
||||
mSunriseTime,
|
||||
mDaytime,
|
||||
mNightTime
|
||||
};
|
||||
private static final int[][] COLORS = new int[][] {
|
||||
SUNRISE,
|
||||
DAY,
|
||||
NIGHT
|
||||
};
|
||||
private static final float[][] POSITIONS = new float[][] {
|
||||
SUNRISE_START_POSITIONS,
|
||||
DAY_START_POSITIONS,
|
||||
NIGHT_START_POSITIONS
|
||||
};
|
||||
}
|
||||
@@ -35,9 +35,9 @@ import org.mockito.Mockito.verify
|
||||
|
||||
import kotlin.math.ceil
|
||||
|
||||
private val PAINT = TextPaint().apply {
|
||||
private val PAINT = arrayListOf(TextPaint().apply {
|
||||
textSize = 32f
|
||||
}
|
||||
})
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
@@ -50,10 +50,10 @@ class TextAnimatorTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testAnimationStarted() {
|
||||
val layout = makeLayout("Hello, World", PAINT)
|
||||
val layout = makeLayout("Hello, World", PAINT[0])
|
||||
val valueAnimator = mock(ValueAnimator::class.java)
|
||||
val textInterpolator = mock(TextInterpolator::class.java)
|
||||
val paint = mock(Paint::class.java)
|
||||
val paint = arrayListOf(mock(Paint::class.java))
|
||||
`when`(textInterpolator.targetPaint).thenReturn(paint)
|
||||
|
||||
val textAnimator = TextAnimator(layout, {}).apply {
|
||||
@@ -82,10 +82,10 @@ class TextAnimatorTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testAnimationNotStarted() {
|
||||
val layout = makeLayout("Hello, World", PAINT)
|
||||
val layout = makeLayout("Hello, World", PAINT[0])
|
||||
val valueAnimator = mock(ValueAnimator::class.java)
|
||||
val textInterpolator = mock(TextInterpolator::class.java)
|
||||
val paint = mock(Paint::class.java)
|
||||
val paint = arrayListOf(mock(Paint::class.java))
|
||||
`when`(textInterpolator.targetPaint).thenReturn(paint)
|
||||
|
||||
val textAnimator = TextAnimator(layout, {}).apply {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.keyguard
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
@@ -37,13 +38,13 @@ private val PAINT = TextPaint().apply {
|
||||
textSize = 32f
|
||||
}
|
||||
|
||||
private val START_PAINT = TextPaint(PAINT).apply {
|
||||
private val START_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply {
|
||||
fontVariationSettings = "'wght' 400"
|
||||
}
|
||||
})
|
||||
|
||||
private val END_PAINT = TextPaint(PAINT).apply {
|
||||
private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply {
|
||||
fontVariationSettings = "'wght' 700"
|
||||
}
|
||||
})
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
@@ -59,16 +60,16 @@ class TextInterpolatorTest : SysuiTestCase() {
|
||||
val layout = makeLayout(TEXT, PAINT)
|
||||
|
||||
val interp = TextInterpolator(layout)
|
||||
interp.basePaint.set(START_PAINT)
|
||||
TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
|
||||
interp.onBasePaintModified()
|
||||
|
||||
interp.targetPaint.set(END_PAINT)
|
||||
TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
|
||||
interp.onTargetPaintModified()
|
||||
|
||||
// Just after created TextInterpolator, it should have 0 progress.
|
||||
assertThat(interp.progress).isEqualTo(0f)
|
||||
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
val expected = makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
val expected = makeLayout(TEXT, START_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
|
||||
assertThat(expected.sameAs(actual)).isTrue()
|
||||
}
|
||||
@@ -78,15 +79,15 @@ class TextInterpolatorTest : SysuiTestCase() {
|
||||
val layout = makeLayout(TEXT, PAINT)
|
||||
|
||||
val interp = TextInterpolator(layout)
|
||||
interp.basePaint.set(START_PAINT)
|
||||
TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
|
||||
interp.onBasePaintModified()
|
||||
|
||||
interp.targetPaint.set(END_PAINT)
|
||||
TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
|
||||
interp.onTargetPaintModified()
|
||||
|
||||
interp.progress = 1f
|
||||
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
val expected = makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
val expected = makeLayout(TEXT, END_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
|
||||
assertThat(expected.sameAs(actual)).isTrue()
|
||||
}
|
||||
@@ -96,10 +97,10 @@ class TextInterpolatorTest : SysuiTestCase() {
|
||||
val layout = makeLayout(TEXT, PAINT)
|
||||
|
||||
val interp = TextInterpolator(layout)
|
||||
interp.basePaint.set(START_PAINT)
|
||||
TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
|
||||
interp.onBasePaintModified()
|
||||
|
||||
interp.targetPaint.set(END_PAINT)
|
||||
TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
|
||||
interp.onTargetPaintModified()
|
||||
|
||||
// We cannot expect exact text layout of the middle position since we don't use text shaping
|
||||
@@ -107,10 +108,10 @@ class TextInterpolatorTest : SysuiTestCase() {
|
||||
// end state.
|
||||
interp.progress = 0.5f
|
||||
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
|
||||
assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)))
|
||||
.isFalse()
|
||||
assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)))
|
||||
.isFalse()
|
||||
assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0] as TextPaint)
|
||||
.toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
|
||||
assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0] as TextPaint)
|
||||
.toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,10 +119,10 @@ class TextInterpolatorTest : SysuiTestCase() {
|
||||
val layout = makeLayout(TEXT, PAINT)
|
||||
|
||||
val interp = TextInterpolator(layout)
|
||||
interp.basePaint.set(START_PAINT)
|
||||
TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
|
||||
interp.onBasePaintModified()
|
||||
|
||||
interp.targetPaint.set(END_PAINT)
|
||||
TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
|
||||
interp.onTargetPaintModified()
|
||||
|
||||
interp.progress = 0.5f
|
||||
|
||||
Reference in New Issue
Block a user