乐趣区

关于前端:Android-Compose-的使用


Compose 简介

Jetpack Compose 是在 2019 Google I/O 大会上公布的新库,直到 2021 年 7 月公布 release 版本 1.0.0。它的特点是能够用更少的 Kotlin
代码,更便捷地在 Android 平台上实现 UI 的开发。

为什么会推出 Compose

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.

从官网的形容就可看出应用 Compose 能够简化在 Android 上 UI 的开发,能够显著缩小创立页面的工夫,更具备‘现代化’。

随着手机硬件的更新迭代,在手机上构建复杂度比拟高的页面以满足业务的需要成为了可能,基于传统 XML
构建的形式对应的控件也越来越多,而保护个控件之间的状态的同步显得越来越难以保护,须要破费不小的精力来放弃各控件状态的对立上。

基于这一点,Android 推出了 Compose,Compose 申明的 UI 不可变,无奈被外界援用,无奈持有状态,用 @Composable 申明以一个“纯函数”的形式运行,当 State
变动时函数从新执行刷新 UI,能够更好地贯彻申明式 UI 的特点。

什么是申明式 UI

传统的界面编写都是通过命令式的编程形式来实现的,比方在 Android 上是通过 xml 构建进去的不同类型的 view,而后须要扭转状态时间接调用该 view 的办法来产生扭转。

 // 通过 findViewById 来查找对应的 TextView
var tv: TextView = findViewById(R.id.tv)
// 间接调用办法来扭转 TextView 的色彩
tv.setColor(red)

申明式 UI 则只须要形容以后的 UI 状态,不须要为不同 UI 状态的切换进行独自的管制,当须要扭转时只须要扭转对应的状态,剩下的工作就交由框架来实现。

      // 当扭转 name 状态值,就会自动更新 UI 状态
      Text("hello ${name}",
            modifier = Modifier.background(color = Color.Blue)
        )
     

根本用法

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {Greeting("Android")
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {Greeting("Android")
    }
}
  • @Composable:能够看到,只有波及到 Compose 构建的控件的办法都有 @Composable 的注解,只能在同样被 @Composable 注解的办法中调用。
  • @Preview:在办法前加上 @Preview 注解就能在不运行程序的状况下看到相干的布局。在 Android Studio 的右上角会有三个选项,如果抉择 Split 和 Design
    就能够看到对应的显示成果了。

  • setContent:setContent 的作用是和开发 Activity 中应用的 setContentView 性能是一样的,通过 content 传入 @Composable
    标记的办法来构建 UI。

运行后就能够在手机上看到 Hello Android 的文字,除了用 Text 来体现文字的显示,Compose 还有对应的多种属性来扭转控件的显示成果和丰盛的控件来构建简单的界面。

根底控件

Text

Text 相似于 Android View 的 TextView,同样它像 TextView 一样有很多的属性能够设置:

  • text : String:设置文字内容
  • modifier : Modifier:Text 的修饰符
  • color : Color:文字色彩的设置,能够通过应用 Compose 事后定义的如 Color.Blue 或者间接输出色彩值 Color(0xFF000000)
  • fontSize:TextUnit:设置字体大小,如 20.sp
  • fontFamily: FontFamily?:设置字体
  • fontWeight: FontWeight?:字体粗细
  • lineHeight: TextUnit:设置行高
  • letterSpacing:TextUnit:设置字符间距
  • textDecoration : TextDecoration?:设置删除线和下划线
  • maxLine : Int:最大显示的行数
  • fontStyle : FontStyle?:设置字体类型, 如 FontStyle.Italic
  • textAlign:TextAlign?:显示款式,如 TextAlign.Left
  • onTextLayout: (TextLayoutResult) -> Unit:文本计算实现回调
  • overflow: TextOverflow:文本溢出款式

示例

          Text(
                text = "Hello BillionBottle",
                modifier = Modifier.padding(5.dp),
                color = Color.Blue,
                textAlign = TextAlign.Start,
                textDecoration = TextDecoration.LineThrough,
                fontStyle = FontStyle.Italic,
                maxLines = 1
            )

成果:

Button

Button 次要是用来响应用户的点击事件的,它次要有以下属性:

  • onClick : () -> Unit:按钮点击时会进行回调
  • modifier : Modifier:Button 的修饰符
  • enabled : Boolean:设置按钮的有效性,默认是 true
  • shape: Shape:调整按钮的样子,默认是 MaterialTheme.shapes.small
  • border: BorderStroke?:设置按钮的外边框, 如 CutCornerShape(30) 切角形态; RoundedCornerShape(50) 圆角形态
  • elevation: ButtonElevation?:设置按钮在 Z 轴方向上的高度
  • contentPadding: PaddingValues:内容与边界的间隔
  • colors: ButtonColors:设置按钮的色彩,包含设置 enable/disable 的背景和 content 的色彩
  • content: @Composable () -> Unit:为 Button 设置内容,须要传入 @Compose 办法

示例

  Button(onClick = {},
  modifier = Modifier.padding(12.dp),
  colors = ButtonDefaults.buttonColors(
      backgroundColor = Color.Green,
      contentColor = Color.Blue
  ),
  elevation = ButtonDefaults.elevation(
      defaultElevation = 12.dp,
      pressedElevation = 12.dp
  ),
  border = BorderStroke(width = 1.dp, color = Color.Blue)
) {Text(text = "BillionBottle")
}

成果:

Image

Image 对应于 Android View 的 ImageView,能够用来显示图片,它次要有以下属性:

  • bitmap: ImageBitmap:能够间接传入 ImageBitmap 构建, 如想显示 drawable
    文件夹下的图片,能够通过 var imageBitmap = ImageBitmap.imageResource(id = R.drawable.xxx)
  • contentDescription: String?:accessibility services 能够读取辨认
  • modifier : Modifier:Image 的修饰符
  • aligment : Aligment:对齐形式
  • contentScale : ContentScale:图片的显示模式
  • alpha : Float:设置透明度,默认是 1.0f
  • colorFilter : ColorFilter:能够设置色彩滤镜

示例

   // 应用 drawable 下的图片资源显示图片
Image(painter = painterResource(R.drawable.xxx),
    contentDescription = "",
)

Surface

当想要为咱们自定义的一个组件增加背景色彩时,咱们就须要用到 Surface,它次要有以下属性:

  • modifier: Modifier:能够为 Surface 设置修饰符
  • shape: Shape:设置形态,默认是 RectangleShape
  • color: Color:设置背景色
  • contentColor: Color:为 Surface 中的 Text 文字设置色彩,当 Text 没有指定色彩时,就是应用该色彩
  • border: Border?:设置外边框
  • elevation: Dp:为 Surface 设置在 Z 轴方向上的高度
  • content: @Composable () -> Unit:为 Surface 设置内容布局,须要传入 @Compose 办法

示例


    Surface(modifier = Modifier.padding(4.dp), color = Color.Gray) {
        Column {Text(modifier = Modifier.align(Alignment.CenterHorizontally), text = "custom")
            Image(modifier = Modifier.size(150.dp),
                painter = ColorPainter(color = Color.Green),
                contentDescription = "image color"
            )
        }
    }

成果:

Canvas

Canvas 是在屏幕上指定区域执行绘制的组件。留神在应用时须要增加修饰符来指定尺寸,能够通过 Modifier.size
设置固定的大小,也能够应用 Modifier.fillMaxSizeColumnScope.weight 设置绝对父组件大小。如果父组件没有设置大小,那么 Canvas
必须要设置固定的大小。

Canvas 就是相似于原来的自定义 View,然而更加的简便,通过 DrawScope 定义的绘制办法进行绘制出本人想要的成果,能够通过 drawArcdrawCircle
drawLinedrawPoints 等办法来绘制图形(详情可参考 DrawScope 下的办法):



Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    // 绘制一条从左下角到右上角的蓝色的线
    drawLine(start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )

    // 在以 200,1200 地位 120 为半径绘制一个圆
    drawCircle(color = Color.Green, center = Offset(200f, 1200f), radius = 120f)
}

成果:

布局控件

Compose 提供了一些可用的布局组件来使咱们更好地对 UI 元素进行布局:

Column

Android 的 LinearLayout 控件想必对学习 Android 的人来说十分相熟,而 Column 就是十分相似于 LinearLayout 设置竖向排列的布局形式。察看它的申明形式


@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit) {val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

Column 有两个属性能够管制 children 布局形式:
verticalArrangement 是管制子元素的垂直排列形式,默认是 Arrangement.Top, 尽可能的凑近主轴顶部排列。它还有其余的几种取值来示意不同的布局形式:

  • Arrangement.BOTTOM:垂直排列并尽可能凑近底部
  • Arrangement.CENTER:垂直居中排列
  • Arrangement.SpaceBetween:平均的散布子元素
  • Arrangement.SpaceEvenly:使得子元素等同距离均分搁置, 然而元素的头和尾部都没有距离
  • Arrangement.SpaceAround:使得子元素等同距离均分搁置, 子元素的结尾和结尾的距离大小为两头的一半

horizontalAlignment 是管制子元素的程度排列形式,默认是 Alignment.Start 对于个别状况下是从右边开始的。Alignment
上面定义了很多排列的形式,实用于 Column 的次要有三种:

  • Alignment.Start:左对齐
  • Alignment.End:右对齐
  • Alignment.CenterHorizontally:程度居中对齐

那 Column 的子控件是怎么放进去的呢?其实它还有一个属性是 content,它是一个收回子界面元素的函数,外面蕴含了须要的子元素。

例如上面的例子就应用了程度右对齐和垂直底部对齐:

@Composable
fun columnColumn() {
       Column(
            // modifier 会在上面阐明,次要是用来扩大控件的性能如增加边距,宽低等    
            modifier = Modifier.height(100.dp).padding(5.dp),
            verticalArrangement = Arrangement.Bottom,
            horizontalAlignment = Alignment.End
        ) {Text("安卓")
            Text("BillionBottle")
        }
}

成果:

Row

与 Column 不同的是,Row 是以程度方向进行布局的,十分相似于 LinearLayout 设置程度排列的布局形式,Row
也有两个属性来示意程度和垂直方向上的排列形式,它的属性和应用形式也是十分相似于 Column。


@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit) {val measurePolicy = rowMeasurePolicy(horizontalArrangement, verticalAlignment)
    Layout(content = { RowScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

察看它的申明形式能够清晰的看出两者的管制程度和垂直方向的形式都是统一的,Arrangement 次要针对的是它的主轴方向(对于 Column 是垂直方向,对于 Row
则是程度方向), Alignment 就是另一个方向的排列,具体就不细说了,通过一个例子来看是如何应用的吧:


@Composable
fun rowShow() {
    // 创立了一个宽 200 dp,垂直方向上居中,程度对齐的布局
    Row(modifier = Modifier.width(200.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.Start
    ) {Text("安卓")
        Text("BillionBottle")
    }
}

成果:

Box

应用 Box 能够将一个元素叠加放到另一个元素下面,相似于 FrameLayout 布局。查看 Box 的申明和相干属性:

@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit) {val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    Layout(content = { BoxScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

其中 modifiercontent 和后面是一样的,contentAlignment 则是管制 Box
子元素的对齐形式,它有很多种形式,如能够设置顶部或者底部居中的形式,详情能够查看 Alignment 动态属性上面有

// 2D Alignments.

正文相干的内容。

来看看如何应用的吧:


@Composable
fun boxLayout() {
    Box(
        contentAlignment = Alignment.BottomCenter,
        modifier = Modifier
            .width(100.dp)
            .height(50.dp)
            .padding(bottom = 10.dp),
    ) {Text("BillionBottle", modifier = Modifier.background(Color.Yellow))
        Text(
            "安卓",
            modifier = Modifier.background(color = Color.Gray)
        )
    }
}

成果:

修饰符

Compose 基本上对每一个组件提供了修饰符来扩大组件的性能,包含组件的宽高、无障碍信息、用户输出以及用户点击滚动的高级互动。修饰符次要由 Modifier
这个类进行创立的,它的调用形式是链式调用每一次调用玩就会返回本人。罕用的属性由 backgroundheightoffset sizeclickable
等,详情能够参考官网文档 Modifier

须要留神的是通过不同的调用程序可能在界面上会显示不同的成果:

   Column(
        modifier = Modifier
            .width(100.dp)
            .height(100.dp)
    ) {Text("BillionBottle")
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite",
            // 1
            modifier = Modifier
                .background(Color.Green)
                .size(ButtonDefaults.IconSize)
                .padding(2.dp)
        )
    }

通过预览界面 Build & Refresh 能够看出如果将 modifier 的 .size.padding 两个调用调换个地位这个 Icon 的大小就会由比拟大的差异。

通过对各种控件的叠加组合和组合,就可能结构出咱们想要的界面。而且对于原来的 Android View 存在的过多嵌套可能会有性能影响的问题,Compose
能够无效地解决嵌套布局,堪称设计简单界面的绝佳工具。

来个例子

上面是一个自定义的控件,通过传入 name、image 和 content,显示不同的 userProfile :

// @DrawableRes 指明传入的 image 必须为 drawable 下的资源文件
@Composable
fun userProfile(name:String,content:String,desc:String = "",@DrawableRes image:Int) {
        // 增加边距
        Row(modifier = Modifier.padding(all = 8.dp)) {
            Image(painter = painterResource(image),
                contentDescription = desc,
                modifier = Modifier
                    .size(40.dp)
                    // 将图片裁剪成圆形
                    .clip(CircleShape)
            )

            // 增加 Image 和 Column 间距
            Spacer(modifier = Modifier.width(8.dp))

            Column {Text(text = name)
                Spacer(modifier = Modifier.height(4.dp))
                Text(text = content)
            }
        }
}

传入对应的参数后显示的成果:

4. 状态治理

所谓的状态,能够了解为某个值的变动能够是一个布尔值的扭转或者是一个数组的变动,也能够从界面上了解成按钮文字、色彩的状态,而 Compose 是申明式的
UI,次要是依据状态的扭转进行重组的,这个时候就须要退出状态并对相干的状态进行治理。

在 compose runtime 中有个可观测类型的对象 MutableState<T>,能够通过 mutableStateOf 创立:

interface MutableState<T> : State<T> {override var value: T}

// 这三种申明形式都是一样的
val state = remember {mutableStateOf("") }
var value by remember {mutableStateOf("") }
val (value, setValue) = remember {mutableStateOf("") }

remember 是将该状态存储在 Composition 中,当重组产生的时候会主动抛弃原先的对象转而应用扭转状态后的值。只有对 MutableState 的 value
进行扭转就会引起用到该状态的 composable 办法重组。不多说,来看看 state 是如何应用的:

//module
data class Info(var content:String)

@Composable
fun Greeting(name: String) {var info by remember { mutableStateOf(Info("")) }
    MyApplicationTheme {
        // A surface container using the 'background' color from the theme
        Surface(color = MaterialTheme.colors.background) {Column(modifier = Modifier.padding(16.dp)) {if (info.content.isNotEmpty()){Text(text = info.content)
                }
                OutlinedTextField(
                    value = info.content,
                    onValueChange = {info = Info(it)
                    },
                    label = {Text("title") }
                )
            }
        }
    }
}

性能很简略,就是在 OutlinedTextField
中用键盘输入的内容如果不为空就可能实时显示在上方,次要是通过 var info by remember {mutableStateOf(Info("")) } 来进行扭转的,当 info
这个变量的援用产生了扭转的时候,Compose 就会刷新应用到这个变量的组件,对应的组件状态也会产生扭转,所以在应用 Compose 的时候咱们只须要更新数据就能够了。

然而 remember 只能在重组的时候保留状态,一旦其余状况如屏幕旋转等 Configuration 产生扭转的时候 remember
就无能为力了,这时候就须要应用 rememberSaveable。只有是 Bundle 类型的数据,rememberSaveable 就可能主动保留。应用形式:

  • 只有是 Parcelize 类型的,和 remember 雷同:
@Parcelize
data class Info(val content: String): Parcelable

var value by rememberSaveable {mutableStateOf(Info("")) }
  • MapSaver:
data class Info(val content: String)

val infoSaver = run {
    val nameKey = "content"
    mapSaver(save = { mapOf(nameKey to it.content) },
        restore = {Info(it[nameKey] as String) }
    )
}
@Composable
fun CityScreen() {var infoState = rememberSaveable(stateSaver = citySaver) {mutableStateOf(Info(""))
    }
    Column(modifier = Modifier.padding(16.dp)) {if (infoState.value.content.isNotEmpty())
            Text(text = infoState.value.content)

        OutlinedTextField(
            value = infoState.value.content,
            onValueChange = {infoState.value = Info("$it")
            },
            label = {Text("title") }
        )
    }
}
  • ListSaver
data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    // 数组中保留的值和 City 中的属性是程序对应的
    save = {listOf(it.name, it.country) },
    restore = {City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {var selectedCity = rememberSaveable(stateSaver = CitySaver) {mutableStateOf(City("",""))
    }
}

当然,compose 也反对其余类型的 State:

  • LiveData
  • Flow
  • RxJava2

在应用其余的 State 类型前,必须转换为 State<T> 类型,这样 compose 能力辨认出这个是状态值,须要依据这个值进行刷新 ui,例如应用 LiveData 就要在
Composable 办法应用它之前转换成 tate 类型,能够应用 LiveData<T>.observeAsState()

小结

本文仅简略地介绍了 Android Compose 的根本内容,更多丰盛的内容和细节能够去官网查看。随着版本的不断更新,也一直会有新的性能被增加,期待大家去摸索!

更多精彩请关注咱们的公众号「 百瓶技术 」,有不定期福利呦!

退出移动版