Android Jetpack Compose

197 阅读9分钟

Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,抛弃 Android XML 编写方式,以声明式开发,可以帮助您简化并加快 Android 界面开发。

环境搭建

版本配置

  • IDE:Android Studio Arctic Fox ,则2020.3.1+版本,官方地址: developer.android.com/studio ,下载最新的就是。

  • Gradle:7.0 +

  • TargetSdk / CompileSdk:30 +

  • MinSdk:21 +

  • Kotlin:1.7.0(目前 Compose 最新稳定版本适配的 Kotlin 版本,后续随着 Compose 的版本增加,Kotlin 版本也会增加)

  • Compose:1.1.1(目前最新稳定版本)

编译配置

1.打开Project下的build.gradle文件配置插件版本:

buildscript {
    ext {
        compose_version = '1.1.0-beta01'
    }
}

plugins {
    id 'com.android.application' version '7.0.2' apply false
    id 'com.android.library' version '7.0.2' apply false
    //Kotlin版本
    id 'org.jetbrains.kotlin.android' version '1.5.31' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

2.配置Module下的build.gradle文件:

  • 文件最上方引入Kotlin插件:
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
  • 增加Compose支持:
android {

 buildFeatures{
    //开启 Compose 支持
    compose true
 }
 
 kotlinOptions {
    //构建指定 jvm 版本
    jvmTarget = '1.8'
 }

 composeOptions {
    //Compose 是 Kotlin 开发的,需要配置 Kotlin 对应的编译扩展版本
    kotlinCompilerExtensionVersion compose_version
 }
 
}
  • 引入Compose相应库
dependencies {
    //Compose ui 相关的基础支持
    implementation "androidx.compose.ui:ui:$compose_version"
    // Compose Material Design
    implementation 'androidx.compose.material3:material3:1.0.0-alpha01'
    // Compose ui 预览
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    // Compose UI Tests
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
    // 将 Activity 支持 Compose
    implementation 'androidx.activity:activity-compose:1.3.1'
}

基本用法

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

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

@Preview()
@Composable
fun DefaultPreview() {
    ComposeDeamoTheme {
        Greeting("Android")
    }
}
  • @Composable: 可以看到,只要涉及到 Compose 构建的控件的方法都有 @Composable 的注解,只能在同样被 @Composable 注解的方法中调用。

  • @Preview: 在方法前加上 @Preview 注解就能在不运行程序的情况下看到相关的布局。在 Android Studio 的右上角会有三个选项,如果选择 Split 和 Design 就可以看到对应的显示效果了。

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

基础控件

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:文本计算完成回调

示例

@Composable
fun Greeting(name: String) {
    val offset = Offset(5.0f, 10.0f)
    Text(
        text = "Hello $name!",
        color = Color.Green,
        fontSize = 20.sp,
        fontWeight = FontWeight.Bold,
        style = TextStyle(
            shadow = Shadow(
                offset = offset,
                blurRadius = 3f
            )
        )
    )
}

效果

screenshot-20221111-140610.png

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 方法

示例

@Composable
fun Test(context: Context) {
    Button(
        onClick = {
            Toast.makeText(context, "Click Button", Toast.LENGTH_LONG).show()
        },
        modifier = Modifier.padding(12.dp),
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.Green,
            contentColor = Color.Blue
        ),
        elevation = ButtonDefaults.buttonElevation(
            defaultElevation = 12.dp,
            pressedElevation = 12.dp
        ),
        border = BorderStroke(width = 1.dp, color = Color.Blue)
    ) {
        Text(text = "HelloAndroid")
    }
}


@Preview()
@Composable
fun DefaultPreview() {
    Test(LocalContext.current)
}

效果

screenshot-20221111-143410.png

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:可以设置颜色滤镜

示例

@Preview
@Composable
fun Test1() {
    Image(
        painter = painterResource(id = R.mipmap.hht_head_normal),
        contentDescription = "HelloAndroid",
        modifier = Modifier
            .size(width = 100.dp, height = 100.dp)
            .clip(RoundedCornerShape(16.dp)), // 裁剪为圆角矩形,radius为16dp
        contentScale = ContentScale.FillBounds, // 等价于ImageView的fitXY
        alignment = Alignment.Center, // 居中显示
        colorFilter = ColorFilter.lighting(multiply = Color.Red, add = Color.Blue)
    )
}

效果

screenshot-20221111-145344.png

Canvas

Canvas 是在屏幕上指定区域执行绘制的组件。注意在使用时需要添加修饰符来指定尺寸,可以通过 Modifier.size 设置固定的大小,也可以使用 Modifier.fillMaxSizeColumnScope.weight 设置相对父组件大小。如果父组件没有设置大小,那么 Canvas 必须要设置固定的大小。

Canvas 就是类似于原来的自定义 View,但是更加的简便,通过 DrawScope 定义的绘制方法进行绘制出自己想要的效果,可以通过 drawArcdrawCircledrawLinedrawPoints 等方法来绘制图形(详情可参考 DrawScope 下的方法):

示例

@Preview
@Composable
fun Test2(){
    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
        )
        // 在以 400f,400f 位置 120 为半径绘制一个圆
        drawCircle(color = Color.Red, center = Offset(400f, 400f), radius = 120f)
    }
}

效果

screenshot-20221111-150510.png

布局控件

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

Column

Android 的 LinearLayout 控件想必对学习 Android 的人来说非常熟悉,而 Column 就是非常类似于 LinearLayout。 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,它是一个发出子界面元素的函数,里面包含了需要的子元素。

例如下面的例子就使用了水平右对齐和垂直底部对齐:

@Preview
@Composable
fun Test3(){
    Column(
        modifier = Modifier.height(100.dp).padding(5.dp),
        verticalArrangement = Arrangement.Bottom,
        horizontalAlignment = Alignment.End
    ) {
        Text("HelloAndroid")
        Text("WelcomeAndroid")
    }
}

效果

screenshot-20221111-151235.png

Row

与 Column 不同的是,Row 是以水平方向进行布局的,非常类似于 LinearLayout 设置水平排列的布局方式,Row 也有两个属性来表示水平和垂直方向上的排列方式,它的属性和使用方式也是非常类似于 Column。

@Preview
@Composable
fun Test4(){
    Row(
        modifier = Modifier.height(100.dp).padding(5.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement =  Arrangement.Start
    ) {
        Text("HelloAndroid")
        Text("WelcomeAndroid")
    }
}

效果

screenshot-20221111-155000.png

Box

使用 Box 可以将一个元素叠加放到另一个元素上面,类似于 FrameLayout 布局。

其中 modifier 和 content 和前面是一样的,contentAlignment 则是控制 Box 子元素的对齐方式,它有很多种方式,如可以设置顶部或者底部居中的方式等

@Preview
@Composable
fun Test5() {
    Box(
        contentAlignment = Alignment.BottomCenter,
        modifier = Modifier
            .width(200.dp)
            .height(50.dp)
            .padding(bottom = 10.dp),
    ) {

        Text("HelloAndroid", modifier = Modifier.background(Color.Yellow))
        Text(
            "Android",
            modifier = Modifier.background(color = Color.Green)
        )
    }
}

效果

screenshot-20221111-155405.png

ConstraintLayout

ConstraintLayout 是一种布局,让您可以相对于屏幕上的其他可组合项来放置可组合项。它是一种实用的替代方案,可代替使用多个已嵌套的 RowColumnBox 和自定义布局元素这种做法。在实现对齐要求比较复杂的较大布局时,ConstraintLayout 很有用。

在以下情况下,考虑使用 ConstraintLayout

  • 为了避免在屏幕上定位元素时嵌套多个 Column 和 Row,以便提高代码的可读性。
  • 相对于其它可组合项来定位可组合项,或根据引导线、屏障线或链来定位可组合项。

在 View 系统中,建议使用 ConstraintLayout 来创建复杂的大型布局,因为扁平视图层次结构比嵌套视图的效果更好。不过,这在 Compose 中不是什么问题,因为 Compose 能够高效地处理较深的布局层次结构。

开始使用 ConstraintLayout

如需使用 Compose 中的 ConstraintLayout,您需要在 build.gradle 中添加以下依赖项(除了 Compose 设置外):

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

Compose 中的 ConstraintLayout 使用 DSL 按以下方式运作:

  • 使用 createRefs() 或 createRefFor() 为 ConstraintLayout 中的每个可组合项创建引用。
  • 约束条件是使用 constrainAs() 修饰符提供的,该修饰符将引用作为参数,可让您在主体 lambda 中指定其约束条件。
  • 约束条件是使用 linkTo() 或其他有用的方法指定的。
  • parent 是一个现有的引用,可用于指定对 ConstraintLayout 可组合项本身的约束条件。

下面是使用 ConstraintLayout 的可组合项的示例:

fun ConstraintLayoutContent() {
    ConstraintLayout {
        // 为要约束的可组合项创建引用
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            //将参考“按钮”分配给可组合按钮
            //并将其约束到ConstraintLayout的顶部
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        //将参考“文本”指定给可合成文本
        //并将其约束到Button可组合的底部
        Text("Text", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = 16.dp)
        })
    }
}

效果

image.png

ConstraintLayout 概念

ConstraintLayout 包含引导线、屏障线和链等概念,掌握这些概念有助于在您的可组合项内定位元素。

引导线

引导线是设计布局时使用的小型视觉辅助工具。可组合项可以受引导线约束。在父级可组合项内的特定 dp 或 percentage 处定位元素时,引导线很有用。

有两种不同的引导线:垂直和水平引导线。两种水平引导线分别是 top 和 bottom,两种垂直引导线分别是 start 和 end

 // Create guideline from the start of the parent at 10% the width of the Composable
val startGuideline = createGuidelineFromStart(0.1f)
// Create guideline from the end of the parent at 10% the width of the Composable
val endGuideline = createGuidelineFromEnd(0.1f)
//  Create guideline from 16 dp from the top of the parent
val topGuideline = createGuidelineFromTop(16.dp)
//  Create guideline from 16 dp from the bottom of the parent
val bottomGuideline = createGuidelineFromBottom(16.dp)

若要创建引导线,请使用 createGuidelineFrom* 和所需的引导线类型。这会创建一个可在 Modifier.constrainAs() 块中使用的引用。

注意:考虑使用 Spacer 可组合项,以实现与 Rows 和 Columns 类似的效果。

屏障线

屏障线会引用多个可组合项,从而根据所指定边中处于最边缘位置的 widget 创建虚拟引导线。

若要创建屏障线,请使用 createTopBarrier()(或 createBottomBarrier()createEndBarrier()createStartBarrier()),并提供构成屏障线的引用。

val topBarrier = createTopBarrier(button, text)

该屏障线随后可用于 Modifier.constrainAs() 块中。

注意:考虑使用固有特性测量,以实现与 Rows 和 Columns 类似的效果。

链在单条轴(水平或垂直方向)上提供类似于组的行为。另一条轴可单独约束。

若要创建链,请使用 createVerticalChain 或 createHorizontalChain

val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
val horizontalChain = createHorizontalChain(button, text)

该链随后可用于 Modifier.constrainAs() 块中。

链可通过不同的 ChainStyles(决定了可组合项周围空间的处理方式)配置,例如:

  • ChainStyle.Spread:空间会在所有可组合项之间均匀分布,包括第一个可组合项之前和最后一个可组合项之后的可用空间。
  • ChainStyle.SpreadInside:空间会在所有可组合项之间均匀分布,不包括第一个可组合项之前或最后一个可组合项之后的任何可用空间。
  • ChainStyle.Packed:空间会分布在第一个可组合项之前和最后一个可组合项之后,各个可组合项之间没有空间,会挤在一起。

修饰符

Compose 基本上对每一个组件提供了修饰符来扩展组件的功能,包括组件的宽高、无障碍信息、用户输入以及用户点击滚动的高级互动。修饰符主要由 Modifier 这个类进行创建的,它的调用方式是链式调用每一次调用玩就会返回自己。常用的属性由 backgroundheightoffset sizeclickable 等,详情可以参考官方文档 Modifier

需要注意的是通过不同的调用顺序可能在界面上会显示不同的效果:

@Composable
fun ModifierTest(name: String, content: String, desc: String = "") {
  // 添加边距
  Row(modifier = Modifier.padding(all = 8.dp)) {
      Image(
          painter = painterResource(id = R.mipmap.hht_head_normal),
          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)
      }
  }
}

@Preview
@Composable
fun Test6() {
  ModifierTest("HelloAndroid","HelloWord")
}

效果

image.png

小结

本文仅简单介绍了Android Compose是基本内容,进价版传送门