当前位置: 首页 > news >正文

这么注册免费网站上海公司网站建设服务

这么注册免费网站,上海公司网站建设服务,wordpress 企业主题下载,网站开发工具中三剑客包括Kotlin Kotlin的历史 Kotlin由Jet Brains公司开发设计#xff0c;2011年公布第一版#xff0c;2012年开源。 2016年发布1.0正式版#xff0c;并且Jet Brains在IDEA加入对Kotlin的支持#xff0c;安卓自此又有新的选择。 2019年谷歌宣布Kotlin成为安卓第一开发语言#x…Kotlin Kotlin的历史 Kotlin由Jet Brains公司开发设计2011年公布第一版2012年开源。 2016年发布1.0正式版并且Jet Brains在IDEA加入对Kotlin的支持安卓自此又有新的选择。 2019年谷歌宣布Kotlin成为安卓第一开发语言安卓程序员由java转Kotlin已经迫在眉睫。 Kotlin的工作原理 语言分为解释型和编译型两种 语言类型 编译型 编译器直接将源代码一次性编译成二进制文件计算机可直接执行例如C,C。 优点一次编译即可运行运行期不需要编译运行效率高。 缺点不同操作系统需要不同的机器码且修改代码需要真个模块重新编译 解释型 程序运行时解释器会将源码一行一行实时解析成二进制再执行。例如JSPython。 优点平台兼容性好安装对应的虚拟机即可运行。 缺点运行时需要解释执行效率较低。 Java的语言类型 java准确来说属于混合型语言但更偏向于解释型。 编译java存在JIT和AOTJIT即时编译将可将热点代码直接编译成机器码AOT预先编译可再安装时把代码编译成机器码 解释java运行时需编译成class文件java虚拟机再解释执行.class。 Kotlin的运行原理 java虚拟机只认class文件 虚拟机不会关心class时java文件编译来的还是其他文件编译来的。那此时我们创造一套自己的语法规则再做一个对应的编译器则可让我们的语言跑在java虚拟机上。Kotlin则是此原理运行前会先编译成class再供java虚拟机运行。 语法 变量 变量的声明 Kotlin使用varval来声明变量注意Kotlin不再需要;来结尾 var 可变变量对应java的非final变量 var b 1 val不可变变量对应java的final变量 val a 1 两种变量并未声明类型这是因为Kotlin存在类型推导机制上述的a,b会默认为Int。假设想声明具体类型则需下面的方式 var c: Int 1 基本类型 Kotlin不再存在基本类型将全部使用对象类型 Java基本类型 Kotlin对象类型 对象类型说明 int Int 整型 long Long 长整型 short Short 短整型 float Float 单精度浮点型 double Double 双精度浮点型 boolean Boolean 布尔型 char Char 字符型 byte Byte 字节型 var和val的本质区别 Kotlin此设计的原因则是防止非final的滥用若一个变量永远不被修改则有必要给其加上final使其他人看代码时更好理解。 后期我们写代码时则可先使用val若真的需要修改再改为var 函数 函数的声明 无参无返回值 fun test() { } 有参有返回值 参数的类型需要写在形参名后面中间使用连接多个参数使用,分割“返回值使用”拼接 fun add(a: Int, b: Int): Int { return a b } 声明技巧 当函数体只有一行代码时可直接使用下面方式声明方法 fun add (a: Int, b: Int): Int a b Kotlin存在类型推导,返回值类型也可省略 fun add (a: Int, b: Int) a b 函数的调用 fun main() { test() print(add(1, 2)) } //运行结果 //test //3 if语句 Kotlin中的选择控制有两种方式。if和when if 与Java的if区别不大实现一个返回最大值的函数 fun max(a: Int, b: Int): Int { if (a b) return a else return b } Kotlin的if可以包含返回值if语句的最后一行会作为返回值返回 fun max(a: Int, b: Int): Int { return if (a b) a else b } 上述我们说过一行代码可省略返回值 fun max(a: Int, b: Int) if (a b) a else b 查看对应的Java文件其上述实现都与下面代码等价 public static final int max(int a, int b) { return a b ? a : b; } when 实现一个查询成绩的函数用户传入名字返回成绩级别 if实现 Kotlin的if语句必须要有else不然会报错 fun getScore(name: String) if (name Tom) 不及格 else if (name Jim) 及格 else if (name Pony) 良好 else if (name Tony) 优秀 else 名字非法 Kotlin中等价于Java的equals比较的时是对象里的内容 等价于Java的比较的为对象的引用。 when实现 也必须实现else否则报错 fun getScore(name: String) when(name) { Tom - 不及格 Jim - 及格 Pony - 良好 Tony - 优秀 else - 名字非法 } when支持参数检查 fun checkNumber(num: Number) { when (num) { is Int - println(Int) is Double - println(Double) else - println(others) } } when也可不传递形参 使用Boolean使when更加灵活 fun getScore(name: String) when { name Tom - 不及格 name Jim - 及格 name Pony - 良好 name Tony - 优秀 else - 名字非法 } - 后不仅可以只执行一行代码可以多行看一个比较复杂的例子 fun getScore(name: String) when { //若name以Tom开头则命中此分支 name.startsWith(Tom) - { //处理 println(你好我是Tom开头的同学) 不及格 } name Jim - 及格 name Pony - 良好 name Tony - 优秀 else - 名字非法 } 循环语句 Kotlin有两种循环方式while和for-inwhile与java中的while没有区别for-in是对Java for-each的加强Kotlin舍弃了for-i的写法 while不再赘述在学习for-in之前需要明确一个概念-区间 val range 0..10 //区间代表[0,10] for-in需借助区间来使用 fun main() { val range 0..10 for (i in range) { //也可直接for (i in 0..10) println(i) } //输出结果为 从0打印到10 } 0..10 代表双闭区间如果想使用左闭右开呢需要借助until关键字 fun main() { for (i in 0 until 10) { println(i) } //输出结果为 从0打印到9 } 上述实现是逐步进行相当于iKotlin也支持跳步 fun main() { for (i in  0 until 10 step 2) { println(i) } //输出结果为02468 } 上述实现都是升序Kotlin也可降序循环 fun main() { for (i in  10 downTo 1) { println(i) } //输出结果为10 - 1 } for-in不仅可对区间进行遍历还可对集合进行遍历后续在集合处进行展示。 类和对象 创建Person类并声明nameage创建printInfo方法 class Person { var name var age 0 fun printInfo() { println(name s age is age) } } 在main方法中声明一个Person对象并调用printInfo方法 fun main() { val person Person() person.name zjm person.age 20 person.printInfo() } //结果如下zjms age is 20 继承 声明Student类继承PersonKotlin中继承使用****后接父类的构造为什么需要构造后续讲解 class Student : Person(){ //此时Person报错 var number var grade 0 fun study() { println(name is studying) } } Person类当前不可继承查看Person对应的java文件 public final class Person { ... } Person类为final不可被继承因此需借助open关键字 只需在Person类前加上open open class Person { ... } 此时Person的java文件变为 public class Person { ... } 此时Student将不再报错 构造 构造分为主构造和此构造 主构造 主构造直接写在类后面 修改Student类 class Student(val number: String, val grade: Int) : Person(){ ... } 在创建Student对象时如下创建 val student Student(1234, 90) 因之前Person还有name和age下面修改Person类的主构造 open class Person(val name: String, val age: Int) { ... } 此时Student报错因为继承Person时后边使用的是Person()无参构造上面我们修改了Person的构造则不存在无参构造了。 再修改Student class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age){ ... } 此时不在报错声明方式如下 val student Student(zjm, 20, 1234, 90) 在构造时需要进行特殊处理怎么办Kotlin提供了init结构体主构造的逻辑可在init中处理 open class Person(val name: String, val age: Int) { init { println(name is name) println(age is age) } } 上述修改都为主构造那如果类想有多个构造怎么办此时需借助次构造 次构造 此时实现Student的另外两个构造 三个参数的构造nameagenumbergrade不传参默认为0 无参构造字符串默认为int默认为0 class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age){ constructor(name: String, age: Int, number: String) : this(name, age, number, 0) { } constructor() : this(, 0, , 0) { } ... } 创建如下 fun main() { val student1 Student(zjm, 20, 123, 90) val student2 Student(zjm, 20, 123) val student3 Student() } 无主构造 若类不使用主构造则后续继承类也不需要使用构造即可去掉继承类的次构造可以调用父类构造super进行初始化但是次构造的参数在其他地方无法引用 class Student : Person { constructor(name: String, age: Int, number: String) : super(name, age) { } fun study() { //nameage可使用 println(name is studying) //使用number则会报错若number是主构造的参数则可引用 //println(number) 报红 } } 接口 接口的定义 和Java中的接口定义类似 interface Study { fun study() fun readBooks() fun doHomework() } 接口的继承 继承接口只需在后用,拼接需实现Study声明的全部函数 class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age), Study{ ... override fun study() { TODO(Not yet implemented) } override fun readBooks() { TODO(Not yet implemented) } override fun doHomework() { TODO(Not yet implemented) } } Kotlin支持接口方法的默认实现JDK1.8以后也支持此功能方法有默认实现则继承类无需必须实现此方法 interface Study { fun study() { println(study) } fun readBooks() fun doHomework() } 权限修饰符 Java和Kotlin的不同如下表所示 修饰符 Java Kotlin public 所有类可见 所有类可见默认 private 当前类可见 当前类可见 protected 当前类子类同包下类可见 当前类子类可见 default 同包下类可见默认 无 internal 无 同模块下的类可见 Kotlin引入internal摒弃了default 使用 类上 public open class Person(val name: String, val age: Int){...} 变量上 private val value 1 方法上 private fun test() { } 数据类和单例类 数据类 数据类则只处理数据相关与Java Bean类似通常需要实现其getsethashCodeequalstoString等方法 下面实现UserBean包含idnamepwd属性 Java编写入如下 public class UserBean { private String id; private String name; private String pwd; public UserBean() { } public UserBean(String id, String name, String pwd) { this.id id; this.name name; this.pwd pwd; } Override public boolean equals(Object o) { if (this o) return true; if (o null || getClass() ! o.getClass()) return false; UserBean userBean (UserBean) o; return Objects.equals(id, userBean.id) Objects.equals(name, userBean.name) Objects.equals(pwd, userBean.pwd); } Override public int hashCode() { return Objects.hash(id, name, pwd); } Override public String toString() { return UserBean{ id id \ , name name \ , pwd pwd \ }; } public String getId() { return id; } public void setId(String id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd pwd; } } 一行代码即可搞定Kotlin会自动实现上述方法。 data class UserBean(val id: String, val name: String, val pwd: String) 若无data关键字上述方法hashCodeequalstoString无法正常运行去掉data查看Kotlin对应的java文件 public final class UserBean { NotNull private final String id; NotNull private final String name; NotNull private final String pwd; NotNull public final String getId() { return this.id; } NotNull public final String getName() { return this.name; } NotNull public final String getPwd() { return this.pwd; } public UserBean(NotNull String id, NotNull String name, NotNull String pwd) { Intrinsics.checkNotNullParameter(id, id); Intrinsics.checkNotNullParameter(name, name); Intrinsics.checkNotNullParameter(pwd, pwd); super(); this.id id; this.name name; this.pwd pwd; } } 发现上面代码既无hashCodeequalstoString也无set 加上data且把变量改为var对应的java文件如下 public final class UserBean { NotNull private String id; NotNull private String name; NotNull private String pwd; NotNull public final String getId() { return this.id; } public final void setId(NotNull String var1) { Intrinsics.checkNotNullParameter(var1, set-?); this.id var1; } NotNull public final String getName() { return this.name; } public final void setName(NotNull String var1) { Intrinsics.checkNotNullParameter(var1, set-?); this.name var1; } NotNull public final String getPwd() { return this.pwd; } public final void setPwd(NotNull String var1) { Intrinsics.checkNotNullParameter(var1, set-?); this.pwd var1; } public UserBean(NotNull String id, NotNull String name, NotNull String pwd) { Intrinsics.checkNotNullParameter(id, id); Intrinsics.checkNotNullParameter(name, name); Intrinsics.checkNotNullParameter(pwd, pwd); super(); this.id id; this.name name; this.pwd pwd; } NotNull public final String component1() { return this.id; } NotNull public final String component2() { return this.name; } NotNull public final String component3() { return this.pwd; } NotNull public final UserBean copy(NotNull String id, NotNull String name, NotNull String pwd) { Intrinsics.checkNotNullParameter(id, id); Intrinsics.checkNotNullParameter(name, name); Intrinsics.checkNotNullParameter(pwd, pwd); return new UserBean(id, name, pwd); } // $FF: synthetic method public static UserBean copy$default(UserBean var0, String var1, String var2, String var3, int var4, Object var5) { if ((var4 1) ! 0) { var1 var0.id; } if ((var4 2) ! 0) { var2 var0.name; } if ((var4 4) ! 0) { var3 var0.pwd; } return var0.copy(var1, var2, var3); } NotNull public String toString() { return UserBean(id this.id , name this.name , pwd this.pwd ); } public int hashCode() { String var10000 this.id; int var1 (var10000 ! null ? var10000.hashCode() : 0) * 31; String var10001 this.name; var1 (var1 (var10001 ! null ? var10001.hashCode() : 0)) * 31; var10001 this.pwd; return var1 (var10001 ! null ? var10001.hashCode() : 0); } public boolean equals(Nullable Object var1) { if (this ! var1) { if (var1 instanceof UserBean) { UserBean var2 (UserBean)var1; if (Intrinsics.areEqual(this.id, var2.id) Intrinsics.areEqual(this.name, var2.name) Intrinsics.areEqual(this.pwd, var2.pwd)) { return true; } } return false; } else { return true; } } } 此时则和手动编写的java bean功能一样了所有方法都可正常运行 单例类 目前Java使用最广的单例模式的实现如下 public class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } public void test() { ... } } 生成代码如下 object Singleton { fun test() { ... } } 其对应的java文件如下和上述使用最多的java单例实现类似 public final class Singleton { NotNull public static final Singleton INSTANCE; public final void test() { } private Singleton() { } static { Singleton var0 new Singleton(); INSTANCE var0; } } 使用如下 fun main() { Singleton.test() //对应的java代码为Singleton.INSTANCE.test(); } Lambda 许多高级语言都支持Lambdajava在jdk1.8以后才支持Lamda语法Lamda是Kotlin的灵魂所在此小节对Lambda的基础进行学习并借助集合练习。 集合的创建和遍历 List fun main() { //常规创建 val list ArrayListInt() list.add(1) list.add(2) list.add(3) //listOf不可变后续不可添加删除只能查 val list1 listOfInt(1, 2, 3 ,4 ,5) list1.add(6)//报错 //mutableListOf后续可添加删除 val list2 mutableListOfInt(1, 2, 3 ,4 ,5) list2.add(6) //循环 for (value in list2) { println(value) } } Set set用法与List类似只是把listOf替换为mapOf Map fun main() { val map HashMapString, String() map.put(1, zjm) map.put(2, ljn) //Kotlin中map支持类似下标的赋值和访问 map[3] lsb map[4] lyx println(map[2]) println(map.get(1)) //不可变 val map1 mapOfString, String(1 to zjm, 2 to ljn) map1[3] lsb //报错 //可变 val map2 mutableMapOfString, String(1 to zjm, 2 to ljn) map2[3] lsb for ((key, value) in map) { println(key    value) } } Lambda的使用 方法在传递参数时都是普通变量而Lambda可以传递一段代码 Lambda表达式的语法结构 {参数名1 参数类型 参数名2参数类型 - 函数体} Kotlin的list提供了maxByOrNull函数返回当前list中xx最大的元素XX是我们定义的条件可能为长度可能是别的我们拿长度举例。 若不使用maxBy实现如下 fun main() { val list listOfString(a, aba, aabb, a) var maxStr for (str in list) { if (str.length maxStr.length) { maxStr str; } } println(maxStr) } maxByOrNull是一个普通方法需要一个Lambda参数下面结合Lambda使用maxByOrNull fun main() { val list listOfString(a, aba, aabb, a) var lambda {str: String - str.length} var maxStr list.maxByOrNull(lambda) println(maxStr) } 直接当成参数也可传递 var maxStr list.maxByOrNull({str: String - str.length}) 若Lambda为方法的最后一个参数则可将{}提到外面 var maxStr list.maxByOrNull() {str: String - str.length} 若有且仅有一个参数且是Lambda则可去掉() var maxStr list.maxByOrNull {str: String - str.length} Kotlin拥有出色的类型推导机制Lambda参数过多时可省略参数类型 var maxStr list.maxByOrNull {str - str.length} 若Lambda只有一个参数则可用it替代参数名 var maxStr list.maxByOrNull {it.length} 集合还有许多此类函数 创建list后续操作都由此list转换 val list listOfString(a, aba, aabb, a) map 映射返回新集合将集合中的元素映射成另一个值 val newList list.map { it.toUpperCase() }//将集合中的元素都准换成大写 filter过滤返回新集合将集合中的元素进行筛选 val newList list.filter { it.length 3 }//筛选出长度大于3的元素 any返回Boolean集合中是否存在元素满足Lambda的条件有则返回true无则false val isAny list.any {it.length 10} //返回false all返回Boolean集合中元素是否全部满足满足Lambda的条件有则返回true无则false val isAll list.all {it.length 0} //返回true Lambda的简单使用到这就结束了 Java函数式API的使用 Kotlin调用Java方法若该方法接收一个Java单抽象方法接口参数则可使用函数式API。Java单抽象方法接口指的是接口只声明一个方法若有多个方法则无法使用函数式API。 Java单抽象方法接口例如Runnable public interface Runnable { void run(); } 在Java中启动一个线程如下 new Thread(new Runnable() { Override public void run() { System.out.println(test); } }).start(); Kotlin启动线程如下 Kotlin摒弃了new若想声明匿名内部类必须使用object Thread(object : Runnable { override fun run() { println(test) } }).start() 因Runnable是Java单抽象方法接口可对代码进行简化 Thread(Runnable { println(test) }).start() Runnable接口只用一个方法使用Lambda也不会有歧义Kotlin知道此Lambda一定实现的为run函数借用Lambda进一步简化 Thread({ println(test) }).start() 又因Thread只需一个参数Runnable参数则可省略() Thread { println(test) }.start() 与上类似的click也使用上述方法 button.setOnClickListener { println(test) } 这种方式可极大缩减代码量 空指针检查机制 国外统计程序出现最多的异常为空指针异常Kotlin存在编译时检查系统帮助我们发现空指针异常。 查看下面Java代码 public void doStudy(Study study) { study.doHomework(); study.readBooks(); } 上述代码时存在空指针风险的传入null则程序崩溃对其进行改进 public void doStudy(Study study) { if (study ! null) { study.doHomework(); study.readBooks(); } } 对于Kotlin来讲任何参数和变量不能为空 fun study(study: Study) { study.doHomework() study.readBooks() } fun main() { study(null) //报错 study(Student()) //正确 } Kotlin把空指针异常的检查提前到了编译期若空指针则编译期就会崩溃避免在运行期出现问题 若我们有特殊的需求可能需要传递null参数参数则按照下面声明 fun study(study: Study?) { study.doHomework() //报错 study.readBooks()     //报错 } ?的意思则是当前参数可为空如果可为空的话则此对象调用的方法必须要保证对象不为空上面代码没有保证则报错修改如下 fun study(study: Study?) { if (study ! null) { study.doHomework() study.readBooks() } } 也可借助判空辅助工具 判空辅助工具 ?. 其含义是前面对象不为空才执行.后面的方法 fun study(study: Study?) { study?.doHomework() study?.readBooks() } ?: 其含义是前不为空则返回问号前的值为空则返回后的值 比如 val c if (a !null ) { a } else { b } 借助?:则可简化为 val c a ?: b 再比如 fun getTextLength(text: String?): Int { if (text ! null) { return text.length } return 0 } 借助?: 则可简化为 fun getTextLength(text: String?) text?.length ?: 0 !! 有些时候我们想要强行通过编译就需要依靠!!这时就是程序员来保证安全 fun study(study: Study?) { //假设此时为空抛出异常则和java一样 study!!.doHomework() study!!.readBooks() } let函数 let不是关键字而是一个函数提供了函数式API的编程接口会将调用者作为参数传递到Lambda表达式调用之后会立马执行Lambda表达式的逻辑 obj.let { it - //it就是obj //编写操作 } 比如上面函数 fun study(study: Study?) { study.doHomework()  //报错 study.readBooks()     //报错 } 借助let则可改为 fun study(study: Study?) { //此时靠?.则保证了study肯定不为空才会执行let函数 study?.let { //it为study it.doHomework() it.readBooks() } } 全局判空注意事项 //全局变量 var study: Study? null fun study() { //报错 if (study ! null) { study.readBooks() study.doHomework() } } 因全局变量随时有可能被其他线程修改即使判空处理也不能保证其没有空指针风险而let则可规避上述问题 var study: Study? null fun study() { study?.let { it.doHomework() it.readBooks() } } 内嵌表达式 之前我们拼接字符串都是下面这样 var name zjm var age 20 println(My name is name . I am age .) //打印结果 //My name is zjm. I am 20. 现在靠着Kotlin提供的内嵌表达式则不需要拼接只需要下面这样则可实现 var name zjm var age 20 println(My name is $name. I am $age. ) //打印结果 //My name is zjm. I am 20. 内嵌表达式还支持复杂的操作 ${程序员想要的操作} var name zjm var age 20 println(My name is ${if (1 2) zjm else ljn}. I am $age. ) //打印结果 //My name is zjm. I am 20. 函数的参数默认值 Kotlin支持函数存在默认值使用如下 fun main() { myPrint(1) myPrint(1, lalala) } fun myPrint(value: Int, str: String hello) { println(num is $value, str is $str) } //结果如下 //num is 1, str is hello //num is 1, str is lalala 若value想为默认值则会报错因为在使用时传入的第一个参数他认为是int的传入字符串会类型不匹配 fun main() { myPrint(zjm)//报错 } fun myPrint(value: Int 100, str: String) { println(num is $value, str is $str) } Kotlin提供了一种键值对传参来解决上述问题 fun main() { myPrint(str zjm) //正确调用 } fun myPrint(value: Int 100, str: String) { println(num is $value, str is $str) } 回顾之前的主次构造Student如下 class Student(name: String,  age: Int, val number: String, val grade: Int) : Person(name, age){ constructor(name: String, age: Int, number: String) : this(name, age, number, 0) { } ... } 上述的此构造借助参数默认值技巧是可以不写的将第四个参数默认值为0 即可 class Student(name: String,  age: Int, val number: String, val grade: Int 0) : Person(name, age){ ... } 一文快速入门 Kotlin 协程 - 掘金 (juejin.cn) Compose Compose 分享 · 语雀 Jetpack Compose 是用于构建原生 Android 界面的新工具包。 它使用更少的代码、强大的工具和直观的 Kotlin APl可以帮助您简化并加快 Android 界面开发打造生动而精彩的应用。 它可让您更快速、更轻松地构建 Android 界面 为何要选择 Compose 很多 Android 开发都会问View 已经这么成熟了为何我要引入 Compose 历史也总是惊人的相似React 横空出世时很多前端同学也会问jQuery 已经如此强大了为何要引入 JSX、Virtual DOM 争论总是无效的时间会慢慢证明谁才会成为真正的主宰。 现在的前端同学可能连 jQuery 是什么都不知道了。其作为曾经前端的主宰何其强大却也经受不住来自 React 的降维打击。回看这端历史那我们选择 Compose 就显得很自然了。 另一个大趋势是 Kotlin 跨平台的逐渐兴起与成熟也会推动 Compose 成为 Fultter 之外的选择而且可以不用学习那除了写 Flutter 就完全没用的 Dart 语言。 但是我也不推荐大家随随便便就把 Compose 接入的项目中。因为国内的开发现状就是那样迭代速度要求快但是也要追求稳定。而接入 Compose 到使用 Compose 快速迭代也是有一个痛苦的过程的搞不好就要背锅现在这环境背锅可能就代表被裁了。 所以目前 Compose 依旧只能作为简历亮点而非必备点。可是如果你不学万一被要求是必备点那该怎么办 所以即使你不喜欢 Compose 这一套那为了饭碗该掌握的还是得掌握毕竟市场饱和我们是被挑选的哪一方。 Compose 的思想 声明式 UI Compose 的思想与 React、View、Fultter、SwiftUI 都是一脉相传那就是数据驱动 UI 与 声明式 UI。以前的 View 体系我们称它为命令 UI。 命令式 UI 是我们拿到 View 的句柄然后通过执行命令主动更新它的的颜色、文字等等 声明式 UI 则是我们构建一个状态机描述各个状态下 UI 是个什么样子的。 那些写 Compose 怎么都不顺手的童鞋就是总想拿 View 的句柄但又拿不到所以就很痛苦但如果转换到状态机的思维上去定义各种情景的状态那写起来就非常舒服了。 Compose 从 View 体系进化的点就是它贴近于真实的 UI 世界。因为每个界面就是一个复杂的状态机以往我们命令式的操作我们依旧要定义一套状态系统某种状态更新为某种 UI有时候处理得不好还会出现状态错乱的问题。 Compose 则强制我们要思考 UI 的状态机该是怎样子的。 Virtual DOM 在 Compose 的世界中是没有介绍 Virtual DOM 这一概念的但我觉得理解 Virtual DOM 能够帮助我们更好的理解 Compose。 Virtual DOM 的诞生一个原因是因为 DOM/View 节点实在是太重了所以我们不能在数据变更时删除这个节点再重新创建我们也不没有办法通过 diff 的方式去追踪到底发生了哪些变更。但大佬们的思维就比较活跃因为开发过程中关注的一个 DOM/ View 的属性是很少的所以就创造了一个轻量级的数据结构来表示一个 DOM/View 节点由于数据结构比较轻量那么销毁创建就可以随意点。每次更新状态我可以用新状态去创造一个新的 Virtual DOM Tree 然后与旧的 Virtual DOM Tree 进行 diff然后将 diff 的结果更新到 DOM / View 上去 React Native 就是把前端的 DOM 变成移动端的 View因而开启了 UI 跨平台动态化的大门。 那这和 Compose 有什么关系呢我们可以认为Compose 的函数让我们来生成 Virtual DOM 树Compose 内部叫 SlotTable框架用了全新的内部结构来代表 DOM 节点。每次我们状态的变更就会触发 Composable 函数重新执行以生成新的 Virtual DOM这个过程叫做 Recomposition。 所以重点来了发生状态更新后框架会首先去重新生成 Virtual DOM 树交给底层去比对变更最终渲染输出。如果我们频繁的变更状态那就会频繁的触发 Recomposition如果每次还是重新生成一个巨大的 Virtual DOM 树那框架内部的 diff 就会非常耗时那么性能问题随之就来了这是很多同学用 Compose 写出的代码卡顿的原因。 Compose 性能最佳实践 如果我们有了 Virtual DOM 这一层认识那么就能够想到该怎样去保持 Compose 的高性能了那就是 1.减少 Composable 函数自身的计算 2.减小状态变更的频次 3.减小状态变更的造成 Recomposition 的范围以减小 diff 更新量 4.减小 Recomposition 时的变更量以减小 diff 更新量 减少 Composable 函数自身的计算 这个很好理解如果 Recomposition 发生了那么整个函数就会重新执行如果有复杂的计算逻辑那就会造成函数本身的消耗很大而解决措施也简单就是通过 remember 缓存计算结果 Composable func Test(){ val ret remember(arg1, arg2) { // 通过参数判断是否要重新计算 // 复杂的计算逻辑 } } 减少状态变更的频次 这个主要是减少无效的状态变更如果有多个状态其每个状态下的执行结果是一样的那这些状态间的变更就没有意义了应该统一成唯一的状态。 其实官方在 mutableStateOf 的入参 policy 上已经定制了几种判断状态值是否变更的策略 StructuralEqualityPolicy: 通过值判等的来看其是否发生变更 ReferentialEqualityPolicy: 必须是同一个对象才算未发生变更 NeverEqualPolicy : 总是触发状态变更 默认为 StructuralEqualityPolicy也符合一般情况的要求。 除此之外我们减小状态变更频率的手段就是 derivedStateOf。 它的用途主要是我们就是将多个状态值收归为统一的状态值 例如 1.列表是否滚动到了顶部我们拿到的 scorllY 是很频繁变更的值但我们关注的只是 scorllY 0 2.根据内容为空判定发送按钮是否可点击我们关注的是 input.isNotBlank() 3.多个输入的联合校验 4… 我们以发送按钮为例 Composable func Test(){ val input remember { mutabtleStateOf() } val canSend remember { derivedStateOf { input.value.isNotBlank() } } // 使用 canSend SendButton(canSend) // 其它很多代码 } 这样子我们可以多次更新 input 的值但是只有当 canSend 发生变更时才会触发 Test 的 Recomposition。 减小状态变更的造成 Recomposition 的范围 Recomposition 是以函数为作用范围的所以某个状态触发了 Recomposition那么这个函数就会重新执行一次。但需要注意的是不是状态定义的函数执行Recomposition而是状态读取的函数会触发 Recomposition。 还是以上面的输入的例子为例。 如果我在 Test 函数执行期内读取了 input.value 那么 input 变更时就会触发 Test 函数的重组。注意的是函数执行期内读取而不是函数代码里写了 input.value。上面 canSend 的 derivedStateOf 虽然也有调用 input.value但因为它是以 lambda 的形式存在不是会在执行 Test 函数时就执行所以不会因为 input.value 变更就造成 Test 的 Recomposition。 但如果我在函数体内使用 input.value例如 Composable func Test(){ val input remember { mutabtleStateOf() } val canSend remember { derivedStateOf { input.value.isNotBlank() } } Text(input.value) SendButton(canSend) OtherCode(arg1, arg2) OtherCode1(arg1, arg2) } 那就会因为 input 的变更而造成 Test 的重组 canSend 使用 derivedStateOf 也就是做无用功了。更严重的是可能有很多其它与 input 无关的代码也会再次执行。 所以我们需要把状态变更触发 Recomposition 的代码用一个子组件来承载 Composable func InputText(input: () - String){ Text(input()) } Composable func Test(){ val input remember { mutabtleStateOf() } val canSend remember { derivedStateOf { input.value.isNotBlank() } } InputText { input.value } SendButton(canSend) OtherCode(arg1, arg2) OtherCode1(arg1, arg2) } 我们重新创建了一个 InputText 函数然后通过 lambda 的形式传递 input因而现在 input 变更造成的 Recomposition 就局限于 InputText 了而其它的无关代码就不会被执行这样范围就大大缩减了。 减小 Recomposition 时的变更量 加入我们的函数 Recomposition 的范围已经没办法缩减了例如上面 canSend 变更触发 Test 的 Recomposition这造成 OtherCode 组件的重新执行好像无法避免了。其实官方也想到了这种情况所以它框架还会判断 OtherCode 的参数是否发生了变更依此来判断 OtherCode 函数是否需要重新执行。如果参数没有变更那么就可以开心的跳过它那么 Recomposition 的变更量就大幅减小了。 那么怎么判断参数没有发生变更呢如果是基础类型和data class 等的数据结果还好可以通过值判等的形式看其是否变更。但如果是列表或者自定义的数据结构就麻烦了。 因为框架无法知道其内部是否发生了变更。 以 a: List 为例虽然重组时我拿到的是同一个对象 a, 但其实现类可能是 ArraryList, 并且可能调用 add/remove 等方法变更了数据结构。所以在保证正确性优先的情况下框架只得重新调用整个函数。 Composable fun SubTest(a: ListString){ //... } Composable fun Test(){ val input remember { mutabtleStateOf() } val a remember { mutableStateOf(ArrayListString()) } // 因为读取了 input.value 所以每次 input 变更都会早成 Test 的 Recomposition Test(input.value) // 而因为 a 是个 List所以每次 SubTest 也会执行 Recomposition SubTest(a) } 那要怎么规避这个问题呢 那就是使用 kotlinx-collections-immutable 提供的 ImmutableList 等数据结构如此就可以帮助框架正确的判断数据是否发生了变更。 Composable fun SubTest(a: PersistentListString){ //... } Composable fun Test(){ val input remember { mutabtleStateOf() } val a remember { mutableStateOf(persistentListOfString()) } // 因为读取了 input.value 所以每次 input 变更都会早成 Test 的 Recomposition Test(input.value) // 而因为 a 是个 List所以每次 SubTest 也会执行 Recomposition SubTest(a) } 而如果是我们自己定义的数据结构如果是非 data class那就要我们主动加上 Stable 注解告诉框架这个数据结构是不会发生变更或者其变更我们都会用状态机去处理的。特别需要注意的是使用 java 作为实体类而给 compose 使用的情况那就是非常不友好了。 对于列表而言我们往往需要用 for 循环或者 LazyColumn 之类的方式使用: Composable fun SubTest(list: PersistentListItemData){ for(item in list){ Item(item) } } 这个写法如果 list 不会变更那也没什么问题可是如果列表发生了变更例如原本是 12345, 我删了一项变成 1345。 那么在 Recomposition 的时候框架在比对变更时发现从第二项开始就全不同了那么剩下的 Item 就得全部重新重组一次了这也是非常耗费性能的所以框架提供了 key 的功能通过它框架可以检测列表的 Item 移动的情况。 Composable fun SubTest(list: PersistentListItemData){ for(item in list){ key(item.id){ Item(item) } } } 不过需要注意的是 key 需要具有唯一性。 LazyColumn 的 item 也有 key 的功能其作用类似其还有 contentType 的传参其作用和 RecyclerView 的多 itemType 类似也是一个可以使用的优化措施。 最后 Compose 业务上能做的优化大体上就是这些了。总之我们就是我们要保持组件的颗粒度尽可能的小容易变动的要独立出来非常稳定的也要独立出来尽量使用 Immutable 的数据结构。 如此之后 Compose 的流畅度还是非常不错的。 如果还觉得卡那多半是因为你使用的是 Debug 包Compose 会在 Debug 包加很多调试信息会很影响其流畅度的。切换到 Release 包可能丝滑感就出来了。 现代化 Android 开发Jetpack Compose 最佳实践_安卓 compose-CSDN博客 Compose 初上手 注解 Compose 所有的组合函数都必须添加 Compose 注解才可以。 被 Compose 注解的方法只能被同类型的方法调用。 Preview 使用该注解的方法可以不在运行 App 的情况下就可以查看布局。Preview 中常用的参数如下 name: String: 为该Preview命名该名字会在布局预览中显示。 showBackground: Boolean: 是否显示背景true为显示。 backgroundColor: Long: 设置背景的颜色。 showDecoration: Boolean: 是否显示Statusbar和Toolbartrue为显示。 group: String: 为该Preview设置group名字可以在UI中以group为单位显示。 fontScale: Float: 可以在预览中对字体放大范围是从0.01。 widthDp: Int: 在Compose中渲染的最大宽度单位为dp。 heightDp: Int: 在Compose中渲染的最大高度单位为dp。 申明性编程范式 长期以来android 的视图结构一直可以表示为界面微件数。由于应用的状态会因用户交互等因素而发生变化因此界面层次结构需要进行更新以显示当前的数据最常见的就是 findviewById 等函数遍历树并调用设置数据的方法等改变节点这些方法会改变微件的内部状态 再过去的几年中整个行业已经转向声明性界面模型该模型大大的简化了构建和更新界面管理的工程设计改技术的工作原理是在改建上重头生成整个屏幕然后执行必要的更改。此方法可以避免手动更新有状态视图结构的复杂性。Compose 是一个声明性的界面框架。 重新生成整个屏幕所面临的一个难题是在时间计算力和电量方面可能成本高昂为了减轻这一成本Compose 会智能的选择在任何时间需要重新绘制界面的那些部分。这回对设计界面的组件有一定影响。 组合函数 Jetpack Compose 是围绕可组合函数构建的这些函数就是要显示在界面上的元素在函数中只需要描述应用界面形状和数据依赖关系而不用去关系界面的构建过程 如果需要创建组合函数只需要将 Composeable 注解添加到对于的函数上即可需要注意的是组合函数的名称一般都是以大写字母开头的如下 class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PrimaryTheme { Surface( modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background ) { Greeting(Android) } } } } } Composable fun Greeting(name: String) { Text(text Hello $name!, fontSize 18.sp, color Color.Red) } Preview(showBackground true) Composable fun DefaultPreview() { PrimaryTheme { Greeting(Android) } } setContent 块 定义了 Activity 的布局我们不需要去定义 XML 的布局内容只需要在其中调用组合函数即可。 上面的 一个简单的示例Greeting 微件它接收 String 而发出的一个显示问候消息的 Text 微件。此函数不会返回任何内容因为他们描述所需的屏幕状态而不是构造界面微件。 其中 Greeting 就是一个非常简单的可组合函数里面定义了一个 Text顾名思义就是用来显示一段文本 并且我们可以在 Test 函数上添加 PreView 注释这样就可以非常方便的进行预览。 声明式范式转变 在 Compose 的声明方法中微件相对无状态并且不提供 get,set 方法。实际上微件微件不会以对象的形式提供。你可以通过调用带有不同参数的统一可组合函数来更新界面。这使得架构模式如 ViewModel 变得很容易。 引用逻辑为顶级可组合函数提供数据。该函数通过调用其他可组合函数来使用这些数据来描述界面。将适当的数据传递给这些可组合函数并沿层次结构向下传递数据。 当用户与界面交互时界面发起 onClick事件。这些事件会通知应用逻辑应用逻辑可以改变应用状态。当状态发生变化时系统就会重新调用可组合函数。这回导致重新绘制界面描述此过程称为重组。 动态内容 由于可组合函是 kotlin 编写的因此他们可以像任何 kotlin 代码一样动态例如假设你想要的构建一个界面如下 Composable fun Greeting(names: ListString) { for (name in names) { Text(Hello $name) } } 此函数接受一个列表每位每个列表元素生成一个 Text。可组合函数可能性非常复杂你可以使用 if 语句来确定是否需要显示特定的界面元素。例如循环辅助函数等。你拥有地城语言的灵活性这种强大的功能和灵活性是 JetpackCompose 的主要优势之一。 重组 在 Compose 中你可以用新数据再次调用某个可组合函数这回导致组合函数重新进行重组。系统会根据需要使用新数据重新绘制发出的微件。Compose 框架可以只能的重组已经更改的组件。 例如下面这个可组合函数用于显示一个按钮 Composable fun ClickCounter(clicks: Int, onClick: () - Unit) { Button(onClick onClick) { Text(Ive been clicked $clicks times) } } 每次点击按钮就会更新 clicks 的值Compose 会再次调用 lambda 与 Text 函数以显示新值此过程称为 重组。不依赖该值的其他元素不会重组。 重组是指在输入更改的时候再次调用可组合函数的过程。当函数更改时会发生这种情况。当 Compose 根据新输入重组时它仅调用可能已经更改的函数或 lambad而跳过其余函数或 lambda。通过跳过岂会为更改参数的函数或者 lambda Compose 可以高效的重组。 切勿依赖于执行可组合函数所产生的附带效应因为可能会跳过函数的重组如果这样做用户可能在应用中遇到奇怪且不可预测的行为。例如 写入共享对象的属性 更新 viewmodel 中的可观察项 更新共享偏好设置 可组合函数可能会每一帧一样的频繁执行例如呈现动画的时候。所以可组合函数需要快速执行所以避免在组合函数中出现卡顿如果你需要执行高昂的操作请在狗太协程中执行并将结果作为参数传递给可组合函数。 例如下面代码应该将 sp 读取的操作放在 viewmode 中然后在回调中触发更新 Composable fun SharedPrefsToggle( text: String, value: Boolean, onValueChanged: (Boolean) - Unit ) { Row { Text(text) Checkbox(checked value, onCheckedChange onValueChanged) } } 可组合函数可以按照任何顺序执行 如果你看到了可组合函数的代码可能会认为他们按照顺序运行。但实际上未必是这样。如果某个可组合函数包含对其他组合代码的调用这些函数可以按照顺序执行。 Compose 可以选择识别出某些界面元素的优先级高于其他界面元素因此首先绘制这些元素。 假设你有如下代码 Composable fun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() } } 对于这三个的调用可以按照任何顺序进行。这意味着你不能让某个函数设置一个全局变量(附带效应)并让别的函数利用这个全局变量而发生更改。所以每个函数都应该独立。 可组合函数可以并行运行 Compose 可以通过并行运行可组合函数来优化重组。这样依赖Compose 就可以利用多个核心并按照较低的优先级运行可组合函数不在屏幕上 这种优化方方式意味着可组合函数可能会在后台的线程池中执行如果某个可组合函数对 viewModel 调用一个函数则 Compose 可能会同时从多个线程调动该函数。 为了确保应用可以正常运行所有的组合都不应该有附带效应而应该通过始终在界面线程上执行的 onClick 等回调触发附带效应。 调用某个可组合函数时调用可能发生在与调用方不同的线程上。这意味着应避免修改可组合函数 lambda 中的变量代码基因为此类代码并非线程安全代码又因为他是可组合 lambda 不允许的附带效应。 下面展示了一个可组合函数他显示了一个列表已经数量。 Composable fun ListComposable(myList: ListString) { Row(horizontalArrangement Arrangement.SpaceBetween) { Column { for (item in myList) { Text(Item: $item) } } Text(Count: ${myList.size}) } } 此函数没有附带效应他会将输出列表转为界面。才代码非常适合展示小列表。不过此函数写入局部变量则这并不是非线程安全或者正确的代码 Composable Deprecated(Example with bug) fun ListWithBug(myList: ListString) { var items 0 Row(horizontalArrangement Arrangement.SpaceBetween) { Column { for (item in myList) { Text(Item: $item) items // Avoid! Side-effect of the column recomposing. } } Text(Count: $items) } } 在上面例子中每次重组都会修改 items。这可以在动画的第一帧或者在列表更新的时候。但不管怎么样界面都会显示出错误的数量。因此 Compose 不支持这样的写入操作。通过静止此类操作我们允许框架更改线程以执行可组合 lambda。 重组跳过尽可能多的内容 如果界面某些部分无需Compose 会尽力只重组需要更新的部分。这意味着他可以跳过某些内容以重新运行单个按钮的可组合项而不执行树中其上面或下面的任何可组合项。 每个可组合函数和 lambda 都可以自行重组。以下演示了在呈现列表时重组如何跳过某些元素 /** * Display a list of names the user can click with a header */ Composable fun NamePicker( header: String, names: ListString, onNameClicked: (String) - Unit ) { Column { // this will recompose when [header] changes, but not when [names] changes Text(header, style MaterialTheme.typography.h5) Divider() // LazyColumn is the Compose version of a RecyclerView. // The lambda passed to items() is similar to a RecyclerView.ViewHolder. LazyColumn { items(names) { name - // When an items [name] updates, the adapter for that item // will recompose. This will not recompose when [header] changes NamePickerItem(name, onNameClicked) } } } } /** * Display a single name the user can click. */ Composable private fun NamePickerItem(name: String, onClicked: (String) - Unit) { Text(name, Modifier.clickable(onClick { onClicked(name) })) } 这些作用域中的每一个都可能是在重组期间执行唯一一个作用域。当 header 发生更改时Compose 可能会跳至 Column lambda 。二部执行他的任何父项。此外执行 Colum 时如果 names 未更改Compose 可能会旋转跳过 LazyColum 的项。 同样执行所有组合函数或者 lambda 都应该没有附带效应。当需要执行附带效应时应该通过回调触发。 重组是乐观操作 只要 Compose 任务某个可组合函数可能已经更改就会开始重组。重组是乐观操作也就是说 Compose 预计会在参数再次更改之前完成重组。如果某个参数在重组完成之间发生改变Compose 可能会取消重组并使用新的参数重新开始。 取消重组后Compose 会从重组中舍弃界面树。如有附带效应依赖于显示的界面即使取消了组成操作也会应用该附带效应。这可能导致应用状态不一致。 确保每个可组合函数和 lambda 都幂等且没有附带效应以处理乐观的重组 可组合函数可能会非常频繁的运行 在某些情况下可能针对界面每一帧运行一个可组合函数如果该函数成本高昂可能会导致界面卡顿。 例如你的微件重试读取设备配置或者读取 sp他可能会在一秒钟内读取这些数据上百次这回对性能造成灾难性的影响。 如果您的可组合函数需要数据它应为相应的数据定义参数。然后您可以阿静成本高昂的工作移到其他线程并使用 mutableStateOf 或者 LiveData 将相应的数据传递给 Compose。 主题 //深色 val DarkColorScheme darkColors( primary Purple80, onPrimary Color(0xFFFFFFFF), secondary PurpleGrey80, ) //亮色 val LightColorScheme lightColors( primary Purple40, onPrimary Color(0xFF333333), secondary PurpleGrey40, ) Composable fun PrimaryTheme( darkTheme: Boolean isSystemInDarkTheme(), // Dynamic color is available on Android 12 dynamicColor: Boolean true, content: Composable () - Unit ) { MaterialTheme( colors LightColorScheme, typography Typography, content content ) } 默认的主题定义如上所示最终会调用 MaterialTheme。 Material 主题主要包含三个属性分别是 颜色排版和内容Api 如下 Composable fun MaterialTheme( colors: Colors MaterialTheme.colors, // 颜色集合 typography: Typography MaterialTheme.typography, // 排版集合 shapes: Shapes MaterialTheme.shapes, // 形状集合 content: Composable () - Unit // 要展示的内容 ) 颜色 class Colors( primary: Color, // 主颜色屏幕和元素都用这个颜色 primaryVariant: Color, // 用于区分主颜色比如app bar和system bar secondary: Color, // 强调色悬浮按钮单选/复选按钮高亮选中的文本链接和标题 secondaryVariant: Color, // 用于区分强调色 background: Color, // 背景色在可滚动项下面展示 surface: Color, // 表层色展示在组件表层比如卡片清单和菜单(CardView,SheetLayout,Menu)等 error: Color, // 错误色展示错误信息比如TextField的提示信息 onPrimary: Color, // 在主颜色primary之上的文本和图标的颜色 onSecondary: Color, // 在强调色secondary之上的文本和图标的颜色 onBackground: Color, // 在背景色background之上的文本和图标的颜色 onSurface: Color, // 在表层色surface之上的文本和图标的颜色 onError: Color, // 在错误色error之上的文本和图标的颜色 isLight: Boolean // 是否是浅色模式 ) 更多的可以查看 lightColorScheme 函数。 排版 Immutable class Typography internal constructor( val h1: TextStyle, val h2: TextStyle, val h3: TextStyle, val h4: TextStyle, val h5: TextStyle, val h6: TextStyle, val subtitle1: TextStyle, val subtitle2: TextStyle, val body1: TextStyle, val body2: TextStyle, val button: TextStyle, val caption: TextStyle, val overline: TextStyle ) 形状 class Shapes( // 小组件使用的形状比如: ButtonSnackBar悬浮按钮等 val small: CornerBasedShape RoundedCornerShape(4.dp), // 中组件使用的形状比如Card(就是CardView)AlertDialog等 val medium: CornerBasedShape RoundedCornerShape(4.dp), // 大组件使用的形状比如ModalDrawer或者ModalBottomSheetLayout(就是抽屉布局和清单布局) val large: CornerBasedShape RoundedCornerShape(0.dp), ) 使用 setContent { PrimaryTheme { Surface( modifier Modifier.fillMaxSize(), color MaterialTheme.color.background ) { Greeting(Android) } } } Composable fun PrimaryTheme( themeType: ThemeType themeTypeState.value, content: Composable () - Unit ) { val shapes Shapes( small RoundedCornerShape(4.dp), medium RoundedCornerShape(8.dp), large RoundedCornerShape(12.dp), ) MaterialTheme( colors getThemeForTheme(themeType), typography Typography, shapes shapes, content content ) } UI SetContent setContent { PrimaryTheme { Surface( modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background ) { Greeting(Android) } } } 同 Android 中的 SetContentView。 Theme 创建项目之后就会生成一个 项目名称Theme 的 Compose 方法我们可以通过更改其中的颜色来完成对主题的修改。具体如上面的主题所示. Modifier Modifier 本质是一个接口可以用来修饰各种布局例如 宽高padding 等常见的如下 padding有四个重载方法 plus将其他的 Modifer 加入到当前的 Modifer 中。 fillMaxHeightfillMaxWidthfillmaxSize类似于 match_parent填充整个父 Layout withheightsize 设置宽高度 rtlltr开始布局的方向 widthInheightInsizeIn 设置布局的宽度和高度的最大值和最小值 gravity元素的位置 等等 需要注意的是 Modifier 系列的方法都支持链式调用 ColumnRow 类似于 LinearLayoutColumn 是横向的Row 是竖向的。有四个参数 Modifer 具体值如上述所示 verticalArrangement子元素竖向的排列规则 常见的就是上下左右中比较特殊的就是 SpaceEvenly 均匀分配 SpaceBetween 第一个元素前和最后一个元素后没有空隙其他的按比例放入。 SpaceAround 把整体中的一半空隙凭据放入第一个和最后一个的开始和结束剩余的一半等比放入各个元素。 horizontalAlignment和上面一个只不过方向不同 content要显示的内容 栗子Composable () - Unit setContent { PrimaryTheme { Surface( modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background ) { Column { Row { Button( onClick { themeTypeState.value ThemeType.RED_THEME }, modifier Modifier.width(100.dp), colors ButtonDefaults.buttonColors(containerColor Color.Yellow) ) { Greeting(name 按钮1) } Button( onClick { themeTypeState.value ThemeType.GREEN_THEME }, modifier Modifier.width(100.dp), colors ButtonDefaults.elevatedButtonColors() ) { Greeting(name 按钮2) } } Greeting(name Hello Android) Greeting(name Hello 345) } } } } Text fun Text( text: String, //显示内容 modifier: Modifier Modifier, //修饰可修改透明度边框背景等 color: Color Color.Unspecified, //文字颜色 fontSize: TextUnit TextUnit.Unspecified,// size fontStyle: FontStyle? null, //文字样式粗体斜体等 fontWeight: FontWeight? null,//文字厚度 fontFamily: FontFamily? null,//字体 letterSpacing: TextUnit TextUnit.Unspecified, //用于与文本相关的维度值的单位。该组件还在测试中 textDecoration: TextDecoration? null,//文字装饰中划线下划线 textAlign: TextAlign? null,对齐方式 lineHeight: TextUnit TextUnit.Unspecified,//行高 overflow: TextOverflow TextOverflow.Clip,//如何处理溢出默认裁切 softWrap: Boolean true,//是否软换行 maxLines: Int Int.MAX_VALUE,//最大行数 onTextLayout: (TextLayoutResult) - Unit {},//计算布局时回调 style: TextStyle LocalTextStyle.current //文本的样式配置如颜色、字体、行高等。 ) modifier在此处用来修饰 TextModifer 提供了很多扩展如透明度背景边框等 示例 Composable fun Greeting(name: String) { Text( text name, fontSize 18.sp, fontWeight FontWeight.Medium, color MaterialTheme.colorScheme.primary, modifier Modifier.height(30.dp) ) } Button fun Button( onClick: () - Unit,//点击时调用 modifier: Modifier Modifier,//同上 enabled: Boolean true,//是否启用 elevation: ButtonElevation? ButtonDefaults.buttonElevation(),// z轴上的高度 shape: Shape FilledButtonTokens.ContainerShape.toShape(), border: BorderStroke? null, colors: ButtonColors ButtonDefaults.buttonColors(), contentPadding: PaddingValues ButtonDefaults.ContentPadding, content: Composable RowScope.() - Unit ) shape 调整 button 的样式例如 RoundedCornerShape 是圆角矩形的样式CircleShape 是圆形的样式CutCornerShape 是切角样式 border 外边框默认是 nullBorder 有两种使用方式1 Border(size: Dp, color: Color),2 Border(size: Dp, brush: Brush) 。 第二种需要自己创建一个笔刷去绘制外边框例如要实现渐变的外边框。 colors 按钮的颜色默认是 ButtonDefaults.buttonColors() 。可选的有 其中可以设置按钮的背景色未启用的颜色等。 栗子 Button( onClick { themeTypeState.value ThemeType.GREEN_THEME }, modifier Modifier.width(100.dp), colors ButtonDefaults.buttonColors( containerColor Color.Yellow, contentColor Color.Red, disabledContainerColor Color.Black, disabledContentColor Color.Green ) ) { Greeting(name 按钮2) } OutLinedButton 具有外边框的按钮内部使用的也是 Button。默认会有一个边框其参数和 Button 一致效果如下 TextButton 默认的 button 在有主题的时候默认背景是主题颜色而 textButton 背景默认是透明的。TextButton 默认使用的颜色是 ButtonDefaults.textButtonColors() Image Composable fun Image( painter: Painter, bitmap: ImageBitmap, // contentDescription: String?, modifier: Modifier Modifier, alignment: Alignment Alignment.Center, contentScale: ContentScale ContentScale.Fit, alpha: Float DefaultAlpha, colorFilter: ColorFilter? null ) painter图片资源使用 PainterResource 来完成。 contentDescription无障碍提示文本信息 contentScale 类似于 ImageView 中的 scaleType 属性。 colorFilter将某种颜色应用到图片上 alpha不透明度 示例 Composable Preview fun Image() { Image( painter painterResource(id R.drawable.one), contentDescription 无障碍提示, contentScale ContentScale.Crop, modifier Modifier .width(100.dp) .height(100.dp) ) } 像一些圆图或者边框啥的就可以在 modifer 中直接设置了如下 Composable Preview fun Image() { Image( painter painterResource(id R.drawable.one), contentDescription 无障碍提示, contentScale ContentScale.Crop, modifier Modifier .width(100.dp) .height(100.dp) .clip(shape CircleShape) .border(2.dp, color Color.Red, shape CircleShape) ) } 加载网路图片 加载网路图片需要借助第三方库 coil使用方式如下 //图片加载库 implementation(io.coil-kt:coil:2.0.0) implementation(io.coil-kt:coil-compose:2.0.0) Composable Preview fun Image() { AsyncImage( model https://img0.baidu.com/it/u3147375221,1813079756fm253fmtautoapp120fJPEG?w500h836, contentDescription 无障碍提示, contentScale ContentScale.Crop, modifier Modifier .width(100.dp) .height(100.dp) .clip(shape CircleShape) .border(2.dp, color Color.Red, shape CircleShape) ) } Spacer 和原生的一样需要空白区域时可以使用 Spacer 使用方式如下 Spacer(modifier Modifier.height(100.dp)) Surface 对内容进行装饰例如设置背景shape 等 fun Surface( modifier: Modifier Modifier, shape: Shape Shapes.None, color: Color MaterialTheme.colorScheme.surface, contentColor: Color contentColorFor(color), tonalElevation: Dp 0.dp, shadowElevation: Dp 0.dp, border: BorderStroke? null, content: Composable () - Unit ) color 设置 Surface 的背景色默认是主题中的 surface 颜色。 contentColor此 Surface 为其子级提供的首选内容颜色。默认为 [color] 的匹配内容颜色或者如果 [color] 不是来自主题的颜色这将保持在此 Surface 上方设置的相同值。 tonalElevation当 [color] 为 [ColorScheme.surface] 时高程越高浅色主题颜色越深深色主题颜色越浅。 shadowElevation阴影大小 Scaffold 脚手架的意思和 Flutter 中的 Scaffold 是一样的通过 Scaffold 我看可以快速的对页面进行布局例如设置导航栏侧滑栏底部导航等等。 fun Scaffold( modifier: Modifier Modifier, topBar: Composable () - Unit {}, bottomBar: Composable () - Unit {}, snackbarHost: Composable () - Unit {}, floatingActionButton: Composable () - Unit {}, floatingActionButtonPosition: FabPosition FabPosition.End, containerColor: Color MaterialTheme.colorScheme.background, contentColor: Color contentColorFor(containerColor), content: Composable (PaddingValues) - Unit ) topBarToolbar常用的有 CenterAlignedTopAppBar,SmallTopAppBarMediumTopAppBar 等。 bootomBar底部导航栏 snackbarHost floatingActionButton按钮 floatingActionButtonPosition按钮位置 containerColor背景颜色 contentColor内容首选颜色 看一个栗子 Scaffold( topBar { //..... }, bottomBar bottomBar, ) { Box( modifier Modifier .fillMaxSize() .padding (top it.calculateTopPadding(), bottom it.calculateBottomPadding()) ) { content.invoke(it) } } 需要注意的是如果使用了 toolbar 或者 bootomBar就会把 content 中的内容挡住这个时候就需要使用 PaddingValue 设置内边距了。 还有一点须要注意如果要使用沉浸式状态栏就需要自定义 topBar 了要不然状态栏会被 topBar 覆盖。下面代码是设置沉浸式状态栏的。 ///系统 UI 控制器 implementation com.google.accompanist:accompanist-systemuicontroller:0.24.8-beta //正确获取状态栏高度 api com.google.accompanist:accompanist-insets-ui:0.24.8-beta override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { SetImmersion() PrimaryTheme { SetContent() } } } Composable private fun SetImmersion() { if (isImmersion()) { val systemUiController rememberSystemUiController() SideEffect { systemUiController.run { setSystemBarsColor(color Color.Transparent, darkIcons isDark()) setNavigationBarColor(color Color.Black) } } } } 底部导航栏 Composable fun MainCompose(navController: NavHostController, mainBottomState: MutableStateInt) { SetScaffold( bottomBar { BottomBar(mainBottomState) } ) { when (mainBottomState.value) { 0 - HomeCompos(navController) 1 - ProjectCompos() 2 - FLCompos() else - UserCompos() } } } Composable private fun BottomBar(mainBottomState: MutableStateInt) { BottomNavigation( backgroundColor MaterialTheme.colors.background, ) { navigationItems.forEachIndexed { index, navigationItem - BottomNavigationItem( selected mainBottomState.value index, onClick { mainBottomState.value index }, icon { Icon( imageVector navigationItem.icon, contentDescription navigationItem.name ) }, label { BottomText( isSelect mainBottomState.value index, name navigationItem.name ) }, selectedContentColor Color.White, unselectedContentColor Color.Black ) } } } Composable fun BottomText(isSelect: Boolean, name: String) { if (isSelect) { Text( text name, color MaterialTheme.colors.primary, fontSize 12.sp ) } else { Text( text name, color Color.Black, fontSize 12.sp ) } } Android | Compose 初上手_android compose-CSDN博客 Compose 和 Android 传统View 互相调用 1. 前言 Compose 具有超强的兼容性兼容现有的所有代码Compose 能够与现有 View 体系并存可实现渐进式替换。这就很有意义了我们可以在现有项目中一小块一小块逐步地替换Compose或者在旧项目中实现新的需求的时候使用Compose。 今天我们就来演示一下Compose和Android View怎么互相调用以及在双层嵌套(原生View嵌套ComposeCompose中又嵌套原生View)的情况下在最外层原生View中怎么获取到Compose内部的原生View。 2. Android 传统 View 调用 Compose 2.1 新建传统View体系的Android项目 新建项目的时候选择 Empty Activity 2.2 项目添加Compose配置 2.2.1 在android代码块添加 在app的build.config android代码块中添加 buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion 1.1.1 //对应Kotlin版本1.6.10 } 注意这里的kotlinCompilerExtensionVersion和Kotlin的版本对应 kotlin版本 : 1.6.10 对应 kotlinCompilerExtensionVersion 1.1.1 kotlin版本 : 1.7.0 对应 kotlinCompilerExtensionVersion 1.2.0 kotlin版本 : 1.7.20 对应 kotlinCompilerExtensionVersion 1.3.2 kotlin版本 : 1.8.10 对应 kotlinCompilerExtensionVersion 1.4.3 kotlin版本 : 1.9.10 对应 kotlinCompilerExtensionVersion 1.5.3 2.2.2 在dependencies中添加依赖 在app的build.config dependencies代码块中添加 dependencies { //...省略... def compose_ui_version 1.1.1 implementation androidx.compose.ui:ui:$compose_ui_version implementation androidx.compose.ui:ui-tooling-preview:$compose_ui_version androidTestImplementation androidx.compose.ui:ui-test-junit4:$compose_ui_version debugImplementation androidx.compose.ui:ui-tooling:$compose_ui_version debugImplementation androidx.compose.ui:ui-test-manifest:$compose_ui_version implementation androidx.activity:activity-compose:1.3.1 implementation androidx.compose.material:material:1.1.1 } Compose 最新的版本可以使用 物料清单 (BoM) 来进行依赖这样会更方便 比如 implementation platform(androidx.compose:compose-bom:2023.01.00) 2.3 定义Compose函数 在MainActivity.kt中定义Compose函数 Composable fun ComposeContent() { Box( modifier Modifier.fillMaxSize(), contentAlignment Alignment.Center ) { Text(text Hello world!) } } 2.4 修改xml文件 在activity_main.xml中添加androidx.compose.ui.platform.ComposeView 2.5 关联Compose函数 在MainActivity.kt中先通过findViewById找到ComposeView然后通过composeView.setContent将Android 传统View和Compose建立关联。 2.6 运行项目 可以发现界面显示如下成功在传统View项目中调用了Compose了 3. Compose中调用Android View 3.1 调用传统View的日历 3.1.1 使用AndroidView 在Composable内使用: androidx.compose.ui.viewinterop.AndroidView然后在factory里面返回原生View即可 3.1.2 显示效果如下 3.2 调用传统View的WebView 3.2.1 添加网络权限 首先需要在AndroidManifest.xml中添加网络权限 3.2.2 首先要注册WebView的生命周期 3.2.3 创建有状态的WebView 创建有状态的WebView并注册生命周期 3.2.4 调用Android View 3.2.5 显示效果如下所示 4. 双层嵌套获取AndroidView中的原生View id 有时候我们会遇到这种情况就是在原生项目了页面中有部分使用了Compose然后在Compose中又有部分组件使用了原生View这种情况下要如何取到AndroidView中的原生View id 呢 ? 4.1 在定义Xml中定义ComposeView 4.2 关联Compose函数 在MainActivity.kt中先通过findViewById找到ComposeView然后通过composeView.setContent将Android 传统View和Compose建立关联。 4.3 创建ids.xml定义原生view id 在resources/values目录下创建ids.xml 4.4 实现ComposeContent 4.5 在外层的原生代码处获取Compose中的原生View 在原生代码的地方通过composeView.findViewById查找id为my_calendar_view的原生View 注意这里的window?.decorView?.post : 必须在页面加载完成后才能查找到my_calendar_view对应的原生View如果直接在onCreate里面去查找会发现composeView.findViewByIdCalendarView(calendarViewId)返回的是null 4.6 运行项目 选择任意一个日期可以发现弹出的toast是!!!! year年month月day日即原生的setOnDateChangeListener覆盖了Compose中的setOnDateChangeListener监听这样说明我们也在原生代码处取到了Compose内部的原生View了。 Compose 和 Android 传统View 互相调用_compose androidview-CSDN博客 Android Jetpack Android Jetpack 是一套用于简化 Android 应用开发的库、工具和指南集合。它提供了一种组件化的架构方式帮助开发者更轻松地构建稳定、高效和可扩展的 Android 应用。 Android Jetpack 包含多个组件可以根据不同的需求选择使用其中一些核心组件包括 LiveData一种可感知生命周期的数据持有类可供应用程序的不同组件之间共享数据。ViewModel用于存储和管理与界面相关的数据以支持应用程序配置更改时的数据持久性。Room提供了一个抽象层使得在 SQLite 数据库上进行更方便的访问和操作。Navigation用于实现应用内导航的组件简化了界面之间的跳转和传递数据的操作。WorkManager用于处理后台任务如延迟执行、周期性执行和网络连接状态变化后执行。Paging用于加载和显示大型数据集的分页库可以实现无限滚动列表等功能。Data Binding用于在布局文件中绑定UI元素和数据。WorkManager用于管理后台任务和调度。Security用于加密和解密数据以及处理其他与安全相关的任务。CameraX用于简化相机应用程序的开发。Hilt用于依赖注入。 除了这些核心组件外Jetpack 还包括其他组件如安全性组件、应用启动优化组件、测试组件等可以根据项目的需要选择使用。 以下是一个简单示例展示了如何在Android应用程序中使用Hilt进行依赖注入 首先在您的项目的build.gradle文件中添加Hilt依赖 接下来在您的Application类上添加HiltAndroidApp注解这将告诉Hilt这是您的应用程序的入口点 然后在您的依赖关系图Dependency Graph中您可以使用注解来标记依赖项的创建和提供方式。例如假设您有一个名为MyRepository的类它需要一个MyApiService实例作为依赖项 请注意Inject注解表示MyApiService是MyRepository的依赖项。 然后您需要在依赖关系图中提供MyApiService的实例。您可以使用Provides注解来完成这个任务。例如 在这个示例中Provides注解表示提供了MyApiService的实例。 最后在使用MyRepository的地方您可以使用Inject注解来自动注入依赖项。例如在Activity中 现在当您使用MainActivity时MyRepository的实例将由Hilt自动注入。 请注意为了使Hilt能够工作您还需要在应用程序的组件如Activity、Fragment等上添加AndroidEntryPoint注解。例如在MainActivity上 ·  Android官方架构组件Lifecycle生命周期组件详解原理分析 ·  Android官方架构组件ViewModel:从前世今生到追本溯源 ·  Android官方架构组件LiveData: 观察者模式领域二三事 ·  Android官方架构组件Paging分页库的设计美学 ·  Android官方架构组件Paging-Ex为分页列表添加Header和Footer ·  Android官方架构组件Paging-Ex列表状态的响应式管理 ·  Android官方架构组件Navigation大巧不工的Fragment管理框架 ·  Android官方架构组件DataBinding-Ex:双向绑定篇
http://www.sczhlp.com/news/161876/

相关文章:

  • 电商网名大全wordpress主题 SEO优化
  • 沈阳做网站公司哪家好交通网站建设
  • p2c网站方案nas wordpress备案
  • 网站建设的提升广东建设注册中心网站
  • 高碑店住房和城乡建设局网站友链交换网站源码
  • 创立制作网站公司现在建设一个网站多少钱
  • 图文可以做网站设计吗深度网络
  • ps怎么做网站首页和超链接成都优化官网推广
  • html如何建网站浙江建设特种证书查询
  • 为中小型企业构建网站做网页赚钱的网站
  • 男女做床网站下载好字体怎么导入wordpress
  • 网站外链资源找做cad彩拼的网站
  • 盈利型网站做销售最挣钱的10个行业
  • 浙江省建设建材工会网站wordpress引用jquery
  • 衡水做网站哪家好网站制作哪家好又便宜
  • iis配网站施工企业自营率怎么算
  • 网站页面图片布局如何设计建立网站模板
  • 电商网站规划论文百度认证有什么用
  • 网站美化的目标妇科医院网站建设
  • 成都个人网站制作开通网站费可以做待摊费用吗
  • 贵阳市乌当区住房与城乡建设局网站烟台微信公众号开发
  • 余姚网站建设 熊掌号自己制作上传图片的网站怎么做
  • 珠海网站建设工程食品电子商务网站建设规划书
  • 企业展示网站建设怎么把网页放到网站上
  • 网站建设公司的市场开发方案seo网络优化前景怎么样
  • 静态网页设计网站制作在putty做网站要拷贝什么
  • 大型门户网站源码电子商务网站建设实训心得体会
  • 邳州哪家做百度推广网站深圳建网站哪个公
  • 免费国外建站免费学平面设计的网站
  • 南海网站建设哪家好seo怎么优化一个网站