Activity嵌入和多窗口模式支持


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 的“设置”应用。图 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声明方式

  1. 创建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>
  1. 添加依赖库,解析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()
 }
}
  1. 为规则定义创建 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。

  1. Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT:该标识符指示在多窗口模式下启动目标活动。在 Android 7.0 及更高版本中,多窗口模式允许用户同时在一个屏幕上显示多个应用程序或活动。使用此标识符启动的活动将会在当前活动的旁边打开。
  2. Intent.FLAG_ACTIVITY_NEW_TASK:该标识符指示在新任务中启动目标活动。任务是可以包含多个活动的活动栈。通常情况下,一个应用程序在一个任务中运行。使用此标识符启动的活动将在一个新的任务中打开。
  3. 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

公众号同步连载中,若您觉得还不错,欢迎关注~
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值