Jetpack Compose入门与基本原理

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   树结构显示
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容