Android开发:Jetpack Compose编程知识全汇总 (含详细实例讲解)

Column和Row可以理解为在View/Layout体系中的纵向和横向的ViewGroup

Column(

verticalArrangement:Arrangement // 控制纵向布局关系

horizontalAlignment:Alignment // 控制横向对齐关系

)

Row(

horizontalArrangement:Alignment // 控制横向布局关系

verticalAlignment:Arrangement // 控制纵向对齐关系

)

@Composable

fun TestColumnRow() {

Column(

modifier = Modifier.fillMaxHeight().background(Color.Yellow),

verticalArrangement = Arrangement.SpaceBetween,

horizontalAlignment = Alignment.Start

) {

Text(text = “java”)

Text(text = “android”)

Text(text = “python”)

}

Row(

modifier = Modifier.fillMaxWidth().background(Color.LightGray),

verticalAlignment = Alignment.Top,

horizontalArrangement = Arrangement.SpaceBetween

) {

Text(text = “java”)

Text(text = “android”)

Text(text = “python”)

}

}

四、进阶使用

状态管理

所有 Android 应用都有核心界面更新循环,如下所示:

Compose 专为单向数据流而打造。这是一种状态向下流动而事件向上流动的设计。

使用单向数据流的应用的界面更新循环如下所示:

事件:事件由界面的一部分生成并且向上传递。

更新状态:事件处理脚本可以更改状态。

显示状态:状态会向下传递,界面会观察新状态并显示该状态。

举两个例子展示:

//内部状态管理

@Composable

fun CounterInner() {

val count = remember { mutableStateOf(0) }

Button(onClick = { count.value += 1 })

{

Text(text = “Count: ${count.value}”)

}

}

解释一下上图的数据流情况

事件:当点击发生时候,会触发count.value

更新状态:mutableStateOf会进行处理,然后设置count的状态

显示状态:系统会调用count的观察器,并且界面会显示新状态

//支持其他可观察类型的状态管理

class CountViewModel : ViewModel() {

// LiveData holds state which is observed by the UI

// (state flows down from ViewModel)

private val _count = MutableLiveData(0)

val count: LiveData = _count

// onNameChanged is an event we’re defining that the UI can invoke

// (events flow up from UI)

fun onCountChanged(newCount: Int) {

_count.value = newCount

}

}

@Composable

fun Counter(countViewModel: CountViewModel = viewModel()) {

val observeAsState = countViewModel.count.observeAsState(0)

val count = observeAsState.value

Button(

colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),

onClick = { countViewModel.onCountChanged(count + 1) },

) {

Text(text = “I’ve been clicked $count times”)

}

}

解释一下上图的数据流情况

事件:当点击发生时候,会触发onCountChanged

更新状态:onCountChanged会进行处理,然后设置_count的状态

显示状态:系统会调用count的观察器,并且界面会显示新状态

状态提升
  • 无状态可组合项是指本身无法改变任何状态的可组合项。无状态组件更容易测试、发生的错误往往更少,并且更有可能重复使用。

  • 如果您的可组合项有状态,您可以通过使用状态提升使其变为无状态。

  • 状态提升是一种编程模式,在这种模式下,通过将可组合项中的内部状态替换为参数和事件,将状态移至可组合项的调用方。

  • 状态提升的过程可让您将单向数据流扩展到无状态可组合项。在这些可组合项的单向数据流示意图中,随着更多可组合项与状态交互,状态仍向下流动,而事件向上流动。

@Composable

fun Counter(countViewModel: CountViewModel = viewModel()) {

val observeAsState = countViewModel.count.observeAsState(0)

val count = observeAsState.value

ButtonCount(count = count, onCountChanged = { countViewModel.onCountChanged(it) })

}

@Composable

fun ButtonCount(

/* state */ count: Int,

/* event */ onCountChanged: (Int) -> Unit ) {

Button(

colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),

onClick = { onCountChanged(count + 1) },

) {

Text(text = “I’ve been clicked $count times”)

}

}

互操作
Android View中的Compose

如果想使用Compose的情况下,又不想迁移整个应用,可以在xml里面增加ComposeView,类似于占位符,然后在Actviity/fragment中寻找该控件并调用setContent方法即可,在该方法中即可使用compose相关属性

<androidx.constraintlayout.widget.ConstraintLayout

xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

tools:context=“.AndroidViewComposeActivity”>

<TextView

android:id=“@+id/hello_world”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Hello Android!”

app:layout_constraintTop_toTopOf=“parent” />

<androidx.compose.ui.platform.ComposeView

android:id=“@+id/compose_view_text”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

app:layout_constraintTop_toBottomOf=“@id/hello_world” />

<androidx.compose.ui.platform.ComposeView

android:id=“@+id/compose_view_img”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

app:layout_constraintTop_toBottomOf=“@id/compose_view_text” />

</androidx.constraintlayout.widget.ConstraintLayout>

class AndroidViewComposeActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_android_view_compose)

findViewById(R.id.compose_view_text).setContent {

MaterialTheme {

Text(“Hello Compose!”)

}

}

findViewById(R.id.compose_view_img).setContent {

val imageResource = imageResource(id = R.drawable.header)

val imageModifier = Modifier.preferredHeight(180.dp)

.fillMaxWidth()

.padding(16.dp)

.clip(RoundedCornerShape(4.dp))

MaterialTheme {

Image(

bitmap = imageResource,

modifier = imageModifier,

contentScale = ContentScale.Crop

)

}

}

}

}

Compose中的Android View

如果碰到在Compose环境中,想要使用Android的View视图的情况,只需要使用AndroidView函数即可

@Composable

fun CustomView() {

val selectedItem = remember { mutableStateOf(0) }

val context = AmbientContext.current

val customView = remember {

// Creates custom view

Button(context).apply {

// Sets up listeners for View -> Compose communication

setOnClickListener {

selectedItem.value += 1

}

}

}

// Adds view to Compose

AndroidView({ customView }) {

view ->

// View’s been inflated - add logic here if necessary

// As selectedItem is read here, AndroidView will recompose

// whenever the state changes

// Example of Compose -> View communication

view.text = selectedItem.value.toString()

}

}

如果是需要使用xml的配置情况,也使用AndroidView函数即可

@Composable

fun CustomView2() {

val context = AmbientContext.current

val customView = remember {

// Creates custom view

View.inflate(context, R.layout.layout_custom_view, null)

}

AndroidView({ customView })

}

与通用库集成
ViewModel

从源码可看出,viewmodel函数底层也是通过ViewModelProvider进行获取的

@Composable

fun viewModel(

modelClass: Class,

key: String? = null,

factory: ViewModelProvider.Factory? = null )
VM = AmbientViewModelStoreOwner.current.get(modelClass, key, factory)
数据流

Compose也是适配Android主流的基于流的方案,如

  • LiveData.observeAsState()

  • Flow.collectAsState()

  • Observable.subscribeAsState()

在Compose中,LiveData.observeAsState()获取的State对象赋值给Text

@Composable

fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {

// by default, viewModel() follows the Lifecycle as the Activity or Fragment

// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.

// name is the current value of [helloViewModel.name]

// with an initial value of “”

val observeAsState = helloViewModel.name.observeAsState(“”)

Column {

Text(text = observeAsState.value)

TextField(

value = observeAsState.value,

onValueChange = { helloViewModel.onNameChanged(it) },

label = { Text(“Name”) }

)

}

}

异步操作

此处需要补充说明的是Compose的生命周期

Compose通过一系列Effect方法,实现生命周期函数

| Compose生命周期 | 说明 | 对应React |

| — | — | — |

| onActive | compose函数第一次被渲染到画面 | componentWillMount componentDidMount |

| onDispose | compose函数从画面上移除 | componentWillUnmount |

| onCommit | compose函数每次执行,画面重新渲染 | componentDidUpdate |

所以onCommit函数的使用类似于React的useEffect,支持可观察函数

@Suppress(“ComposableNaming”)

@Composable

/inline/

fun </reified/ V1> onCommit(

v1: V1,

/noinline/

callback: CommitScope.() -> Unit ) {

remember(v1) { PreCommitScopeImpl(callback) }

}

仅当v1发生变化时onCommit才会执行

举个例子使用异步操作

@Composable fun fetchImage(url: String): ImageAsset? {

// Holds our current image, and will be updated by the onCommit lambda below

var image by remember(url) { mutableStateOf<ImageAsset?>(null) }

onCommit(url) {

// This onCommit lambda will be invoked every time url changes

val listener = object : ExampleImageLoader.Listener() {

override fun onSuccess(bitmap: Bitmap) {

// When the image successfully loads, update our image state

image = bitmap.asImageAsset()

}

}

// Now execute the image loader

val imageLoader = ExampleImageLoader.get()

imageLoader.load(url).into(listener)

onDispose {

// If we leave composition, cancel any pending requests

imageLoader.cancel(listener)

}

}

// Return the state-backed image property. Any callers of this function

// will be recomposed once the image finishes loading

return image

}

五、原理解析

因为代码是基于Kotlin注解动态生成的,查看方法可以先build一个apk,然后查看其中的classess.dex文件,使用dex2jar转为jar包,然后使用jd-gui进行查看,下图是反编译得到的源码

//CountActivityKt.class->CountActivity->CounterInner(Composer,int):void

public static final void CounterInner(Composer<?> paramComposer, int paramInt) {

paramComposer.startRestartGroup(-908461591,“C(CounterInner)47@2322L30,48@2374L20,48@2357L91:CountActivity.kt#ffoge4”);

if (paramInt != 0 || !paramComposer.getSkipping()) {

paramComposer.startReplaceableGroup(-3687207, “C(remember):Remember.kt#9igjgp”);

Object object = paramComposer.nextSlot();

if (object == SlotTableKt.getEMPTY()) {

object = MutableStateKt.mutableStateOf d e f a u l t ( I n t e g e r . v a l u e O f ( L i v e L i t e r a l s default(Integer.valueOf(LiveLiterals default(Integer.valueOf(LiveLiteralsCountActivityKt.INSTANCE.Int a r g − 0 arg-0 arg0call-mutableStateOf f u n − fun- funanonymous$ a r g − 0 arg-0 arg0call-remember v a l − c o u n t val-count valcountfun-CounterInner()), null, 2, null); paramComposer.updateValue(object);

}

paramComposer.endReplaceableGroup();

MutableState mutableState = (MutableState)object; paramComposer.startReplaceableGroup(-3686846, “C(remember)P(1):Remember.kt#9igjgp”);

boolean bool = paramComposer.changed(mutableState);

object = paramComposer.nextSlot();

if (object == SlotTableKt.getEMPTY() || bool) {

object = new CountActivityKt$CounterInner$1$1(mutableState);

paramComposer.updateValue(object);

}

paramComposer.endReplaceableGroup();

ButtonKt

.Button((Function0)object, null, false, null, null, null, null, null, null,(Function3)ComposableLambdaKt.composableLambda(paramComposer, -819892270, true, “C49@2406L36:CountActivity.kt#ffoge4”, new CountActivityKt$CounterInner$2(mutableState)), paramComposer, 805306368, 510);

} else {

paramComposer.skipToGroupEnd();

}

ScopeUpdateScope scopeUpdateScope = paramComposer.endRestartGroup();

if (scopeUpdateScope == null)

return;

scopeUpdateScope.updateScope(new CountActivityKt$CounterInner$3(paramInt));

}

仔细查看源码可知

  1. Composeable Annotation:

  2. 当编译器看到Composeable注解时,会插入额外的参数和函数调用等模板代码,

  3. 其中头部会加入startRestartGroup,尾部会加入endRestartGroup,中部函数部分会加入分组信息(startReplaceableGroup,endReplaceableGroup)

  4. 底层是通过Gap Buffer的方式进行Layoutnode的复用和管理

  5. 位置记忆化:

  6. 执行时候会记忆代码执行顺序及缓存每个节点

  7. 打下分组信息(startReplaceableGroup,endReplaceableGroup),以组别进行更新

  8. 重组:

  9. 获取到可组合函数(State),并与当前方法的Composer对象进行绑定

  10. 将状态保管到Composer内部的槽表中进行管理

  11. 内部的layoutnode复用和管理通过Gap Buffer方式进行

六、其他

客观地讲,Compose 确实是一套比较难学的东西,因为它毕竟太新也太大了,它是一个完整的、全新的框架,确实让很多人感觉学不动,这也是个事实。那怎么办呢?学不动怎么办呢?

如果你是因为缺少学习资料,而我正好薅到这本谷歌内部大佬根据实战编写的《Jetpack Compose最全上手指南》,从入门到精通,教程通俗易懂,实例丰富,既有基础知识,也有进阶技能,能够帮助读者快速入门,是你学习Jetpack Compose的葵花宝典,快收藏起来!!!

第一章 初识 Jetpack Compose

1. 为什么我们需要一个新的UI 工具?

2. Jetpack Compose的着重点

  • 加速开发

  • 强大的UI工具

  • 直观的Kotlin API

3. API 设计

4. Compose API 的原则

  • 一切都是函数

  • 顶层函数(Top-level function)

  • 组合优于继承

  • 信任单一来源

5. 深入了解Compose

  • Core

  • Foundation

  • Material

6. 插槽API

第二章 Jetpack Compose构建Android UI

=

1. Android Jetpack Compose 最全上手指南

  • Jetpack Compose 环境准备和Hello World

  • 使用Material design 设计

  • Compose 布局实时预览

  • ……

2. 深入详解 Jetpack Compose | 优化 UI 构建

  • Compose 所解决的问题

  • Composable 函数剖析

  • 声明式 UI

  • 组合 vs 继承

  • 封装

  • 重组

  • ……

3. 深入详解 Jetpack Compose | 实现原理

  • @Composable 注解意味着什么?

  • 执行模式

  • Positional Memoization (位置记忆化)

  • 存储参数

  • 重组

  • ……

第三章 Jetpack Compose 项目实战演练(附Demo)

1. Jetpack Compose应用1

Android高级架构师

由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。

  • 330页PDF Android学习核心笔记(内含上面8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT部分大厂面试题(有解析)

好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
pack Compose | 实现原理**

  • @Composable 注解意味着什么?

  • 执行模式

  • Positional Memoization (位置记忆化)

  • 存储参数

  • 重组

  • ……

第三章 Jetpack Compose 项目实战演练(附Demo)

1. Jetpack Compose应用1

Android高级架构师

由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。

  • 330页PDF Android学习核心笔记(内含上面8大板块)

[外链图片转存中…(img-eCxSleP0-1714737127137)]

[外链图片转存中…(img-TyhxNnvU-1714737127138)]

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

[外链图片转存中…(img-TKAZC7cH-1714737127140)]

  • Android BAT部分大厂面试题(有解析)

[外链图片转存中…(img-QXgrljux-1714737127141)]

好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值