NativeScript 的 Jetpack Compose 入门指南

通过声明式方式构建用户界面,这在 Web 开发领域已被广泛采用,如今许多大型应用程序都是遵循这些原则开发的。举例来说,谷歌推出了 Jetpack Compose,苹果则在 WWDC19 上发布了 SwiftUI,两者都得到了开发者社区的热烈反响。本文中,我们将演示如何在 NativeScript 中使用 Jetpack Compose,共同探索构建精彩用户界面的新可能性,为基于 NativeScript 构建的 Android 应用提供了一种高效且愉快的 Jetpack Compose 集成方案。

创建 NativeScript 应用

我们可以使用标准的 TypeScript 模板来创建一个应用:

lua 复制代码
ns create jetpackcompose --ts
cd jetpackcompose

这会搭建一个通常被称为"原生风味"(vanilla)的 NativeScript 应用。当然,你也可以选用自己最熟悉的框架。为 Angular(及其他大多数框架)设置插件,通常只需要注册视图即可,我们稍后会在下文中演示。

安装 Jetpack Compose 插件:

bash 复制代码
npm install @nativescript/jetpack-compose

注意: Jetpack Compose 要求你的最低 SDK 版本至少为 API 21 (Lollipop)。你可以在 app.gradle 文件中添加 minSdkVersion 21 来设置。

如果你计划直接在 Android Studio 中构建你的库,那么无需做其他事情,只需将构建好的 .aar 文件放入 App_Resources/Android/libs/ 目录,然后跳转到下一节。但如果你打算直接在 App_Resources/Android/src/main/java 目录下的 .kt 文件中编写 Kotlin 代码,那么还需要额外几步操作。

首先,在 app.gradle 中添加你的 Compose 依赖项:

kotlin 复制代码
dependencies {
    def compose_version = "1.2.1"
    implementation "androidx.compose.ui:ui:$compose_version"
    // 工具支持 (预览等)
    implementation "androidx.compose.ui:ui-tooling:$compose_version"

    // 添加你的 Jetpack Compose UI 所需的其他依赖项
    // 比如 Material Design:
    // implementation 'androidx.compose.material:material:$compose_version'
}

接着,修改 android 部分以启用 Compose:

arduino 复制代码
android {
    // 其他设置,如 targetSdk 等。

    buildFeatures {
        compose true
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
}

最后,通过创建文件 App_Resources/Android/gradle.properties 来启用 Kotlin:

ini 复制代码
useKotlin=true
kotlinVersion=1.7.20 # 你可以在这里选择你的 Kotlin 版本

使用 Jetpack Compose

A. 创建你的 Jetpack Compose 视图和包装器

创建文件 App_Resources/Android/src/main/java/BasicView.kt

kotlin 复制代码
package com.example

import android.content.Context
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

class BasicView {
    fun generateComposeView(view: ComposeView): ComposeView {
        return view.apply {
            setContent {
                MaterialTheme {
                    Text("Hello from Jetpack Compose")
                }
            }
        }
    }

    fun updateData(value: Map<Any, Any>) {
    }
    var onEvent: ((String) -> Unit)? = null

}

若想使用插件默认处理 Compose 视图的方式,你的实现遵循以下接口非常重要:

kotlin 复制代码
class Example {
    fun generateComposeView(view: ComposeView): ComposeView {
      // 将你的 Compose 视图渲染到 ComposeView 中
    }

    fun updateData(value: Map<Any, Any>) {
      // 此函数接收来自 NativeScript 的数据
      // value 是一个转换为 Map 的 JS 对象
    }

    // 这是你将发送回 NativeScript 的事件
    // 需要传递数据时,只需调用 onEvent?.invoke(v)
    var onEvent: ((Any) -> Unit)? = null

}

B. 通过 composeId 注册你的 Jetpack Compose 视图

这一步可以在 NativeScript 应用的启动引导文件中完成(通常是 app.tsmain.ts)。

typescript 复制代码
import { registerJetpackCompose, ComposeDataDriver } from '@nativescript/jetpack-compose';

// A. 你可以使用 'ns typings android --aar {path/to/{name}.aar}' 为你自己的 Compose 提供者生成类型定义
// B. 否则,可以通过声明你知道已提供的包解析路径来忽略此步骤
declare var com;
registerJetpackCompose('sampleView', (view) => new ComposeDataDriver(new com.example.BasicView(), view));

此外,如果你想使用 Angular,可以注册 Compose 视图本身:

typescript 复制代码
import { registerElement } from '@nativescript/angular';
import { JetpackCompose } from '@nativescript/jetpack-compose';

registerElement('JetpackCompose', () => JetpackCompose)

C. 插入到任意 NativeScript 布局中

app/main-page.xml

xml 复制代码
<Page
  xmlns="http://schemas.nativescript.org/tns.xsd" 
  xmlns:jc="@nativescript/jetpack-compose" 
  class="page">
  <StackLayout>
    <jc:JetpackCompose composeId="sampleView" height="100" />
  </StackLayout>
</Page>

现在你可以使用 ns debug android 来运行应用了。

使用 Android Studio 开发和预览 Jetpack Compose

运行一次应用后,你可以在 Android Studio 中打开 platforms/android 文件夹,在那里你能找到 BasicView.kt 文件。从这里开始,你可以修改它并预览你的更改(只要在你想预览的 @Composable 函数上加上 @Preview 装饰器即可)。

重要提示: 保存这个文件并不会改变你 App_Resources 内部的 BasicView.kt 文件,所以编辑完成后,请务必小心地将文件内容复制回去!这将是未来改进开发体验的一个方面。

或者你也可以创建一个新的 Android 库来开发所有的 Jetpack Compose 视图。

与 NativeScript 互相发送和接收数据

首先,让我们给 BasicView 添加一些绑定,让它现在能在 updateData 中接收数据并展示出来,同时在数据更新后发出一个事件:

kotlin 复制代码
package com.example

import android.content.Context
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

class BasicView {
    data class ExampleUiState(
        val text: String = ""
    ) {}
    class ExampleViewModel(
    ) : ViewModel() {

        var uiState by mutableStateOf(ExampleUiState())
    }

    var mViewModel = ExampleViewModel()
    fun generateComposeView(view: ComposeView): ComposeView {

        return view.apply {
            setContent {
                MaterialTheme {

                    val uiState = mViewModel.uiState;
                    // 在 Compose 世界里
                    Text(uiState.text)
                }
            }
        }
    }

    fun updateData(value: Map<Any, Any>) {
        val v = value["data"] as String;
        onEvent?.invoke(v)
        mViewModel.uiState = ExampleUiState(v);
    }

    var onEvent: ((String) -> Unit)? = null

}

在 NativeScript 布局中使用你的 Jetpack Compose

app/main-page.xml:

xml 复制代码
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page"
    xmlns:jc="@nativescript/jetpack-compose">
    <StackLayout>
        <Label text="下面这个视图就是在 NativeScript 内部的 Jetpack Compose!" textWrap="true"></Label>
        <jc:JetpackCompose composeEvent="{{ onEvent }}" data="{{ text }}" composeId="sampleView"></jc:JetpackCompose>
        <Label text="这又回到了 NativeScript"></Label>
        <TextView textChange="{{ onTextChange }}" text="{{ text }}" textWrap="true"></TextView>
    </StackLayout>
</Page>

app/main-page.ts:

typescript 复制代码
import { Observable } from '@nativescript/core';
import { registerJetpackCompose, ComposeDataDriver } from '@nativescript/jetpack-compose';
import { EventData, Page, PropertyChangeData } from '@nativescript/core';

// A. 你可以使用 'ns typings android --aar {path/to/{name}.aar}' 为你自己的 Compose 提供者生成类型定义
// B. 否则,可以通过声明你知道已提供的包解析路径来忽略此步骤
declare var com;
registerJetpackCompose('sampleView', (view) => new ComposeDataDriver(new com.example.BasicView(), view));

export function navigatingTo(args: EventData) {
  const page = <Page>args.object;
  page.bindingContext = new DemoModel();
}

export class DemoModel extends Observable {
  text = '';

  onEvent(evt: JetpackComposeEventData<string>) {
    console.log('onEvent', evt.data);
  }

  onTextChange(evt: PropertyChangeData) {
    console.log('textChange', evt.value);
    this.set('text', evt.value);
  }
}

现在每当你修改 NativeScript 的 TextView 中的文本时,它都会同步更新 Jetpack Compose 视图中的文本!

颜色选择器示例

最后这个例子,将通过使用一个颜色选择器来改变 NativeScript 视图的背景色:

app.gradle

arduino 复制代码
implementation "com.github.skydoves:colorpicker-compose:1.0.0"

package com.example

kotlin 复制代码
import android.content.Context
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.github.skydoves.colorpicker.compose.ColorEnvelope
import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.ImageColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController

class ColorPickerCompose {
    fun generateComposeView(view: ComposeView): ComposeView {
        return view.apply {
            setContent {
                val controller = rememberColorPickerController()
                HsvColorPicker(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(450.dp)
                        .padding(10.dp),
                    controller = controller,
                    onColorChanged = { colorEnvelope: ColorEnvelope ->
                        onEvent?.invoke(colorEnvelope.hexCode)
                    }
                )
            }
        }
    }

    fun updateData(value: Map<Any, Any>) {}

    var onEvent: ((String) -> Unit)? = null

}
xml 复制代码
<StackLayout backgroundColor="{{ backgroundColor }}">
    <Label text="下面这个视图就是在 NativeScript 内部的 Jetpack Compose!" textWrap="true"></Label>
    <StackLayout backgroundColor="lightblue">
        <jc:JetpackCompose composeEvent="{{ onEvent }}" data="{{ text }}" composeId="jetpackCompose"></jc:JetpackCompose>
    </StackLayout>
    <Label text="这又回到了 NativeScript"></Label>
    <TextView text="{{ backgroundColor }}" textWrap="true"></TextView>
</StackLayout>

dev.to/valorsoftwa...

相关推荐
用户69371750013842 小时前
AI来了,同事们的效率为什么差这么多?
android·前端·ai编程
凡小烦2 小时前
从定制化页签tab到compose列表使用
android·前端
kekegdsz2 小时前
高丢包、高延迟、断网秒切:开源一个 Android 弱网测试利器
android·测试
StarShip3 小时前
JVM堆栈溢出监测原理
android·java
Ehtan_Zheng3 小时前
ArrayDeque 是 Kotlin 开发者工具箱中一个被低估的集合类
android
嗷o嗷o3 小时前
Android BLE 扫描连接与收发消息实战
android
古法安卓3 小时前
Android-LowmemoryKiller机制
android·后端·android studio
kerli3 小时前
Compose 组件:BoxWithConstraints作用及其原理
android·前端
努力学习的小廉3 小时前
Python 零基础入门——基础语法(二)
android·开发语言·python