

Android 12 - Letterbox 模式
source link: https://blog.hanschen.site/2021/10/21/android-12-letterbox/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android 12 - Letterbox 模式
随着越来越多大屏和折叠屏设备出现,很多应用并未对不同尺寸的设备进行 UI 适配,这时候应用选择以特定的宽高比显示(虽然 Google 不建议这这样做,官方还是希望开发者可以对不同的屏幕尺寸进行自适应布局~),当应用的宽高比和它的容器比例不兼容的时候,就会以 Letterbox 模式打开。
Letterbox 模式下界面会以指定的比例显示,周围空白区域可以填充壁纸或者颜色。至于 Letterbox 的外观可受以下因素影响:
config_letterboxActivityCornersRadius
: 界面圆角大小config_letterboxBackgroundType
: 背景填充类型, 分别有:LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND
: 颜色受android:colorBackground
影响LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING
: 颜色受android:colorBackgroundFloating
影响LETTERBOX_BACKGROUND_SOLID_COLOR
: 颜色受config_letterboxBackgroundColor
影响LETTERBOX_BACKGROUND_WALLPAPER
: 显示壁纸,此选项和FLAG_SHOW_WALLPAPER
类似,会导致壁纸窗口显示
config_letterboxBackgroundWallpaperBlurRadius
: 壁纸模糊程度config_letterboxBackgroundWallaperDarkScrimAlpha
: 壁纸变暗程度
2. 何时触发
Letterbox 的触发条件一般有:
android:resizeableActivity=false
且应用声明的宽高比与容器不兼容的时候(如屏幕宽高超出android:maxAspectRatio
)setIgnoreOrientationRequest(true)
系统设置忽略屏幕方向后,以横屏模式下打开一个强制竖屏的界面
3. 实现方案
Letterbox 显示的实现并不复杂,Android 12 在 ActivityRecord
中增加了 LetterboxUiController
用以控制 Letterbox
的布局和显示, 先来看看处于 Letterbox 模式时 SurfaceFlinger 状态:
可以看到,跟正常情况相比,除了界面本身的大小和位置被缩放到指定比例外,四周还多了两个 Layer,挂在 ActiviRecord 节点下面, 这两个 Layer 可根据配置进行指定的颜色填充,如果背景是壁纸的话,还可以设置壁纸的 dim 值和模糊程度,这些都可以通过 SurfaceControl 接口轻松实现。
下面简单分析一下代码:
// LetterboxUiController.java
void updateLetterboxSurface(WindowState winHint) {
final WindowState w = mActivityRecord.findMainWindow();
if (w != winHint && winHint != null && w != null) {
return;
}
// 对界面四周需要显示的 Layer 进行位置计算
layoutLetterbox(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
// 对 Surface 执行创建、参数设置等操作
mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction());
}
}
void layoutLetterbox(WindowState winHint) {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || winHint != null && w != winHint) {
return;
}
updateRoundedCorners(w);
updateWallpaperForLetterbox(w);
// 是否进入 Letterbox 模式的关键判断
if (shouldShowLetterboxUi(w)) {
if (mLetterbox == null) {
// 把具体逻辑委托给 Letterbox
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
mLetterboxConfiguration::isLetterboxActivityCornersRounded,
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
this::getLetterboxWallpaperDarkScrimAlpha);
mLetterbox.attachInput(w);
}
mActivityRecord.getPosition(mTmpPoint);
// Get the bounds of the "space-to-fill". The transformed bounds have the highest
// priority because the activity is launched in a rotated environment. In multi-window
// mode, the task-level represents this. In fullscreen-mode, the task container does
// (since the orientation letterbox is also applied to the task).
final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
final Rect spaceToFill = transformedBounds != null
? transformedBounds
: mActivityRecord.inMultiWindowMode()
? mActivityRecord.getRootTask().getBounds()
: mActivityRecord.getRootTask().getParent().getBounds();
// 位置计算
mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
}
}
// Letterbox.LetterboxSurface.java
public void applySurfaceChanges(SurfaceControl.Transaction t) {
if (!needsApplySurfaceChanges()) {
// Nothing changed.
return;
}
mSurfaceFrameRelative.set(mLayoutFrameRelative);
if (!mSurfaceFrameRelative.isEmpty()) {
if (mSurface == null) {
// 创建挂在 ActivityRecord 节点下的 Surface, 设置为 ColorLayer 类型
createSurface(t);
}
// 设置颜色、位置、裁剪
mColor = mColorSupplier.get();
t.setColor(mSurface, getRgbColorArray());
t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
// 对壁纸背景设置透明度和模糊度
mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
updateAlphaAndBlur(t);
t.show(mSurface);
} else if (mSurface != null) {
t.hide(mSurface);
}
if (mSurface != null && mInputInterceptor != null) {
mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative);
t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle);
}
}
本文只是简单分析了下 Letterbox 模式的触发条件和显示的大概逻辑,还有很多细节没有涉及,比如详细的触发逻辑判断可以查看 LetterboxUiController#shouldShowLetterboxUi
方法
Recommend
-
56
欧盟以谷歌违反反垄断法为由对其开出了 43.4 亿欧元的创纪录罚款。原因是谷歌的移动操作系统安卓的反竞争行为。这是欧盟...
-
45
Activity的四种启动模式如下: **standard、singleTop、singleTask、singleInstance ** standard-默认模式 1.谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中 ...
-
42
本文来自微信公众号:
-
27
什么是任务 任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。 如何管理任务 启动模式定义 Activity 的新实例如何...
-
16
Android Dark Theme/深色模式使用体验 2020-05-07Android 虽说一行代码AppCompatDelegate.setDefaultNightMode就能...
-
12
Android开发之Activity的启动模式_dmk877的专栏-CSDN博客黑发不知勤学早,白首方悔读书迟。——《劝学》 今天花了整个下午+晚上的的时间学习了Activity的启动模式,本来以为这个知识点很简单,但是在学习的过程中发现,Activity的启动模式并没有自己...
-
9
今天中午午休时,我把手机开飞行模式了,能看到 WiFi 、蜂窝数据和蓝牙都关闭了,心想,这时候还能收到短信吗?顺着好奇心,我们不妨来研究一下源码,看看点击飞行模式都发生了什么? 基于 Android 9.0 源码分析。 AirplaneModeTile#handleClick...
-
8
从 Android 6.0(API 级别 23)开始,Android 引入了两个省电功能:Doze模式(官方翻译为低电耗模式)和 App Standby模式(官方翻译为应用待机模式),可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命。Doze模式通过在设备长...
-
9
随着 Android Q 发布,「黑暗模式」或者说是「夜间模式」终于在此版本中得到了支持,官方介绍见:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme
-
21
Support is great. Feedback is even better."Thank you for looking into Letterbox. It is still in a beta stage but already very useful so I use it myself on all my own sites. Please let me know if you spot any bugs and errors. There...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK