Jetpack Compose中的状态栏适配(Window Insets)

news2024/11/24 14:30:26

除了app的内容区域外,还有一些其他的固定元素会显示在手机屏幕上,顶部的状态栏、 刘海、 底部的导航栏,还有输入法键盘,它们都是系统的UI, 也叫Insets.

如图所示:

顶部的状态栏通常被用来展示通知, 设备状态等;
底部导航栏通常显示三个导航按钮: back, home, recent.
它们两个合称为system bars.

Android的Insets类描述的是偏移尺寸信息, 确实我们开发中更关注的也就是这些系统UI的尺寸信息.

本文介绍用Compose做UI之后, 借助于Accompanist Insets: Guide - Accompanist.
几种常见的和Insets相关的情形是如何做的.

内容区域

Going Edge-to-Edge

新创建一个用Compose写的app, 默认是一个没有Inset处理的普通App.

那能不能让app的内容显示在这些system bars区域, 做成edge-to-edge的形式?
当然是可以的.

这里澄清两个概念:

  • edge-to-edge: app的内容在system bars后面绘制, system bars仍然以半透明的形式存在.
  • 不同于"沉浸式"(immersive mode), 沉浸式需要将system bars隐藏, app内容完全全屏, 多用于看视频, 画画等场景.

内容区域延伸到system bars

内容延伸到status bar和navigation bar区域很容易, 只需要加一行代码:

WindowCompat.setDecorFitsSystemWindows(window, false)

 这个值默认是true, 表示默认行为: app的内容会自动找到内嵌区域绘制.
设置为false之后, app的内容就会延伸到system bars下层.

区别见下图: 左边为默认显示, 右边为添加了这个flag为false的设置之后的情况:

嗯, 内容是绘制出去了, 但是却被遮挡了.

这时候就需要用到systemuicontroller来改颜色:
加上这么几行就可以改自己喜欢的颜色:

val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
 
SideEffect {
    systemUiController.setSystemBarsColor(
        color = Color.Green.copy(alpha = 0.1f),
        darkIcons = useDarkIcons
    )
}

这里改的是system bars, 也即status bar和navigation bar都改了. 也有单独只改一个的方法.
为了demo, 把颜色设置成透明的绿(如左图);
正常应用场景有可能得用Color.Transparent(如下图).

延伸却内嵌

紧接做了几个页面的UI之后, 发现有的内容遮盖在状态栏和底部, 体验不是很好.
能不能把有文字内容的部分让出来呢?

于是, 添加了这个依赖: Insets for Jetpack Compose

简单两行就把上下的距离留了出来:

ProvideWindowInsets {
    Sample1(modifier = Modifier.systemBarsPadding())
}

等等, 这么一处理, 如果忽略system bars颜色的设置.
和最开始默认的情形看起来是一模一样.

那么我们是不是可以直接删掉WindowCompat.setDecorFitsSystemWindows(window, false)这行, 用默认设置就好了?

  • 是. 如果你的需求真的是这样.
  • 不是. 如果你需要把app背景绘制出去; 如果你还有输入框的处理.

如果需求想要的是背景延伸出去, 文字内嵌.
分别给上下两个元素加了不同的padding:

Column(
    modifier = modifier.fillMaxSize()
            .background(color = Color.Blue.copy(alpha = 0.3f)),
    verticalArrangement = Arrangement.SpaceBetween
) {
    Text(
        modifier = Modifier.fillMaxWidth()
                .background(color = Color.Yellow.copy(alpha = 0.5f))
                .statusBarsPadding(),
        text = "Top Text",
        style = MaterialTheme.typography.h2
    )
    Text(text = "Content", style = MaterialTheme.typography.h2)
    Text(
        modifier = Modifier.fillMaxWidth()
                .navigationBarsPadding()
                .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Bottom Text",
        style = MaterialTheme.typography.h2
    )
}

运行以后如下图中右边所示:

注意这里modifier的顺序, 上下延伸出去的颜色是不同的, 下面延伸出去的其实是Column的颜色.

左边是把insets padding加在整体布局的情况, 如果用的是system bars的话, 和默认UI效果是一样的.

具体根据需求定制即可.

LazyColumn的padding和content padding

有一个非常长的LazyColumn, 在edge-to-edge的设计下应该怎么显示呢?
这里有三种选择:

  1. List完全全屏: LazyColumn {}
  2. List留出上下padding: LazyColumn(modifier = Modifier.systemBarsPadding()) {}
  3. List留出Content padding:
LazyColumn(
    contentPadding = rememberInsetsPaddingValues(
        insets = LocalWindowInsets.current.systemBars,
        applyTop = true,
        applyBottom = true,
    )
) {}

其实1和2的行为非常类似, 只是显示区域大小的区别.
content padding只是在第一个item的上面和最后一个item的下面加上padding,
在滚动的中间过程中内容是可以全屏的, 只有到头或者到底了才会显示出padding.

content padding用动图更能说明情况:

内容区域处理总结

Insets这个库提供了这么几个Modifier:

  • Modifier.statusBarsPadding()
  • Modifier.navigationBarsPadding()
  • Modifier.systemBarsPadding()
  • Modifier.imePadding()
  • Modifier.navigationBarsWithImePadding()
  • Modifier.cutoutPadding()
    可以直接在布局中用上, 就获取了应该有的padding, 比如statusBarPadding是top, navigationBarsPadding是bottom.
    这都不用开发者自己想.

如果这些都不满足你的需求, 也可以直接用尺寸:

  • Modifier.statusBarsHeight()
  • Modifier.navigationBarsHeight()
  • Modifier.navigationBarsWidth()
    或者更直接地用LocalWindowInsets.current自己获取想要inset类型的相关尺寸.

输入框元素和键盘

on-screen keyboard, 又叫IME (Input Method Editor),
一般点击输入框会弹出, IME也是一种Inset.

输入框被键盘遮挡问题

当输入框处于屏幕上半屏的时候, 基本不用考虑键盘遮挡的问题.
但是当输入框在屏幕下半屏, 我们需要在键盘弹出来的时候让输入框完全显示出来而不被盖住.

解决这个问题需要这么几个东西:

  • Activity的android:windowSoftInputMode="adjustResize", 表示在键盘弹出时, Activity会改变布局大小, 这种改变是挤压型的.
  • Modifier.imePadding的使用, 给布局加上一个恰好等于键盘高度的bottom padding. 通常是给输入框的父布局, 加在哪一层视情况而定.
  • 如果上面两个都设置了仍然不能把输入框完全显示出来, 可能需要再加入点强力的唤醒行为.

根据这个issue下的这条comment,
可以用这个Modifier, 在这个ui获取到焦点的时候, 自己把自己bring into view。

@ExperimentalComposeUiApi
fun Modifier.bringIntoViewAfterImeAnimation(): Modifier = composed {
    val imeInsets = LocalWindowInsets.current.ime
    var focusState by remember { mutableStateOf<FocusState?>(null) }
    val relocationRequester = remember { RelocationRequester() }
 
    LaunchedEffect(
        imeInsets.isVisible,
        imeInsets.animationInProgress,
        focusState,
        relocationRequester
    ) {
        if (imeInsets.isVisible &&
            !imeInsets.animationInProgress &&
            focusState?.isFocused == true) {
            relocationRequester.bringIntoView()
        }
    }
 
    relocationRequester(relocationRequester)
        .onFocusChanged { focusState = it }
}

这个ReloactionRequest已经deprecated了, Compose新版的叫BringIntoViewRequester.

IME padding计算和布置

.imePadding()的值是变化的, 在没有键盘的情况下是0, 等有键盘的时候变为键盘高度.

计算键盘弹出的高度要注意:

  • 最简单的情况直接用.imePadding()完事, 布局的bottom padding自动和IME贴合.
  • 如果整体已经有了navigation bar的高度, 可以考虑用.navigationBarsWithImePadding(), 它是取IME和navigation bar高度的最大值.
  • 如果键盘上方出现了白条, 说明padding算多了, 要么是布局中已经有inner padding, 要么就是已经加过navigationBarsPadding. 这时候可以自己做一个减法处理.
    比如这个:
LazyColumn(
    contentPadding = PaddingValues(
        bottom = with(LocalDensity.current) {
            LocalWindowInsets.current.ime.bottom.toDp() - innerPadding.bottom
        }.coerceAtLeast(0.dp)
    )
) { /* ... */ }

.imePadding放在哪里, 关系到什么样的区域会被显示出来, 被包裹的区域会显示在键盘上方.

来举个例子, 有个带输入框的界面.

我们给它整体设置一个.navigationBarsWithImePadding(), 表示没键盘的时候, 底部留navigation bar的高度, 有键盘的时候留键盘的高度:

Column(
    modifier = Modifier.fillMaxSize().statusBarsPadding().navigationBarsWithImePadding()
        .background(color = Color.Cyan.copy(alpha = 0.2f)),
    verticalArrangement = Arrangement.SpaceBetween
) {
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Top Text",
        style = MaterialTheme.typography.h2
    )
    Text(text = "Content", style = MaterialTheme.typography.h2)
    MyTextField("Text Field 1")
    MyTextField("Text Field 2")
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Bottom Text",
        style = MaterialTheme.typography.h2
    )
}

键盘弹出时, Bottom Text也会被顶上去, 这是因为imePadding作用于整块的布局.

如果我们这样改, 只包裹输入框的部分, 那么键盘就不会把底部的UI顶上去:

Column(
    modifier = Modifier.fillMaxSize().statusBarsPadding()
        .background(color = Color.Cyan.copy(alpha = 0.2f)),
    verticalArrangement = Arrangement.SpaceBetween
) {
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Top Text",
        style = MaterialTheme.typography.h2
    )
    Text(text = "Content", style = MaterialTheme.typography.h2)
 
    Text(
        modifier = Modifier.fillMaxWidth()
            .background(color = Color.Yellow.copy(alpha = 0.5f)),
        text = "Bottom Text",
        style = MaterialTheme.typography.h2
    )
}

两种效果见图:

键盘部分总结延伸

总结: 输入框键盘的处理包括了:

  • adjustResize.
  • 设置合理的bottom padding: 在哪里设置, 需要设置多少.
  • 让View主动bring自己到可见位置.

Insets库里还提供了键盘随着滚动消失和出现的例子. 感兴趣可以看下.

accompanist insets使用总结

accompanist insets库帮我们做了两部分内容:

  • 获取各种insets信息然后用CompositionLocalProvider提供.
  • Provider内部, 通过Modifier获取直接可用的modifier或者尺寸, 也可以直接获取.

但是这个库用起来也有一些需要注意的地方, 比如:

  • 如果忘记设置WindowCompat.setDecorFitsSystemWindows(window, false), 得到的值都是0.
  • ProvideWindowInsets的参数: consumeWindowInsets这个值默认是true, 建议设置为false, 方便内层的ui继续用这些inset的值.
@Composable
fun ProvideWindowInsets(
    consumeWindowInsets: Boolean = true,
    windowInsetsAnimationsEnabled: Boolean = true,
    content: @Composable () -> Unit
)
  • 如果在布局中嵌套使用ProvideWindowInsets, 可能就无法按照预期工作, (不知道是不是暂时性的issue).

References

  • Lay out your app within window insets
  • Accompanist Insets: Guide - Accompanist
  • Sample: accompanist/sample/src/main/java/com/google/accompanist/sample/insets at main · google/accompanist · GitHub
  • Video: Animating your keyboard using WindowInsets

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/599705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软考A计划-网络规划设计师-学习笔记-上

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Linux 如何判断文件的类型

在Linux中&#xff0c;我们如何判断一个文件的类型和用户权限呢&#xff1f; 在c语言中&#xff0c;Linux为我们提供了一个结构体stat我们可以通过 #include<sys/stat.h>引入后使用。然后通过stat中的st_mode来判断文件的类型。如下图&#xff0c;我们要知道文件是什么类…

超级入门:R 语言的 5 种基本数据类型

一、R语言简介 R语言是一种用于统计计算和绘图的编程语言&#xff0c;它是由新西兰奥克兰大学的 Ross Ihaka 和 Robert Gentleman 开发的。R语言支持向量和矩阵计算&#xff0c;因此也可以用于数值分析和线性代数。它主要应用于数据分析、统计学习、数据挖掘、数据可视化等领域…

【Springboot】集成QQ邮箱信息发送

系列文章目录 文章目录 系列文章目录前言添加Maven依赖QQ邮箱开启POP服务配置application.properties文件Controller层编写 vue前端&#xff08;也可以直接省略&#xff09; 前言 这篇博客用于简单实现SpringBoot中发送请求&#xff0c;用户可以收到邮件。 添加Maven依赖 <…

ISIS 实验

(1)拓扑图 2&#xff09;需求&#xff1a; -实现PC1和PC2的通信 3&#xff09;配置步骤&#xff1a; -配置接口IP地址 -开启ISIS---类似于在OSPF中创建进程 -配置NET地址---类似于在OSPF中创建区域&#xff0c;指定Router-id -在接口上启用ISIS--类似于在OSPFv2中用ne…

SKNet讲解

SKNet讲解 0. 引言1. 网络结构1.1 Split部分1.2 Fuse部分1.3 Select部分1.4 三分支的情况 2. SKNet网络体系结构3. 分析与解释4. 代码总结 0. 引言 视皮层神经元的感受野大小受刺激的调节&#xff0c;即对不同刺激&#xff0c;卷积核的大小应该不同&#xff0c;但在构建CNN时一…

<DB2>《DB2创建分区表及相关操作》(精华)

《DB2创建分区表及相关操作》 1 基本概念2 操作2.1 查看数据库中存在的分区表2.2 查看分区表详细2.3 断开对数据表的访问连接2.4 备份数据2.5 拆离分区2.6 添加分区2.7 导入数据2.8 校验前后数据2.9 删除临时表数据 1 基本概念 当表中的数据量不断增大&#xff0c;查询数据的速…

使用Kettle实现数据排序

一、Kettle的安装 1.下载Kettle的安装包文件 在Windows系统中打开浏览器&#xff0c;访问Kettle官网&#xff08;https://sourceforge.net/projects/pentaho/&#xff09;&#xff0c;下载Kettle安装文件pdi-ce-9.1.0.0-324.zip。 或者在我的百度网盘分享里面下载 链接&…

【软件测试】软件测试总结笔记(2)

软件测试过程&#xff08;内容&#xff09; 1.单元测试基本概念定义⭐单元测试环境⭐单元测试内容单元测试用例的设计思路⭐单元测试的过程 2. 集成测试集成测试内容集成测试优点⭐集成测试层次集成测试方法Drivers and Stubs ⭐集成策略&#xff08;基于分解的集成&#xff09…

crontab定时任务介绍

1 crontab概述 crontab是linux操作系统上用来设置定时任务的基础命令&#xff0c;是基于crond服务实现任务调度执行。 当安装完成操作系统后&#xff0c;默认会安装crond服务及其附属命令&#xff0c;并且会自动启动crond进程&#xff0c;crond进程每分钟会定期检查是否有要执…

Python量化交易:策略创建运行流程

学习目标 目标 知道策略的创建和运行知道策略的相关设置知道RQ的策略运行流程应用 无 1、体验创建策略、运行策略流程 1.1 创建策略 1.2 策略界面 2、 策略界面功能、运行介绍 2.1 一个完整的策略需要做的事情 选择策略的运行信息&#xff1a; 选择运行区间和初始资金选择回…

水库大坝安全问题有哪些?

我国现有水库大坝9.8万余座&#xff0c;80%水库大坝修建于上世纪50至70年代&#xff0c;受经济、技术等历史因素的影响&#xff0c;存在坝体结构破损、坝基渗漏、坝体渗漏、坝面变形等严重的安全隐患。 一、水库大坝的安全问题主要包括以下几个方面&#xff1a; 1.坝体结构破损…

“微商城”项目(3页面布局)

1.设置标题 设置页面头部标题&#xff0c;方便告诉用户当前显示的是哪一个页面。编辑src\router.js文件&#xff0c;示例代码如下。 routes: [{ path: /, redirect: /home, meta: { title: 首页 } },{ path: /home, component: Home, name: home, meta: { title: 首页 } } ] …

ConditionObject的await方法分析

ConditionObject的await方法分析 判断当前线程是否中断&#xff0c;中断直接抛出非法监视器状态异常要是没有中断则通过addConditionWaiter()方法将该节点加入到Condition的单向链表中通过fullyRelease(node)方法一次新释放掉锁资源初始化一个状态模式为0的标记执行while判断&…

Linux基础【Linux开发】

Linux基础【Linux开发】 1.Linux系统结构2.Linux内核下载流程3. 文本编辑器4. 软件包管理5. shell命令5.1 shell作为命令语言5.2 shell作为编程语言 6. makefile工程管理文件 1.Linux系统结构 2.Linux内核下载流程 Linux内核官方网站&#x1f447; 3. 文本编辑器 vim 老…

TextCNN:用于文本分类的CNN网络

参考资料&#xff1a;文本分类之TextCNN与DPCNN TextCNN 是在2014年的论文 《Convolutional Neural Networks for Sentence Classification》中提出来的。 以下是TextCNN的网络结构&#xff1a; &#xff08;1&#xff09;TextCNN的第一层为 Embedding 层 Embedding 层的输入…

C++的右值引用和移动语义

1.左值和右值 在C中&#xff0c;每个表达式或者是左值&#xff0c;或者是右值。 左值(lvalue)&#xff1a;可以出现在赋值表达式左侧的值&#xff0c;例如变量名a、数据成员a.m、解引用表达式*p等。左值可以被赋值和取地址。右值(rvalue)&#xff1a;只能出现在赋值表达式右侧…

十七、多线程(下)

文章目录 一、线程互斥&#xff0c;它是对的&#xff0c;但是不合理&#xff08;饥饿问题&#xff09;——同步二、条件变量&#xff08;一&#xff09;概念&#xff08;二&#xff09;条件变量接口1. pthread_cond_init 创建条件变量2. pthread_cond_wait 等待条件满足3. pthr…

类的成员之:构造器(构造方法)

1.构造器的特征&#xff1a; 它具有与类相同的名称它不声明返回值类型。&#xff08;与声明为void不同&#xff09;不能被static、final、synchronized、abstract、native修饰&#xff0c;不能有return语句返回值 2.构造器的作用&#xff1a; 1.创建对象2.初始化对象的…

StackLLaMA: A hands-on guide to train LLaMA with RLHF

Paper name StackLLaMA: A hands-on guide to train LLaMA with RLHF Paper Reading Note Project URL: https://huggingface.co/blog/stackllama Code URL: https://huggingface.co/docs/trl/index TL;DR Huggingface 公司开发的 RLHF 训练代码&#xff0c;已集成到 hugg…