美文网首页
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