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才能灵活运用它

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值