react naive封装原生UI(android篇)

react naive封装原生UI(android篇)

一、基本视图封装

这里支持封装原生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

image-20210406145938515

  • 配置到临时文件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使用字符串

image-20210406164114758

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事件

image-20210406153921865

如上图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 }} />
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星雨在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值