React-Native系列Android自定義原生UI元件
由於官方的Android原生UI元件解釋的並不是很完整,根據個人的不斷摸索,終於成功完成原生元件的製作,所以寫下這篇文章作為記錄,也給讓小白們少走些彎路。
我這裡通過講解制作一個繪圓元件的流程,來學習製作android原生UI元件。這個繪圓元件並沒有實際的使用價值,只是為了更容易的瞭解android原生UI元件的製作過程。好了廢話不多說,現在開始吧。
- react-native版本:0.33.0
首先初始化react-native專案,最好能弄個VPN
react-native init AndroidNativeModule
初次build android專案,先開啟虛擬機器或連線手機。
react-native run-android
專案的建立就講到這,詳細的專案建立和排錯請自行百度。現在我們用Android Stuido進入android專案。
建立基本的UI元件框架,這裡元件名稱嚴格更具功能進行命名,為了看的更清楚進行了一些打包
- CircleManager.java CIrcle原生元件管理器,實現JS和JAVA資訊傳遞
- CircleView.java Circle原生元件
- MainPackage.java 自定義元件註冊包
接下來給建立好的java新增基本結構,然後在後面更具需要新增細節功能
- CircleView.java 基礎結構
package com.androidnativemodule.module.circle;
import android.content.Context;
import android.view.View;
/**
* 圓形元件元件基礎類
*/
public class CircleView extends View {
public CircleView(Context context) {
super(context);
}
}
UI元件繼承於View類,這裡沒有加什麼功能,就建立一個基本的類
- CircleManager.java
package com.androidnativemodule.module.circle;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
/**
* 圓形元件基礎類管理器
*/
public class CircleManager extends SimpleViewManager<CircleView> {
/**
* 設定js引用名
* @return String
*/
@Override
public String getName() {
return "MCircle";
}
/**
* 建立UI元件例項
* @param reactContext
* @return CircleView
*/
@Override
protected CircleView createViewInstance(ThemedReactContext reactContext) {
return new CircleView(reactContext);
}
}
所有的元件管理器需要繼承SimpleViewManager類,後面加上自己定義的元件基礎類,下面2個方法是SimpleViewManager類的必須實現的方法,記住getName()返回的名字要和JS裡的應用名進行統一。
- MainPackge.java
package com.androidnativemodule;
import com.androidnativemodule.module.circle.CircleManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 自定義元件模組註冊類
*/
public class MainPackage implements ReactPackage {
/**
* 建立原生模組
* @param reactContext
* @return
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* 建立原生UI元件控制器
* @param reactContext
* @return
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new CircleManager()
);
}
}
這個是註冊類,實現ReactPackage介面,可以想自己設計的元件統一寫在這個類中,然後一起在Application中註冊,注意:返回空值需要返回Collections.emptyList()
。它還可以註冊原生模組,這個官方有詳細的講解,地址如下:http://facebook.github.io/react-native/docs/native-modules-android.html
註冊類寫好後,還需要將這個類載入進Application中,我們在通過MainApplication中將其加入進去。
- MainApplication.java
package com.androidnativemodule;
import android.app.Application;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new MainPackage() // 在這裡載入我們自己的註冊類
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
到這裡我們的自定義UI元件框架基本完成,我們可以run一下看看,如果沒有效果,可以嘗試重新react-native run-android
下。
在Android Monitor中可以看到已經載入了我們的CircleModule,只有有沒我們JS並沒有例項話他,所以Could not find generated setter for class com.androidnativemodule.module.circle.CircleManager
。
接下來我們給我們的原生UI元件加上功能。
- CircleView.java
package com.androidnativemodule.module.circle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
/**
* 圓形元件元件基礎類
*/
public class CircleView extends View {
private final String TAG = "CircleView";
private Paint mPaint; // 畫筆
public CircleView(Context context) {
super(context);
mPaint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(100, 100, 100, mPaint); // 畫一個半徑為100px的圓
Log.d(TAG, "繪圖");
}
}
這裡我先在CircleView內寫死程式碼,看看這個原生UI到底能不能在JS中使用
現在我們來到react-native專案,我們先定製一個Circle元件的JS介面,方便其他元件呼叫。
- 建立Circle.js
import { PropTypes } from 'react';
import { requireNativeComponent, View } from 'react-native';
const MCircle = requireNativeComponent('MCircle', {
propTypes: {
...View.propTypes // 包含預設的View的屬性
},
});
export default MCircle;
使用requireNativeComponent根據先前在管理器中定義好的元件名引用原生元件,由於我們還沒建立介面,所以這個元件暫時只有父類View的介面。
現在我們在index.android.js中例項化元件看看效果
- index.android.js
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import Circle from './Circle';
class AndroidNativeModule extends Component {
render() {
return (
<View style={styles.container}>
<Circle style={{width: 100, height: 100}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('AndroidNativeModule', () => AndroidNativeModule);
效果圖:
可以看到成功使用Android canvas 畫了一個圓
現在我們給他加一些介面,從而實現JS和JAVA的通訊,直接在JS中更改元件樣式。
- CircleView.java
package com.androidnativemodule.module.circle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import com.facebook.react.uimanager.PixelUtil;
/**
* 圓形元件元件基礎類
*/
public class CircleView extends View {
private final String TAG = "CircleView";
private Paint mPaint; // 畫筆
private float mRadius; // 圓的半徑
public CircleView(Context context) {
super(context);
mPaint = new Paint();
}
/**
* 設定圓的背景色
* @param color
*/
public void setColor(Integer color) {
mPaint.setColor(color); // 設定畫筆顏色
invalidate(); // 更新畫板
}
/**
* 設定圓的半徑
* @param radius
*/
public void setRadius(Integer radius) {
/**
* 由於JS傳過的數字是dip單位,需要轉換為實際畫素
* 使用com.facebook.react.uimanager包中的PixelUtil,進行轉換
*/
mRadius = PixelUtil.toPixelFromDIP(radius);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint); // 畫一個半徑為100px的圓
Log.d(TAG, "繪圖");
}
}
這裡添加了2個介面,實現對圓的大小和顏色更改,還使用了PixelUtil
實現畫素的轉換
- CircleManager.java
package com.androidnativemodule.module.circle;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* 圓形元件基礎類管理器
*/
public class CircleManager extends SimpleViewManager<CircleView> {
/**
* 設定js引用名
* @return String
*/
@Override
public String getName() {
return "MCircle";
}
/**
* 建立UI元件例項
* @param reactContext
* @return CircleView
*/
@Override
protected CircleView createViewInstance(ThemedReactContext reactContext) {
return new CircleView(reactContext);
}
/**
* 傳輸背景色引數
* @param view
* @param color
*/
@ReactProp(name = "color")
public void setColor(CircleView view, Integer color) {
view.setColor(color);
}
/**
* 傳輸半徑引數
* @param view
* @param radius
*/
@ReactProp(name = "radius")
public void setRadius(CircleView view, Integer radius) {
view.setRadius(radius);
}
}
管理器中引用介面,將引數傳輸過去
現在我們的CircleModule已經完成了,然後我們修改下Circle元件JS介面,實現介面使用。
- Circle.js
'use strict';
import React, { Component, PropTypes } from 'react';
import {
View,
requireNativeComponent,
processColor // 字元Color轉換為數字
} from 'react-native';
const MCircle = requireNativeComponent('MCircle', {
propTypes: {
color: PropTypes.number,
radius: PropTypes.number,
...View.propTypes // 包含預設的View的屬性
},
});
class Circle extends Component {
static propTypes = {
radius: PropTypes.number,
color: PropTypes.string, // 這裡傳過來的是string
...View.propTypes // 包含預設的View的屬性
}
render() {
const { style, radius, color } = this.props;
return (
<MCircle
style={style}
radius={radius}
color={processColor(color)}
/>
);
}
}
module.exports = Circle;
由於color使用的是String,我們可以用react-native的processColor將其轉換為數字,從而可以讓java識別出顏色,為了方便使用,所以我這邊重寫建立了Component,作為中間元件,對color進行轉換。
然後我的就可以通過Circle.js使用CircleModule了,例如:
<Circle
style={{width: 100, height: 100}}
color="#25c5f7"
radius={50}
/>
效果圖:
到此Android自定義原生UI元件CircleModule的設計就結束了,如果有錯誤的地方,大家可以指出來反饋給我,也希望大家可以將自己的react-native開發心得分享出來,大家一起來學習。