了解如何使用 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.
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:
-
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:
-
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.
# 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:
-
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:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.dependency 'DGCharts', '~> 5.1.0'
# Swift/Objective-C compatibility
.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.
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.vendored_frameworks = 'Frameworks/MyFramework.framework'
# Swift/Objective-C compatibility
.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:
"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:
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:
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:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0@aar'
}
4
¥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.
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:
import * as React from 'react';
export default function ExpoRadialChartView() {
return <div>Not implemented</div>;
}
5
¥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.
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:
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
¥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.
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:
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:
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.