前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >compose--初入compose、资源获取、标准控件与布局

compose--初入compose、资源获取、标准控件与布局

作者头像
aruba
发布2022-12-06 15:33:57
5.6K0
发布2022-12-06 15:33:57
举报
文章被收录于专栏:android技术android技术

compose正式发布已经一年多了,越来越多的开发人员选择使用它,声明式UI也是未来的一个主流趋势,本人也是一年前学习后,并没有真正的使用,所以本着边学习,边分享的心态,准备写个compose系列的文章

首先compose目前只支持kotlin,基于google对移动端的鸿图,未来应该也不会支持其他语言,和传统安卓的xml布局不同,compose是通过kotlin定义一个一个组件,由于是通过代码定义的组件,每个组件都可以很方便的重用,这点在UI开发时确实便利了不少。至于声明式UI和命令式UI的区别,相信你会在后续实际使用时有很大的感触

一、认识compose

通过官方文档我们可以了解到compose的编程思想。官方地址:https://developer.android.google.cn/jetpack/compose/mental-model

我这边也是根据官方文档,对重要的部分和自己的想法进行融合,来介绍什么是compose。这部分内容都是概念性的,但是贯穿整个compose的学习,应该进行着重深入理解

1. 重组
1.1 安卓传统UI

先来说在安卓传统UI,大致的流程就是xml中我们定义了一系列的布局(组件)和控件后,由ActivityonCreate()触发xml解析,生成View树DecorView,并ActivityhandleResumeActivity()ViewRootImpl绑定,通过Binder通信,交由由WindowManagerService创建surface进行渲染,最终呈现在手机屏幕

当然了,我们只需要关注在onCreate()中设置xml即可,由于布局是一次性加载的,即生成View树的过程是同步进行的

1.2 compose UI

对与compose而言,每个可组合函数(组件)的调用可能发生在与调用方不同的线程上,即每个组件添加至View树的过程,都是通过协程进行的,上树的过程未必按代码调用的顺序执行

1.3 什么是重组?

在compose中,每个可组合函数调用直至渲染完成,称之为重组

通过异步上树虽然带来了性能的提升,但是管理方面变得困难,所以compose规定,每个可组合函数都是独立运行的存在,可组合函数内部应该仅处理的UI操作,重组的发生的时机并不由我们控制,而是由compose内部自动管理,后续我们可以使用状态来通知compose进行重组

二、创建compose项目

推荐使用最新的android studio,低版本并不支持compose,也可以查看官方文档-快速入门:https://developer.android.google.cn/jetpack/compose/setup

1.创建项目

我这边尝鲜使用MD3风格的项目,实际开发中google也推荐:UI设计从MD2转变为MD3

2.BOM

对于compose的版本管理,官方推荐使用BOM,导入BOM后的好处是:导入compose其他库组,都将使用BOM中定义的版本,后续更新,我们只需要更新BOM的版本即可。下面是官方给出的BOM:compose版本对应关系:

库组

版本 (2022.10.00)

版本 (2022.11.00)

androidx.compose.animation:animation

1.3.0

1.3.1

androidx.compose.animation:animation-core

1.3.0

1.3.1

androidx.compose.animation:animation-graphics

1.3.0

1.3.1

androidx.compose.foundation:foundation

1.3.0

1.3.1

androidx.compose.foundation:foundation-layout

1.3.0

1.3.1

androidx.compose.material:material

1.3.0

1.3.1

androidx.compose.material:material-icons-core

1.3.0

1.3.1

androidx.compose.material:material-icons-extended

1.3.0

1.3.1

androidx.compose.material:material-ripple

1.3.0

1.3.1

androidx.compose.material3:material3

1.0.0

1.0.1

androidx.compose.material3:material3-window-size-class

1.0.0

1.0.1

androidx.compose.runtime:runtime

1.3.0

1.3.1

androidx.compose.runtime:runtime-livedata

1.3.0

1.3.1

androidx.compose.runtime:runtime-rxjava2

1.3.0

1.3.1

androidx.compose.runtime:runtime-rxjava3

1.3.0

1.3.1

androidx.compose.runtime:runtime-saveable

1.3.0

1.3.1

androidx.compose.ui:ui

1.3.0

1.3.1

androidx.compose.ui:ui-geometry

1.3.0

1.3.1

androidx.compose.ui:ui-graphics

1.3.0

1.3.1

androidx.compose.ui:ui-test

1.3.0

1.3.1

androidx.compose.ui:ui-test-junit4

1.3.0

1.3.1

androidx.compose.ui:ui-test-manifest

1.3.0

1.3.1

androidx.compose.ui:ui-text

1.3.0

1.3.1

androidx.compose.ui:ui-text-google-fonts

1.3.0

1.3.1

androidx.compose.ui:ui-tooling

1.3.0

1.3.1

androidx.compose.ui:ui-tooling-data

1.3.0

1.3.1

androidx.compose.ui:ui-tooling-preview

1.3.0

1.3.1

androidx.compose.ui:ui-unit

1.3.0

1.3.1

androidx.compose.ui:ui-util

1.3.0

1.3.1

androidx.compose.ui:ui-viewbinding

1.3.0

1.3.1

工程中导入:

代码语言:javascript
复制
dependencies {
    def composeBom = platform('androidx.compose:compose-bom:2022.10.00')
    implementation composeBom

...
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
    implementation 'androidx.activity:activity-compose'
    implementation "androidx.compose.ui:ui"
    implementation "androidx.compose.ui:ui-tooling-preview"
    implementation 'androidx.compose.material3:material3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.3.1"
    debugImplementation "androidx.compose.ui:ui-tooling"
    debugImplementation "androidx.compose.ui:ui-test-manifest"
}
3.kotlin-compose compiler版本对应

BOM中不包含Compose编译器库,所以我们需要手动对应下kotlin版本与compose compiler版本,下面是两者的兼容关系,官网也可以查询到最新的对应关系:

https://developer.android.google.cn/jetpack/androidx/releases/compose-kotlin

Compose Compiler 版本

兼容的 Kotlin 版本

1.4.0-alpha01

1.7.20

1.3.2

1.7.20

1.3.1

1.7.10

1.3.0

1.7.10

1.3.0-rc02

1.7.10

1.3.0-rc01

1.7.10

1.3.0-beta01

1.7.10

1.2.0

1.7.0

1.2.0-rc02

1.6.21

1.2.0-rc01

1.6.21

1.2.0-beta03

1.6.21

1.2.0-beta02

1.6.21

1.2.0-beta01

1.6.21

1.2.0-alpha08

1.6.20

1.2.0-alpha07

1.6.10

1.2.0-alpha06

1.6.10

1.2.0-alpha05

1.6.10

1.2.0-alpha04

1.6.10

1.2.0-alpha03

1.6.10

1.2.0-alpha02

1.6.10

1.2.0-alpha01

1.6.10

1.1.1

1.6.10

1.1.0

1.6.10

1.1.0-rc03

1.6.10

1.1.0-rc02

1.6.10

1.1.0-rc01

1.6.0

1.1.0-beta04

1.6.0

1.1.0-beta03

1.5.31

1.1.0-beta02

1.5.31

1.1.0-beta01

1.5.31

1.1.0-alpha06

1.5.31

1.1.0-alpha05

1.5.31

1.0.5

1.5.31

1.0.4

1.5.31

1.1.0-alpha04

1.5.30

1.1.0-alpha03

1.5.30

1.0.3

1.5.30

1.1.0-alpha02

1.5.21

1.1.0-alpha01

1.5.21

1.0.2

1.5.21

1.0.1

1.5.21

1.0.0

1.5.10

1.0.0-rc02

1.5.10

1.0.0-rc01

1.5.10

我这边使用的是1.3.1,对应kotlin版本是1.7.10,工程中build.gradle

代码语言:javascript
复制
android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.3.1"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

主工程中build.gradle:

代码语言:javascript
复制
plugins {
    id 'com.android.application' version '7.3.1' apply false
    id 'com.android.library' version '7.3.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
4.预览compose函数与启动
4.1 预览compose函数

引入了ui-tooling-preview库组后,我们可以使用@Preview注解可组合函数,并实现预览组件

4.2 启动

启动到模拟器的效果:

三、资源获取

xml中,我们常常会使用资源id获取到资源文件,比如:color、drawable、string等,在compose中,通过以下函数获取,这些函数都位于androidx.compose.ui.res包下:

当然我们并不需要使用里面全部的类,掌握下面列出的即可:

资源获取方式

描述

stringResource

获取对应id的string资源,并支持传入多个参数,来实现字符串格式化

colorResource

获取对应id的color资源

painterResource

获取对应id的图片资源,可以是一个vector,也可以是drawable

dimensionResource

获取对应id的dimen资源,由于compose推荐使用md主题设置dimen,用的也不多

四、标准控件

compose本身内置了一些组件,官方说法所有组件都是可组合函数,这边仅仅是便于传统开发理解,分成控件和布局来介绍,这些内置可组合函数分散在各个不同的库组内,如:androidx.compose.foundationandroidx.compose.foundation.layoutandroidx.compose.material3

其中控件大多位于md包下,他们都具有MD风格,也是官方推荐使用的组件:

1.Text

Text用于呈现一段文字,是使用最多的组件,官方也详细的介绍了该组件:https://developer.android.google.cn/jetpack/compose/text

1.1 基本使用

所有compose函数都要由@Composable注解,并且每个可组合函数都是可以重用的组件:

代码语言:javascript
复制
@Composable
@Preview
fun MyText() {
    Text(text = "hello world!")
}

预览效果:

1.2 使用资源获取文本

通过stringResource(id)获取String,可以达到同样的效果

代码语言:javascript
复制
@Composable
@Preview
fun MyText() {
    Text(text = stringResource(id = R.string.hello))
}
1.3 AnnotatedString

传统UI的TextView,可以通过Span来改变文本的内嵌样式,比如个别字颜色设置、设置背景颜色等效果

compose中可以使用AnnotatedString来达到这种效果,通过buildAnnotatedString()构建一个AnnotatedStringAnnotatedString可以包含多个 SpanStyle(点击跳转API)ParagraphStyle(点击跳转API)

  • SpanStyle:设置文本的内嵌样式
  • ParagraphStyle:设置文本的行高,对齐方式,文字方向和文字缩进样式

例子:

代码语言:javascript
复制
@Composable
@Preview
fun MyText() {
    Text(
        text = buildAnnotatedString {
            withStyle(
                style = ParagraphStyle(
                    lineHeight = 30.sp,//行高
                    textAlign = TextAlign.Left,//左对齐
                    textIndent = TextIndent(firstLine = 10.sp)//缩进
                )
            ) {
                withStyle(
                    style = SpanStyle(
                        fontSize = 20.sp,
                        color = Color.Red,//设置颜色为红色
                        fontWeight = FontWeight.Medium//加粗
                    )
                ) {
                    append("hi\n")
                }
            }

            withStyle(
                style = ParagraphStyle(
                    lineHeight = 60.sp,
                )
            ) {
                withStyle(
                    style = SpanStyle(
                        color = Color.Red,
                        shadow = Shadow(//设置阴影
                            color = Color.Blue,//阴影颜色
                            blurRadius = 3f,//虚化
                            offset = Offset(5f, 20f)//x,y轴的偏移
                        )
                    )
                ) {
                    append("你好\n")
                }
            }
        }
    )
}

预览效果:

1.4 其他参数

其他参数可以通过源码查看:

代码语言:javascript
复制
@Composable
fun Text(
    text: String,
    modifier: Modifier = Modifier,//修饰符
    color: Color = Color.Unspecified,//颜色
    fontSize: TextUnit = TextUnit.Unspecified,//字体
    fontStyle: FontStyle? = null,//字体样式,正常或斜体
    fontWeight: FontWeight? = null,//字体粗细
    fontFamily: FontFamily? = null,//字体
    letterSpacing: TextUnit = TextUnit.Unspecified,//字间距
    textDecoration: TextDecoration? = null,//字体装饰,删除线、下划线等
    textAlign: TextAlign? = null,//内容对齐方式,居中、左对齐、右对齐等
    lineHeight: TextUnit = TextUnit.Unspecified,//行高
    overflow: TextOverflow = TextOverflow.Clip,//内容超出处理方式,截断、使用...等
    softWrap: Boolean = true,//是否自动换行
    maxLines: Int = Int.MAX_VALUE,//最大行数
    onTextLayout: (TextLayoutResult) -> Unit = {},//文本变化导致重组的回调
    style: TextStyle = LocalTextStyle.current//更丰富的字体样式,包含上面大多数设置,以及SpanStyle和ParagraphStyle
) {
...
}

其中Modifier后续会详细介绍,举例使用里面的几个参数设置,如使用TextStyle去除首行的顶部行间距:

代码语言:javascript
复制
<string name="hello">hello!\nworld</string>
代码语言:javascript
复制
@Composable
@Preview
fun MyText() {
    Text(
        text = stringResource(id = R.string.hello),
        fontWeight = FontWeight.Medium,
        overflow = TextOverflow.Clip,
        //将当前的style和另一个合并,以另一个设置的属性为优先
        style = LocalTextStyle.current.merge(
            TextStyle(
                lineHeight = 2.5.em,
                platformStyle = PlatformTextStyle(
                    includeFontPadding = false//配合trim
                ),
                lineHeightStyle = LineHeightStyle(
                    alignment = LineHeightStyle.Alignment.Center,
                    // trim生效需要includeFontPadding = false
                    // trim是指将行间距尽可能的去除
                    // FirstLineTop:将第一行顶部的行间距去除
                    trim = LineHeightStyle.Trim.FirstLineTop
                )
            )
        )
    )
}

预览效果:

2.Image

Image用于展现图片

2.1 基本使用

必传入参为图片资源对象painter和内容描述contentDescriptioncontentDescription主要是为了残疾人使用的,国外对于残疾人使用也非常的重视,此外使用python自动化测试也可以通过contentDescription找到该组件:

代码语言:javascript
复制
@Composable
@Preview
fun MyImage() {
    Image(
        painter = painterResource(id = R.drawable.ic_launcher_foreground),//指定图片资源
        contentDescription = "my image" //描述,残疾人以及自动化测试使用
    )
}

预览效果:

2.2 其他参数

相较于TextImage的参数少很多:

代码语言:javascript
复制
@Composable
fun Image(
    painter: Painter,
    contentDescription: String?,
    modifier: Modifier = Modifier,//修饰符
    alignment: Alignment = Alignment.Center,//图片对齐方式
    contentScale: ContentScale = ContentScale.Fit,//图片的拉伸方式
    alpha: Float = DefaultAlpha,//图片透明度
    colorFilter: ColorFilter? = null//通过ColorFilter对颜色矩阵进行变换
) {
    
}

参数还是比较简单的,ContentScale的几种方式可以通过官网认识:ContentScale介绍(点击跳转),其中ColorFilter和传统UI自定义控件时,使用的高级渲染效果相同,ColorFilter分别拥有三个伴生方法,对应不同的渲染方式:

使用tint例子,使用SrcIn模式合成一个红色:

代码语言:javascript
复制
@Composable
@Preview
fun MyImage() {
    Image(
        painter = painterResource(id = R.drawable.ic_launcher_foreground),
        contentDescription = "my image",
        colorFilter = ColorFilter.tint(
            color = Color.Red,
            blendMode = BlendMode.SrcIn
        )
    )
}

预览效果:

使用colorMatrix例子,颜色增强:

代码语言:javascript
复制
@Composable
@Preview
fun MyImage() {
    Row {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = "my image1",
            colorFilter = ColorFilter.colorMatrix(
                ColorMatrix().apply {
                    setToScale(1.2f, 1.2f, 1.2f, 1f)//颜色增强
                }
            )
        )

        Spacer(modifier = Modifier.width(10.dp))

        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = "my image2",
        )
    }
}

预览效果,左边为颜色增强后:

使用lighting例子,添加红色向量:

代码语言:javascript
复制
@Composable
@Preview
fun MyImage() {
    Row {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = "my image1",
            // 红色向量添加255,红色加绿色 = 黄色
            colorFilter = ColorFilter.lighting(
                Color(red = 0xff, green = 0xff, blue = 0xff),
                Color(red = 0xff, green = 0, blue = 0)
            )
        )

        Spacer(modifier = Modifier.width(10.dp))

        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = "my image2",
        )
    }
}

预览效果,左边为添加红色向量后:

2.3 Icon

同样用于显示图标,Icon功能比Image少,只支持tint,并且该tint为一个Color对象,不支持模式,只支持染色:

代码语言:javascript
复制
@Composable
@Preview
fun MyImage() {
    Icon(
        painter = painterResource(id = R.drawable.ic_launcher_foreground),
        contentDescription = "icon",
        tint = Color.Blue // 将图标染成蓝色
    )
}

预览效果:

3.TextField

TextField就是输入框,并且需要用到state,关于state后续会详细介绍

3.1 基本使用

TextField必须传入的两个参数,一个是value,一个是onValueChange ,结合之前的重组概念来理解,每次重组都会重新调用可组合函数,所以输入框内容value必须是一个全局对象,在compose中,可以使用remember函数来使得一个变量成为全局变量,从而不受重组时代码调用导致重新初始化操作的影响

此外,只有state的改变才能通知compose进行重组,所以value又必须是一个state对象,并在onValueChange中对state进行改变,才能够进行组件的刷新

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyTextField() {
    var text by remember { mutableStateOf("") }// 定义state对象:text ,并设为全局
    TextField(
        value = text,//text 与TextField进行绑定
        onValueChange = { text = it },//当输入框值发生变换时,改变text值,从而引起状态的刷新,进而重组
        label = { Text("hint") }//提示
    )
}

效果:

3.2 TextFieldValue

value的参数类型除了支持String外,还支持TextFieldValueTextFieldValue具有更好的自定义性,如使用AnnotatedString使文本具有样式、TextRange指定光标位置:

代码语言:javascript
复制
@Immutable
class TextFieldValue constructor(
    val annotatedString: AnnotatedString,//带样式的字符串
    selection: TextRange = TextRange.Zero,//
    composition: TextRange? = null
) {
...
}

例子:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Preview
@Composable
fun TextFieldValuePreview(
) {
    val textFieldValueState = remember {
        mutableStateOf(
            TextFieldValue(
                annotatedString = buildAnnotatedString {
                    append("hi")

                    withStyle(
                        style = SpanStyle(
                            color = Color.Red,
                            //设置阴影
                            shadow = Shadow(
                                color = Color.Blue,//阴影颜色
                                blurRadius = 3f,//虚化
                            )
                        )
                    ) {
                        append("你好\n")
                    }
                },
                selection = TextRange(2)// 光标默认显示在第二个字符位置
            )
        )
    }

    val showKeyboard = remember { mutableStateOf(true) }
    val focusRequester = remember { FocusRequester() }
    val keyboard = LocalSoftwareKeyboardController.current

    // 显示键盘
    LaunchedEffect(focusRequester) {
        if (showKeyboard.value) {
            focusRequester.requestFocus()
            delay(100)
            keyboard?.show()
        }
    }

    TextField(
        modifier = Modifier.focusRequester(focusRequester),
        value = textFieldValueState.value,
        onValueChange = {
        }
    )
}

效果:

3.3 其他参数
代码语言:javascript
复制
@ExperimentalMaterial3Api
@Composable
fun TextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,// 是否可用
    readOnly: Boolean = false,// 是否只读
    textStyle: TextStyle = LocalTextStyle.current,// 和Text一样支持的TextStyle
    label: @Composable (() -> Unit)? = null,//提示,有内容时自动缩小并上移
    placeholder: @Composable (() -> Unit)? = null,//提示,有内容时自动消失
    leadingIcon: @Composable (() -> Unit)? = null,//文本前的图标
    trailingIcon: @Composable (() -> Unit)? = null,//文本尾的图标
    supportingText: @Composable (() -> Unit)? = null,//文本下方的文本
    isError: Boolean = false,//是否错误,错误会将label、下划线、下方文本、文本尾的图标的图标染红
    visualTransformation: VisualTransformation = VisualTransformation.None,//输入内容的视觉类型,如密码显示*
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,//键盘类型和imeAction
    keyboardActions: KeyboardActions = KeyboardActions.Default,//imeAction触发时的回调
    singleLine: Boolean = false,//是否单行
    maxLines: Int = Int.MAX_VALUE,//最大行数
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },//传入状态,从而监听用户触摸操作,如点击、拖拽
    shape: Shape = TextFieldDefaults.filledShape,//设置背景形状
    colors: TextFieldColors = TextFieldDefaults.textFieldColors()// 颜色集,通过设置相应的颜色,可以改变如错误发生时的颜色
) {
...
}

例子:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyTextField() {
    var text by remember { mutableStateOf("") }
    TextField(
        value = text,
        onValueChange = { text = it },
        placeholder = { Text("haha") },
        leadingIcon = {//设置文本前图片
            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "leadingIcon"
            )
        },
        trailingIcon = {//设置文本后图片
            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "leadingIcon"
            )
        },
        supportingText = {//设置文本下的文本
            Text("supportingText")
        },
        isError = true,// 设置发生错误
        visualTransformation = PasswordVisualTransformation(),//视觉为密码
        shape = RoundedCornerShape(10.dp),//背景为圆角
        colors = TextFieldDefaults.textFieldColors(//错误时,下划线显示黄色
            errorIndicatorColor = Color.Yellow
        )
    )
}

效果:

3.4 OutlinedTextField

OutlinedTextField是含有一个边框的输入框,其他用法和TextField相同

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyTextField() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        modifier = Modifier.padding(start = 10.dp, top = 10.dp),
        value = text,
        onValueChange = { text = it },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Number,
            imeAction = ImeAction.Search
        ),
        keyboardActions = KeyboardActions { 
            
        }
    )
}

效果:

4. Button

Button需要传入一个点击事件onClicklambda表达式,和一个content内容组件的lambda表达式,border边框支持Shader(点击跳转详情),其他参数说明如下:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,// 是否可用
    shape: Shape = ButtonDefaults.shape,// 背景形状
    colors: ButtonColors = ButtonDefaults.buttonColors(),//颜色集,背景、内容的可用和非可用颜色
    elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),//阴影,默认、按下、不可用等状态下的阴影
    border: BorderStroke? = null,//边框,支持Shader
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,// 内容组件的padding
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },//触摸事件的状态改变
    content: @Composable RowScope.() -> Unit//按钮的内容组件
) {

}
4.1 基本使用

Buttoncontent是一个RowScope的作用域,也就是以行来摆放组件

例子:

代码语言:javascript
复制
@Preview
@Composable
fun MyButton() {
    Button(
        onClick = { /*TODO*/ },
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.Cyan,
            contentColor = Color.Red
        ),
        elevation = ButtonDefaults.buttonElevation(defaultElevation = 3.dp),
        border = BorderStroke(
            1.dp,
            Brush.linearGradient(
                0f to Color.Transparent,
                1f to Color.DarkGray
            )
        ),
        contentPadding = PaddingValues(
            start = 10.dp,
            top = 5.dp
        ),
        content = {
            Text("点我")
            Text("点我")
        }
    )
}

预览效果:

4.2 IconButton

IconButtoncontent需要传入一个Icon组件,其他用法和Button相同:

代码语言:javascript
复制
@Composable
fun MyIconButton() {
    IconButton(
        onClick = { /*TODO*/ },
        colors = IconButtonDefaults.iconButtonColors(contentColor = Color.Green),
        content = {
            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "icon"
            )
        }
    )
}

预览效果:

4.3 IconToggleButton

IconToggleButton具有选中和未选中状态,checked入参需要配合state对象使用,onCheckedChange用于选中状态切换的处理,其他用法和Button相同:

代码语言:javascript
复制
@Preview
@Composable
fun MyIconToggleButton() {
    var checked by remember { mutableStateOf(false) }

    IconToggleButton(
        checked = checked,
        onCheckedChange = {
            checked = it
        },
        modifier = Modifier
            .width(100.dp)
            .height(100.dp),
        colors = IconButtonDefaults.iconToggleButtonColors(
            contentColor = Color.Green,//选中为绿色
            checkedContentColor = Color.Red//非选中为红色
        ),
        content = {
            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "icon"
            )
        }
    )
}

效果:

https://upload-images.jianshu.io/upload_images/6288115-25c6151f7697ea9e.gif?imageMogr2/auto-orient/strip|imageView2/2/w/442/format/webp

4.4 Switch

Switch为开关样式的IconToggleButton组件,thumbContent参数支持指定开关按钮的Icon,其他用法与IconToggleButton相同:

代码语言:javascript
复制
@Preview
@Composable
fun MySwitch() {
    var checked by remember { mutableStateOf(false) }

    Switch(
        checked = checked,
        onCheckedChange = { checked = it },
        thumbContent = {
            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                contentDescription = "icon"
            )
        }
    )
}

效果:

4.5 RadioButton

RadioButton为单选框

代码语言:javascript
复制
@Preview
@Composable
fun MyRadioButton() {
    var selected by remember { mutableStateOf(false) }

    RadioButton(
        selected = selected,
        onClick = { selected = !selected }
    )
}

效果:

https://upload-images.jianshu.io/upload_images/6288115-251284cd8afa5b54.gif?imageMogr2/auto-orient/strip|imageView2/2/w/442/format/webp

4.6 RadioButton

Checkbox为复选框

代码语言:javascript
复制
@Preview
@Composable
fun MyCheckbox() {
    var selected by remember { mutableStateOf(false) }

    Checkbox(
        checked = selected,
        onCheckedChange = { selected = it }
    )
}

效果:

4.7 ExtendedFloatingActionButton

ExtendedFloatingActionButton为悬浮按钮,控制expanded参数可以展开和缩小,此外还支持shape设置背景形状、elevation设置阴影:

代码语言:javascript
复制
@Composable
fun ExtendedFloatingActionButton(
    text: @Composable () -> Unit,// 文字
    icon: @Composable () -> Unit,// 图标
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    expanded: Boolean = true,// 是否展开
    shape: Shape = FloatingActionButtonDefaults.extendedFabShape,//背景形状
    containerColor: Color = FloatingActionButtonDefaults.containerColor,//容器颜色
    contentColor: Color = contentColorFor(containerColor),//内容组件颜色
    elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),//阴影
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
...
}

例子:

代码语言:javascript
复制
@Preview
@Composable
fun MyExtendedFloatingActionButton() {
    var expanded by remember { mutableStateOf(false) }
    
    ExtendedFloatingActionButton(
        text = { Text(text = "点我") },
        icon = { /*TODO*/ },
        onClick = { expanded = !expanded },
        expanded = expanded,
        shape = RoundedCornerShape(30.dp)
    )
}

效果:

5.Spacer

Spacer表示间距,用来代表一片隔离区域,隔离组件与组件

代码语言:javascript
复制
@Preview
@Composable
fun MySpacer() {
    Row {
        Text("hi")
        Spacer(modifier = Modifier.width(20.dp))
        Text("hi")
    }
}

预览效果:

6.Divider

Divider可以用来表示一条分割线,默认是一条横向的,所以通过Modifier来改变

代码语言:javascript
复制
@Preview
@Composable
fun MyDivider() {
    Row() {
        Text(
            "hi",
            modifier = Modifier.weight(1f)
        )
        Divider(
            color = Color.Blue,
            modifier = Modifier
                .fillMaxHeight()//充满整个组件
                .width(1.dp)//宽度为1dp
        )
        Text(
            "hi",
            modifier = Modifier.weight(1f)
        )
    }
}

预览效果:

6.1 IntrinsicSize

从上面的预览效果可以知道,将Divider设置为最大高度后,MyDivider组件充满了整个屏幕,如果想到达到Divider的高度不计入MyDivider的高度,并随着MyDivider的高度进行填充,就需要用到IntrinsicSize

IntrinsicSize表示允许父组件优先查询下子组件的高度,所以设置给父组件,这边给Row设置Modifier

代码语言:javascript
复制
@Preview
@Composable
fun MyDivider2() {
    Row(modifier = Modifier.height(IntrinsicSize.Min)) {//高度设置为IntrinsicSize
        Text(
            "hi",
            modifier = Modifier.weight(1f)
        )
        Divider(
            color = Color.Red,
            modifier = Modifier
                .fillMaxHeight()//充满整个组件
                .width(1.dp)//宽度为1dp
        )
        Text(
            "hi",
            modifier = Modifier.weight(1f)
        )
    }
}

预览效果:

五、标准布局

compose中的布局也不多,最基础的为ColumnRowBox,官方给出的定义如下图:

1.Row

上面我们使用过一个Row,它的作用域是RowScope,同横向LinearLayout

代码语言:javascript
复制
@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,// 内容组件水平排列方式
    verticalAlignment: Alignment.Vertical = Alignment.Top,//内容组件垂直对齐方式
    content: @Composable RowScope.() -> Unit// 内容组件
) {
    
}
1.1 Arrangement

关于Arrangement的几种方式,官方给出的图示:

1.2 基本使用
代码语言:javascript
复制
@Preview
@Composable
fun MyRow() {
    Row(
        modifier = Modifier.width(100.dp),
        horizontalArrangement = Arrangement.End,//内容组件往右对齐
        verticalAlignment = Alignment.CenterVertically//内容组件垂直居中
    ) {
        Text("hi")
        Text("你好\n 张三")
    }
}

预览效果:

2.Column

Column就是竖直方向摆放组件的布局,用法上和Row相同,同竖向LinearLayout

代码语言:javascript
复制
@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,//内容组件垂直对齐方式
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,//内容组件水平对齐方式
    content: @Composable ColumnScope.() -> Unit//内容组件
) {
    
}
2.1 基本使用
代码语言:javascript
复制
@Preview
@Composable
fun MyColumn() {
    Column(
        modifier = Modifier.height(100.dp),
        horizontalAlignment = Alignment.CenterHorizontally,//内容组件水平居中
        verticalArrangement = Arrangement.SpaceBetween//内容组件垂直分布到两侧
    ) {
        Text("hi")
        Text("你好\n 张三")
    }
}

预览效果:

3.Box

Box类似FrameLayout,可以堆叠摆放子组件

代码语言:javascript
复制
@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,//内容组件的对齐方式
    propagateMinConstraints: Boolean = false,//是否指定内容组件使用该组件的最小约束(最小宽高)
    content: @Composable BoxScope.() -> Unit
) {

}
3.1 基本使用

下面两个Image的宽高设定为40dp,由于Box设置了最小约束为50dp和70dp,所以Image变大了:

代码语言:javascript
复制
@Preview
@Composable
fun MyBox() {
    Box(
        modifier = Modifier
            .sizeIn(50.dp, 70.dp),//设置内容组件的最小宽度和高度为50dp、70dp,配合propagateMinConstraint=true使用
        propagateMinConstraints = true,//使内容组件最小宽度和高度生效
        contentAlignment = Alignment.BottomEnd
    ) {
        // propagateMinConstraints,内部需要一个组件撑大整体的大小
        Box(Modifier.size(50.dp,150.dp).background(Color.Cyan))
        Image(
            modifier = Modifier.size(40.dp, 40.dp),
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            contentScale = ContentScale.FillHeight // 图片高度拉伸
        )
        Image(
            modifier = Modifier.size(40.dp, 40.dp),
            painter = painterResource(id = R.drawable.ic_launcher_foreground),
            contentDescription = null,
            contentScale = ContentScale.FillHeight // 图片高度拉伸
        )
    }
}

预览效果:

4.Scaffold

Scaffold预设了很多槽位(存放子组件)和功能,Scaffold的学习可以通过官网:Scaffold官方示例(有些参数只有MD2版本才有)

4.1 topBar

槽位topBar就是给顶部子组件准备的,如:TopAppBar

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyScaffold() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = {//标题
                    Text(
                        modifier = Modifier.padding(start = 10.dp),
                        text = "topBar"
                    )
                },
                navigationIcon = {//导航图标
                    Icon(Icons.Default.ArrowBack, contentDescription = null)
                },
                actions = {//右侧按钮
                    IconButton(onClick = { /*TODO*/ }) {
                        Icon(Icons.Filled.Favorite, contentDescription = null)
                    }
                    IconButton(onClick = { /*TODO*/ }) {
                        Icon(Icons.Filled.Search, contentDescription = null)
                    }
                },
                colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
            )
        }
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

预览效果:

4.2 bottomBar

topBar对应,bottomBar是用来存放底部子组件的槽位,如:BottomAppBarBottomNavigation

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyScaffold() {
    Scaffold(
        topBar = {
           ...
        },
        bottomBar = {
            BottomAppBar(
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                tonalElevation = 2.dp,
            ) {
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(Icons.Filled.Home, contentDescription = null)
                }
                Spacer(modifier = Modifier.weight(1f))
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(Icons.Filled.ShoppingCart, contentDescription = null)
                }
                IconButton(onClick = { /*TODO*/ }) {
                    Icon(Icons.Filled.Info, contentDescription = null)
                }
            }
        }
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

效果:

4.3 floatingActionButton

floatingActionButton是专门为FloatingActionButton准备的槽位,配合floatingActionButtonPosition可以改变槽位的位置,目前只支持底部居中和底部靠右:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyScaffold() {
    Scaffold(
        topBar = {
            ...
        },
        bottomBar = {
            ...
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { /*TODO*/ }) {
                Text(text = "hi")
            }
        },
        floatingActionButtonPosition = FabPosition.Center
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

效果:

4.4 snackbarHost

snackbarHost槽位用于展示一个提示SnackbarHost,需要通过SnackbarHostState来控制该子组件的显示:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyScaffold2() {
    val scaffoldState by remember { mutableStateOf(SnackbarHostState()) }
    val scope = rememberCoroutineScope()

    Scaffold(
        topBar = {
            ...
        },
        bottomBar = {
            ...
        },
        floatingActionButton = {
            FloatingActionButton(onClick = {
                scope.launch {
                    scaffoldState.showSnackbar("hi,this is snack bar")
                }
            }) {
                Text(text = "hi")
            }
        },
        snackbarHost = { SnackbarHost(hostState = scaffoldState) },
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

效果:

SnackbarHostState还支持显示的时长,相应的点击动作,基于协程返回消失或点击动作的结果:

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyScaffold2() {
    val scaffoldState by remember { mutableStateOf(SnackbarHostState()) }
    val scope = rememberCoroutineScope()

    Scaffold(
        topBar = {
            ...
        },
        bottomBar = {
            ...
        },
        floatingActionButton = {
            FloatingActionButton(onClick = {
                scope.launch {
                    val result = scaffoldState.showSnackbar(
                        message = "hi,this is snack bar",
                        duration = SnackbarDuration.Short,
                        actionLabel = "click"
                    )

                    when (result) {
                        SnackbarResult.ActionPerformed -> {
                            /* Handle snackbar action performed */
                            scaffoldState.currentSnackbarData?.dismiss()
                        }
                        SnackbarResult.Dismissed -> {
                            /* Handle snackbar dismissed */
                        }
                    }
                }
            }) {
                Text(text = "hi")
            }
        },
        snackbarHost = { SnackbarHost(hostState = scaffoldState) },
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

效果:

4.5 drawerContent

drawerContent是抽屉菜单的槽位,它是一个ColumnScope,注意目前MD3版本并不支持,所以要使用,需要使用MD2Scaffold

代码语言:javascript
复制
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun MyModalNavigationDrawer() {
    val drawerState = rememberScaffoldState()
    val scope = rememberCoroutineScope()

    androidx.compose.material.Scaffold(
        topBar = {
            TopAppBar(
                title = {//标题
                    Text(
                        modifier = Modifier.padding(start = 10.dp),
                        text = "topBar"
                    )
                },
                navigationIcon = {//导航图标
                    Icon(
                        modifier = Modifier.clickable {
                            scope.launch {
                                drawerState.drawerState.apply {
                                    if (isClosed) open() else close()
                                }
                            }
                        },
                        imageVector = Icons.Default.List,
                        contentDescription = null
                    )
                },
                colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
            )
        },
        drawerContent = {
            Text("title")

            Row(modifier = Modifier.padding(start = 10.dp,top = 10.dp)) {
                Image(Icons.Default.Phone, contentDescription = null)
                Text(text = "my phone")
            }
            Row(modifier = Modifier.padding(start = 10.dp,top = 10.dp)) {
                Image(Icons.Default.Call, contentDescription = null)
                Text(text = "call")
            }
            Row(modifier = Modifier.padding(start = 10.dp,top = 10.dp)) {
                Image(Icons.Default.Delete, contentDescription = null)
                Text(text = "delete cache")
            }
        },
        scaffoldState = drawerState
    ) { paddings ->
        Box(modifier = Modifier.padding(paddings))
    }
}

效果:

5. ModalDrawer

ModalDrawer仅仅是抽屉栏,同样只在MD2中才有,需要DrawerState控制展开和收起:

代码语言:javascript
复制
@Preview
@Composable
fun MyModalDrawer() {
    val drawerState =
        androidx.compose.material.rememberDrawerState(androidx.compose.material.DrawerValue.Closed)
    val scope = rememberCoroutineScope()

    ModalDrawer(
        drawerState = drawerState,
        drawerContent = {
            // Drawer content
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text("hi")
            }
        }
    ) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            // Screen content
            Button(onClick = {
                scope.launch {
                    drawerState.apply {
                        if (isClosed) open() else close()
                    }
                }
            }) {
                Text("点我展开抽屉")
            }
        }
    }
}

效果:

此外BottomDrawer代表底部的抽屉栏,用法上和ModalDrawer差不多

6.ModalBottomSheetLayout

ModalBottomSheetLayout是底部菜单,需要使用ModalBottomSheetState控制显示和消失:

代码语言:javascript
复制
@OptIn(ExperimentalMaterialApi::class)
@Preview
@Composable
fun MyModalBottomSheetLayout() {
    val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    val scope = rememberCoroutineScope()

    ModalBottomSheetLayout(
        sheetState = sheetState,
        sheetContent = {
            // Sheet content
            Box(
                modifier = Modifier.height(400.dp),
                contentAlignment = Alignment.Center
            ) {
                Text("Sheet")
            }
        }
    ) {
        // Screen content
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Button(onClick = {
                scope.launch {
                    sheetState.apply {
                        if (isVisible) hide() else show()
                    }
                }
            }) {
                Text("点我展开")
            }
        }
    }
}

此外,BottomSheetScaffold代表带有底部sheetContent槽位的Scaffold,用法和Scaffold差不多

7.BackdropScaffold

BackdropScaffold官方的说法为背景幕,就是两个布局可以堆叠,并前面的布局可以下移隐藏,通过BackdropScaffoldState控制是否隐藏:

代码语言:javascript
复制
@OptIn(ExperimentalMaterialApi::class)
@Preview
@Composable
fun MyBackdropScaffold() {
    val scaffoldState = rememberBackdropScaffoldState(
        BackdropValue.Concealed
    )
    val scope = rememberCoroutineScope()

    BackdropScaffold(
        scaffoldState = scaffoldState,
        appBar = {
            // Top app bar
            androidx.compose.material.TopAppBar(
                title = {//标题
                    Text(
                        modifier = Modifier.padding(start = 10.dp),
                        text = "topBar"
                    )
                },
                navigationIcon = {//导航图标
                    Icon(
                        modifier = Modifier.clickable {
                            scope.launch {
                                scaffoldState.apply {
                                    if (isConcealed) reveal() else conceal()
                                }
                            }
                        },
                        imageVector = Icons.Default.List,
                        contentDescription = null
                    )
                }
            )
        },
        backLayerContent = {
            // Back layer content
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White)
            ) {

            }
        },
        frontLayerContent = {
            // Front layer content
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Magenta)
            ) {

            }
        }
    )
}

效果:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、认识compose
    • 1. 重组
      • 1.1 安卓传统UI
      • 1.2 compose UI
      • 1.3 什么是重组?
  • 二、创建compose项目
    • 1.创建项目
      • 2.BOM
        • 3.kotlin-compose compiler版本对应
          • 4.预览compose函数与启动
            • 4.1 预览compose函数
            • 4.2 启动
        • 三、资源获取
        • 四、标准控件
          • 1.Text
            • 1.1 基本使用
            • 1.2 使用资源获取文本
            • 1.3 AnnotatedString
            • 1.4 其他参数
          • 2.Image
            • 2.1 基本使用
            • 2.2 其他参数
            • 2.3 Icon
          • 3.TextField
            • 3.1 基本使用
            • 3.2 TextFieldValue
            • 3.3 其他参数
            • 3.4 OutlinedTextField
          • 4. Button
            • 4.1 基本使用
            • 4.2 IconButton
            • 4.3 IconToggleButton
            • 4.4 Switch
            • 4.5 RadioButton
            • 4.6 RadioButton
            • 4.7 ExtendedFloatingActionButton
          • 5.Spacer
            • 6.Divider
              • 6.1 IntrinsicSize
          • 五、标准布局
            • 1.Row
              • 1.1 Arrangement
              • 1.2 基本使用
            • 2.Column
              • 2.1 基本使用
            • 3.Box
              • 3.1 基本使用
            • 4.Scaffold
              • 4.1 topBar
              • 4.2 bottomBar
              • 4.3 floatingActionButton
              • 4.4 snackbarHost
              • 4.5 drawerContent
            • 5. ModalDrawer
              • 6.ModalBottomSheetLayout
                • 7.BackdropScaffold
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档