首页指南参考教程

封装第三方原生库

了解如何使用 Expo 模块围绕两个独立的原生库创建一个简单的封装器。


Expo 模块使得在 React Native 项目中轻松使用为 Android 和 iOS 构建的原生外部库成为可能。本教程重点介绍如何利用 Expo Modules API 使用可在两个原生平台上访问的两个类似库来创建径向图表。

¥Expo modules make it possible to easily use native, external libraries built for Android and iOS in React Native projects. This tutorial focuses on utilizing the Expo Modules API to create radial charts using two similar libraries accessible on both native platforms.

  • MPAndroidChart by PhilJay

  • 图表由 Daniel Cohen Gindi 绘制

    ¥Charts by Daniel Cohen Gindi

iOS 库受到 Android 库的启发,因此它们都具有非常相似的 API 和功能。这使它们成为本教程的一个很好的例子。

¥The iOS library is inspired by the Android library, so they both have a very similar API and functionality. This makes them a good example for this tutorial.

1

创建一个新模块

¥Create a new module

你可以首先创建一个新的空 Expo 模块。我们正在为本教程创建一个单独的项目。但是,你可以在现有项目中创建新模块。

¥You can start by creating a new empty Expo module. We're creating a separate project for this tutorial. However, you can create a new module inside your existing project.

从一个新项目开始

¥Start with a new project

要创建一个可以在 npm 上发布并在任何 Expo 应用中使用的空 Expo 模块,请运行以下命令:

¥To create an empty Expo module that can be published on npm and utilized in any Expo application, run the following command:

Terminal
npx create-expo-module expo-radial-chart

提示:如果你不打算发布此库,请按 return 以接受终端窗口中所有提示的默认值。

¥Tip: If you aren't going to ship this library, press return for all of the prompts to accept the default values in the terminal window.

现在,打开新创建的 expo-radial-chart 目录开始编辑原生代码。

¥Now, open the newly created expo-radial-chart directory to start editing the native code.

从现有项目开始

¥Start with an existing project

或者,你可以使用新模块作为现有项目内的视图。在项目目录中运行以下命令:

¥Alternatively, you can use the new module as a view inside the existing project. Run the following command in your project's directory:

Terminal
npx create-expo-module --local expo-radial-chart

现在,打开新创建的 modules/expo-radial-chart 目录开始编辑原生代码。

¥Now, open the newly created modules/expo-radial-chart directory to start editing the native code.

2

运行示例项目

¥Run the example project

为了验证一切是否正常运行,让我们运行示例项目。在终端窗口中,启动 TypeScript 编译器以观察更改并重建模块 JavaScript。

¥To verify that everything is functioning correctly, let's run the example project. In the terminal window, start the TypeScript compiler to watch for changes and rebuild the module JavaScript.

Terminal
# Run this in the root of the project to start the TypeScript compiler
npm run build

在另一个终端窗口中,编译并运行示例应用:

¥In another terminal window, compile and run the example app:

Terminal
cd example
# Run the example app on iOS
npx expo run:ios
# Run the example app on Android
npx expo run:android

3

添加原生依赖

¥Add native dependencies

现在,通过编辑 android/build.gradle 和 ios/ExpoRadialChart.podspec 文件将原生依赖添加到模块中:

¥Now, add the native dependencies to the module by editing the android/build.gradle and ios/ExpoRadialChart.podspec files:

android/build.gradle
dependencies {
  implementation project(':expo-modules-core')
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
ios/ExpoRadialChart.podspec
    s.static_framework = true

    s.dependency 'ExpoModulesCore'
+   s.dependency 'DGCharts', '~> 5.1.0'

    # Swift/Objective-C compatibility
Are you trying to use an .xcframework or .framework dependency?

在 iOS 上,你还可以通过使用 vendored_framework 配置选项来使用打包为框架的依赖。

¥On iOS, you can also use dependencies bundled as a framework by using the vendored_framework config option.

ios/ExpoRadialChart.podspec
    s.static_framework = true
    s.dependency 'ExpoModulesCore'
+   s.vendored_frameworks = 'Frameworks/MyFramework.framework'
    # Swift/Objective-C compatibility
Are you trying to use a .aar dependency?

在 android 目录中,创建另一个名为 libs 的目录并将 .aar 文件放入其中。然后,从自动链接中将文件添加为 Gradle 项目:

¥Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the file as a Gradle project from autolinking:

expo-module.config.json
    "android": {
+     "gradleAarProjects": [
+       {
+         "name": "test-aar",
+         "aarFilePath": "android/libs/test.aar"
+       }
+     ],
    "modules": [

最后,将依赖添加到 android/build.gradle 文件中的 dependencies 列表中,使用依赖的指定名称加上 ${project.name}$ 前缀:

¥Finally, add the dependency to the dependencies list in the android/build.gradle file, using the dependency's specified name with ${project.name}$ prefix:

android/build.gradle
dependencies {
  implementation project(':expo-modules-core')
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation project(":${project.name}\$test-aar")
}

在 android 目录中,创建另一个名为 libs 的目录并将 .aar 文件放入其中。然后,将该文件夹添加为存储库:

¥Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the folder as a repository:

android/build.gradle
  repositories {
    mavenCentral()
+   flatDir {
+       dirs 'libs'
+   }
  }

最后,将依赖添加到 dependencies 列表中。使用包路径代替文件名,其中末尾包含 @aar

¥Finally, add the dependency to the dependencies list. Instead of the filename, use the package path, which includes the @aar at the end:

android/build.gradle
dependencies {
  implementation project(':expo-modules-core')
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0@aar'
}

4

定义一个 API

¥Define an API

要在应用中正确使用模块,请定义属性的类型。该模块接受一系列系列 - 每个系列都有一个颜色和一个百分比值。

¥To use the module in the app without mistakes, define the types for the props. This module accepts a list of series — each with a color and a percentage value.

src/ExpoRadialChart.types.ts
import { ViewStyle } from 'react-native/types';

export type ChangeEventPayload = {
  value: string;
};

type Series = {
  color: string;
  percentage: number;
};

export type ExpoRadialChartViewProps = {
  style?: ViewStyle;
  data: Series[];
};

由于我们不会在本例中在 Web 上实现该模块,因此让我们替换 src/ExpoRadialChartView.web.tsx 文件:

¥Since we won't implement the module on the web in this example, let's replace the src/ExpoRadialChartView.web.tsx file:

src/ExpoRadialChartView.web.tsx
import * as React from 'react';

export default function ExpoRadialChartView() {
  return <div>Not implemented</div>;
}

5

在 Android 上实现该模块

¥Implement the module on Android

现在,你可以通过编辑占位符文件并进行以下更改来实现原生功能:

¥Now you can implement the native functionality by editing the placeholder files with the following changes:

  • 创建一个 PieChart 实例并设置其 layoutParams 以匹配父视图。然后,使用 addView 函数将其添加到视图层次结构中。

    ¥Create a PieChart instance and set its layoutParams to match the parent view. Then, add it to the view hierarchy using the addView function.

  • 定义接受 Series 对象列表的 setChartData 函数。你可以迭代列表,为每个系列创建 PieEntry 并将颜色存储在单独的列表中。然后,创建一个 PieDataSet,用它创建一个 PieData 对象,并将其设置为 PieChart 实例上的数据。

    ¥Define a setChartData function that accepts a list of Series objects. You can iterate over the list, create a PieEntry for each series and store the colors in a separate list. Then, create a PieDataSet, use it to create a PieData object, and set it as data on the PieChart instance.

android/src/main/java/expo/modules/radialchart/ExpoRadialChartView.kt
package expo.modules.radialchart

import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.views.ExpoView


class Series : Record {
  @Field
  val color: String = "#ff0000"

  @Field
  val percentage: Float = 0.0f
}

class ExpoRadialChartView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
  internal val chartView = PieChart(context).also {
    it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    addView(it)
  }

  fun setChartData(data: ArrayList<Series>) {
    val entries: ArrayList<PieEntry> = ArrayList()
    val colors: ArrayList<Int> = ArrayList()
    for (series in data) {
      entries.add(PieEntry(series.percentage))
      colors.add(Color.parseColor(series.color))
    }
    val dataSet = PieDataSet(entries, "DataSet");
    dataSet.colors = colors;
    val pieData = PieData(dataSet);
    chartView.data = pieData;
    chartView.invalidate();

  }
}

你还需要使用 Prop 函数来定义 data prop,并在 prop 更改时调用原生 setChartData 函数:

¥You also need to use the Prop function to define the data prop and call the native setChartData function when the prop changes:

android/src/main/java/expo/modules/radialchart/ExpoRadialChartModule.kt
package expo.modules.radialchart

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class ExpoRadialChartModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoRadialChart")

    View(ExpoRadialChartView::class) {
      Prop("data") { view: ExpoRadialChartView, prop: ArrayList<Series> ->
        view.setChartData(prop);
      }
    }
  }
}

6

在 iOS 上实现该模块

¥Implement the module on iOS

现在,你可以通过编辑占位符文件并进行以下更改来实现原生功能:

¥Now you can implement the native functionality by editing the placeholder files with the following changes:

  • 创建一个新的 PieChartView 实例并使用 addSubview 函数将其添加到视图层次结构中。

    ¥Create a new PieChartView instance and use the addSubview function to add it to the view hierarchy.

  • 设置 clipsToBounds 属性并覆盖 layoutSubviews 函数以确保图表视图始终与父视图大小相同。

    ¥Set the clipsToBounds property and override the layoutSubviews function to make sure the chart view is always the same size as the parent view.

  • 最后,创建一个接受系列列表的 setChartData 函数,使用数据创建 PieChartDataSet 实例,并将其分配给 PieChartView 实例的 data 属性。

    ¥Finally, create a setChartData function that accepts a list of series, creates a PieChartDataSet instance with the data, and assigns it to the data property of the PieChartView instance.

ios/ExpoRadialChartView.swift
import ExpoModulesCore
import DGCharts

struct Series: Record {
  @Field
  var color: UIColor = UIColor.black

  @Field
  var percentage: Double = 0
}

class ExpoRadialChartView: ExpoView {
  let chartView = PieChartView()

  required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)
    clipsToBounds = true
    addSubview(chartView)
  }

  override func layoutSubviews() {
    chartView.frame = bounds
  }

  func setChartData(data: [Series]) {
    let set1 = PieChartDataSet(entries: data.map({ (series: Series) -> PieChartDataEntry in
      return PieChartDataEntry(value: series.percentage)
    }))
    set1.colors = data.map({ (series: Series) -> UIColor in
      return series.color
    })
    let chartData: PieChartData = [set1]
    chartView.data = chartData
  }
}

你还需要使用 Prop 函数来定义 data prop,并在 prop 更改时调用原生 setChartData 函数:

¥You also need to use the Prop function to define the data prop and call the native setChartData function when the prop changes:

ios/ExpoRadialChartModule.swift
import ExpoModulesCore

public class ExpoRadialChartModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoRadialChart")

    View(ExpoRadialChartView.self) {
      Prop("data") { (view: ExpoRadialChartView, prop: [Series]) in
        view.setChartData(data: prop)
      }
    }
  }
}

7

编写一个示例应用来使用该模块

¥Write an example app to use the module

你可以更新示例目录中的应用来测试该模块。使用 ExpoRadialChartView 组件渲染具有三个切片的饼图:

¥You can update the app inside the example directory to test the module. Use the ExpoRadialChartView component to render a pie chart with three slices:

example/App.tsx
import { ExpoRadialChartView } from 'expo-radial-chart';
import { StyleSheet } from 'react-native';

export default function App() {
  return (
    <ExpoRadialChartView
      style={styles.container}
      data={[
        {
          color: '#ff0000',
          percentage: 0.5,
        },
        {
          color: '#00ff00',
          percentage: 0.2,
        },
        {
          color: '#0000ff',
          percentage: 0.3,
        },
      ]}
    />
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

提示:如果你在现有应用中创建了模块,请确保使用相对导入直接从模块目录导入它:import { ExpoRadialChartView } from '../modules/expo-radial-chart';

¥Tip: If you created the module inside an existing application, make sure to import it directly from your modules directory by using a relative import: import { ExpoRadialChartView } from '../modules/expo-radial-chart';

8

重建并启动你的应用

¥Rebuild and launch your application

为了确保你的应用在两个平台上成功构建,请重新运行步骤 2 中的构建命令。在任何平台上成功构建应用后,你将看到一个包含三个切片的饼图:

¥To make sure your app builds successfully on both platforms, rerun the build commands from step 2. After the app is successfully built on any of the platform you'll see a pie chart with three slices:

下一步

¥Next step

恭喜!你已经使用 Expo Modules 围绕两个独立的第三方原生库创建了第一个简单封装器。了解有关 Expo 模块 API 参考 中 API 的更多信息。

¥Congratulations! You have created your first simple wrapper around two separate third-party native libraries using Expo Modules. Learn more about the API in the Expo Module API reference.

Expo 中文网 - 粤ICP备13048890号