自 2019 年三星发布了第一台(柔宇不算) Galaxy Z Fold 之后,Android 厂商们都连续跟进了各自的可折叠方案,之后折叠屏手机商场一向坚持快速增长,例如 2023 年上半年全体销量 227 万台,同比增长 102.0%。
尽管比照上半年手机总体出货量 1.3 亿台只能算是零头,可是不可否认,现在开发者的 App 遇到可折叠手机的概率并不低,特别这部分用户大概率还归于「高产值」用户。
所以 2023 年开端,折叠屏适配也逐渐开端成为 Android 的干流 KPI 之一,那么不适配的话会怎么样?适配的话又是经过什么办法?本篇将带你深化了解这个论题。
⚠️本文超长,可保藏以备不时之需。
Letterboxing 形式
首先,假如不适配的话,你的运用大概率(不用定)会是 Letterboxing 形式的显现办法,或许你会看到 App 以如下图所示的办法存在,也便是当运用的宽高比和屏幕份额不兼容时,App 或许会以 Letterbox 形式翻开。
一般是 App 锁死旋转方向和采用了不可调整巨细。
当然,是否进入 Letterboxing 形式和 TargetSDK 版别、 App 装备和屏幕分辨率都有联系,而且不同 OS 版别上 Letterboxing 形式的出现办法也或许有所不同,例如:
-
Android 12(API 31)开端引入了 Letterboxing 增强功用,可由手机厂家装备支撑:
- 圆角: 窗口支撑圆角
- 体系栏透明度:掩盖 App 的状况栏和导航栏支撑半透明
- 可装备的宽高比:能够调整 App 的宽高比改善运用的外观
-
12L(API 32)添加了:
- 可装备方位:在大屏幕上,设备厂商能够将运用装备在显现屏的左边或右侧。
- 重启按钮:设备厂商能够为尺度兼容形式的重启按钮赋予新的外观。(尺度兼容形式能够让 App 的宽或许高尽或许充满屏幕)
当体系确认能够经过从头缩放运用以填充显现窗口来改善 Letterboxing 的显现时,Android 会将 App 置于尺度兼容形式,这时分体系显现一个重启控件,确认后会从头创立 App 进程、从头创立 Activity 偏重绘进行适配。
-
Android 13(API 33)添加了一个用户引导的提示对话框 :
那么什么时分会进入 Letterboxing 形式 ?一般能够简略理解为:
-
android:resizeableActivity=false
下运用声明的宽高比与容器不兼容时(例如屏幕宽度超越android:maxAspectRatio
)。 -
setIgnoreOrientationRequest(true)
下体系设置疏忽屏幕方向后,横向翻开强制竖屏的界面。
这儿的中心点其实是 resizeableActivity
,它用于声明体系是否能够调节 App 巨细去适应不同尺度的屏幕, 其实严格来说 resizeableActivity
不用定会导致运用必定进入 Letterboxing 形式,这也 API 版别有联系:
- 在 Android 7.0(API 24)引入了分屏形式装备
resizeableActivity
。 - 在 Android 11(API 30)及更低版别上,用于装备 App 是否支撑多窗口形式,假如 false 就不支撑,会进入 Letterboxing 形式。
- 在 Android 12(API 31)及更高版别上,无论
resizeableActivity
设置什么,App 都会支撑大屏幕 (sw >= 600dp) 上的多窗口形式,所以仅用于指定 App 是否支撑小屏幕(sw < 600dp)上的多窗口形式。
sw >= 600dp
能够简略理解为你的屏幕的肯定宽度大于 600dp
那有的人就说了,假如我在 Android 12 就运用 android:resizeableActivity=false
然后什么都不适配会怎么样?我只能说,「有必定概率」会如下图所示一样,直接 crash 。
那是不是我不运用高版别的 TargetSDK 就能够不用作业适配了呢?
也不完满是,至少你需求对你的 App 或许 Activity
进行一些简略的装备,由于早在 Android 7.0(API 24)开端,resizeableActivity
的默许值就被改为 true。
所以假如你不想适配大屏形式 UI,期望进入 Letterboxing 形式,仍是需求手动在 AndroidManifest 中的 application
或对应的 Activity
装备上 android:resizeableActivity="false"
。
别的,Letterboxing 形式的显现形式和 maxAspectRatio
也有关,当屏幕份额超越 maxAspectRatio
时才会用黑边填充,一般官方主张把 maxAspectRatio 设为 2.4 (12 : 5),装备办法也和 API Level 有联系:
-
Android 8.0 及以上能够经过
android:maxAspectRatio
装备<activity android:name=".MainActivity" android:maxAspectRatio="2.4" />
-
Android 8.0 以下能够经过
meta-data android.max_aspect
装备<meta-data android:name="android.max_aspect" android:value="2.4" />
PS :假如
resizeableActivity
是true,maxAspectRatio
会不生效。
如图是前面说到 Android 12L(API 32)的重启按钮能够让 App 一端尽或许适配屏幕减少黑边。
还有一点,在折叠屏翻开和闭合的时分,在屏幕发生了改变时,体系或许会毁掉并从头创立整个 Activity
,所以咱们需求装备 android:configChanges
来避免重启:
android:configChanges="screenLayout|smallestScreenSize|screenSize"
最终还需求留意 supports_size_changes
,假如不想支撑多窗口形式,可是又或许会由于体系逼迫进入多窗口形式,然后又不期望每次都被重启,那么能够装备 supports_size_changes
来确保运转的连续性。
<meta-data
android:name="android.supports_size_changes" android:value="true" />
所以这儿简略做个总结便是:
-
当运用的宽高比与其屏幕份额不兼容,App 锁死旋转方向和巨细时会进入 Letterboxing 形式
-
resizeableActivity
的作用主要看 TargetSDK 版别, Android 12(API 31)及更高版别上或许仍是会进去分屏形式 -
maxAspectRatio
的作用主要看resizeableActivity
-
装备
android:configChanges
和supports_size_changes
避免重启Activity
确保连续性
官方适配支撑
接下来便是介绍适配方案,首先咱们看这张图,其实官方现已依据运用场景为咱们定义好运用主张,其间要害的几个信息有:
- Compose
- Activity Embedding
- SlidingPaneLayout
别的,在官方的不同屏幕尺度匹配里设定了窗户尺度等级标准,例如:
- Compact: 一般手机设备,宽度 < 600dp
- Medium:折叠屏或平板的竖屏,600dp < 宽度 < 840dp
- Expanded:翻开屏幕,平板或平板电脑等,宽度 > 840dp
当然还有依据高度去判别的,可是大多数 App 能够经过仅考虑宽度窗口巨细类别来构建呼应式 UI,
Compose
其实 Compose 不用多说,在折叠屏适配上呼应式布局本身就具有先天优势,合作 Jetpack WindowManager API 供给的当时的屏幕参数,就能够很灵敏地达到适配不同 UI 作用。
例如 Compose 能够运用 material3-window-size-class
库,然后运用 calculateWindowSizeClass()
计算当时窗口的 WindowSizeClass
,然后改动 UI 的布局:
import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Calculate the window size class for the activity's current window. If the window
// size changes, for example when the device is rotated, the value returned by
// calculateSizeClass will also change.
val windowSizeClass = calculateWindowSizeClass(this)
// Perform logic on the window size class to decide whether to use a nav rail.
val useNavRail = windowSizeClass.widthSizeClass > WindowWidthSizeClass.Compact
// MyScreen knows nothing about window size classes, and performs logic based on a
// Boolean flag.
MyScreen(useNavRail = useNavRail)
}
}
}
别的还能够经过 com.google.accompanist:accompanist-adaptive
的 TwoPane 进行适配。
TwoPane
供给了两个固定的槽位,两个槽位的默许方位由 TwoPaneStrategy
驱动,它能够决议将两个槽位水平或笔直摆放,并可装备它们之间的间隔。
更多可见:github.com/google/acco…
不同场景 Compose 还能够运用 FlowLayout 适配折叠改变 ,FlowLayout 包括 FlowRow
和 FlowColumn
,当一行(或一列)放不下里边的内容时,会主动换行,这在折叠屏翻开和缩短场景也非常有用。
关于 Compose 适配折叠屏 Demo 还能够参阅 : github.com/android/com…
Activity Embedding
Activity Embedding 便是经过在两个 Activity 或同一 Activity 的两个实例之间拆分窗口,来优化大屏幕的支撑。
理论上 Activity Embedding 不需求代码重构,能够经过创立 XML 装备文件或进行 Jetpack WindowManager API 调用来确认 App 怎么显现其 Activity(并排或堆叠) 。
Activity Embedding 默许会主动保护对小屏幕的支撑,当运用坐落小屏幕设备上时,Activity 会一个一个地堆叠在另一个之上;在大屏幕上,Activity 会翻开并排显现。
在这个基础上,它能够适应设备方向的改变,并在可折叠设备上无缝作业,在设备折叠或翻开时堆叠被拆开的 Activity,例如在谈天列表和谈天概况页面进行拆分和堆叠。
无论是 Android 12L(API 32)以上的大屏设备,仍是更早期折叠屏平台版别的设备,Jetpack WindowManager 都能帮助构建 Activity Embedding 多窗格布局,这种依据多个 Activity 而非 fragment 或依据视图的布局(如
SlidingPaneLayout
)的办法能够最简略供给大屏幕用户体验而无需重构源代码。
一个常见的示例是列表-概况分屏,为了确保高质量的出现,体系先发动列表 Activity,然后运用立即发动概况 Activity,过渡体系比及这两个 Activity 都绘制完成后再将它们一起显现出来,对用户来说,这两个 Activity 是作为一个页面发动。
现在大多数运转 Android 12L(API 32)及更高版别的大屏幕设备都支撑 Activity Embedding。
运用 Jetpack WindowManager 办理和装备 Activity Embedding 其实适当灵敏,能够预先装备 XML 规则,或许直接经过 API 进行办理装备,关于 XML 装备文件中定义的规则,设置以下特点:
-
splitRatio
:设置容器份额。该值为开区间 (0.0, 1.0) 内的浮点数。 -
splitLayoutDirection
:指定切割容器相关于互相的布局办法。值包括:-
ltr
: 左到右 -
rtl
: 右到左 -
locale
:ltr
或rtl
由语言环境设置决议
-
能够看到 Jetpack WindowManager 非常丰富且灵敏的装备支撑,而不是单纯简略的对 Activity 进行均匀切割,乃至你还能够装备一个空白 Placeholder 来进行占位显现。
运用 Activity Embedding 你需求依赖 implementation 'androidx.window:window:xxx'
,然后将该 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
特点添加到运用清单文件的 <application>
中,并将值设置为 true,
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
android:value="true" />
</application>
</manifest>
之后就能够经过 xml 创立各种 Split Rule 或许 WindowManager API 创立 Split Rule 然后调用。
<!-- main_split_config.xml -->
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<!-- Define a split for the named activities. -->
<SplitPairRule
window:splitRatio="0.33"
window:splitLayoutDirection="locale"
window:splitMinWidthDp="840"
window:splitMaxAspectRatioInPortrait="alwaysAllow"
window:finishPrimaryWithSecondary="never"
window:finishSecondaryWithPrimary="always"
window:clearTop="false">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
<!-- Specify a placeholder for the secondary container when content is
not available. -->
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity"
window:splitRatio="0.33"
window:splitLayoutDirection="locale"
window:splitMinWidthDp="840"
window:splitMaxAspectRatioInPortrait="alwaysAllow"
window:stickyPlaceholder="false">
<ActivityFilter
window:activityName=".ListActivity"/>
</SplitPlaceholderRule>
<!-- Define activities that should never be part of a split. Note: Takes
precedence over other split rules for the activity named in the
rule. -->
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".ExpandedActivity"/>
</ActivityRule>
</resources>
更多可见:developer.android.com/guide/topic…
SlidingPaneLayout
SlidingPaneLayout
支撑在大屏幕设备并排显现两个窗格,一起还会主动进行调整,在手机等小屏幕设备只显现一个窗格,所以在可折叠场景下也非常有用。
SlidingPaneLayout
会依据两个窗格的宽度来确认是否并排显现这些窗格,例如:
- 假如丈量后发现列表窗格的最小尺度为 200dp,而详细信息窗格需求 400dp,那么只需可用宽度不小于 600dp,
SlidingPaneLayout
就会主动并排显现两个窗格 - 假如子视图的总宽度超越了
SlidingPaneLayout
中的可用宽度,这些视图就会堆叠在一起。
假如视图没有堆叠,那么
SlidingPaneLayout
支撑对子视图运用布局参数layout_weight
,以指定在丈量结束后怎么区分剩余的空间。
例如这个比方运用了 SlidingPaneLayout
,布局将 RecyclerView 作为其左边窗格,将 FragmentContainerView 作为其主要详细信息视图,用于显现左边窗格中的内容,其实就相似前面介绍的在 Compose 里运用 TwoPane 的 UI。
<!-- two_pane.xml -->
<androidx.slidingpanelayout.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The first child view becomes the left pane. When the combined
desired width (expressed using android:layout_width) would
not fit on-screen at once, the right pane is permitted to
overlap the left. -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"/>
<!-- The second child becomes the right (content) pane. In this
example, android:layout_weight is used to expand this detail pane
to consume leftover available space when the
the entire window is wide enough to fit both the left and right pane.-->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detail_container"
android:layout_width="300dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="#ff333333"
android:name="com.example.SelectAnItemFragment" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
别的 SlidingPaneLayout
还能够和 Navigation
合作办理 Fragment 事物,而且它现在还会辨认和适应折叠和铰链状况,例如:
运用的设备带有遮挡部分屏幕的铰链,它会主动将 App 的内容放置在任一侧。
SlidingPaneLayout
还引入了确定形式,支撑在窗格堆叠时操控滑动行为,例如:
为了避免用户滑到空窗格,需求点击击列表项才干加载有关该窗格的信息,但允许他们滑回到列表,在有空间并排显现两个视图的可折叠设备或平板电脑上,确定形式将被疏忽。
更多可见: developer.android.com/guide/topic…
自定义适配
除了官方的适配方案,或许咱们还需更灵敏的自定义适配方案,那么首先第一件事便是咱们需求知道怎么辨认折叠屏。
辨认折叠屏
仍是前面说到的 Jetpack WindowManager ,Jetpack WindowManager 的 FoldingFeature
供给了有关可折叠显现器的信息的类型,包括:
-
state
:设备的折叠状况,FLAT
(完全翻开) 或HALF_OPENED
(处于翻开和封闭状况之间的中间方位) -
orientation
:折叠或铰链的方向,HORIZONTAL
或许VERTICAL
-
occlusionType
:折叠或铰链是否隐藏了部分显现屏,NONE
(不遮挡)或许FULL
(遮挡) -
isSeparating
:折叠或铰链是否创立两个显现区域,true(半开/双屏) 或 false
在 Android 11 官方还供给了读取折叠视点的支撑:新增的类型 TYPE_HINGE_ANGLE 支撑以及新的 SensorEvent
,SensorEvent
能够监控合页视点,并供给设备的两部分之间的视点丈量值:
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
hingeAngleSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
而关于折叠屏的姿态,咱们能够经过 Jetpack WindowManager
的 API 来实现:
-
设备处于 TableTop 形式,屏幕半开而且铰链处于水平方向
fun isTableTopMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
-
设备处于 Book 形式,屏幕半开而且铰链处于笔直方向
fun isBookMode(foldFeature: FoldingFeature) = foldFeature.isSeparating && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
例如 Google Duo team 就经过 Jetpack WindowManager 辨认折叠屏状况,然后依据翻开状况在播映过程调整界面 UI。
简略介绍一下,便是在初始化时经过 WindowManager 库获取 Flow<WindowLayoutInfo>
,让手机知道现在处于桌面形式以及怎么获取折叠的方位:
override fun onStart() {
super.onStart()
initializePlayer()
layoutUpdatesJob = uiScope.launch {
windowInfoRepository.windowLayoutInfo
.collect { newLayoutInfo ->
onLayoutInfoChanged(newLayoutInfo)
}
}
}
override fun onStop() {
super.onStop()
layoutUpdatesJob?.cancel()
releasePlayer()
}
每次取得新的布局信息时,都能够查询显现功用并查看设备在当时显现中是否有折叠或铰链:
private fun onLayoutInfoChanged(newLayoutInfo: WindowLayoutInfo) {
if (newLayoutInfo.displayFeatures.isEmpty()) {
// The display doesn't have a display feature, we may be on a secondary,
// non foldable-screen, or on the main foldable screen but in a split-view.
centerPlayer()
} else {
newLayoutInfo.displayFeatures.filterIsInstance(FoldingFeature::class.java)
.firstOrNull { feature -> isInTabletopMode(feature) }
?.let { foldingFeature ->
val fold = foldPosition(binding.root, foldingFeature)
foldPlayer(fold)
} ?: run {
centerPlayer()
}
}
}
假如方向为水平且 FoldingFeature.isSeparating()
回来 true,则设备能够在桌面形式下运用,在这种状况下,能够计算折叠的相对方位并将控件移动到对应方位,否则将其移动到 0(屏幕底部)。
private fun centerPlayer() {
ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0)
binding.playerView.useController = true // use embedded controls
}
private fun foldPlayer(fold: Int) {
ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)
binding.playerView.useController = false // use custom controls
}
窗口巨细适配
折叠设备的适配里,窗口巨细获取也是非常重要的一点,可是其实 Android 开展至今,其间一些 API 现已被弃用,或许说还在被误用,针对大屏幕设配的适配上,由于有 Letterboxing 等状况,所以其实旧的 API 现已无法满意需求。
现在已弃用且经常被误用的 Display API 有:
- getMetrics()
- getSize()
- getRealMetrics()
- getRealSize()
- getRectSize()
- getWidth()
- getHeight()
经常被误用的 View API 有:
- getWindowVisibleDisplayFrame()
- getLocationOnScreen
例如 Display
的 getSize()
和 getMetrics()
在 API 30 中现已被弃用,取而代之的是新 WindowManager
办法。
Android 12(API 31)弃用了 Display
的 getRealSize()
和 getRealMetrics()
,更新的还有与之相关的 getMaximumWindowMetrics()
办法。
由于折叠屏和多屏幕下,你的 App 实践尺度和屏幕实践尺度之间并不用定一致,所以不能依赖物理显现尺度来定位 UI 元素,现在推荐依赖于 WindowMetrics 的 API :
- Platform:
- getCurrentWindowMetrics()
- getMaximumWindowMetrics()
- Jetpack:
- WindowMetricsCalculator#computeCurrentWindowMetrics()
- WindowMetricsCalculator#computeMaximumWindowMetrics()
这儿的 Platform 是 Android 11(API 30)引入了 WindowManager
办法来供给在多窗口形式下运转的运用的鸿沟:
-
getCurrentWindowMetrics()
:回来体系当时窗口状况目标WindowMetrics
-
getMaximumWindowMetrics()
:回来体系的最大窗口状况WindowMetrics
Jetpack WindowManager 库办法 computeCurrentWindowMetrics()
和 computeMaximumWindowMetrics()
别离供给相似的功用,但向后兼容到 API 14。
val windowMetrics = context.createDisplayContext(display)
.createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION, null)
.getSystemService(WindowManager::class.java)
.maximumWindowMetrics
所以,经过 WindowManager
,咱们能够动态去办理窗口的巨细改变,辨认折叠屏的改变状体,例如在onConfigurationChanged()
来装备当时窗口巨细的运用布局:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this@MainActivity)
val bounds = windowMetrics.getBounds()
...
}
最终,在窗口自定义适配上,便是陈词滥调的论题了,例如:
- 运用
wrap_content
、match_parent
避免硬编码 - 运用
ConstraintLayout
做根布局,方便屏幕尺度改变,视图主动移动和拉伸 - 在 App 的 AndroidManifest 里将
application
或activity
的android:resizeableActivity
特点设置为true
来支撑巨细调整并支撑呼应式/自适应布局。 -
res/layout/
能够经过创立如 layout-w600dp 的等目录来供给自适应的布局
多窗口和生命周期
已然折叠屏纯在多个区域,就或许存在多窗口,乃至不止两个窗口,这种状况下自然而然就存在生命周期适配的问题,例如多个 App 一起拜访 Camera 。
关于多窗口的进程,能够简略介绍下:
-
Android 7.0 支撑分屏:左右/上下显现两个窗口
-
Android 8.0 支撑画中画形式,此时处于画中画的
Activity
虽处于前台,但处于Paused
状况 -
Android 9.0 (API 28) 及以下:多窗口下只要取得焦点运用处于
Resumed
状况,其它可见Activity
仍处于Paused
状况 -
Android 10.0 (API 29) :多窗口形式时,每个
Acttivity
全部处于Resumed
状况
看到没有,不同 API 级别下居然生命周期都不一样,所认为解决 Android 9.0 及以下只要取得焦点运用才处于 Resume
状况问题,App 端可添加下列特点,手动添加敞开支撑多项 Resumed
:
<meta-data
android:name="android.allow_multiple_resumed_activities" android:value="true" />
也便是俗称的 Multi-resume 状况。
为了支撑 Multi-resume 状况, 自然就需求一个新的生命周期回调 ,那便是 onTopResumedActivityChanged()
。
当 Activity 取得或失去顶部 Resume 方位时,体系会调用该办法,例如运用同享单例资源(例如麦克风或摄像头)时:
override fun onTopResumedActivityChanged(topResumed: Boolean) {
if (topResumed) {
// Top resumed activity
// Can be a signal to re-acquire exclusive resources
} else {
// No longer the top resumed activity
}
}
比方关于运用相机的场景,针对上述封装,在 Android 10(API 级别 29)经过CameraManager.AvailabilityCallback#onCameraAccessPrioritiesChanged()
供给了一个回调提示,标明现在或许是能够尝试拜访相机的机遇。
这儿需求留意的是,运用 resizeableActivity=false
并不能确保独占相机拜访权限,由于运用相机的其他 App 或许会在多方显现器上翻开(分屏)。
所以需求 App 在收到 CameraDevice.StateCallback#onDisconnected()
回调后处理相关行为,假如 onDisconnected 之后还操作 API,体系就会抛出 CameraAccessException
.
事实上只需经过回调做好判别,其实这个「焦点」切换体验是无缝的。
在多窗口形式下,Android 或许会禁用或疏忽不适用于与其他 Activity 或运用同享设备屏幕的 Activity 的功用。
别的,Activity 也供给了一些办法来支撑多窗口形式:
-
isInMultiWindowMode()
是否处于多窗口形式。 -
isInPictureInPictureMode()
Activity 是否处于画中画形式。留意:画中画形式是多窗口形式的特例,假如
isInPictureInPictureMode()
回来 true,则isInMultiWindowMode()
也会回来 true。 -
onMultiWindowModeChanged()
Activity 进入或退出多窗口形式时,体系都会调用此办法。假如 Activity 正在进入多窗口形式,则体系向该办法传递一个值 true;假如 Activity 正在脱离多窗口形式,则体系向该办法传递一个值 false。
-
onPictureInPictureModeChanged()
Activity 进入或退出画中画形式时,体系都会调用此办法。假如 Activity 正在进入画中画形式,则体系向该办法传递一个 true 值;假如 Activity 正在脱离画中画形式,则体系向该办法传递一个 false 值。
Fragment 同样供给了相似办法,如 Fragment.onMultiWindowModeChanged()
。
Flutter
3.13 开端 Flutter 也添加了一个新的 API 来匹配显现器的各种特点 #41685,其间新的 FlutterView.display 回来一个 Display 目标,Display 目标会报告显现器的物理尺度、设备像素比和刷新率:
@override
void didChangeMetrics() {
final ui.Display? display = _display;
if (display == null) {
return;
}
if (display.size.width / display.devicePixelRatio < kOrientationLockBreakpoint) {
SystemChrome.setPreferredOrientations(<DeviceOrientation>[
DeviceOrientation.portraitUp,
]);
} else {
SystemChrome.setPreferredOrientations(<DeviceOrientation>[]);
}
}
这个新 API 的主要目的,是前面说到过的内容,由于假如一旦进入了 Letterboxing 形式, Flutter 的 MediaQuery
或许就会无法获取到完整的 avalalbe 屏幕尺度,所以新的 API 便是供给折叠改变后的实在尺度给开发者适配的空间。
别的,Flutter 上关于支撑多个显现器尺度的支撑还在同步 #125938 、#125939 ,感兴趣的也能够重视一下。
最终
能看到这儿的都是很有耐心的同志,本次调研的涉及的内容较多,掩盖知识点也有点广,有的或许不够深化,大体仍是供给了方向和思路,主要涉及:
- 兼容的 Letterboxing 形式体现
-
resizeableActivity
等装备的不同行为 - Compose /Activity Embedding /SlidingPaneLayout 的适配方案
- 折叠屏的判别、窗口适配和生命周期兼容
- Flutter API
我信任还有很多的 App 没有方案对折叠屏做适配,究竟「又不是不能用」,可是了解完本篇,至少能够给你供给一些底气,至少看起来假如真要适配,也不是什么做不到的事情。
假如你还有什么想说的,欢迎留言评论交流。