Activity Embeding和Intent Flag两种方式均能够支持多个Activity并排显示,这在大屏(如Pad、折叠屏)设备上的适配显得尤为重要,这意味着你可以将任意的页面组合显示(如,聊天页和视频页),以提升App的屏幕效用,给用户以更多的选择。
相同点 | 不同点 | |
Activity Embeding | 组合两个页面 感知分屏模式的变更 不支持华为设备 手动处理音视频冲突 | 页面链接更无缝,页面宽度无法调整 页面分为main和placeholder两部分,可单独指定各部分宽度 指定规则,哪些组合可以分屏显示,某些页面全屏显示 系统要求:安卓12L及以上版本 API版本差异大,后续升级成本高 |
Intent Flag | 支持两个页面调整宽度(既可拖拽调整,和“微信”表现一致) 适配成本相对较低,指定启动Flag即可 新打开的页面在当前页面的左侧或右侧 分屏下也可用,占据当前非分屏空间 系统要求:安卓12及以上版本 |
Activity Embeding
官方文档:https://developer.android.com/guide/topics/large-screens/activity-embedding?hl=zh-cn
Activity 嵌入可以将应用的一个任务窗口拆分到两个 Activity 中,或者拆分到同一个 Activity 的两个实例中,从而优化大屏设备上的应用。
图 1. 并排显示 activity 的“设置”应用。
如果应用由多个 Activity 组成,Activity 嵌入让您能够在平板电脑、可折叠设备和 ChromeOS 设备上提供增强的用户体验。
系统会自动维护对小屏幕的支持。当应用在配备小屏幕的设备上时,Activity 会相互堆叠。在大屏幕上,Activity 会并排显示。系统会根据您已创建的配置(不需要分支逻辑)来确定呈现方式。
Activity 嵌入支持设备屏幕方向的变化,并且可以在可折叠设备上无缝运行,该功能会随着设备折叠和展开而堆叠和取消堆叠 Activity。
大多数搭载 Android 12L(API 级别 32)及更高版本的大屏幕设备均支持 Activity 嵌入。下面简要介绍其实现方式。
分屏配置
添加依赖:implementation 'androidx.window:window:1.1.0-beta02'
<!-- 告知系统您的应用已实现 activity 嵌入 -->
<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声明方式
- 创建XML资源文件,定义用于分屏、占位、拒绝分屏的Activity。
<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>
- 添加依赖库,解析xml中定义的规则:
implementation 'androidx.startup:startup-runtime:1.1.1'
class SplitInitializer : Initializer<RuleController> {
override fun create(context: Context): RuleController {
return RuleController.getInstance(context).apply {
setRules(RuleController.parseRules(context, R.xml.main_split_config))
}
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
- 为规则定义创建
content provider
,添加对RuleController
初始化程序实现SplitInitializer
的引用
<!-- AndroidManifest.xml -->
<provider android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- Make SplitInitializer discoverable by InitializationProvider. -->
<meta-data android:name="${applicationId}.SplitInitializer"
android:value="androidx.startup" />
</provider>
InitializationProvider
会在调用应用的 onCreate()
方法之前发现并初始化 SplitInitializer
。因此,分屏规则会在应用的主要 activity 启动时生效。
Jetpack Window API方式
//在Application#onCreate方法中调用
class SplitManager {
companion object {
fun create(context: Context) {
//创建一个分屏对过滤器,将 ListActivity 和 DetailActivity 标识为共享分屏的 activity
val splitPairFilter = SplitPairFilter(
ComponentName(context, ListActivity::class.java),
ComponentName(context, DetailActivity::class.java),
null
)
//将过滤条件添加到过滤条件集
val filterSet = setOf(splitPairFilter)
//为分屏创建布局属性
val splitAttributes = SplitAttributes.Builder().apply {
setSplitType(SplitAttributes.SplitType.ratio(0.33f))
setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
}.build()
//构建分屏对规则
val splitPairRule = SplitPairRule.Builder(filterSet).apply {
setDefaultSplitAttributes(splitAttributes)
setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
setMinWidthDp(540)
setMinSmallestWidthDp(460)
setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
setClearTop(false)
}.build()
//获取 WindowManager RuleController 的单例实例并添加规则
val ruleController = RuleController.getInstance(context)
ruleController.addRule(splitPairRule)
//创建占位规则
val placeholderActivityFilter = ActivityFilter(
ComponentName(context, ListActivity::class.java),
null
)
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
val splitPlaceholderRule = SplitPlaceholderRule.Builder(
placeholderActivityFilterSet,
Intent(context, PlaceholderActivity::class.java)
).apply {
setDefaultSplitAttributes(splitAttributes)
setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
setMinWidthDp(540)
setMinSmallestWidthDp(460)
setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
}.build()
ruleController.addRule(splitPlaceholderRule)
//创建拒绝分屏activity规则
val summaryActivityFilter = ActivityFilter(
ComponentName(context, SummaryActivity::class.java),
null
)
val summaryActivityFilterSet = setOf(summaryActivityFilter)
val activityRule =
ActivityRule.Builder(summaryActivityFilterSet).setAlwaysExpand(true).build()
ruleController.addRule(activityRule)
}
}
}
效果展示
https://live.csdn.net/v/embed/341571
Intent Flag
官方文档:https://developer.android.com/guide/topics/large-screens/multi-window-support?hl=zh-cn
多窗口模式允许多个应用同时共享同一屏幕。在这种模式下,系统可以左右或上下并排显示两个应用(分屏模式),在应用中用小窗口叠加显示其他应用(画中画模式),或者让各个应用分别在可移动且可调整大小的窗口中显示(自由窗口模式)。
声明方式
启动新 Activity 时,您可以指示应尽可能将新 Activity 显示在当前 Activity 旁边。请使用 Intent 标志 FLAG_ACTIVITY_LAUNCH_ADJACENT
,告知系统尽量在相邻的窗口中创建新 Activity,以便两个 Activity 共享屏幕。系统会尽可能做到这一点,但无法保证最终结果。
在 API 级别 30 及更低级别中,如果您在任务堆栈中启动 Activity,该 Activity 会替换屏幕上的 Activity,并沿用其所有的多窗口模式属性。如果您要在多窗口模式下以单独的窗口启动新 Activity,那么必须在新的任务堆栈中启动此 Activity。
Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
:该标识符指示在多窗口模式下启动目标活动。在 Android 7.0 及更高版本中,多窗口模式允许用户同时在一个屏幕上显示多个应用程序或活动。使用此标识符启动的活动将会在当前活动的旁边打开。Intent.FLAG_ACTIVITY_NEW_TASK
:该标识符指示在新任务中启动目标活动。任务是可以包含多个活动的活动栈。通常情况下,一个应用程序在一个任务中运行。使用此标识符启动的活动将在一个新的任务中打开。Intent.FLAG_ACTIVITY_MULTIPLE_TASK
:该标识符指示允许目标活动在多个任务中同时存在。通常情况下,一个活动只能在一个任务中运行。使用此标识符启动的活动将允许在多个任务中同时打开。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btn.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
//新堆栈,多窗口模式
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
startActivity(intent)
}
binding.btn2.setOnClickListener {
//沿用堆栈,多窗口属性
val intent = Intent(this, MainActivity4::class.java)
startActivity(intent)
}
Helper.getRunningActivity(this)
}
}
如何退出多窗口模式,并启动一个新的Activity呢?使用finishAffinity()
,在Android应用程序中结束当前活动(Activity)及其所有父活动,并将应用程序的任务(Task)从后台堆栈中移除。
class MainActivity2 : AppCompatActivity() {
private lateinit var binding: ActivityMain2Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMain2Binding.inflate(layoutInflater)
setContentView(binding.root)
binding.btn.setOnClickListener {
finishAffinity()
val intent = Intent(this, MainActivity3::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
效果展示
https://live.csdn.net/v/embed/341572 (画面被压缩有点失真…)
华为平行世界
上述两种方案不支持华为的产品(华为系统是基于Android 10开发的),可参考华为官方的开发指导(平行世界),实现类似的效果,链接如下:
https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/adaptation-guidance-0000001054031462
公众号同步连载中,若您觉得还不错,欢迎关注~