赞
踩
在Compose中,每个组件都是一个带有@Composable注解的函数,被称为Composable。Compose已经预置了很多基于MD设计规范的Composable组件。
在布局方面,Compose提供了Column、Row、Box三种布局组件(感觉跟flutter差不多),类似于传统视图开发中的LinearLayout(Vertical)、LinearLayout(Horizontal)、RelateiveLayout,可以满足各类产品的常见布局需求。
在传统开发中,使用xml文件来描述组件的样式,而compose则是使用Modifier修饰符。Modifier允许我们通过链式调用的方式为组件应用一系列的样式设置,如边距、字体、位移等。在Compose中,每个基础的Composable组件都有一个modifier参数,通过传入自定义的Modifier来修改组件的样式。
常见修饰符
Modifier.size | 设置被修饰组件的大小 | Image( painter = painterResource(id = R.drawable.ic_launcher),contentDescription = null, modifier = Modifier.size(width = 10.dp, height = 10.dp) ) |
Modifier.background | 设置被修饰组件的背景颜色。背景色支持纯色背景,也可以使用brush设置渐变背景 | Image(painter = painterResource(id = R.drawable.ic_launcher),contentDescription = null,modifier = Modifier.size(width = 100.dp, height = 100.dp).background(brush = Brush.verticalGradient(colors = listOf(Color.Blue,Color.Red))))) |
Modifier.fillMaxXXX() | 让组件在高度或者宽度上填满父空间 | fillMaxSize():填满整个父空间, fillMaxHeight():高度填满父空间,fillMaxWidth():宽度填满父空间 |
Modifier.border&Modifier.padding | border用来为被修饰组件添加边框。边框可以指定颜色、粗细、以及通过shape指定形状。padding用来为被修饰组件增加间隙。可以在border前后各插入一个padding,区分对外和对内的间距 | Box(modifier = Modifier.padding(5.dp).border(2.dp, Color.Green, shape = RoundedCornerShape(2.dp)).padding(5.dp)) { Spacer(modifier = Modifier.size(width = 100.dp, height = 10.dp).background(Color.Blue)) } |
Modifier.offset | 用来移动被修饰组件的位置,分别传入水平方向和垂直方向的偏移量即可 | Box(modifier = Modifier.size(100.dp).background(Color.Blue).offset(x = 10.dp, y = 10.dp).background(Color.Cyan)) {} |
作用域限定Modifier修饰符
Compose充分发挥kotlin的语法特性,让某些Modifier修饰符只能在特定作用域中使用,有利于类型安全地调用它们。所谓“作用域”,在kotlin中就是一个带有Receiver的代码块。注意Receiver代码块默认可以跨层级访问。在compose的DSL中,一般只需要调用当前作用域的方法,为此可以通过@LayoutScopeMarker注解来规避该问题。常见组件的Receiver作用域类型均已使用@LayoutScopeMarker注解进行了声明,使用了该注解之后,像跨级调用外层作用域的方法必须通过显式指明Receiver具体类型。
@LayoutScopeMarker
@Immutable
interface ColumnScope
常见的作用域限定Modifier修饰符:
matchParentSize
matchParentSize是只能在BoxScope中使用的作用域限定修饰符。当使用matchParentSize设置尺寸时,可以保证当前组件的尺寸与父组件相同。而父组件默认的是wrapContent。如果使用fillMaxSize取代matchParentSize,那么该组件的尺寸会被设置为父组件所允许的最大尺寸,这样会导致背景铺满整个屏幕,二者区别如下图所示。
Column(modifier = Modifier.background(Color.DarkGray)) {
Box() {
Box(modifier = Modifier
// .fillMaxSize()
.matchParentSize()
.background(Color.Cyan))
Spacer(modifier = Modifier.size(50.dp).padding(10.dp).background(Color.Red))
}
}
matchParentSize效果如下所示:
fillMaxSize效果如下所示:
weight
在RowScope和ColumnScope中,可以使用专属的weight修饰符来设置尺寸。与size不同的是weight允许组件通过百分比设置尺寸,也就是允许组件可以自适应适配各种屏幕尺寸的移动端设备。
Column(modifier = Modifier
.background(Color.DarkGray)
.width(300.dp)
.height(200.dp)) {
Spacer(modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.White))
Spacer(modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.Red))
Spacer(modifier = Modifier.fillMaxWidth().weight(1f).background(Color.Blue))
}
Modifier实现原理
各位读者可以参考下面这篇博客:
图解Compose Modifier实现原理 ,竟然如此简单!
@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 )
//append用来添加子串的文本 //withStyle为append的子串指定文字或段落样式 Text(text = buildAnnotatedString { withStyle(style = SpanStyle(fontSize = 24.sp)){ append("你现在学习的章节是") } withStyle(style = SpanStyle(fontSize = 24.sp, fontWeight = FontWeight.W900)){ append("Text") } append("\n") withStyle(style = ParagraphStyle(lineHeight = 25.sp)){ append("在刚刚讲过的内容中,我们学会了如何应用文字样式,以及如何限制文本的行数和处理溢出的视觉效果。") append("\n") append("现在,我们正在学习") withStyle(style = SpanStyle(fontWeight = FontWeight.W900, textDecoration = TextDecoration.Underline, color = Color(0xff59a869))){ append("AnnotatedString") } } })
SelectionContainer选中文字
Text自身默认是不能被长按选择的,Compose提供了专门的SelectionContainer组件,对包裹的Text进行选中。
SelectionContainer() {
Text(text = "我是可以被复制的文字")
}
ClickableText
Compose提供了一种可点击文本组件,可以响应对文字的点击,并返回点击位置。可以让AnnotatedString子串在相应的ClickedText中点击后,做出不同的动作。在AnnotatedString中可以为子串添加一个tag标签,在处理onClick事件时,根据tag实现不同的逻辑。
ClickableText(text = annotated, onClick ={ annotated.getStringAnnotations( tag = "URL", start = it, end = it ).firstOrNull()?.let { Log.e("MainActivity",it.toString()) } } ) val annotated = buildAnnotatedString { withStyle(style = ParagraphStyle(lineHeight = 25.sp)){ // 开始一个注解区域 pushStringAnnotation(tag = "URL", annotation = "https://www.baidu.com") // 追加带有注解的文本 withStyle( style = SpanStyle( fontWeight = FontWeight.W900, textDecoration = TextDecoration.Underline, color = Color(0xff59a869) ) ){ append("AnnotatedString") } // 结束注解区域 pop() //下面这段文本不会有注解 withStyle( style = SpanStyle( fontWeight = FontWeight.W900, textDecoration = TextDecoration.Underline, color = Color(0xff59a869) ) ){ append("NotAnnotatedString") } } }
TextField输入框
TextField组件是我们最常用的文本输入框,它也遵循MD设计准则。它也有一个低级别的底层组件,BasicTextField,与TextField和OutlinedTextField不同的是,BasicTextField拥有更多的自定义效果。由于TextField和OutlinedTextField是根据MD准则设计的,无法直接修改输入框的高度,如果尝试修改高度,会看到输入区域被截断,影响正常输入。
fun TextField( value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, readOnly: Boolean = false, textStyle: TextStyle = LocalTextStyle.current, label: @Composable (() -> Unit)? = null, placeholder: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, supportingText: @Composable (() -> Unit)? = null, isError: Boolean = false, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = false, maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = TextFieldDefaults.filledShape, colors: TextFieldColors = TextFieldDefaults.textFieldColors() ) fun BasicTextField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, readOnly: Boolean = false, textStyle: TextStyle = TextStyle.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = false, maxLines: Int = Int.MAX_VALUE, visualTransformation: VisualTransformation = VisualTransformation.None, onTextLayout: (TextLayoutResult) -> Unit = {}, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, cursorBrush: Brush = SolidColor(Color.Black), decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit = @Composable { innerTextField -> innerTextField() } )
TextField输入框案例如下:
@Composable fun TextFieldSample(){ var text by remember { mutableStateOf("") } Box( modifier = Modifier .fillMaxSize() .background(Color(0xfd3d3d3)), contentAlignment = Alignment.Center ) { BasicTextField( modifier = Modifier .padding(10.dp) .background(Color.White, CircleShape) .height(30.dp) .fillMaxSize(), value = text, onValueChange = {text = it}, decorationBox = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 2.dp, horizontal = 8.dp) ) { Icon(imageVector = Icons.Filled.Search, contentDescription = null) Box(modifier = Modifier .padding(horizontal = 10.dp) .width(100.dp), contentAlignment = Alignment.CenterStart) { if(text.isEmpty()){ Text( text = "输入点东西看看吧~", style = TextStyle( color = Color(0,0,0,128) ) ) } it() } Box(contentAlignment = Alignment.CenterEnd){ if(text.isNotEmpty()){ IconButton( onClick = { text="" }, modifier = Modifier.size(16.dp)) { Icon(imageVector = Icons.Filled.Close, contentDescription = null, tint = Color.Red) } } } } }) } }
图片组件
@Composable fun Icon( imageVector: ImageVector,//矢量图对象,可以显示SVG格式的图标 contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current ) @Composable fun Icon( bitmap: ImageBitmap,//位图对象,可以显示JPG、PNG等格式的图标 contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current ) @Composable fun Icon( painter: Painter,//代表一个自定义画笔,可以使用画笔在Canvas上直接绘制图标 contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current )
按钮组件
@Composable fun ButtonSample(){ val interactionSource = remember { MutableInteractionSource() } val pressState = interactionSource.collectIsPressedAsState() val borderColor = if (pressState.value) Color.Green else Color.White Button( modifier = Modifier.clickable { //该方法在button组件会失效 Log.e("MainActivity","点击Butttonclickable") }, interactionSource = interactionSource, border = BorderStroke(2.dp, color = borderColor), onClick = { Log.e("MainActivity","点击Buttton") }) { Icon(imageVector = Icons.Filled.Done, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize)) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) Text(text = "确认") } }
IconButton(
onClick = { text="" },
modifier = Modifier.size(16.dp)) {
Icon(imageVector = Icons.Filled.Close, contentDescription = null, tint = Color.Red)
@Composable fun FloatButton(){ FloatingActionButton( onClick = {}) { Row() { Text(text = "添加到我的喜欢") Icon(imageVector = Icons.Filled.Done, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize)) } } ExtendedFloatingActionButton( onClick = { /*TODO*/ }) { Row() { Text(text = "添加到我的喜欢") Icon(imageVector = Icons.Filled.Done, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize)) } } }
选择器
@Composable
fun ChecBoxDemo(){
val checkedState = remember {
mutableStateOf(true)
}
Checkbox(
checked = checkedState.value,
onCheckedChange ={checkedState.value = it},
colors = CheckboxDefaults.colors(checkedColor = Color(0xff0079d3)))
}
@Composable fun ChecBoxDemo(){ val (state,onStateChange) = remember { mutableStateOf(true) } val (state2,onStateChange2) = remember { mutableStateOf(true) } val parentState = remember(state,state2) { if (state&&state2) ToggleableState.On else if (!state&&!state2) ToggleableState.Off else ToggleableState.Indeterminate } val onParentClick = { val s = parentState != ToggleableState.On onStateChange(s) onStateChange2(s) } TriStateCheckbox( colors = CheckboxDefaults.colors(checkedColor = Color.Red), state = parentState, onClick = onParentClick, ) Column(Modifier.padding(10.dp,0.dp,0.dp,0.dp)) { Checkbox(checked = state, onCheckedChange = onStateChange ) Checkbox(checked = state2, onCheckedChange = onStateChange2 ) } }
@Composable
fun ChecBoxDemo(){
val checkedState = remember {
mutableStateOf(true)
}
Switch(checked = checkedState.value, onCheckedChange = {checkedState.value = it})
}
var sliderPosition by remember {
mutableStateOf(0f)
}
Slider(value = sliderPosition, onValueChange = {sliderPosition = it})
var progress by remember { mutableStateOf(0.1f) } val animatedProgress by animateFloatAsState(targetValue = progress, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec) Column { CircularProgressIndicator(progress = animatedProgress) Spacer(modifier = Modifier.requiredHeight(30.dp)) OutlinedButton(onClick = { if(progress<1f)progress +=.1f }) { Text(text = "增加进度") } }
@Composable fun DialogDemo(){ val openDialog = remember { mutableStateOf(true) } val dialogWidth = 200.dp val dialogHeight = 50.dp if(openDialog.value){ Dialog( onDismissRequest = { openDialog.value = false }, properties = DialogProperties( dismissOnBackPress = true,//允许通过点击物理返回键取消对话框 dismissOnClickOutside = true//允许点击对话框外部取消对话框 ) ) { Box(modifier = Modifier .size(dialogWidth, dialogHeight) .background(Color.Blue)) } } }
线性布局
线性布局对应于传统视图中的linearLayout,Compose根据orientation的不同又分为Column和Row。
帧布局
Spacer留白
在很多时候,需要让两个组件之间留有空白的间隔,这个时候就可以使用该组件。其实当Box组件没有content时,完全可以用Spacer替换。可以给Spacer做如下封装,可以更方便地用在水平或垂直布局中。
@Composable
fun WidthSpacer(value:Dp){
Spacer(modifier = Modifier.width(value))
}
@Composable
fun HeightSpacer(value:Dp){
Spacer(modifier = Modifier.height(value))
}
ConstraintLayout约束布局
各位读者可以参考下面这篇博客:
Compose ConstraintLayout 详讲
Scaffold脚手架
Scaffold组件实现了MD的布局结构,通过配合其他MD组件可以轻松构建MD风格的页面。
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @Composable fun Sample(){ var selectedItem by remember { mutableStateOf(0) } val items = listOf( Item("主页",R.drawable.ic_launcher), Item("列表",R.drawable.ic_launcher), Item("设置",R.drawable.ic_launcher), ) Scaffold( topBar ={ TopAppBar( title = { Text(text = "主页")}, navigationIcon = { IconButton(onClick = { /*TODO*/ }) { Icon(imageVector = Icons.Filled.Menu, contentDescription = null) } }) }){ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){ Text(text = "主页面") } } }
很多产品中都有展示一组数据的需求场景,如果数据数量是可以枚举的,则仅需通过Column组件来枚举列出。然而很多时候,列表中的项目会非常多,我们需要滑动列表来查看所有内容,可以通过给Column的modifier添加verticalScroll方法来让列表实现滑动。
@Composable fun ListDemo(){ val initialCapacity = 1000 // 设置你想要的初始容量 val items = ArrayList<String>(initialCapacity) for (i in 1..initialCapacity) { items.add("我是text$i") } Column( modifier = Modifier .verticalScroll(state = ScrollState(5), enabled = true) ) { items.forEach{ Text(text = "$it") } } }
给Column的Modifier添加verticalScroll方法可以让列表实现滑动。但是如果列表过长,众多的内容会占用大量的内存。然而更多的内容对于用户其实都是不可见的,没有必要加载到内存。所以compose提供了专门用于处理长列表的组件,LazyColumn和LazyRow,其作用类似于传统视图中的Listview和RecyclerView。
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(Color.Cyan),
contentPadding = PaddingValues(30.dp),//为内容设置外边距
verticalArrangement = Arrangement.spacedBy(10.dp)//为每个item设置间隔
) {
items(1000){
Text(text = "$it")
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。