前言

上一章咱们讲解了 Compose 根底UI 和 Modifier 关键字,本章首要讲解 Compose 分包以及自界说 Composable;

Compose 如何分包

咱们在运用 Button 控件的时分,发现假如咱们想给按钮设置文本的时分,Button 函数并没有直接供给设置 text 的参数,要咱们自己去调用 Text 进行设置;

Column {
    Button(onClick = {}) {        
        Text(text = "我是老A")    
    }
}

可能到这儿的时分,咱们就会困惑了,Compose 为什么要这么搞呢?咱们能够去源码中一探究竟,咱们能够看到 Button 函数是在androidx.compose.material3 这个包下面

Jetpack Compose -> 分包 & 自界说Composable

Button 来自 compose.material3 这个组下面的,也便是 Maven 包的 groupId 是 androidx.compose.material3,对应的便是 build.gradle 中的依靠关系

Jetpack Compose -> 分包 & 自界说Composable

其实Compose 由 androidx 中的 7 个 Maven 组 ID 构成。每个组都包括一套特定用处的功用,并各有专属的版别说明;

Jetpack Compose -> 分包 & 自界说Composable

Compose 其实一共是分了6层,material 和 material3 是一个,仅仅不同的分支;每个组下面有不同的分包,咱们其实能够看到 ui 下面就有不同的ui、ui-tooling-preview、ui-graphics 等等,Android 团队这么分包,其实是针对 View 体系的一个优化

View 体系是没有这个分层的,这就导致后期越来越严重的扩展性问题,例如 View 体系中的 ListView,ListView 中有一个对 View 的收回复用机制,这个机制 RecyclerView 是没有办法复用的,也便是它们两个各自维护着一套复用机制,这便是分层不清晰导致的;

所以 Compose 在设计之初就清晰了分层概念,分层之后的各自扩展,就不会受到限制;

compose.compiler 严格来说,它其实并不归于这7层,它供给的并不是库依靠,它代表的是 kotlin 编译插件,转化 @Composable functions 并启用优化功用,它是负责编译进程的,咱们在依靠里边也完全不需求去装备它,只需求在 Compose 的专用装备地方去写上你要的编译插件版别就行,对应的便是这儿:

Jetpack Compose -> 分包 & 自界说Composable

Compose 剩余的 Group 都是咱们开发 Compose 的时分会用到的,不过它们有依次递进的依靠关系;

最基层是 compose.runtime 它包括了 Compose 编程模型和状况办理的根本构件块,以及 Compose 编译器插件的方针核心运转时,是最底层的概念模型,比方用来保存状况的 State 就在 compose.runtime,还有mutableStateOf、remember

往上一层是 compose.ui 它是用来供给 ui 最根底的功用,比方制作、测量、布局、接触反应等最底层的支持,比方咱们运用的一切控件函数,终究都会调用到一个叫 Layout 的函数,这个函数就在 ui 这层;

再往上一层是 compose.animation,它是用来构建动画的;

在往上一层是 compose.foundation,它供给的是一套相对完整牢靠的 UI 体系,例如 Colum、Row、Image 等都在这一层;

再往上一层便是 comose.material/material3 了,这是一个封装了 一堆 material design 风格控件的包,假如不想运用 MD 风格,能够运用 foundation 层自己拼装一套风格出来;

接下来便是同一个组下面的多个包应该如何引证?例如 compose.ui 下的

implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")

一般来说,咱们只需求引进和组名相同的包就能够了,由于一般这个包就包括了这个组下其他包的一切依靠,除了测验组的这种,例如 compose.ui:ui 下不会包括 compose.ui:ui-test-xxx 和 compose.ui:ui-tooling 由于 test 和 东西类的一般都不会编译进咱们的 apk 中;

例如 @Preview 就归于 ui-tooling 下的

@Preview
@Composable
fun preview() {    
    Column {        
        Button(onClick = {}) {            
            Text(text = "我是老A")        
        }        
        OutlinedButton(onClick = { /*TODO*/ }) {            
            Text(text = "我是老A")        
        }        
        TextButton(onClick = { /*TODO*/ }) {            
            Text(text = "我是老A")        
        }    
    }
}

还有 material3 供给的一些矢量图组件

implementation("androidx.compose.material3:material3-icon-extends")
implementation("androidx.compose.material3:material3-icon-core")

也是需求独自依靠的;

compose.ui:ui 一般包括了 ui 下的一切, compose.material3:material3 一般包括了 material 下的一切;

自界说Composable

用自界说函数的方法来写 Composable,而 Composable 是一种简化的方法,它指的是带有这个 Composable 注解的函数,那么这个注解到底是做什么的呢?咱们来一探究竟

咱们在运用的 Text 函数、Image 函数等其实都带有 Composable 注解,可是这些函数并不是原封不动的被调用的,而是会在编译进程中被动了四肢,给它们增加了一些函数参数,然后在运转的时分,调用的其实是那些被改正的参数更多的版别,比方说它们被参加的其间一个参数便是 Composer 类型的,总之这些 Composable 函数在编译的时分会被 Compose 的编译器插件(Compiler Plugin)修正,增加一些参数,运转的时分也是调用的这些被修正正的函数;

那么,编译器为什么要修正它们呢?

最重要的一点便是:要在代码中增加一些咱们没有写出来的功用,这些功用关于开发者来说不需求,只需求在程序运转的时分能用到就能够了,所以编译的时分增加,即便利了开发者,又不影响程序的运转;

这其实也是一种面向切面(AOP)编程的思想;

那么编译器插件又是怎样认出这些函数的呢?它怎样直到哪些应该被修正呢?

靠的便是 @Composable 注解;只有被加了这个注解的才会进行修正,起到了识别符的作用;咱们能够来看一个小例子:

Jetpack Compose -> 分包 & 自界说Composable

假如 ui 函数没有增加 @Composable 注解,编译器直接报错了,便是由于这个函数内部调用了被 @Composable 注解的函数,所以咱们能够理解为:一切调用了被 @Composable 注解的函数的函数,也有必要增加上 @Composable 注解;提到这儿的时分,可能会有人有疑问了,setContent 函数增加了 @Composable 注解了吗?假如没有增加,那么它内部怎样能够调用 Compose 函数?假如增加了,那么 MainActivity 为什么不必增加 @Composable 注解?咱们来看看 setContent 的完成:

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,    
    content: @Composable () -> Unit) {
}

咱们发现,setContent 函数并没有被 @Composable 注解符号,它仅仅把一个 @Composable 注解的函数作为了参数,所以 setContent 不需求被其注解;可是终归仍是需求一个被 @Composeable 注解的函数来调用这个参数,那么这个函数是哪个函数呢?它便是 invokeComposable 函数

Jetpack Compose -> 分包 & 自界说Composable

默许看不了,咱们 Decompile to Java 看下

Jetpack Compose -> 分包 & 自界说Composable

便是将 composable 强转成了一个 Function2 函数,然后进行调用;

所以自界说 Composable 便是声明的函数被 Composable 注解符号,本质上便是为了便利咱们在开发中能够将咱们的界面元素进行拆分,然后完成不同的功用;一般咱们在自界说 Composable 的时分,直接的只会调用一个 Composable 函数,这样便利咱们关于布局的操控

@Composable
fun ui() {
    Column {
        Text("老A")
        Text("Mars")
    }
}

而不是

@Composable
fun ui1() {
    Text("老A")
    Text("Mars")
}

那么外部在调用 ui1 函数的时分,咱们的布局就不受操控了,假如外部调用的时分 放到了 Column 中,那么就会竖向摆放,假如放到了 Row 中,就会横向摆放,假如放到了 Box 中就会叠加摆放;

而 ui 函数咱们能够自己操控布局的摆放,经过 Column、Row 等函数,而不必受外界调用操控;

自界说 Composable 的运用场景

再说运用场景的时分,咱们能够先想领一个问题,自界说 Composable 在传统 View 中的等价物是什么?自界说View?仍是 xml 文件?仍是 自界说View + xml 文件?

自界说View?

@Composable
fun ui() {
    Column {            
        Text(text = "老A")            
        Text(text = "Mars")
    }
}

这种写法,看起来更像传统的 自界说 LinearLayout

class CustomLinearLayout(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
    val name: TextView by lazy { TextView(context) }        
    val alias: TextView by lazy { TextView(context) }        
    init {        
        orientation = VERTICAL        
        //
        name.text = "老A"
        alias.text = "Mars" 
        ...        
        // 省掉部分代码              
        addView(name)        
        addView(alias)    
    }
}

看起来更像是 自界说 View 的等价物;

xml文件?

可是,这种简易布局咱们一般也不会这样去运用,一般都是直接在 xml 中进行了声明

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"    
    android:layout_width="match_parent"    
    android:layout_height="match_parent">     
    <TextView         
        android:layout_width="wrap_content"         
        android:layout_height="wrap_content"/>        
    <TextView        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"/>    
</LinearLayout>

这样更直观,快捷,看起来也更像 compose 的写法,一个父控件,两个子控件;

自界说View + xml?

可是假如咱们对 Composable 函数做如下改动运用:

@Composable
fun ui(name: String) {    
    Column {        
        Text(text = name)        
        Text(text = "Mars")
    }
}

咱们设置了一个 name 作为参数来传入进来,那么咱们就能够在调用的时分传入不同的值,来表现不同的数据,并且,这个 Composable 函数还能够这么改

@Composable
fun ui(name: String) {    
    Column {        
        val realName = remember {            
            if (name.length > 8) {                
                "我是laoA"            
            } else {                
                "我是马尔斯"            
            }        
        }        
        Text(text = realName)        
        Text(text = "Mars")
    }
}

关于 Compose 能够这么写,可是关于传统的 xml 完成不了,一旦咱们对界面有了定制的需求后,就只能经过自界说 View 来完成了;

所以,看起来自界说 Composable 更像传统 View 的自界说 View + xml 文件!

所以自界说 Composable 的运用场景也就能知道了;

界面声明咱们一般是一个 Activity 对应一个 xml 的文件,那么当咱们运用 Compose 的时分,也能够一个 MainActivity 对应一个 MainLayout 的 Composable 的函数;

当咱们既需求 xml 的简洁有需求自界说view的逻辑处理才能,那么都是能够运用自界说 Composable 的;遇到任务需求对界面有定制需求,就直接运用 Composable 函数处理;

传统自界说 View 还能对布局、制作、接触反应进行定制,这一类的高档自界说 View 在 Compose 中是怎样完成的呢?

其实仍是用的自界说 Composable,当然假如你不自界说 Composable,直接硬写也是能够的,可是就失去了扩展、复用的才能,具体写法上,大部分用的是 Modifier,后边章节会详解自界说 Compose 中的高档自界说 View;

好了,自界说 Composable 就讲到这儿吧~~

下一章预告

MutableState 和 mutableStateOf 详解;

欢迎三连

来都来了,点个重视,点个赞吧,你的支持是我最大的动力~~