1.基本UI
1.1 Jetpack Compose UI组件
(1) Image、Text
glide picasso(新版本被移除) 支持compose, fresco 未支持
google 支持库 https://github.com/google/accompanist
(compose 作曲,composer作曲家,accompanist伴奏者)
(2) Button
(3) Box -- FrameLayout,RelativeLayout,ConstraintLayout
(4) Colume,Row --- LinearLayout
(5)LazyColume --- RecyclerView,ListView
(6) ? --- ViewPager
1.2 Modifier对调用顺序敏感
为什么做内外边距的区分?
主要影响组件的内部大小和背景色
1.3 Jetpack compose 分层结构及package
compose.material
使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,提供与 www.material.io 上描述的组件一致的组件。 例如:Button,FloatingActionButton
compose.foundation
使用现成可用的构建块编写 Jetpack Compose 应用,为 Compose 界面提供了与系统无关的构建块,例如 Row
和 Column
、LazyColumn
、Image等特定手势的识别等,更方便的使用UI层,还可扩展 Foundation 以构建您自己的设计系统元素。
compose.animation
在 Jetpack Compose 应用中构建动画。
compose.ui
与设备互动所需的 Compose UI 的基本组件(ui-text
、ui-graphics
和 ui-tooling
等,提供预览功能)组成。这些模块实现了界面工具包的基本组件,例如 LayoutNode
、Modifier
、输入处理程序、自定义布局和绘图。
compose.runtime
Compose 的编程模型和状态管理的基本构建块,以及 Compose 编译器插件针对的核心运行时。提供了 Compose 运行时的基本组件,例如 remember
、mutableStateOf
、@Composable
注解和 SideEffect
。
compose.compiler
借助 Kotlin 编译器插件,转换 @Composable functions(可组合函数)并启用优化功能。
implementation "androidx.compose.material:material:compose_version"
implementation "androidx.compose.ui:ui-tooling:compose_version"
implementation "androidx.compose.material:material-iconextended:compose_version"
2.状态订阅与自动更新
2.1 状态订阅
val name = mutableStateOf("Hello World!")
name.value可以被订阅,值的更新可引起组件刷新
var name by mutableStateOf("Hello World!")
2.2 自动更新
recompose时 重新执行代码
Text(name)
(1)使用remember之后,记住了上次的值,但是name还是被重新赋值了,在@Composable环境中都需要加remember
@Composable
private fun rememberTest() {
Log.e("test", "name 初始 Hello World!")
var name by remember {
mutableStateOf("Hello World!")
}
Log.e("test", "name.hashCode = ${name.hashCode()}")
Text(text = name)
LaunchedEffect("key1"){
delay(3000)
Log.e("test", "name = Hello Android!")
name = "Hello Android!"
}
}
(2)带key的remeber,remeber根据key来决定是否更新记住的上次的值
@Composable
private fun rememberWithKeyTest() {
var name by remember {
mutableStateOf("Hello World!") // length = 12
}
showStringLength(name)
lifecycleScope.launch {
delay(3000)
name = "Hello Android!" //length = 14
}
}
@Composable
fun showStringLength(value: String) {
val key2 = "key2"
val length = remember(value, key2) { value.length }
Text(text = "string length = $length")
}
(3)List 和Map的监听更新
var nums2 = mutableStateListOf(1, 2, 3)
var numMap = mutableStateMapOf(1 to "One", 2 to "Two", 3 to "Three")
@Composable
fun listUpdateTest2() {
Column {
Text(text = "强制刷新 $flag",
Modifier
.clickable { flag++ }
.padding(10.dp))
Button(onClick = {
nums2[0] = nums2.last() + 1
nums2.add(nums2.last() + 1)
}) {
Text(text = "加一项")
}
for (num in nums2) {
Text(text = "第$num 项")
}
}
}
2.3 recompose的刷新优化
2.3.1传值更新
@Composable
fun valuesUpdateTest() {
Column {
Text(text = "强制刷新 $flag",
Modifier
.clickable { flag++ }
.padding(20.dp))
Log.e("test", "workLongTime 1")
workLongTime()
Log.e("test", "workLongTime 2")
}
}
@Composable
fun workLongTime(valueInt: Int = 0) {
println("test workLongTime() exe")
Text("workLongTime valueInt")
}
编译器无法识别没有状态更新的地方,可以将不更新的地方包在@Composable函数里
2.3.2传引用更新
data class User(val name: String)
data class User(var name: String)
@Stable
data class User(var name: String)
var user = User("Android")
var user2 = User("Android")
var workUser = user
@Composable
fun refTest() {
Column {
Text(text = "强制刷新 $flag",
Modifier
.clickable {
flag++
workUser = user2
}
.padding(20.dp))
Log.e("test", "workLongTime 1")
workLongTime2(workUser)
Log.e("test", "workLongTime 2")
}
}
@Composable
fun workLongTime2(user: User) {
println("test workLongTime() exe")
Text("workLongTime ${user.name}")
}
为什么值相等了还会更新?如果对象可以监听更新,对象不替换,那么他的监听可能出错
2.3.3最小更新范围
@Composable
fun foo() {
var text by remember { mutableStateOf("强制刷新") }
var flag by remember { mutableStateOf(1) }
Log.e(TAG, "Foo")
Column {
Log.e(TAG, "Colume")
Button(onClick = {
text = "强制刷新 ${flag++}"
}) {
Log.e(TAG, "Button content lambda 1")
remember {
Log.e(TAG, "Button content lambda 2")
}
Box {
Log.e(TAG, "Box")
Text(text).also { Log.e(TAG, "Text") }
}
}.also { Log.e(TAG, "Button") }
}
}
@Composable
fun Wrapper(content: @Composable () -> Unit) {
Log.d(TAG, "Wrapper recomposing")
Box {
Log.d(TAG, "Box content")
content()
}
}
@Composable
fun textCompose(text: String) {
Text(text).also { Log.e(TAG, "Text") }
}
1.Compose如何确定重绘范围?
(1)Compose在编译期分析出访问某state的代码,并记录其引用。当此state变化时,会根据引用找到这受影响的代码并标记为Invalid,在下一帧到来之前参与到Recompose中。
(2)可以被标记为Invalide的代码一般是一个 @Composable的非inline且无返回值的lambda或者function
(3)Invalid代码遵循影响范围最小化原则;
(4) Compose关心的是代码块中是否有对state的read,而不是write。
(5) 即使使用=,也不代表text的对象会发生变化,text指向的MutableState实例是永远不会变的,变得只是内部的value
2.为什么必须是非inline且无返回值(返回Unit)?
(1)对于inline函数,由于在编译期在调用方中展开,因此无法再下次重绘时找到合适的调用入口,只能共享调用方的重绘范围
(2)对于有返回值的函数,新的返回值会影响调用方,因此无法单独重绘,必须连同调用方一同参与重绘。
3. 基本原理分析
3.1 从ComponentActivity.setContent方法分析
- window--LinearLayout--android.R.id.content 是否存在子viwe ComposeView
2.将@Composable函数向下传递,进而创建Composition(调用createComposition()方法)
3.将ComposeView通过setContentView设置给页面根布局
1.被包裹起来的content,继续向下传递
2.给ComposeView添加子View AndroidComposeView(进行onMeasure,onLayout,dispatchDraw,dispatchTouchEvent)
1.创建的Composition里有包含根结点的UiApplier(owner.root)和CompositionContext
2.被包裹起来的content,继续向下传递
1.借助Kotlin编译器插件转换函数类型,真正执行@Composable函数
2.执行slottable的更新方法,将传入的结点插入LayoutNode
3.2 从Text("Hello")分析
1,2处 recordFixup,recordInsertUpFixup分别装进MutableList<Change>,Stack<Change>里等待Slottable处理
3处 Slottable是平面结构,来更新LayoutNode结点
4,5处 UiApplier将LayoutNode插入树结构
一个Compose页面的整体结构
decorView(FrameLayout)
LinearLayout
android.R.id.content
ComposeView
AndroidComposeView
root : LayoutNode
LayoutNode---Column(Modifier,MeasurePolicy)
LayoutNode---Text("Hello")(Modifier,MeasurePolicy)
LayoutNode---Image(Modifier,MeasurePolicy)
....
Text("Hello") 调用
|
Slottable 中间层
|
LayoutNode 树结构显示