美文网首页
Jetpack Compose入门与基本原理

Jetpack Compose入门与基本原理

作者: 肖霄的潇潇 | 来源:发表于2021-08-22 18:19 被阅读0次

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

企业微信截图_779f11f4-54f9-4ede-b01b-b5a49b1a4705.png

compose.material
使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,提供与 www.material.io 上描述的组件一致的组件。 例如:Button,FloatingActionButton

compose.foundation
使用现成可用的构建块编写 Jetpack Compose 应用,为 Compose 界面提供了与系统无关的构建块,例如 RowColumnLazyColumn、Image等特定手势的识别等,更方便的使用UI层,还可扩展 Foundation 以构建您自己的设计系统元素。

compose.animation
在 Jetpack Compose 应用中构建动画。

compose.ui
与设备互动所需的 Compose UI 的基本组件(ui-textui-graphicsui-tooling 等,提供预览功能)组成。这些模块实现了界面工具包的基本组件,例如 LayoutNodeModifier、输入处理程序、自定义布局和绘图。

compose.runtime
Compose 的编程模型和状态管理的基本构建块,以及 Compose 编译器插件针对的核心运行时。提供了 Compose 运行时的基本组件,例如 remembermutableStateOf@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方法分析

ComponentActivity setContent方法
  1. window--LinearLayout--android.R.id.content 是否存在子viwe ComposeView
    2.将@Composable函数向下传递,进而创建Composition(调用createComposition()方法)
    3.将ComposeView通过setContentView设置给页面根布局
ViewGroup的setContent方法

1.被包裹起来的content,继续向下传递
2.给ComposeView添加子View AndroidComposeView(进行onMeasure,onLayout,dispatchDraw,dispatchTouchEvent)

Wrapper的doSetContent方法

1.创建的Composition里有包含根结点的UiApplier(owner.root)和CompositionContext
2.被包裹起来的content,继续向下传递

Composer的composeContent

1.借助Kotlin编译器插件转换函数类型,真正执行@Composable函数
2.执行slottable的更新方法,将传入的结点插入LayoutNode

3.2 从Text("Hello")分析

Composer的createNode方法

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   树结构显示

相关文章

网友评论

      本文标题:Jetpack Compose入门与基本原理

      本文链接:https://www.haomeiwen.com/subject/wakhiltx.html