Compose:自定义 Composable

什么是 Composable

当我们在写 Compose 函数时,如果没有添加 @Composable 注解,IDE 将会提醒报错:

在这里插入图片描述

为什么要加 @Composable 注解才能用 Compose 组件?

其实是因为 在编译过程中这些组件会被 Compose 的编译器插件(Compiler Plugin)添加修改一些参数,调用的时候也是使用被修改参数后的函数

添加的参数准确的说是一个叫 Composer 类型的参数,因为它是 Composable 的必要参数,通过注解的方式在编译时就加上,开发者也能很方便不用手动加上。

这种方式就是典型的面向切面编程(AOP)。在我们熟知的 AOP 方案主要有两种:

  • Annotation Processor 注解处理器

  • 修改字节码比如 ASM

而 Compose 用的是更彻底的一种方式,通过编译器插件的方式直接干预编译过程,这样做的好处主要有几点:

  • 直接干预编译过程能做更多的事情,功能也比注解处理器这些强

  • Compose 是跨平台的,注解处理器和修改字节码的方案只能用在 JVM,无法用于跨平台

怎么 让编译器插件准确的识别到哪些函数是 Compose 组件的函数,就是用的 @Composable 注解,@Composable 就是识别符的作用

所以对于为什么要加上 @Composable 注解的原因,就是 Compose 编译器插件要识别到注解的函数,从而在编译期间修改添加上参数,规定作为 Compose 组件的函数以及调用了 Compose 组件函数的函数都要加上 @Composable 注解

那或许又有疑问了:这些组件函数都要加上 @Composable 注解,调用到的地方也要加上,但总会有一个函数最终是要调用到的,而且没有加注解,是哪一个呢?

最终它会在 Composer.kt 的 invokeComposable() 函数调用:

// 第一个参数是 Composer
internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) {
	@Suppress("UNCHECKED_CAST")
	// 直接进行强转
	val realFn = composable as Function2<Composer, Int, Unit>
	realFn(composer, 1)
}

可以看到上面非常简单的直接将函数类型参数 composable 做强制转型,这样其实是可以的。
Compose 之所以限制我们在非 Composable 函数里调用 Composable 函数,主要是它没办法在编译过程中自动的识别到,从而就不能自动的加上额外的参数,最终导致程序没法正常运行

也可以看到 invokeComposable() 函数是加上了 Composer 类型的参数的,是官方规定可以调用的函数,所以可以这么用。

Compose 的做法类似于 suspend 关键字都是通过编译器,但不同的地方是,suspend 是加进 Kotlin 编译器里面,而 Composable 用的外挂的方式实现的。至于它为什么没有演变成像 suspend 加到编译器里面,只是因为 Compose 不是 Kotlin 语言的功能,而是一个基于 Kotlin 的 UI 框架,所以不适合加到 Kotlin 编译器里面

为了能够更好的表示 “加了 @Composable 注解的函数“,Google 将这类函数叫做 Composable,或者 Composable 函数。

自定义 Composable

简单理解自定义 Composable 就是自己写 Composable 函数,一般我们写 Composable 函数开头字母都是大写,这样能更方便我们开发时一眼就能看出来它是一个 Composable。

或许你会认为能自定义 Composable 函数,那就可以随意写了,例如这样:

class MainActivity : ComponentActivity {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContent {
			Column {
				CustomComposable()
			}
			Row {
				CustomComposable()
			}
			Box {
				CustomComposable()
			}
		}
	}
}

// CustomComposable 这个 Composable 不可掌控不实用,会被外部 Composable 调整
@Composable
private fun CustomComposable() {
	Text("name")
	Image(painterResource(R.drawable.ic_launcher_background), "Avatar")
}

虽然按上面的写法不会报错,但是这样的 Composable 是很不实用的,Composable 的位置不可掌控,这就类似传统 UI 使用 merge 标签:

// 由外部决定摆放位置,一般开发基本不会用到这种处理方式
<merge >
	<ImageView />
	<Button />
</merge>

正确自定义 Composable 的方式是,在一个 Composable 函数内只能调用一个别的 Composable,这样是为了方便状态控制等处理

@Composable 
private fun CustomComposable() {
	// 只有一个 Column,方便状态控制
    Column {
        Text("text")
        Image(painterResource(R.drawable.ic_launcher_background), "Avatar")
    }
}

那什么场景该使用自定义 Composable?和传统 UI 相比是自定义 View 吗?

class CustomTestView(context: Context?, attrs: Attrubites) : LinearLayout(context, attrs) {
	private val icon by lazy { ImageView(context) }
	private val text by lazy { TextView(context) }
	
	init {
		orientation = VERTICAL
		...
		addView(icon)
		addView(text)
	}
}

你认为我们写布局的时候会像上面的方式那么写吗?自定义 View 里面再创建子控件和设置属性等等。你确实可以这么写,不过我们更习惯的方式是用 xml 来写上面的布局吧:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemes.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:text="name" />

	<ImageView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />
</LinearLayout>

对比可以发现,自定义 Composable 的写法更像是传统 UI 中 xml 的写法。那自定义 Composable 就是 xml 写法?也不是。我们把自定义 Composable 再改改:

class MainActivity : ComponentActivity {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContent {
			Box {
				CustomComposable("Android")
				CustomComposable("kotlin")
			}
		}
	}
}

@Composable
private fun CustomComposable(name: String) {
	// name 如果相同,remember 内的代码块不会重复执行直接返回上次的结果,性能比较好
    val calculatedName = remember(name) {
        name + if (name.length > 5) {
            ": long name"
        } else {
            ": short name"
        }
    }
    Column {
        Text(calculatedName)
        Image(painterResource(R.drawable.ic_launcher_background), "Avatar")
    }
}

现在再看看上面的 Composable 里面加了逻辑,xml 能加逻辑吗?其实是不能的,在 xml 不能实现任何逻辑处理的,它是一个标记语言,所以是没法实现想自定义 Composable 能处理逻辑的效果的。

实际上 自定义 Composable 相比于传统 UI 的等价物就是 xml 布局 + 自定义 View,xml 只能布局简单直观,而自定义 View 是能处理逻辑的,所以二者的结合才是自定义 Composable。

所以 自定义 Composable 的使用场景就是遇到任何需要对界面进行定制的需求,我们就可以使用它。当然还有绘制、布局、触摸反馈这类高级定制

总结

什么是 Composable?

Composable 就是带 @Composable 注解的函数,@Composable 注解是用于编译器识别。

  • 所有加了 @Composable 注解的函数,都需要在别的加了 @Composable 的函数里面才能调用

  • 所有内部没调用任何其他 @Composable 函数的函数,都没必要加上这个注解,因为没意义

除了有特殊需求,否则一个 Composable 里面只能调用一个别的 Composable,这样是为了方便状态控制等处理。

什么时候自定义 Composable?

自定义 Composable 和传统的写法相比,类似于 xml 布局 + 自定义 View 的方式(自定义 View 并不是一定要自定义测量布局绘制,也可以是添加基础逻辑处理)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值