react naive封装原生UI(android篇)
文章目录
- react naive封装原生UI(android篇)
- 一、基本视图封装
- 1.1、首先我们需要自定义view
- 1.2、创建一个ViewManager的子类,实现createViewInstance方法
- 1.3、把这个视图管理类注册到应用程序包的createViewManagers里
- 1.4、将程序包注册到rn应用中
- 1.5、js端调用自定义组件
- 二、封装props属性
- 2.1、原生部分
- 2.2、js部分
- 三、封装ref调用原生方法
- 3.1、原生部分
- 3.2、js部分
- 四、封装props事件
- 4.1、原生部分
- 4.2、js部分
一、基本视图封装
这里支持封装原生view桥接到rn,支持封装viewgroup作为容器接受js组件到rn
1.1、首先我们需要自定义view
这里以普通的弧形切割自定义view作为示例,也可以封装RecycleView、SmartRefleshLayout等
public class RadianView extends FrameLayout {
private float radiusLength = 1600;
private float yOffset = 0; //Y轴偏移量(用于处理margin、padding布局产生的Canvas偏移)
public RadianView(Context context) {
this(context, null);
}
public RadianView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RadianView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
public void setView(View child) {
this.addView(child);
}
@Override
protected void dispatchDraw(Canvas canvas) {
Path path = new Path();
path.addCircle(canvas.getWidth() / 2, canvas.getHeight() - radiusLength + yOffset, radiusLength, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
public void setRadiusLength(float radiusLength) {
this.radiusLength = radiusLength;
}
public void setyOffset(float yOffset) {
this.yOffset = yOffset;
}
}
1.2、创建一个ViewManager的子类,实现createViewInstance方法
这里addView是桥接viewGroup容器的方法,桥接view可以不使用。同时这里的props属性没有需要也可以不添加
public class RadianViewManager extends ViewGroupManager<RadianView> {
@Override
public String getName() {
return "RadianView";
}
@Override
protected RadianView createViewInstance(ThemedReactContext reactContext) {
return new RadianView(reactContext);
}
@Override
public void addView(RadianView parent, View child, int index) {
parent.setView(child);
}
@ReactProp(name = "radius")
public void setRadius(RadianView radianView, float size) {
if (radianView != null) {
radianView.setRadiusLength(size);
}
}
@ReactProp(name = "yOffset")
public void setYOffset(RadianView radianView, float size) {
if (radianView != null) {
radianView.setyOffset(size);
}
}
}
1.3、把这个视图管理类注册到应用程序包的createViewManagers里
这部分代码基本都是写死的,按照这个模板写即可
public class RadianViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new RadianViewManager());
}
}
1.4、将程序包注册到rn应用中
这步骤也是固定操作,在rn0.60版本一下肯定要加这一步,rn0.60版本上也可以使用动态注册
- 1.4.1、注册到MainApplication
- 配置到临时文件PackageList中
可以在项目根目录下创建文件
react-native.config.js
, 然后禁止 async-storage autolink:
module.exports = {
dependencies: {
'@react-native-community/async-storage': {
platforms: {
android: null, // disable Android platform, other platforms will still autolink if provided
},
},
},
};
module.exports = {
dependency: {
platforms: {
ios: { project: "lib/ios/react-native-amap-geolocation.podspec" },
android: { sourceDir: "lib/android" }
}
},
dependencies: {
"react-native-amap-geolocation": {
root: __dirname,
platforms: {
ios: { podspecPath: __dirname + "/lib/ios/react-native-amap-geolocation.podspec" },
android: {
sourceDir: __dirname + "/lib/android",
packageImportPath: "import cn.qiuxiang.react.geolocation.AMapGeolocationPackage;",
packageInstance: "new AMapGeolocationPackage()"
}
}
}
}
};
1.5、js端调用自定义组件
通过const AudioWaveView = requireNativeComponent(‘AudioWaveView’);找到原生组件即可
二、封装props属性
2.1、原生部分
在原生的RadianViewManager管理类中添加props属性如下即可,全部代码也可参考上面 #1.2
@ReactProp(name = "radius")
public void setRadius(RadianView radianView, float size) {
if (radianView != null) {
radianView.setRadiusLength(size);
}
}
2.2、js部分
js端直接使用props属性即可,项目实际使用代码片段如下
import React, {Component} from 'react'
import {
View,
Animated,
ImageBackground, Easing, requireNativeComponent,
} from 'react-native'
import ScreenUtils from "../utils/ScreenUtils";
import PropTypes from 'prop-types'
import bg_top_rectangle from '@assets/res/bg/bg_top_rectangle.png';
const width = ScreenUtils.width;
const RadianView = requireNativeComponent('RadianView');
export default class TopWithBgView extends React.Component {
//约束
static propTypes = {
title: PropTypes.string,
radian: PropTypes.number,
radius: PropTypes.number,
};
//默认参数
static defaultProps = {
title: '',
radian: 25, //ios弧度默认值
radius: 1600, //android半径默认值
};
constructor(props) {
super(props);
const {defaultHigh = 150} = props;
this.state = {
highValue: new Animated.Value(defaultHigh),
};
this.scale = 1600 * (width / 375)
}
_onLayout = (e) => {
const {height} = e.nativeEvent.layout;
this.state.highValue.setValue(height);
};
render() {
const {radian, radius, style,} = this.props;
return (
<View style={[{width: ScreenUtils.width, alignItems: "center", overflow: 'hidden'}, style]}>
<RadianView radian={radian} radius={radius}>
<ImageBackground source={bg_top_rectangle} resizeMode={'stretch'}
style={{width: ScreenUtils.width}}>
{this.props.children}
</ImageBackground>
</RadianView>
</View>
)
}
}
三、封装ref调用原生方法
通过rn的ref直接调用原生组件方法,当然也可以通过Promise、Callback或者Emit调用,但是这样代码太混乱了。
3.1、原生部分
首先在ViewManager中重写getCommandsMap、receiveCommand方法来定义可以调用的方法命令。如下
public class AudioWaveViewManager extends ViewGroupManager<AudioWaveView> {
public static final int COMMAND_START = 1;
public static final int COMMAND_STOP = 2;
@Override
public String getName() {
return "AudioWaveView";
}
@Override
protected AudioWaveView createViewInstance(ThemedReactContext reactContext) {
return new AudioWaveView(reactContext);
}
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.<String, Integer>builder()
.put("start", COMMAND_START)
.put("stop", COMMAND_STOP)
.build();
}
@Override
public void receiveCommand(@NonNull AudioWaveView audioWaveView, int commandId, @Nullable ReadableArray args) {
super.receiveCommand(audioWaveView, commandId, args);
switch (commandId) {
case COMMAND_START:
audioWaveView.start();
break;
case COMMAND_STOP:
audioWaveView.stop();
break;
}
}
}
同时android官方建议receiveCommand的commandId使用字符串
3.2、js部分
然后再js端可访问:
UIManager.[UI组件名].[Constants(静态值)/Commands(命令/方法)]
import React, { Component } from 'react';
import {
requireNativeComponent,
View,
findNodeHandle,
UIManager
} from 'react-native';
import PropTypes from 'prop-types';
const AudioWaveView = requireNativeComponent('AudioWaveView', AudioWaveComponent);
export default class AudioWaveComponent extends Component {
static defaultProps = {}
start = () => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(this),
UIManager.getViewManagerConfig('AudioWaveView').Commands.start,
null
)
}
stop = () => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(this),
UIManager.getViewManagerConfig('AudioWaveView').Commands.stop,
null
)
}
render() {
return <AudioWaveView {...this.props} />
}
}
AudioWaveComponent.name = 'AudioWaveView'
AudioWaveComponent.propTypes = {
// start: PropTypes.string,
...View.propTypes // 包含默认的View的属性
}
四、封装props事件
如上图onRefresh所示,如果原生自定义view需要回调事件给js端,考虑怎样处理比较合适呢
4.1、原生部分
首先原生这边我们需要在ViewManager重写getExportedCustomDirectEventTypeConstants方法注册桥接到js的事件。
参数释意:
1、”javaEventNmae“是java端发送事件事件名称
2、”registrationName“字符串的值是固定的,不能修改
3、"onChange"字符串是定义在js端的回调方法
如下
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("javaEventName", MapBuilder.of("registrationName", "onChange"))
.build();
}
依然是原生这边,我们需要在自定义view中具体调用,如下
public void test(){
//向js发送事件
WritableMap event = Arguments.createMap();
event.putString("test","123");//key用于js中的nativeEvent
ReactContext reactContext = (ReactContext) getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),//native层和js层两个视图会依据getId()而关联在一起
"javaEventName",//事件名称
event//事件携带的数据
);
}
4.2、js部分
最后在js端使用即可,注意这里需要取nativeEvent数据
<AudioWaveView ref={ref => this.audioWaveRef = ref}
onChange={event=>{
const {nativeEvent}=event;
console.warn('语音收到onChange方法:'+JSON.stringify(nativeEvent))
}}
style={{ width: width * 0.9, height: 42 }}
source={{ centerSeparate: 2 }} />