Jetpack Compose入门

简介

Jetpack Compose是用于构建原生Android界面的新工具包。它是一种声明式的UI布局,其官方声称可简化并加快Android上的界面开发,使用更少的代码、强大的工具和直观的Kotlin API,快速让应用生动而精彩。

官网:https://developer.android.com/jetpack/compose?hl=zh-cn

我这里也写了一个ComposeDemo,可以对照着看:https://github.com/dreamgyf/ComposeDemo

这个Demo实现了:

  • Compose替代传统布局z

Compose替代传统布局

  • 网格列表效果,类似于传统布局中的RecyclerView配合GridLayoutManager

网格列表

  • 在传统View中使用Compose

  • 在Compose中使用传统View

  • 自定义布局

前置工作

使用Jetpack Compose需要先引入一些依赖:

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.1'
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
	//网络图片加载三方库
    implementation "io.coil-kt:coil-compose:1.4.0"
}

可组合函数

Jetpack Compose是围绕着可组合函数构建起来的,这些函数以程序化方式定义应用的界面,只需描述应用界面的外观并提供数据依赖项,而不必关注界面的构建过程。此类函数有几个要点:

  • 所有可组合函数必须使用@Composable注解修饰
  • 可组合函数可以像正常函数一样接受参数
@Composable
fun Demo(name: String) {
    
    
    Text(text = "Hello, ${
      
      name}!")
}
  • 可组合函数内部可以书写正常代码(譬如可以通过if else控制显示的控件)
@Composable
fun Demo(showPic: Boolean) {
    
    
    if (showPic) {
    
    
    	Image(
            painter = painterResource(id = R.drawable.demo),
            contentDescription = null
        )
    }
    Text(text = "Hello, compose!")
}

单位

Android常用的单位dpsp等,在Compose中以类的形式被定义,使用的方式也很简单,Compose借助了kotlin的扩展属性,扩展了IntDoubleFloat三个基础类,使用方式如下:

//dp
1.dp; 2.3f.dp; 4.5.dp
//sp
1.sp; 2.3f.sp; 4.5.sp

资源

如何在Compose中使用资源呢,可以通过xxxResource方法

//图片资源
fun painterResource(@DrawableRes id: Int): Painter
//尺寸资源
fun dimensionResource(@DimenRes id: Int): Dp
//颜色资源
fun colorResource(@ColorRes id: Int): Color
//字符串资源
fun stringResource(@StringRes id: Int): String
//字体资源
fun fontResource(fontFamily: FontFamily): Typeface

Modifier

ModifierCompose中的布局修饰符,它控制了布局的大小,padding,对齐,背景,边框,裁切,点击等属性,几乎所有的Compose布局都需求这项参数,是Compose布局中的重中之重

这里介绍一些常用的基本属性,文中没列到的属性可以去官网查看:https://developer.android.com/jetpack/compose/modifiers-list?hl=zh-cn

尺寸

  • fillMaxWidthfillMaxHeight相当于xml布局中的match_parent
  • fillMaxSize相当于同时设置了fillMaxWidthfillMaxHeight
  • wrapContentWidthwrapContentHeight相当于xml布局中的wrapContent
  • wrapContentSize相当于同时设置了wrapContentWidthwrapContentHeight
  • widthheight则是设置固定宽高,单位为Dp
  • size相当于同时设置了widthheight
  • weight属性仅在RowColumn的内部作用域中可以使用,相当于传统LinearLayout布局中的weight属性

padding

padding方法有几个重载,这些API很简单,看参数就很容易能明白意思

对齐

align属性,使控件可以在父布局中以一种方式对齐,相当于xml布局中的layout_gravity属性。另外还有alignBy以及alignByBaseline属性可以自行研究

绘图

  • background设置背景,不过不能设置图片,如果想以图片作为背景可以使用Box布局,在底部垫一个Image控件
  • alpha设置透明度
  • clip裁剪内容,这个功能很强大,可以直接将视图裁出圆角,圆形等形状

操作

  • clickable方法,可以设置控件的点击事件回调
  • combinedClickable方法,可以设置控件的点击、双击、长按事件回调
  • selectable方法,将控件配置为可点击,同时可以设置点击事件

滚动

  • horizontalScroll:使控件支持水平滚动
  • verticalScroll:使控件支持垂直滚动

注意事项

Modifier中设置属性的前后顺序是很重要的,譬如想要一个背景为蓝色的圆角布局,需要先设置clip,再设置background,反过来background会超出圆角范围

Spacer

Compose中没有了margin的概念,可以用Spacer替代,Spacer为留白的意思,使用起来也很简单

//水平间隔8dp
Spacer(modifier = Modifier.width(8.dp))

基础布局

Row & Column

这是两个基本布局组件,其中Row为水平布局,Column为垂直布局,他们俩接受的参数相似,其中两个参数为horizontalArrangementverticalAlignment,他们一个表示水平布局方式,一个表示垂直布局方式,他们默认值为STARTTOP,这两个参数用起来就和传统布局的gravity参数一样

Box

Box也是一种基本布局组件,Box布局中的组件是可以叠加的,类似传统布局中的FrameLayout,可以通过contentAlignment参数调整叠加的方式,其默认值为TopStart,叠加到左上角,这个参数也和FrameLayoutgravity参数一样

基础控件

Text

文本控件,对应传统控件TextView,它有以下一些属性

属性 说明
text 文本内容
color 文字颜色
fontSize 文字大小
fontStyle 文本样式(可以设置斜体)
fontWeight 字重(粗体等)
fontFamily 字体
letterSpacing 文字间距
textAlign 文本对齐方式
lineHeight 行高
maxLines 最大行数

Image

图片控件,对应传统控件ImageView,它有以下一些属性

属性 说明
painter 图片内容
contentDescription 无障碍描述(可为null)
alignment 对齐方式
contentScale 缩放方式(和scaleType属性类似)
alpha 透明度

在开发中经常会面对从网络价值图片的情况,这时候可以借助一些第三方库来解决,这里以coil库为例:

  1. 先添加依赖
implementation "io.coil-kt:coil-compose:1.4.0"
  1. 使用
Image(
	modifier = Modifier
		.size(68.dp, 68.dp)
		.clip(RoundedCornerShape(6.dp)),
	contentScale = ContentScale.Crop,
    painter = rememberImagePainter(picUrl), //使用rememberImagePainter方法填入图片url
    contentDescription = null
)

列表

Compose有两种组件LazyRowLazyColumn,一种水平,一种垂直,对应着传统UI中的RecyclerView,用这些组件可以方便的构建列表视图,它们需要提供一个LazyListScope.()块描述列表项内容

LazyListScopeDSL提供了多种函数来描述列表项:

//用于添加单个列表项
fun item(key: Any? = null, content: @Composable LazyItemScope.() -> Unit)
//用于添加多个列表项
fun items(
        count: Int,
        key: ((index: Int) -> Any)? = null,
        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
    )
//用于添加多个列表项
fun <T> LazyListScope.items(
    items: List<T>,
    noinline key: ((item: T) -> Any)? = null,
    crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
)

示例:

val list = mutableListOf(0, 1, 2, 3, 4)

LazyColumn {
    
    
    //增加单个列表项
    item {
    
    
        Text(text = "First item")
    }

    //增加5个列表项
    items(5) {
    
     index ->
        Text(text = "Item: $index")
    }
    
    //增加5个列表项
    items(list) {
    
     listItem ->
		Text(text = "Item: $listItem")
    }

    //增加单个列表项
    item {
    
    
        Text(text = "Last item")
    }
}

可以使用contentPadding为内容添加内边距,使用verticalArrangementhorizontalArrangement,以Arrangement.spacedBy()为列表项之间添加间距

状态

Compose中,数据的更新和传统命令式UI不同,是通过一种可观察类型对象,当一个可观察类型对象发生改变时,这个对象对应观察的部分会发生重组,从而自动更新UI

可观察类型MutableState<T>通常是通过mutableStateOf()函数创建的,这个对象的value发生变化时,对应UI也会跟着随之变化

//这里使用了kotlin的by关键字,是一种代理模式
//如果使用 = 的话,这个对象的类型会发生变化,需要count.value这样使用它的值
//var count = mutableStateOf(0)
var count by mutableStateOf(0)

@Composable
fun Demo(count: Int) {
    
    
    Column {
    
    
        Text(text = "count: ${
      
      count}")
        Button(onClick = {
    
     addCount() }) {
    
    
            Text(text = "add count")
        }
    }
}

fun addCount() {
    
    
    //++count.value
    ++count
}

@Preview
@Composable
fun Preview() {
    
    
    //当点击Button时,触发点击事件,更新可观察对象count,触发UI重组
    //Demo(count.value)
    Demo(count)
}

关于Context

Compose中可以通过LocalContext.current获得当前Context

在传统View中使用Compose

可以在一个传统布局xml中插入一个ComposeView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/hello_world"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello from XML layout" />

    <!-- 插入ComposeView -->
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

然后在代码中设置这个ComposeView

findViewById<ComposeView>(R.id.compose_view).setContent {
    
    
	Text("Hello Compose!")
}

在Compose中使用传统View

可以使用AndroidView这个composable函数,这个函数接受一个factory参数,这个参数接受一个Context,用于构建传统View,要求返回一个继承自View的对象

@Composable
fun Demo() {
    
    
    Column {
    
    
        Text(text = "Compose Text")
        AndroidView(factory = {
    
     context ->
            //这里也可以使用LayoutInflater从xml中解析出一个View
            TextView(context).apply {
    
    
                text = "传统TextView"
            }
        })
    }
}

自定义UI

Compose中,如果想要自定义一些简单的UI是很简单的,只需要写一个Composable函数就可以了,我们主要学习一下怎么自定义一些复杂的UI

我们先看一下怎么自定义一个布局,对应着传统UI中的ViewGroup,以一个简单的例子来说,我们自定义一个布局,让其中的子布局呈左上到右下依次排列:

@Composable
fun MyLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    
    
    Layout(modifier = modifier, content = content) {
    
     measurables, constraints ->
        //测量每个子布局
        val placeables = measurables.map {
    
     measurable ->
            measurable.measure(constraints)
        }

        //设置布局大小为最大可容纳大小
        layout(constraints.maxWidth, constraints.maxHeight) {
    
    
            var xPosition = 0
            var yPosition = 0

            //放置每个子View
            placeables.forEach {
    
     placeable ->
                placeable.placeRelative(x = xPosition, y = yPosition)

                //下一个子View的坐标为上一个子View的右下角
                xPosition += placeable.width
                yPosition += placeable.height
            }
        }
    }
}

我们再看一个使用Canvas自定义View的方式,这个更简单,就是画一条水平线:

@SuppressLint("ModifierParameter")
@Composable
fun HorizontalLine(modifier: Modifier = Modifier.fillMaxWidth()) {
    
    
    Canvas(modifier = Modifier
        .then(modifier), onDraw = {
    
    
        drawLine(color = Color.Black, Offset(0f, 0f), Offset(size.width, 0f), 2f)
    })
}

我们将两者一起用一下看看效果

@Preview(showBackground = true)
@Composable
fun Preview() {
    
    
    MyLayout {
    
    
        Text(text = "Text1")
        HorizontalLine(Modifier.width(50.dp))
        Text(text = "Text2")
    }
}

效果图

其实Compose中的自定义UI的思路和传统自定义View是一样的,只不过需要熟悉Compose中的各种Api才能灵活运用它

猜你喜欢

转载自blog.csdn.net/qq_34231329/article/details/125536321