原文:Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用 | Stars-One的杂货小窝

本篇主要讲解常用的布局,会与原生Android的布局控件进行对比说明,请确保了解Android原生基本布局的知识,否则阅读文章会存在有难度

之前我也是在第一篇中的入门实现了一个简单的登录页面,也是有读者评论说我界面太丑了

当时入门便是想整的简单些,今天我便是实现美化来学习下布局的相关使用,这位同学看好了哦!

本系列以往文章请查看此分类链接Jetpack compose学习

登录页的美化工作

首先,我是先到网上找到了一份比较好看的登录页,地址为登录页|UI|APP界面|喵喵wbh - 原创作品 - 站酷 (ZCOOL),如下图所示

我们照着实现,最终效果是这样的(可能稍微有点不太像,不过应该还凑合看得过去吧!!)

背景图设置和注册按钮

按照UI设计图,我们需要设置背景图,这里compose并不想之前Android原生组件,可以直接设置图片,我是采取的Box布局来实现

Box布局与Frameayout相似,组件会按照顺序从下向上排(z轴方向)

图片由于设计图没给出来,于是我自己随便找了张图片代替

Box(Modifier.fillMaxSize()) {
Image(painter = painterResource(id = R.drawable.bg_login), contentDescription = null)
}

Modifier.fillMaxSize()作用是让布局填充满宽度(与原生中的match_parent同作用)

效果如下图所示

这个时候我们考虑右上角加上有个注册按钮,同时,还需要个白色背景(放输入框和登录按钮等),于是我们可以这样写

Box(Modifier.fillMaxSize()) {
Image(painter = painterResource(id = R.drawable.bg_login), contentDescription = null)
Text(
text = "注册",
color = Color.White,
fontSize = 20.sp,
textAlign = TextAlign.End,
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
)
Column() {
Spacer(modifier = Modifier.weight(1f))
Column(
modifier = Modifier
.weight(3f)
.background(Color.White)
.padding(40.dp)
.fillMaxWidth()
) {
//后面输入框等组件在这里加,由于代码过长,为了方便阅读,后续贴出的代码都是在这里的代码
}
}
}
  • textAlign是文字对齐方式,但是需要Text自身宽度有空余才能看见效果(即设置个超过文本字数的宽度或直接填充父布局),Text组件的默认宽度是自适应的

  • Spacer是空格布局,其背景色是透明的,Android原生的margin属性的替代组件(因为设计问题,compose组件只提供padding设置)

  • Modifier.weight(1f)表示权重,接收Float类型的数值,如果在Row使用,就是宽度权重占1,在Column使用,则是高度权重占1

上述代码,我们将注册的文字设置在右上方,且又加上加上了个Column,这个时候我们是将Column又分成了两个组件,一个是Spacer(占1/4),一个是Column(占3/4)

由于上方是Spacer,其背景色是透明的,所以不会影响展示注册文字按钮(当然这里,我是用的Text组件,其实也可以使用TextButton组件)

效果如下所示

输入框样式调整

接下来我们调整下输入框的样式


val pwdVisualTransformation = PasswordVisualTransformation()
var showPwd by remember {
mutableStateOf(true)
} val transformation = if (showPwd) pwdVisualTransformation else VisualTransformation.None Column() {
TextField(
modifier = Modifier.fillMaxWidth(),
value = name,
placeholder = {
Text("请输入用户名")
},
onValueChange = { str -> name = str },
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
leadingIcon = {
Icon(
imageVector = Icons.Default.AccountBox,
contentDescription = null
)
})
TextField(
value = pwd, onValueChange = { str -> pwd = str },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text("请输入密码")
},
visualTransformation = transformation,
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
leadingIcon = {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = null
)
},trailingIcon = {
if (showPwd) {
IconButton(onClick = { showPwd = !showPwd}) {
Icon(painter = painterResource(id = R.drawable.eye_hide), contentDescription =null,Modifier.size(30.dp))
}
} else {
IconButton(onClick = { showPwd = !showPwd}) {
Icon(painter = painterResource(id = R.drawable.eye_show), contentDescription =null,Modifier.size(30.dp))
}
}
}
)
}}

这里设置了输入框的背景色,改为了Color.Transparent,且给前面设置了一个图标

密码则是有个显示和隐藏密码的开关,具体解释可以看之前文章Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝

效果如下图所示

快捷登录与忘记密码

Row(horizontalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth()) {
Text(text = "快捷登录", fontSize = 16.sp, color = Color.Gray)
Text(text = "忘记密码", fontSize = 16.sp, color = Color.Gray)
}

horizontalArrangement设置Row水平排列方式,取值感觉和前端的Flex布局很相似

SpaceBetween的效果是布局里的组件元素左右两边对齐

效果如下

登录按钮

Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if (name == "test" && pwd == "123") {
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "登录失败", Toast.LENGTH_SHORT).show()
}
},
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xff5c59fe)),
contentPadding = PaddingValues(12.dp, 16.dp)
) {
Text("登录", color = Color.White, fontSize = 18.sp)
}

登录按钮设置为圆角的按钮,且改变了下颜色

注意: 颜色的设置好像不支持这种类型:#5c59fe,使用的使用应该这样使用:Color(0xff5c59fe),需要把#替换为0xff

第三方登录

Row(horizontalArrangement = Arrangement.SpaceBetween,verticalAlignment = Alignment.CenterVertically) {
Row(
Modifier
.height(1.dp)
.weight(1f)
.background(Color(0xFFCFC5C5))
.padding(end = 10.dp)){}
Text(text = "第三方登录", fontSize = 16.sp, color = Color.Gray)
Row(
Modifier
.height(1.dp)
.weight(1f)
.background(Color(0xFFCFC5C5))
.padding(start = 10.dp)){}
} Spacer(modifier = Modifier.height(20.dp))
Row(Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) {
repeat(3){
Column(Modifier.weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
Image(modifier = Modifier.size(50.dp),painter = painterResource(id = R.drawable.qq), contentDescription = null)
Text("QQ", color = Color(0xffcdcdcd), fontSize = 16.sp,fontWeight = FontWeight.Bold)
}
}
}

下面的第三方登录左右两边各有一个横线,我是使用了Row作为线条(compose里也没有组件,这样做应该没啥大问题)

至于底部的布局,每个Item是个Column,并使用居中堆积,且使用了权重平分了外面一个Row布局

这里简单起见,就直接用了个循环(不会告诉你我懒得下图标了)

至此,美化的工作就到这里了,下面针对上述出现的布局进行使用的讲解

源码

@Preview(showBackground = true)
@Composable
fun LoginPageDemo() {
var name by remember { mutableStateOf("") }
var pwd by remember { mutableStateOf("") } val pwdVisualTransformation = PasswordVisualTransformation()
var showPwd by remember {
mutableStateOf(true)
} val transformation = if (showPwd) pwdVisualTransformation else VisualTransformation.None ComposeDemoTheme { Box(Modifier.fillMaxSize()) {
Image(painter = painterResource(id = R.drawable.bg_login), contentDescription = null)
Text(
text = "注册",
color = Color.White,
fontSize = 20.sp,
textAlign = TextAlign.End,
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
)
Column() {
Spacer(modifier = Modifier.weight(1f))
Column(
modifier = Modifier
.weight(3f)
.background(Color.White)
.padding(40.dp)
.fillMaxWidth()
) {
Column() {
TextField(
modifier = Modifier.fillMaxWidth(),
value = name,
placeholder = {
Text("请输入用户名")
},
onValueChange = { str -> name = str },
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
leadingIcon = {
Icon(
imageVector = Icons.Default.AccountBox,
contentDescription = null
)
})
TextField(
value = pwd, onValueChange = { str -> pwd = str },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text("请输入密码")
},
visualTransformation = transformation,
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
leadingIcon = {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = null
)
}, trailingIcon = {
if (showPwd) {
IconButton(onClick = { showPwd = !showPwd }) {
Icon(
painter = painterResource(id = R.drawable.eye_hide),
contentDescription = null,
Modifier.size(30.dp)
)
}
} else {
IconButton(onClick = { showPwd = !showPwd }) {
Icon(
painter = painterResource(id = R.drawable.eye_show),
contentDescription = null,
Modifier.size(30.dp)
)
}
}
}
)
}
Spacer(modifier = Modifier.height(20.dp))
Row(horizontalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth()) {
Text(text = "快捷登录", fontSize = 16.sp, color = Color.Gray)
Text(text = "忘记密码", fontSize = 16.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.height(20.dp))
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
if (name == "test" && pwd == "123") {
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "登录失败", Toast.LENGTH_SHORT).show()
}
},
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xff5c59fe)),
contentPadding = PaddingValues(12.dp, 16.dp)
) {
Text("登录", color = Color.White, fontSize = 18.sp)
} Spacer(modifier = Modifier.height(100.dp))
Row(horizontalArrangement = Arrangement.SpaceBetween,verticalAlignment = Alignment.CenterVertically) {
Row(
Modifier
.height(1.dp)
.weight(1f)
.background(Color(0xFFCFC5C5))
.padding(end = 10.dp)){}
Text(text = "第三方登录", fontSize = 16.sp, color = Color.Gray)
Row(
Modifier
.height(1.dp)
.weight(1f)
.background(Color(0xFFCFC5C5))
.padding(start = 10.dp)){}
}
Spacer(modifier = Modifier.height(20.dp))
Row(Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) {
repeat(3){
Column(Modifier.weight(1f),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {
Image(modifier = Modifier.size(50.dp),painter = painterResource(id = R.drawable.qq), contentDescription = null)
Text("QQ", color = Color(0xffcdcdcd), fontSize = 16.sp,fontWeight = FontWeight.Bold)
}
}
}
}
}
}
}
}

布局容器

Box

首先介绍一下Box布局,和FrameLayout的特性一样,是按顺序排的

fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
)
  • modifier 修饰符(下一篇讲)
  • contentAlignment 内容对齐方式(之前在Image图片使用的时候提过了,详见上一篇)
  • propagateMinConstraints 是否应将传入的最小约束传递给内容,不太懂具体是什么效果

Row

Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
)
  • horizontalArrangement 子元素的水平方向排列效果

  • verticalAlignmentment 子元素的垂直方向对齐效果

horizontalArrangement

由上述代码提示图片,取值有五种,分别为:

  • Arrangement.Start 左排列
  • Arrangement.Center 居中排列
  • Arrangement.End 右排列
  • Arrangement.SpaceBetween 左右对齐排列,最左和最右组件元素靠边
  • Arrangement.SpaceArround 左右对齐排列,最左和左右组件元素有间隔,且间隔相同,中间则是平分
  • Arrangement.SpaceEvenly 左右对齐排列,且各组件元素间距相同

注意:使用此布局也是需要Row布局的宽度并不是自适应的

Column() {
Row(horizontalArrangement = Arrangement.Start,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
Row(horizontalArrangement = Arrangement.Center,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
Row(horizontalArrangement = Arrangement.End,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
Row(horizontalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
Row(horizontalArrangement = Arrangement.SpaceAround,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
Row(horizontalArrangement = Arrangement.SpaceEvenly,modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}
}

PS: 感觉和前端的Flex布局很像,这里用文字描述可能不太清楚,可以参考下我的文章CSS Flex 弹性布局使用 | Stars-One的杂货小窝或者参考下Flex布局的学习资料

补充下,Row本身是不支持滚动的(Column同理),但是想要滚动的话,可以使用Modifier.horizontalScroll()来实现,代码如下

Row(Modifier.horizontalScroll(rememberScrollState())) {
}
  • Modifier.horizontalScroll() 水平滚动
  • Modifier.verticalScroll() 垂直滚动

注意:compose似乎不支持一个水平滚动嵌套垂直滚动(或垂直滚动中嵌套水平滚动),所以相应布局需要合理设计

此外,提及下,如果想使用像ListViewRecyclerView那样的列表组件,在Compose中可以使用LazyRowLazyColumn,这部分内容之后会讲解到,敬请期待

verticalAlignmentment

取值有三个值:

  • Alignment.CenterVertically 居中
  • Alignment.Top 靠顶部
  • Alignment.Bottom 靠底部

与上面一样,布局高度如果是自适应的,则不会有效果

Row(verticalAlignment = Alignment.CenterVertically) {
Box(
Modifier
.background(Color.Green)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Blue)
.size(100.dp)) {
}
Box(
Modifier
.background(Color.Red)
.size(100.dp)) {
}
}

Column

此布局和Row布局的参数一样,只是名字有所区别,使用方法和上面都一样

  • verticalArrangement 垂直方向排列
  • horizontalAlignmentment 水平方向对齐

Spacer

Spacer,直接翻译的话,应该是空格,其主要就是充当margin的作用,一般使用modifier修饰符来设置宽高占位来达到margin效果

Card

官方封装好的Material Design的卡片布局

fun Card(
modifier: Modifier = Modifier,
shape: Shape = MaterialTheme.shapes.medium,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
border: BorderStroke? = null,
elevation: Dp = 1.dp,
content: @Composable () -> Unit
)
Card(modifier = Modifier.fillMaxWidth().padding(20.dp),elevation = 10.dp) {
Text(text = "hello world")
}

效果如下:

参考

Jetpack Compose学习(5)——从登录页美化开始学习布局组件使用的更多相关文章

  1. Jetpack Compose学习(1)——从登录页开始入门

    原文地址:Jetpack Compose学习(1)--从登录页开始入门 | Stars-One的杂货小窝 Jetpack Compose UI在前几天出了1.0正式版,之前一直还在观望,终于是出了正式 ...

  2. Jetpack Compose学习(2)——文本(Text)的使用

    原文: Jetpack Compose学习(2)--文本(Text)的使用 | Stars-One的杂货小窝 对于开发来说,文字最为基础的组件,我们先从这两个使用开始吧 本篇涉及到Kotlin和DSL ...

  3. Spring Security笔记:自定义登录页

    以下内容参考了 http://www.mkyong.com/spring-security/spring-security-form-login-example/ 接上回,在前面的Hello Worl ...

  4. 通过ajax前端后台交互/登录页和注册页前端后台交互详解/前端后台交互基础应用/几个后台函数的基础应用/php文件函数基础应用/php字符传函数基础应用/php数组函数基础应用

      前  言  PHP     学习了好久的PHP,今天做一个可以后台交互的登录页和注册页,没做什么判断,简单的了解一下. 具体的内容分析如下: ① PHP中的数据传输-->>由注册页传输 ...

  5. lfs(systemd版本)学习笔记-第2页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...

  6. lfs(systemv版本)学习笔记-第2页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemv)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...

  7. Flask学习之五 用户登录

    英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-logins 中文翻译地址:http:// ...

  8. Jetpack Compose What and Why, 6个问题

    Jetpack Compose What and Why, 6个问题 1.这个技术出现的背景, 初衷, 要达到什么样的目标或是要解决什么样的问题. Jetpack Compose是什么? 它是一个声明 ...

  9. Jetpack Compose 1.0 终于要投入使用了!

    前言 Jetpack Compose 是用于构建原生界面的「新款 Android 工具包」.2021 Google IO 大会上,Google宣布:「Jetpack Compose 1.0 即将面世」 ...

  10. Android Kotlin Jetpack Compose UI框架 完全解析

    前言 Q1的时候公司列了个培训计划,部分人作为讲师要上报培训课题.那时候刚从好几个Android项目里抽离出来,正好看到Jetpack发布了新玩意儿--Compose,我被它的快速实时打包给吸引住了, ...

随机推荐

  1. AT指令(转)

    资料来自网络 附录AT指令简编一. 一般命令1.AT+CGMI 给出模块厂商的标识.2.AT+CGMM 获得模块标识.这个命令用来得到支持的频带(GSM 900,DCS 1800 或PCS 1900) ...

  2. python连接MongoDB

    1.安装pymongo库 windows下: pip install pymongo 或者 easy_install install pymongo 2.使用pymongo模块连接mongoDB数据库 ...

  3. C# 单例模式(Singleton Pattern)(转SuagrMatl)

    单例模式的定义: 确保一个类只有一个实例,并提供一个全局访问点. 首先实例大家应该都明白就是类生成对象的过程简单的就是String s=new String(),则s就是个实例. Q:如何只生成一个实 ...

  4. AllocateHWnd SetTimer API

    unit Unit2; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  5. 7个管理和优化网站资源的 Python 工具

    前一篇:

  6. JVM规范系列第2章:Java虚拟机结构

    本规范描述的是一种抽象化的虚拟机的行为,而不是任何一种(译者注:包括 Oracle 公司自己的 HotSpot 和 JRockit 虚拟机)被广泛使用的虚拟机实现. 记住:JVM规范是一种高度抽象行为 ...

  7. Android 支付密码输入框,自定义EditText实现密码输入框功能;

    刚撸出来的密码输入框,注释和逻辑看着挺清晰的,一些属性还没有添加,下个博客把属性添加上去: 看一下图: 直接看代码吧! import android.content.Context; import a ...

  8. 模拟多级复选框效果的jquery代码

    jquery做了个多级复选框的效果,代码总共就20+行就over了. 我又想用js来做一个看看,才写了几个方法就写不动了,兼容性要考虑很多,而且代码量直线上升. 主要分享下jquery的这个效果的实现 ...

  9. js获取文件输入框的真实目录

    1.问题 页面有一个input file服务器控件,一个div,div是image标签的容器,当点击input file的值改变,我们往div里追加image标签: 但当通过js的onchange事件 ...

  10. python3的leetcode题,两个数求和等于目标值,返回这两个数的索引组成的列表(三种方法)

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为gai目标值的 两个 整数. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元素. 示例: 给定 ...