一、状态栏显示AM/PM
先查看状态栏的布局文件:\vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\status_bar.xml
找到和时间相关的布局:
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:gravity="center_vertical|start"
/>
再根据此自定义的java文件:
\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\Clock.java
其是继承TextView的自定义时钟布局文件,其中定义了三个跟AM/PM显示有关的静态全局变量:
private static final int AM_PM_STYLE_NORMAL = 0;// AM/PM大小与前面时间的大小一致。
private static final int AM_PM_STYLE_SMALL = 1;// AM/PM大小是前面时间的大小的0.7倍,具体是在getSmallTime()方法中实现。
private static final int AM_PM_STYLE_GONE = 2;// AM/PM不可见
状态栏显示AM/PM的修改方法如下(在其构造函数中进行修改):
public Clock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mCommandQueue = Dependency.get(CommandQueue.class);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.Clock,
0, 0);
try {
mSystemUIFactoryBase = OpSystemUICustomizationFactoryBase.getOpFactory(context);
mStatusBarExt = mSystemUIFactoryBase.makeSystemUIStatusBar(context);
/*mAmPmStyle = mStatusBarExt.getClockAmPmStyle(a.getInt(R.styleable.Clock_amPmStyle,
AM_PM_STYLE_GONE));*/
mAmPmStyle = mStatusBarExt.getClockAmPmStyle(a.getInt(R.styleable.Clock_amPmStyle,
AM_PM_STYLE_SMALL));// 设置为AM/PM相对时间0.7倍显示,具体为啥为0.7倍参考方法getSmallTime
mNonAdaptedColor = getCurrentTextColor();
} finally {
a.recycle();
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mUserTracker = Dependency.get(UserTracker.class);
}
查看Clock.java下的getSmallTime()方法:
private final CharSequence getSmallTime() {
Context context = getContext();
boolean is24 = DateFormat.is24HourFormat(context, mCurrentUserId);
if (mDateTimePatternGenerator == null) {
// Despite its name, getInstance creates a cloned instance, so reuse the generator to
// avoid unnecessary churn.
mDateTimePatternGenerator = DateTimePatternGenerator.getInstance(
context.getResources().getConfiguration().locale);
}
final char MAGIC1 = '\uEF00';
final char MAGIC2 = '\uEF01';
final String formatSkeleton = mShowSeconds
? is24 ? "Hms" : "hms"
: is24 ? "Hm" : "hm";
String format = mDateTimePatternGenerator.getBestPattern(formatSkeleton);
if (!format.equals(mContentDescriptionFormatString)) {
mContentDescriptionFormatString = format;
mContentDescriptionFormat = new SimpleDateFormat(format);
/*
* Search for an unquoted "a" in the format string, so we can
* add marker characters around it to let us find it again after
* formatting and change its size.
*/
if (mAmPmStyle != AM_PM_STYLE_NORMAL) {
int a = -1;
boolean quoted = false;
for (int i = 0; i < format.length(); i++) {
char c = format.charAt(i);
if (c == '\'') {
quoted = !quoted;
}
if (!quoted && c == 'a') {
a = i;
break;
}
}
if (a >= 0) {
// Move a back so any whitespace before AM/PM is also in the alternate size.
final int b = a;
while (a > 0 && Character.isWhitespace(format.charAt(a-1))) {
a--;
}
format = format.substring(0, a) + MAGIC1 + format.substring(a, b)
+ "a" + MAGIC2 + format.substring(b + 1);
}
}
mClockFormat = new SimpleDateFormat(format);
}
String result = mClockFormat.format(mCalendar.getTime());
if (mAmPmStyle != AM_PM_STYLE_NORMAL) {
int magic1 = result.indexOf(MAGIC1);
int magic2 = result.indexOf(MAGIC2);
if (magic1 >= 0 && magic2 > magic1) {
SpannableStringBuilder formatted = new SpannableStringBuilder(result);
if (mAmPmStyle == AM_PM_STYLE_GONE) {
formatted.delete(magic1, magic2+1);
} else {
if (mAmPmStyle == AM_PM_STYLE_SMALL) {
CharacterStyle style = new RelativeSizeSpan(0.7f);// 此RelativeSizeSpan就是实现文字相对大小的样式,故为上述提到的AM/PM为时间大小的0.7倍
formatted.setSpan(style, magic1, magic2,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
formatted.delete(magic2, magic2 + 1);
formatted.delete(magic1, magic1 + 1);
}
return formatted;
}
}
return result;
}
二、锁屏界面显示AM/PM
锁屏界面对应的布局文件:\SystemUI\res-keyguard\layout\keyguard_clock_switch.xml
# 锁屏界面显示通知时的时间布局
<com.android.keyguard.KeyguardClockFrame
android:id="@+id/lockscreen_clock_view"
android:layout_width="match_parent"
android:layout_height="@dimen/small_clock_height"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:clipChildren="false"
android:paddingStart="@dimen/clock_padding_start"
android:visibility="invisible" />
# 锁屏界面不显示通知的时间布局
<com.android.keyguard.KeyguardClockFrame
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:visibility="gone" />
根据id查询具体布局使用到的位置:选中id对应的参数,Ctrl + N
查询到文件:SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
在如下加载布局的方法使用:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
onConfigChanged();
}
再根据对mSmallClockFrame、mLargeClockFrame两个参数在上述Java文件的查询和跟踪,发现在下面方法中设置了时间的布局:
void setClock(ClockController clock, int statusBarState) {
mClock = clock;
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
if (clock == null) {
if (mLogBuffer != null) {
mLogBuffer.log(TAG, LogLevel.ERROR, "No clock being shown");
}
return;
}
// Attach small and big clock views to hierarchy.
if (mLogBuffer != null) {
mLogBuffer.log(TAG, LogLevel.INFO, "Attached new clock views to switch");
}
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
updateClockTargetRegions();
updateStatusArea(/* animate= */false);
}
在通过查找ClockController,发现其为定义在下面文件中的一个接口:
\vendor\mediatek\proprietary\packages\apps\SystemUI\plugin\src\com\android\systemui\plugins\ClockProviderPlugin.kt
/** Interface for controlling an active clock */
interface ClockController {
/** A small version of the clock, appropriate for smaller viewports */
val smallClock: ClockFaceController
/** A large version of the clock, appropriate when a bigger viewport is available */
val largeClock: ClockFaceController
/** Determines the way the hosting app should behave when rendering either clock face */
val config: ClockConfig
/** Events that clocks may need to respond to */
val events: ClockEvents
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
fun initialize(
resources: Resources,
dozeFraction: Float,
foldFraction: Float,
)
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter)
}
在溯源ClockFaceController,发现其定义在如下文件中了一个内部类:
\vendor\mediatek\proprietary\packages\apps\SystemUI\customization\src\com\android\systemui\shared\clocks\DefaultClockController.kt
查看此文件,其中的init方法如下:
init {
val parent = FrameLayout(ctx)
smallClock =
DefaultClockFaceController(
layoutInflater.inflate(R.layout.clock_default_small, parent, false)
as AnimatableClockView,
settings?.seedColor
)
largeClock =
LargeClockFaceController(
layoutInflater.inflate(R.layout.clock_default_large, parent, false)
as AnimatableClockView,
settings?.seedColor
)
clocks = listOf(smallClock.view, largeClock.view)
events = DefaultClockEvents()
events.onLocaleChanged(Locale.getDefault())
}
其中显示全部通知时的锁屏时间布局为:\vendor\mediatek\proprietary\packages\apps\SystemUI\customization\res\layout\clock_default_small.xml
不显示通知的锁屏时间布局为:
\vendor\mediatek\proprietary\packages\apps\SystemUI\customization\res\layout\clock_default_large.xml
下面查看clock_default_small.xml:
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:textSize="@dimen/small_clock_text_size"
android:fontFamily="@*android:string/config_clockFontFamily"
android:elegantTextHeight="false"
android:ellipsize="none"
android:singleLine="true"
android:fontFeatureSettings="pnum"
chargeAnimationDelay="350"
dozeWeight="200"
lockScreenWeight="400" />
其也是一个自定义布局:\vendor\mediatek\proprietary\packages\apps\SystemUI\customization\src\com\android\systemui\shared\clocks\AnimatableClockView.kt
锁屏界面显示AM/PM具体的修改如下:
fun refreshTime() {
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
//add code
var formattedText = DateFormat.format(format, time)
val formattedTextStr = trimAllWhitespace(formattedText.toString())
if (format!!.contains("a")) {
var formatted = SpannableStringBuilder(formattedTextStr)
val ampm = DateFormat.format("a", time).toString()
val style = RelativeSizeSpan(0.5f) //参考Clock.java中设置字体风格
val ampmStart = formattedTextStr!!.indexOf(ampm, 0)
val ampmLength = ampm.length
formatted.setSpan(style, ampmStart, ampmStart + ampmLength, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
formattedText = formatted
}
//end code
logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: new formattedText=$str1" }
)
// Setting text actually triggers a layout pass (because the text view is set to
// wrap_content width and TextView always relayouts for this). Avoid needless
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
logBuffer?.log(TAG, DEBUG,
{ str1 = formattedText?.toString() },
{ "refreshTime: done setting new time text to: $str1" }
)
// Because the TextLayout may mutate under the hood as a result of the new text, we
// notify the TextAnimator that it may have changed and request a measure/layout. A
// crash will occur on the next invocation of setTextStyle if the layout is mutated
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
}
requestLayout()
logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
}
}
// add function 去除解析的时间格式字符串中间的空白字符串
fun trimAllWhitespace(str: String?): String? {
if (str != null) {
val len = str.length
if (len > 0) {
val dest = CharArray(len)
var destPos = 0
for (i in 0 until len) {
val c = str[i]
if (!java.lang.Character.isWhitespace(c)) {
dest[destPos++] = c
}
}
return String(dest, 0, destPos)
}
}
return str
}
做到此处,还是不能解决,需要修改下面参数,还是在AnimatableClockView.kt中,修改下面参数:
@VisibleForTesting
var isAnimationEnabled: Boolean = false//true
取消动画,不然字体还是会显示为与时间同样大小(因为需求是需要设置锁屏界面的AM/PM比时间小)。
如果显示尺寸过大,则需要修改下面参数:
\vendor\mediatek\proprietary\packages\apps\SystemUI\customization\res\values\dimens.xml
<resources>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">150dp</dimen>
<dimen name="small_clock_text_size">60dp</dimen>
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
<!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
</resources>
对上面的large_clock_text_size和small_clock_text_size进行修改。
最后编译运行解决。