Compose概述

一、Compose概述

Jetpack Compose 是Google发布的一个Android原生现代UI工具包,它完全采用Kotlin编写,是一套声明式UI框架,可以使用Kotlin语言的全部特性,可以帮助你轻松、快速的构建高质量的Android应用程序。

包括一下几个方面:

  • 加速开发

如果你是一个初级开发工程师,你总是希望有更多的时间来写业务逻辑,而不是花时间在一些如:动画、颜色变化等事情上,而Jetpack Compose 为我们提供了很多开箱即用的Material 组件,如果的APP是使用的material设计的话,那么使用Jetpack Compose 能让你节省不少精力。

  • 强大的UI工具

上图是使用Jetpack Compose 开发UI时,在Android Studio 上的预览,你可以看到,在左边编码时,右边你能同时展现UI即时预览,比如在明/暗模式下的状态切换,都能在右边及时展示出来。

  • 直观的Kotlin API

对于开发者而言,Jetpack Compose 的用途不仅仅是Android UI,因此用Kotlin来编写他们并开源。当然,所有Android代码都是开源的,但特别强调的是Compose代码,它每天在这里更新(https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/ui

)。因此,您可以查看和使用代码,同时也可以在此处提供反馈。

由于Compose仍在开发之中,因此每个开发人员的反馈都很重要。

二、Compose优势 

1.声明式UI

即时响应,(处理业务逻辑就好,而不用关系各种颜色、动画的变化)

加速开发

声明式UI 只会刷新需要刷新的部分;

自动更新 和手动更新一样高效;

靠的就是 编译器插件 直接干预了编译过程 @Composable注解

所以带来的另一个好处就是节省了大量的关于UI的繁复的代码,以及无需那些mvvm,mvp等为了解决逻辑层和UI层交互问题的框架了

2.View体系由原先两种代码

XML+java

现在都只用kotlin就好了

Compose集合了两者的优势(既直观简单 又可以处理逻辑)

省去了很多findViewById的绑定操作(省去加载资源文件的时间,省去加载完后反射获取View的时间)

3.Compose 结构扁平

复杂的嵌套不会带来重复测量导致的过多性能的损耗

4.Live Literals

可以实现简单的实时性预览

5.组合由于继承

Compose都是函数,不存在继承的关系,原有的View作为所有控件的基类

三、Compose的使用

1.一切皆函数

每个控件对应一个函数

按照官方的建议,我们可以把UI拆分成多个小的Compose函数,每个函数其实最终会被插件编译生成一个View,然后可以复用这些Compose函数

2.@Composeable

在函数前加@Composeable注解,就可以返回一个类似Flutter中的Widget的UI

这个注释有点类似于kotlin的suspend关键字,但它不是是关键字,关键字一般是语言层面的,但这个注释只是一个UI库的东西。应用层生成的控件要加入这个体系都需要编译器插件额外添加参数才能使用,编译器插件需要通过识别这个注解来为指定方法添加参数。

3.@Preview

@Preview注解,可以在右边实时预览,改动函数后,刷新一个预览即可,添加该注解的外层函数不能有参数,但是里面可以嵌套一个带参数的函数来预览。可以在@Preview后面添加一个名字,如:@Preview("Text preview")

4.与原View体系的对应控件

TextView ->Text

ImageView ->Image

纵向的LinearLayout ->Column

横向的LinearLayout ->Row

FrameLayout ->Box

ListView ->Colum

RecyclerView->LazyColumn

Button是一个专门用来点击的空壳,不是控件,有点像一个布局,里面的内容要自己去填写进去

。。。

5.原先的资源文件又xml变成了kotlin

例如theme包下的Color.kt 文件

val Red200 = Color(0xfff297a2)
val Red300 = Color(0xffea6d7e)
val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)

6.通过Modifier配置控件属性

Modifier是有顺序的,不同执行顺序会有不同效果

对控件属性的设置:

通过Modifer配置所有控件的共有属性(比如宽高,点击事件,背景,间距等)

通过参数配置控件的特有属性(比如Text的字体大小,颜色)

Modifer里的设置顺序不同会有不同的效果

简单例子

有道云笔记

四、自动更新机制
 

Compose是申明式UI,但并不是因为其是申明式UI,所以就实现了响应式

var content = "wyz"
@Composable
fun contentText() {
    Text(content)//这里当content发生改变时,Text不会发生重新刷新
}

Compose的响应来自State这个工具的配合

val content by mutableStateOf("wang")
@Composable
fun ReFresh() {
    Text(content)//有了State的配合,当content的内容发送改变时,Text会自动刷新
}

对比Livedata

  • LiveData也是数据绑定
  • LiveData数据绑定的是行为(比如当数据更新的时候,绑定setText()这个行为来更新界面)
  • Compose State直接绑定界面本身,声明式UI,非响应式,一劳永逸,节省了大量的关于UI的繁复的代码,以及无需那些mvvm,mvp等为了解决逻辑层和UI层交互问题的框架了

原生Android无法做到:指定某行代码,在运行时重新执行一次

Compose 配合State被代理的变量可以做到

State的作用只是用来监听的,当其包裹的内容发送改变时,会通知使用到它的Compose空间进行局部刷新

State会对被代理的内容的get和set方法加钩子,监听其变化

而局部刷新的功能与State无关(只是通知作用),是Compose实现

黑魔法的原理:

Compose编译器插件会修改代码的逻辑

把可能要重新执行的代码包起来,加上一个返回值

返回值就包括着原先代码块的代码,

然后把这段代码块(同一层级所有@Compose函数)存起来,

当代码块的条件达成的时候(入参改变发生时)重新用新的入参去执行代码块,得到新的值,更新相应代码块

var name by mutableStateOf("wyz")
setContent {
    Text(name)//就是可能需要重新执行的标志(@Composeable方法传入了一个变量)
    /*TODO*/
}
var name by mutableStateOf("wyz")
setContent {
    Text(name)//就是可能需要重新执行的标志(@Composeable方法传入了一个变量)
    /*TODO*/
}

被保存起来的代码(伪代码)

WrapperFun{
    Text(name)
    /*TODO*/
}

当name发生改变时,WrapperFun中的代码会被重新执行

Compose 重复的刷新叫做ReCompose

不需要ReCompose的地方加上remember,把数据缓存起来,不需要重复执行

val name = mutableStateOf("wyz")
var textNum=1
@Composable
fun testView(){
    Column {
        Text(name.value, Modifier.clickable {
            name.value="wang"
            textNum++//这里的变量虽然不是State,当时下面那个Text与上面属于同一个层级,Column下面的控件
            //所以当name触发了第一个Text reCompose时,第二个Text也属于第一个Text重新刷新时候要执行的代码块
            //所以两个Text都会重新刷新
        })
        Text(textNum.toString()) //点击第一个Text,这里也会刷新
        //流程是 Text(1)click-》
        // name改变-》
        // Text(1)同一层级下代码块重新执行-》
        // Text(2)也被重新刷新
    }
}

Compose控件是无状态的

如果不是外部传入的参数,Compose组件内部是拿不到这个组件内的局部变量的

因为Compose有别于传统的控件,

个人理解:传统View是类,能生成实例,Compose都是函数,执行完就完了,没有生成实例,

这个特性的特点就是无状态(缺点:没法拿到一个实例的某个变量,优点:可以局部ReCompose,局部更新)

想要把某个控件的状态共享出去,只能把状态作为参数,又由外层传入,相当于共享给外部

如果最外层要得到这个参数,只能一层层传出去,

@Composable
fun father(){
    Box {
        son()//Compose无状态的特性,这里拿不到son的任何属性
    }
}

@Composable
fun son() {
    val content = "wyz"
    Text(content)
}
@Composable
fun son(sonContent: String) {//如果Text的content需要让外部拿到,需要把它通过参数传入,抛给外层
    Text(sonContent)
}

@Composable
fun father() {
    Box {
        val sonContent = "wyz"
        son(sonContent)
        print(sonContent)//这样就拿到了子控件的属性"content"
    }
}

这里的原则是参数能不往外提就尽量收拢在控件内部,就像变量设置private一样

State 就是对内容的代码,当被外部调用set的时候,对所有调用其get方法的Compose代码块出发reCompose

但是以下代码有问题:(不会触发任何刷新)

val nums = mutableStateOf(mutableListOf("one","two","three"))
@Composable
fun testView(){
    Column {
        Button(onClick =
        {
            nums.add(nums.last()+1)//实际上点击了Button后nums被调用的是add而不是set,所以下面那个“ListView”(nums并不会触发重新刷新,界面丝毫未发生改变)
        }){
            Text("加上1")
        }
        for (num in nums){
            Text(num)
        }
    }
}

用到list时候,要使用mutableStateListOf

map用到mutableStateMapOf

val nums = mutableStateListOf("one","two","three")//mutableStateListOf用来包裹list,当被调用add或者remove时也会触发刷新
@Composable
fun ListView(){
    Column {
        Button(onClick =
        {
            nums.add(nums.last()+1)
        }){
            Text("加上1")
        }
        for (num in nums){
            Text(num)
        }
    }
}

官方为我们默认做好的Recompose的优化:

编译器插件会默认为@Composable函数 插入一个判断条件,判断@Composable函数的入参是否发生改变

如果不变,那该函数不会触发ReCompose

好处:在同一层级其他Compose出发ReCompose时,不变的Compose控件避免不必要的刷新

当然,这个只是消除了自动刷新带来的性能损耗(是消除与传统UI的性能差距,不是做到比传统性能更好)

val name = mutableStateOf("wyz")
var textNum=1
@Composable
fun testView(){
    Column {
        Text(name.value, Modifier.clickable {
            name.value="wang"
            textNum++//这里的变量虽然不是State,当时下面那个Text与上面属于同一个层级,Column下面的控件
            //所以当name触发了第一个Text reCompose时,第二个Text也属于第一个Text重新刷新时候要执行的代码块
            //所以两个Text都会重新刷新
        })
        Heavy()//点击第一个Text,这里不会刷新
        Text(textNum.toString()) //点击第一个Text,这里也会刷新
    }
}
@Composable
fun Heavy(){//由于没有入参,同层级其他控件触发ReCompose时这个函数会跳过重新执行
    Text("aaa")//
}

判断一个@Composable函数是否会被动刷新的规则比较复杂(这里还漏了很多细节),需要后续进行补充,详情看Compose第六讲

以下写法,如果入参发生改变也不会出发ReCompose

@Composable
fun Heavy(content:Int){//由于入参没有用到,同层级其他控件触发ReCompose时这个函数会跳过重新执行
    Text("aaa")
}

但是这里还有个问题要后续思考:

如果是这样呢:

val name = mutableStateOf("wyz")
var textNum=1
@Composable
fun testView(){
    Column {
        Text(name.value, Modifier.clickable {
            name.value="wang"
            textNum++//这里的变量虽然不是State,当时下面那个Text与上面属于同一个层级,Column下面的控件
            //所以当name触发了第一个Text reCompose时,第二个Text也属于第一个Text重新刷新时候要执行的代码块
            //所以两个Text都会重新刷新
        })
        Heavy()//这里会不会重新刷新
        Text(textNum.toString()) //点击第一个Text,这里也会刷新
    }
}

@Composable
fun Heavy(){//由于没有入参,同层级其他控件触发ReCompose时这个函数会跳过重新执行吗?
    Text(textNum.toString()))//这里的值实际上已经发生了改变
}

 五、自定义Compose

1.写法上的优势

Compose的写法很像xml文件,都是声明各种结构布局,比直接使用代码编写要直观方便很多

但是xml不能包含任何的逻辑计算

原生Android View体系中:xml+java/kotlin(自定义View)

xml:简单+直观

自定义View:能处理逻辑,定制程度更高(绘制,触摸,测量)

Compose自定义View 约等于 xml + 逻辑

具有两者的所有优点

但,要怎么自定义 onMeasure onLayout onDraw onTouchEvent 这些呢

2.怎么写

1.支持配置参数

如果一个控件的各种属性需要能提供给上层配置,需要将Modifier作为参数暴露出去

每个自定义的@Composable函数都加上一个默认的Modifier,这样的好处是,但外部需要配置内部控件时传入,不需要时不用写

@Composable
fun Custom(modifier: Modifier = Modifier) {//这里这个Modifier是Compose实现好的一个默认的Modifier实例,类似占位符
    Text("yz", modifier = modifier
        .padding(10.dp,)
        .background(
            color = Color.Green,
            shape = RectangleShape
        )
        .padding(50.dp)
        .width(20.dp)
        .height(80.dp)
        .clickable {
            sonName = "12121"
        }
    )//这里要加默认的配置
}

2.自定义onDraw

这里的api跟原生Canvas的api很类似

1.通过Modifier来对原有组件进行绘制

//将内容一起绘制
Modifier.drawWithContent() {
    drawRect(Color.Gray)
    drawContent()
}

/*专门绘制背景的*/
Modifier.drawBehind {
        drawCircle(Color.Cyan)
    }

//对绘制之前的准备工作进行缓存
Modifier.drawWithCache {  }

2.专门用了绘制的组件Canvas

Canvas(Modifier.size(100.dp)){
    drawCircle(Color.Cyan)
}

3.自定义Layout

LayoutModifier是用于跟布局相关的各种场景,比如Modifier.padding、Modifier.layout都是是这个的子类

Modifier.layout 的 measurable就是LayoutNode

LayoutNode

4.自定义Measure

六、Compose定位

1.对比原先View体系

Compose的所有控件都是独立于android平台(这里的独立是相对原生Android View系统而言的,指上层独立,底层还是依赖原声,这样设计是为了与原生View系统仍保持交互)

独立于android平台:

1.上层暴露给开发者的接口全部与Android无关(独立于android平台是指上层独立于android)

2.底层会依赖于Android

例如Compose的Image和Text底层是android的canvas的drawBitmap 和drawText

2.这个特性的两个好处

1.可以支持预览:如果一个控件依赖于平台,那离开平台时它的功能肯定是残缺的

2.可以支持跨平台(目前还可以支持PC端,以后不清楚)

3.对比Compose与Flutter

1.Flutter

Flutter底层直接操作NDK(在原生的更底层,android的canvas最后也是调用原生NDK来工作),直接操作更底层的渲染引擎,就可以完全不依赖于原生

优势:完全绕开原生Android系统

弊端:不能与原生View有任何交互,因为Flutter沉到了比原生View系统更下的层级

2.Compose

Compose底层还是Android原生的Canvas操作,是在原生View的系统那一级

优势:可以保持与原生View进行交互(这是其不想完全绕开原生的原因)且性能不会受影响

目前,Compose已经支持了PC和Web

3.两者的关系

对于它的定位:这个一套全新的UI框架,由于同样作为响应式编程,很多人会拿它和Flutter对比,Android团队对它的定位更像是Kotlin,是对于原始android的进一步补充,而不是跨平台框架。

使用场景:

  • 原生Android可以实现的都可以使用Compose来加速开发,无法做到原生Android做不到的
  • 要使用Flutter的场景优先使用Flutter(Compose目前没支持ios)

现在Android出了很多独立于平台的东西:RecyclerView ConstarintLayout AppCompat ViewModel LiveData Fragment Activity

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`docker-compose`是一个用于管理应用及其依赖环境的强大工具。它允许开发者通过单个YAML文件配置应用程序的所有服务、网络和卷,并使用一个命令启动整个应用环境。以下是关于`docker-compose`的一些基本概述: ### `docker-compose`的主要用途 1. **简化部署**:通过将所有服务、依赖项和配置信息组织在一个地方,`docker-compose`使得应用的部署过程变得简单而统一。 2. **跨平台一致性**:无论您是在本地开发环境中还是在生产服务器上运行应用,`docker-compose`都能提供一致的体验。 3. **便于维护**:对于有多个微服务的应用程序,`docker-compose`帮助保持各个组件之间的相互依赖性和配置的一致性。 4. **快速迭代**:支持快速测试和修改服务配置,无需每次都重启容器。 5. **集成自动化**:可以轻松地将构建、测试、部署等步骤整合到CI/CD流程中。 ### 使用示例 假设有一个简单的Web应用,包含前端(React)和服务端(Node.js)。我们可以创建两个Dockerfile分别构建这两个服务,并编写一个`docker-compose.yml`文件来定义它们以及它们之间的连接。 ```yaml version: '3' services: web: build: . ports: - "8080:80" depends_on: - db db: image: postgres:latest ``` 在这个例子中,我们定义了两个服务:web和服务端数据库。`web`服务使用当前目录下的Dockerfile进行构建,并将其暴露在外部主机的8080端口上。同时,它依赖于名为`db`的服务。这意味着在启动web服务之前,数据库服务会先启动并准备就绪。 ### 安装和运行 通常,您需要安装`docker`和`docker-compose`。然后,只需在包含`docker-compose.yml`文件的项目根目录下执行以下命令即可启动应用: ``` docker-compose up ``` 这个命令将会根据`docker-compose.yml`文件的内容启动所有指定的服务,并自动处理依赖关系。当所有的服务都已准备好时,终端将显示一条确认消息。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值