前言
通过前面的一番折腾,项目是已经搭好构建完成了,接下来就应该进入compose的编码的环节了,首先应该明白,compose是用来替换原生的xml方案去实现界面布局显示的,在此之前先对原生的xml进行一个简单的回顾
传统布局方式
xml布局会被反射加载为具体的一个个View对象:
有一定的Android开发基础的朋友都知道,Android的原生布局,是通过在Activity的onCreate()方法中,通过setContentView()传入xml的布局资源id,然后再PhoneWindow类中,初始化了decor和基础布局后,调用LayoutInflater.inflate()方法,调用XmlResourceParser对我们写入的xml进行解析,把解析后的控件全路径取出来,调用createViewFromTag(),再通过Factory2里面的工厂方法对一个一个的控件类进行反射实例化,最终在通过addView添加到视图体系里面。
在传统模式下,我们看的的视图都是一个一个的android.view.View的对象,View对象通过自己的onMeasure()和onDraw()方法实现自己的测量规则和绘制流程,然后在ViewGroup的协调下,通过ViewGroup的onLayout()方法调整摆放位置,并且ViewGroup也是继承自View的,所以ViewGroup有自己的测量和绘制,这也会影响到它内部的View的绘制,对于系统没有提供的控件可以直接继承自View或者ViewGroup通过重写onMeasure,onDraw()和onLayout()方法去实现自己的界面逻辑,这一套通过ViewGroup对View进行嵌套的流程我们已经相当熟悉。
顺带提一句,在传统模式下的布局嵌套不适合太深,因为在ViewGroup的测量阶段会对自己内部的子控件也进行测量和布局,嵌套太多会导致更多的性能开销,在ConstraintLayout没有出现之前,为了实现布局的各种左右居中规则,不得不使用LinearLayout嵌套RelativeLayout进行嵌套,所以在ConstraintLayout出来后,我经历过对布局的全面重构。
反过来看Compose,这是一套完全不一样的布局流程,首先不用再编写xml布局文件,通过一个一个的函数嵌套对界面进行搭建,传统模式下对于样式的个性化调整是通过属性的控制,在compose模式下就是对于函数参数的选择传递,所以要了解一个compose控件的使用就得从它的函数参数出发,从控件开始入门。
详解示例代码
还是先从示例代码出发进行解析
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//提供了整个compose运行的最基础的环境,compose代码需要写在setContent之中
//setContent是一个没有传递参数的高阶函数,直接写成lambda的形式
setContent {
ComposeLearnTheme {
Surface(
//Surface传递了参数,调整了尺寸的大小和背景颜色
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
//Greeting在下面可以看到声明,是一个带有@Composable的函数
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
//@Preview注解是compose提供的预览功能,可以直接在IDE中显示效果
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
ComposeLearnTheme {
Greeting("Android")
}
}
在前面的一篇文章中已经提到了这个默认的示例代码,整个compose代码都运行在setContent()方法中,有的函数省略了默认参数,根据lambda的特性,当最后一个函数参数是函数类型的时候,可以直接省略参数的”()“,直接写函数体,这样可以让compose看起来更加的简洁
函数的优点
相较于传入的xml方式,xml由于需要在运行时进行反射实例化,所以xml更多的是一个静态的布局声明,没有在xml中承载太多的运算和逻辑处理,compose采用函数式的写法,则让在使用compose写布局文件的时候提高了灵活性,在Compose函数的前后都可以加入更灵活的运算逻辑
@Composable
compose函数的特点是需要增加一个@Composable注解,标记有这个注解的函数就是一个compose组件函数,再其内部就可以调用其他的compose组件,如果没有这个注解,就是一个普通函数,没有compose函数的运行环境,编译就会报错
把示例代码的Greeting()的注解注释后,由于内部的Text()是一个带有@Composable注解的函数,所以不能在普通函数中调用,示例代码中的调用方法ComposeLearnTheme和Surface应该都是带有了@Composable的注解
@Composable
fun ComposeLearnTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {}
@Composable
fun Surface(
modifier: Modifier = Modifier,
shape: Shape = RectangleShape,
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
border: BorderStroke? = null,
elevation: Dp = 0.dp,
content: @Composable () -> Unit
) {}
从这两个函数可以看到最后一个参数,都是一个带@Composable注解的 “()->Unit” 类型的函数类型参数,并且前面的参数都提供了默认值,所以在调用的时候可以直接省略参数直接写成lambda的形式
顺带提一下,这一点类似kotlin的协程,协程有有一个挂起函数的概念,需要通过@suspend注解进行标记
@Preview
在示例代码中,还有一个通过@Preview注解的函数,这个函数在setContent中没有被调用,解释一下这个的作用是compose在在编译器中集成的预览的功能,可以不用编译运行到机器上,就可以在IDE中看到界面的显示效果,方便对于界面的调试,加速界面的开发
需要注意的是,这个预览注解不能使用在带有参数的compose函数中,毕竟是需要即时显示效果,弄个带参数的预览,这个参数从哪里传递,具体是什么都无从知晓,如果需要预览一个带参数的compose函数,则需要嵌套一个没有参数的compose函数,然后调用的时候写上参数
预览功能还是支持热更新的,当更改代码的时候,会有实时显示的效果,如果没有实时显示,点击一下刷新按钮
默认代码里面的预览功能,内部的显示其实就是一个Text()组件,可见其长宽跟文字的内容适配
如果将上面完整的带有Surface的代码进行预览对比可见,Surface中配置了大小和颜色,如下
可以看到界面撑满了整个屏幕,并且文字组件在默认的坐标系的起始位置,可以知道在compose中就是通过函数的嵌套实现了类型ViewGroup和View的嵌套逻辑
Text()
聊完了示例代码,接下来以示例代码中的Text()函数进行分析
package androidx.compose.material
@Composable
fun Text(
text: String, //文字内容
modifier: Modifier = Modifier, //用来控制通用尺寸,点击事件等的对象
color: Color = Color.Unspecified, //显示文字的颜色
fontSize: TextUnit = TextUnit.Unspecified, //文字大小
fontStyle: FontStyle? = null, //文字的样式,是否是斜体
fontWeight: FontWeight? = null,//文字的比重,加粗
fontFamily: FontFamily? = null, //字体,Monospace,SansSerif等
letterSpacing: TextUnit = TextUnit.Unspecified,//字符间距
textDecoration: TextDecoration? = null,//字体装饰,比如下划线
textAlign: TextAlign? = null,//文字对齐的方式
lineHeight: TextUnit = TextUnit.Unspecified,//文字一行的高度
overflow: TextOverflow = TextOverflow.Clip, //文字超过宽度的显示方式,比如...或者直接裁剪
softWrap: Boolean = true,//是否超过宽度后换行
maxLines: Int = Int.MAX_VALUE,//最大的行数
onTextLayout: (TextLayoutResult) -> Unit = {},//布局后的一个回调
style: TextStyle = LocalTextStyle.current//设置封装好的统一样式
) {
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
}
val mergedStyle = style.merge(
TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing
)
)
BasicText(
text,
modifier,
mergedStyle,
onTextLayout,
overflow,
softWrap,
maxLines,
)
}
可以看到Text()函数内部有很多可以设置的参数,并且这些参数都给了默认值,方便调用的时候更简洁的调用,除了text需要显示的文字内容必填,其他的都可以省略,省略后的样式就是默认字体样式显示在坐标系的起点位置
没有额外配置参数的样式,就跟着各种默认的样式显示
以fontStyle为参考,系统提供了正常和斜体的样式可以配置,其他的参数同理,具体的就不多展示了,通过这些默认参数基本实现了传统样式在xml的基本配置
android:ellipsize="end"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:lineHeight="20dp"
android:gravity="center"
android:fontFamily="serif"
android:letterSpacing="5dp"
/>
compose相对于原生的xml的TextView,提供了有更加方便的下划线的样式风格,那么对于在xml中配置的文字宽度,背景色的设置则统一交给了Modifier参数去配置,毕竟除了文字需要设置宽度间距,其他的界面组件也需要这些通用的配置,Modifier是一块很大的内容,compose相关的一些测量布局,点击事件,滑动监听这些都基本上依靠modifier去实现,在后面的文章会具体的分析,这一篇介绍就简单的展示一下配置的能力
小结
通过对Text()函数参数的分析,了解了在使用compose函数对界面个性化配置参数的方式,除了Text()函数,具体的这些函数有哪些可以配置的参数和功能,点击对应函数的源码基本上可以对其有一个了解,这一篇文章主要对默认示例代码做了一个结构的拆分,了解@Preview预览功能的方便好用,在后面的文章,我会对在开发中常用的组件进行一个简单的介绍,组件还是很多的,这些内容在网上一搜有很多成熟的文章写的比我都好,如果在需要使用具体控件的功能时,在网上搜索会更加简单直接。