文章目录
一、Compose概述
示例:pa
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仍在开发之中,因此每个开发人员的反馈都很重要。
ndas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
二、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里的设置顺序不同会有不同的效果
简单例子
四、自动更新机制:ReComposition
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,把数据缓存起来,不需要重复执行
r
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
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。