前语

相较于传统的XML布局构建办法,Compose旨在运用更少的代码完结一种新式的布局办法,为了投合Google官方的首选开发言语Kotlin,Compose只能运用Kotlin言语开发,从《Compose编程思维》这个专题开端,凭借Google官方开发文档,带领大家从Compose入门到高手的进阶。

1 Compose入门

1.1 声明式UI

假设对flutter有了解,或许有开发经验的同伴,关于声明式UI或许就十分熟悉,它的中心思维在于运用代码(Kotlin或许Dart)完结布局细节,并且不需求手动更新UI,什么意思呢?

先从传统的Java完结办法,布局文件是在xml布局文件中声明,如下:

<TextView
    android:id="@+id/tv_ui"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="UI制作"/>

通过android:text特点可以预先设置文本展现,也可以通过findViewById的办法获取TextView实例目标,通过动态设置案牍的办法刷新页面。

val view = findViewById<TextView>(R.id.tv_ui)
view.text = "动态修正";

而声明式UI(以Compose为例)则是通过可组合函数构建UI,如下:

@Composable
fun showText() {
    val name by remember { mutableStateOf("Alex") }
    Text(text = name)
}

例如咱们要展现文本案牍,那么可以运用Text组件,类似于Java中的TextView,例如name设置为Text需求展现的案牍,当name的值发生改变的时分,Text展现的案牍也会发生改变,可是这个改变是由于值发生改变然后导致组件自动刷新,而不是Java中手动设置值(即自动调用setText办法)刷新。

1.2 Compose组件思维

一个app基本的元素,便是文本和图片,在Compose中对应的组件为TextImage,为什么不必TextView或许ImageView这种咱们常见的Android渠道中的控件命名办法?

其实这儿就涉及到了Compose组件的思维 – 独立于Android渠道。独立于Android渠道并不意味着脱离了Android渠道,像Text最终底层仍是调用了Android原生的drawTextdrawTextRun函数,可是并没有运用任何Android中的组件,这样做的优点便是可以完结多渠道(multi-platform)。

多渠道,意味着可以在桌面版(Windows、linux)、WEB、IOS中运用同一套代码,只需求区分渠道即可。

因此Compose开端对一切的组件起新的名字,Text对应TextViewImage对应ImageViewRecyclerView对应LazyColumn等等,与传统的Android组件并没有任何的联系,可是底层仍然采用了Android原生的API,为什么Compose还要运用Android原生的API,是由于Compose需求跟原生的View交互,由于它绕不开原生。

假设不想跟原生的View有一丝的牵连,那么便是Flutter了,它是直接在NDK的层面与Skia渲染器打交道了,已然深入了底层,自然跟Android原生组件没有任何联系了。

所以总结一下,Compose组件的思维便是绕开了原生的Android组件,直接调用Android底层API渲染,并没有完全脱离原生。

1.3 Compose中那些原生布局的平替

在Android的原生布局中,常用的有束缚布局(ConstrainLayout)、线性布局(LinearLayout)、帧布局(FrameLayout)、ScrollView、RecyclerView、ViewPager。

那么在Compose中对应的组件是什么呢?

  • 帧布局 – Box
@Composable
fun showLayout() {
    Box {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
        Text(text = "Alex")
    }
}

Compose编程思维 -- 初识Compose

  • 线性布局 – Column / Row
@Composable
fun showLayout() {
    Column {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
        Text(text = "Alex")
    }
}

Compose编程思维 -- 初识Compose

Columnvertical特点;Rowhorizontal特点。

  • 相对布局 – Box

这儿需求提一点的便是,相对布局在传统UI中用于定位子View的相对方位,以及View之间的方位联系,在Compose中仍然可以运用Box作为平替,而方位联系则是运用Modifier来完结对应联系。

@Composable
fun textRelativeLayout() {
    Box {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.size(100.dp)
        )
        Text(
            text = "Alex",
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

例如,运用Alignment.Center值就可以让Text显示在父容器的中心方位。

Compose编程思维 -- 初识Compose

  • ConstraintLayout

在Compose中,具有与ConstraintLayout同名的组件,用于处理比RelativeLayout更多的功用,这儿不做赘述,会有专题介绍如安在Compose中运用ConstraintLayout。

  • RecyclerView – LazyColumn
@Composable
fun testRecyclerView() {
    val datas = mutableListOf("A", "B", "C", "D")
    LazyColumn {
        items(datas) { item ->
            Box(modifier = Modifier.size(50.dp)) {
                Text(text = item,
                    modifier = Modifier.align(Alignment.Center))
            }
        }
    }
}

LazyColumn需求一个动态的列表数据,添加到items中,就可以展现全部的列表数据,不需求AdapterViewHolderLayoutManager

并且当咱们需求给列表加上head或许foot的时分,假设运用RecyclerView,那么就需求在数据项的第0个方位和最后一个方位加上view,那么运用LazyColumn则不需求,直接通过item就可以添加一个ItemView。

@Composable
fun testRecyclerView() {
    val datas = mutableListOf("A", "B", "C", "D")
    LazyColumn {
        // 添加一个头部
        item {
            Text(text = "这是列表的头部")
        }
        items(datas) { item ->
            Box(modifier = Modifier.size(50.dp)) {
                Text(text = item,
                    modifier = Modifier.align(Alignment.Center))
            }
        }
    }
}

当然除了LazyColumn,还有LazyRow,归于横向的滑动。除此之外,LazyColumn/LazyRow也有类似于RecyclerView的缓存机制,确保列表的顺畅滑动。

除此之外,像ScrollerView、ViewPager,在Compose中平替的组件还在孵化中,像ViewPager对应的Pager(VerticalPager/HorizontalPager)组件,目前还在测验阶段,假设在项目中想要完结ViewPager的功用,可以自行完结,或许运用原生的ViewPager。

2 Modifier详解

Modifier在Compose中是一个十分重要的组成,像咱们在传统的UI中,需求声明每个组件的间隔或许相关于父容器的间隔,通常运用margin或许padding来完结。

<TextView
    android:id="@+id/tv_ui"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="UI制作"
    android:layout_marginBottom="20dp"
    android:paddingTop="20dp"/>

而在Compose傍边,会运用Modifier来完结,在Compose傍边的每个组件中,都有一个Modifier参数变量。

@Composable
fun Text(
    text: String,
    // 可以设置Modifier
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    // ......
) {

2.1 设置组件padding 和 margin

如下一个可组合函数,笔直线性布局中,有一个Image和Text,其中通过Mofifier设置了Image的padding为12dp,整个线性布局的布景是蓝色的,也是通过Modifier的background特点设置的。

@Composable
fun testModifier() {
    Column(modifier = Modifier.background(Color.Blue)){
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.padding(12.dp)
        )
        Text(text = "测验Modifier")
    }
}

假设设置margin特点,咱们发现Modifier没有margin这个函数,那么在Compose中怎么设置margin呢?可以通过Spacer组件来设置。

@Composable
fun testModifier() {
    Column(modifier = Modifier.background(Color.Blue)) {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.padding(12.dp)
        )
        Spacer(modifier = Modifier.size(10.dp))
        Text(
            text = "测验Modifier",
            modifier = Modifier.background(Color.Red)
        )
    }
}

看下作用:

Compose编程思维 -- 初识Compose

2.2 match_parent和wrap_content怎么完结

其实Modifier除了设置paddingbackground之外,还可以设置组件的巨细,当然与传统的XML布局必须要设置layout_width和layout_height不同的是,Compose默许宽高均为wrap_content,不需求强制设置。

假设想要组件完结match_parent,那么可以运用Modifier的fillMaxHeightfillMaxWidthfillMaxSize函数完结宽高的装备。

@Composable
fun testModifier() {
    Column(
        modifier = Modifier
            .background(Color.Blue)
            .fillMaxHeight()
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_background),
            contentDescription = null,
            modifier = Modifier.padding(12.dp)
        )
        Spacer(modifier = Modifier.size(10.dp))
        Text(
            text = "测验Modifier",
            modifier = Modifier.background(Color.Red)
        )
    }
}

Compose编程思维 -- 初识Compose

假设要设置一个固定的值,那么可以运用Modifier的widthheightsize函数,那么就可以装备组件的具体宽高,假设组件是一个正方形,可以直接调用size函数。

Image(
    painter = painterResource(id = R.drawable.ic_launcher_background),
    contentDescription = null,
    modifier = Modifier
        .padding(12.dp)
        .width(100.dp)
        .height(80.dp)
)

2.3 什么情况下会运用Modifier

假设,咱们要设置Text的文字巨细,和文字色彩,咱们可以运用Modifier来设置吗?其实不能,这两个特点其实需求通过Text结构办法中的参数设置。

Text(
    text = "测验Modifier",
    modifier = Modifier.background(Color.Red),
    fontSize = 20.sp,
    fontWeight = FontWeight(400),
    color = Color.White
)

像文字的色彩、字重、字号都只能通过Text的结构函数中的参数设置,而不能运用Modifier。那么什么情况下会运用Modifier呢?

其实Modifier归于公共特点的调集。其实不难理解,关于Text来说它是展现文本的组件,字号、字重归于其独有的特点,而假设在Modifier中保护这些特点,关于布局、Image这些来说根本用不到的特点,反而会导致Modifier变得越来越臃肿。

所以之后在自定义Compose组件的时分,关于组件的特有特点,需求放在结构参数中。

2.4 点击事情

在Android传统UI中,每一个View都可以被设置点击事情监听器,无论View仍是ViewGroup都是可被点击的,因此Compose中点击事情也是放在了Modifier中,由于归于通用的能力。

Text(
    text = "测验Modifier",
    fontSize = 20.sp,
    modifier = Modifier.background(Color.Red)
        .padding(12.dp)
        .clickable {
        },
    fontWeight = FontWeight(400),
    color = Color.White
)

在Modifier中提供了clickable函数,这儿需求留意一点,clickable的摆放次序是有讲究的,由于咱们给按钮加了一个padding,所以clickable放在了padding后边,那么点击时热区不包含padding,如下所示:

Compose编程思维 -- 初识Compose

假设想要热区包含padding,那么在声明clickable时,需求放在padding的前面,可以这么理解,想要呼应点击事情的区域,需求放在clickable之后。

Text(
    text = "测验Modifier",
    fontSize = 20.sp,
    modifier = Modifier.background(Color.Red)
        .clickable {
        }
        .padding(12.dp),
    fontWeight = FontWeight(400),
    color = Color.White
)

Compose编程思维 -- 初识Compose

通过前面的这些介绍,相信同伴们关于Compose有了一些基础的了解,相信把握了前面的这些常识,通过Compose可以写出一些页面了吧,并且相较于传统的UI,这种声明式的结构显得愈加灵活且高效,当然这也是Compose的冰山一角,后续我将会介绍愈加高阶的Compose常识。