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

假设现在需要在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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容