React-Native随笔

RN原生模块的调用进阶(六)

2018-05-04  本文已影响165人  sleeping_7e17

假设现在需要在RN页面实现轮播图,当然你可以使用react-native-swiper轻松愉快的搞定,no problem,但是我现在就想自己去暴露一个Native的轮播图给js去使用,怎么办?很快为你揭晓。

我们这边就不自己写Native端的轮播图了,使用现成的轮子

'com.youth.banner:banner:1.4.9'

我这边就不引入依赖库了,而是直接把该依赖库里的源文件拷贝了出来,然后去继承了Banner

首先,我们自定义自己的轮播控件,不再赘述,直接上代码

public class YRNBannerView extends Banner implements OnBannerListener {
    private EventDispatcher mEventDispatcher;

    public YRNBannerView(ReactContext reactContext) {
        super(reactContext);
        mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
        setImageLoader(new RNBannerImageLoader());
//        setBannerAnimation(Transformer.DepthPage);
        setIndicatorGravity(BannerConfig.CENTER);

        setIndicatorWidth(DensityUtil.dip2px(reactContext, 6));
        setIndicatorHeight(DensityUtil.dip2px(reactContext, 6));
        setIndicatorMargin(DensityUtil.dip2px(reactContext, 2));

        setOnBannerListener(this);
    }

    @Override
    public void OnBannerClick(int position) {
        mEventDispatcher.dispatchEvent(
                new PageClickEvent(getId(), position));
    }
}

在构造方法里简单对轮播的样式做了些配置并且设置了轮播图的点击事件。

mEventDispatcher.dispatchEvent(new PageClickEvent(getId(), position));

注意这里的EventDispatcher,可以把它理解成android里面的事件分发。当点击轮播图的时候,我们发送了一个PageClickEvent的事件,并且把当前点击的轮播图位置当作参数附带在这个事件上。
那么我们继续看下这个PageClickEvent是个什么东东

class PageClickEvent extends Event<PageClickEvent> {

    public static final String EVENT_NAME = "topPageClick";

    private final int mPosition;

    protected PageClickEvent(int viewTag, int position) {
        super(viewTag);
        mPosition = position;
    }

    @Override
    public String getEventName() {
        return EVENT_NAME;
    }

    @Override
    public void dispatch(RCTEventEmitter rctEventEmitter) {
        rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
    }

    private WritableMap serializeEventData() {
        WritableMap eventData = Arguments.createMap();
        eventData.putInt("index", mPosition);
        return eventData;
    }
}

这里的getEventName方法返回的值EVENT_NAME即topPageClick,这个名称完全是自己定义,但是要与getExportedCustomDirectEventTypeConstants里的MapBuilder.of第一个参数一致,当EventDispatcher分发PageClickEvent的事件后,会调用到这里的dispatch方法。

紧接着我们自己定义一个ViewGroupManager,ViewGroupManager可以理解为对应的是android里面的ViewGroup,上文介绍的SimpleViewManager可以理解为android里面的View

@ReactModule(name = YRNBannerManager.REACT_CLASS)
public class YRNBannerManager extends ViewGroupManager<YRNBannerView> {
    public static final int COMMAND_RELOAD_DATA = 1;
    public static final String REACT_CLASS = "YRNCarousel";

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    protected YRNBannerView createViewInstance(ThemedReactContext reactContext) {
        return new YRNBannerView(reactContext);
    }

    @Override
    public Map getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.of(
                PageClickEvent.EVENT_NAME, MapBuilder.of("registrationName", "onSelectedItem"));
    }

    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(
                "reloadData",
                COMMAND_RELOAD_DATA);
    }

    @Override
    public void receiveCommand(YRNBannerView bannerView, int commandType, @javax.annotation.Nullable ReadableArray args) {
        Assertions.assertNotNull(bannerView);
        Assertions.assertNotNull(args);
        switch (commandType) {
            case COMMAND_RELOAD_DATA: {
                bannerView.start();
                return;
            }
            default:
                throw new IllegalArgumentException(String.format(
                        "Unsupported command %d received by %s.",
                        commandType,
                        getClass().getSimpleName()));
        }
    }

    @ReactProp(name = "showIndicator", defaultBoolean = false)
    public void setShowIndicator(YRNBannerView bannerView, boolean showIndicator) {
        if (showIndicator) {
            bannerView.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);
        } else {
            bannerView.setBannerStyle(BannerConfig.NOT_INDICATOR);
        }
    }

    @ReactProp(name = "fillColor")
    public void setFillColor(YRNBannerView bannerView, String fillColor) {
        bannerView.setSelectedIndicator(fillColor);
    }

    @ReactProp(name = "pageFillColor")
    public void setPageFillColor(YRNBannerView bannerView, String pageFillColor) {
        bannerView.setUnSelectedIndicator(pageFillColor);
    }

    @ReactProp(name = "timesInterval")
    public void setTimesInterval(YRNBannerView bannerView, double timesInterval) {
        int delayTime = (int) (timesInterval * 1000);
        bannerView.setDelayTime(delayTime);
    }

    @ReactProp(name = "dataSource")
    public void setDataSource(YRNBannerView bannerView, ReadableArray dataSource) {
        bannerView.setImages(dataSource.toArrayList());
        List<String> list = new ArrayList<>();
        for (int i = 0; i < dataSource.size(); i++) {
            list.add("");
        }
        bannerView.setBannerTitles(list);
        bannerView.setOffscreenPageLimit(dataSource.size());
    }
}

接下来,我将带领大家一步一步的解释上面的这段代码
如果你看过之前的文章,那么你肯定很清楚getName方法返回值对应着js代码里requireNativeComponent的第一个参数。
createViewInstance方法new了一个轮播图控件的实例,没什么好讲的。
getExportedCustomDirectEventTypeConstants可以简单理解为Native发出的事件在js代码里的哪个function触发
getCommandsMap这个方法一般和下面的receiveCommand方法联合使用,可以简单理解在js里触发一个操作,比如下拉刷新,这个操作怎么从js传递到Native
其他几个方法相信看过上一篇文章的已经知道是什么意思,这里就拿setDataSource来在此说明下,这个方法用于设置轮播图数据,其上的@ReactProp(name = "dataSource")注解表示这个方法是js来触发的,触发的属性叫做dataSource
ok,先简单理解这么多吧

接下来,惯例把自定义的Manager添加到package

public class CommPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CommonModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> views = new ArrayList<>();
        views.add(new CircleManager());
        views.add(new YRNBannerManager());
        return views;
    }
}

最后,就是,写一个轮播图的js,附上代码:

// Carousel.js

import PropTypes from 'prop-types';
import React from 'react';
import {requireNativeComponent, UIManager, findNodeHandle} from 'react-native';
var YRNCarouselManager = require('react-native').NativeModules.YRNCarouselManager;

var YRNCarousel = requireNativeComponent('YRNCarousel', Carousel);

class Carousel extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    }
  }

  componentDidMount() {
        this.reloadData()
  }
 
  render() {
    return <YRNCarousel 
            {...this.props}
            onSelectedItem={this._onSelectedItem}
          />;
  }

  _onSelectedItem = (event) => {
    if (!this.props.onSelectedItem) {
      return;
    }

    // process raw event...
    this.props.onSelectedItem(event.nativeEvent.index);
  }

  reloadData=(data) => (
    UIManager.dispatchViewManagerCommand(
      findNodeHandle(this),
      UIManager.YRNCarousel.Commands.reloadData,
      [data]
    )
)
}

Carousel.propTypes = {
  showIndicator: PropTypes.bool,
  fillColor: PropTypes.string,
  pageFillColor: PropTypes.string,
  timesInterval: PropTypes.number,
  dataSource: PropTypes.array,
  onSelectedItem: PropTypes.func,
};


module.exports = Carousel

到此,我们已经把一个原生轮播控件暴露给了js,下面我们使用它

'use strict'
import React, { Component} from 'react';
import { AsyncStorage,NativeModules,ToastAndroid } from 'react-native';
import {
  AppRegistry,
  StyleSheet,
  Text,
  Image,
  View
} from 'react-native';

import Circle from './Circle';
import Carousel from './Carousel';

let title = 'React Native界面';

export default class YRNTest extends Component {
    /**
    * Callback 通信方式
    */
    callbackComm(msg) {
        NativeModules.CommonModule.rnCallNativeFromCallback(msg,(result) => {
             ToastAndroid.show("CallBack收到消息:" + result, ToastAndroid.SHORT);
        })
    }

    /**
    * Promise 通信方式
    */
    promiseComm(msg) {
        NativeModules.CommonModule.rnCallNativeFromPromise(msg).then(
            (result) =>{
                ToastAndroid.show("Promise收到消息:" + result, ToastAndroid.SHORT)
            }
        ).catch((error) =>{console.log(error)});
    }

    constructor(props) {
            super(props)
            this.state = {
                bannerData: ['https://img14.360buyimg.com/img/jfs/t19060/283/2260839795/40067/39e783f3/5aebb2a3N5ed510c5.jpg',
                'https://img14.360buyimg.com/img/jfs/t19060/283/2260839795/40067/39e783f3/5aebb2a3N5ed510c5.jpg']
            }
    }

  render() {
    return (
                <Carousel
                        style={{ height: 100, backgroundColor: 'transparent', overflow:'hidden' }}
                        showIndicator={true}
                        fillColor='#ff6769'
                        pageFillColor='#ffffff'
                        timesInterval={2.5}
                        dataSource={this.state.bannerData}
                        onSelectedItem={(index) => {
                            console.log(index)
                        }}
                    />
//        <View style={styles.container}>
//            <Circle
//                style={{width: 100, height: 100}}
//                color="#25c5f7"
//                radius={50}
//            />
//        </View>
//      <View style={styles.container}>
//        <Text style={styles.welcome} >
//            {title}
//        </Text>
//        <Text style={styles.welcome} onPress={this.callbackComm.bind(this,'你好啊,android')}>
//             Callback通信方式
//        </Text>
//        <Text style={styles.welcome} onPress={this.promiseComm.bind(this,'你好啊,android')}>
//             Promise通信方式
//        </Text>
//      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  }
});

AppRegistry.registerComponent('YRNTest', () => YRNTest);

我把改动的代码截取出来

import Carousel from './Carousel';

 <Carousel
          style={{ height: 100, backgroundColor: 'transparent', overflow:'hidden' }}
          showIndicator={true}
          fillColor='#ff6769'
          pageFillColor='#ffffff'
          timesInterval={2.5}
          dataSource={this.state.bannerData}
          onSelectedItem={(index) => {
                 console.log(index)
           }}
 />

可以看到这里的showIndicator ,fillColor,pageFillColor,timesInterval,dataSource都依次对应着YRNBannerManager里的@ReactProp(name = "showIndicator", defaultBoolean = false), @ReactProp(name = "fillColor"), @ReactProp(name = "pageFillColor"),@ReactProp(name = "timesInterval"),@ReactProp(name = "dataSource")

 @Override
    public Map getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.of(
                PageClickEvent.EVENT_NAME, MapBuilder.of("registrationName", "onSelectedItem"));
    }

onSelectedItem对应着getExportedCustomDirectEventTypeConstants里的onSelectedItem,上文已经简单介绍过了,再次回顾下,当原生代码触发PageClickEvent事件后,会回调到Carousel.js里onSelectedItem方法,进而调用到_onSelectedItem方法,参数通过event获取

render() {
    return <YRNCarousel 
            {...this.props}
            onSelectedItem={this._onSelectedItem}
          />;
  }

 _onSelectedItem = (event) => {
    if (!this.props.onSelectedItem) {
      return;
    }

    // process raw event...
    this.props.onSelectedItem(event.nativeEvent.index);
  }

另外,注意到在Carousel.js里有这样一段代码

componentDidMount() {
        this.reloadData()
  }

这个的意思是在js里去调用reloadData,我们截取YRNBannerManager部分代码,我们发现这个reloadData与getCommandsMap里的reloadData对应,并且其会触发COMMAND_RELOAD_DATA命令,这个命令由receiveCommand接收到并最终处理bannerView.start()来开始轮播

@Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(
                "reloadData",
                COMMAND_RELOAD_DATA);
    }

    @Override
    public void receiveCommand(YRNBannerView bannerView, int commandType, @javax.annotation.Nullable ReadableArray args) {
        Assertions.assertNotNull(bannerView);
        Assertions.assertNotNull(args);
        switch (commandType) {
            case COMMAND_RELOAD_DATA: {
                bannerView.start();
                return;
            }
            default:
                throw new IllegalArgumentException(String.format(
                        "Unsupported command %d received by %s.",
                        commandType,
                        getClass().getSimpleName()));
        }
    }
附上截图 lunbo.gif
上一篇下一篇

猜你喜欢

热点阅读