RN原生模塊的調(diào)用進(jìn)階(六)

假設(shè)現(xiàn)在需要在RN頁(yè)面實(shí)現(xiàn)輪播圖,當(dāng)然你可以使用react-native-swiper輕松愉快的搞定,no problem,但是我現(xiàn)在就想自己去暴露一個(gè)Native的輪播圖給js去使用,怎么辦?很快為你揭曉。

我們這邊就不自己寫(xiě)Native端的輪播圖了,使用現(xiàn)成的輪子

'com.youth.banner:banner:1.4.9'

我這邊就不引入依賴(lài)庫(kù)了,而是直接把該依賴(lài)庫(kù)里的源文件拷貝了出來(lái),然后去繼承了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));
    }
}

在構(gòu)造方法里簡(jiǎn)單對(duì)輪播的樣式做了些配置并且設(shè)置了輪播圖的點(diǎn)擊事件。

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

注意這里的EventDispatcher,可以把它理解成android里面的事件分發(fā)。當(dāng)點(diǎn)擊輪播圖的時(shí)候,我們發(fā)送了一個(gè)PageClickEvent的事件,并且把當(dāng)前點(diǎn)擊的輪播圖位置當(dāng)作參數(shù)附帶在這個(gè)事件上。
那么我們繼續(xù)看下這個(gè)PageClickEvent是個(gè)什么東東

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,這個(gè)名稱(chēng)完全是自己定義,但是要與getExportedCustomDirectEventTypeConstants里的MapBuilder.of第一個(gè)參數(shù)一致,當(dāng)EventDispatcher分發(fā)PageClickEvent的事件后,會(huì)調(diào)用到這里的dispatch方法。

緊接著我們自己定義一個(gè)ViewGroupManager,ViewGroupManager可以理解為對(duì)應(yīng)的是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());
    }
}

接下來(lái),我將帶領(lǐng)大家一步一步的解釋上面的這段代碼
如果你看過(guò)之前的文章,那么你肯定很清楚getName方法返回值對(duì)應(yīng)著js代碼里requireNativeComponent的第一個(gè)參數(shù)。
createViewInstance方法new了一個(gè)輪播圖控件的實(shí)例,沒(méi)什么好講的。
getExportedCustomDirectEventTypeConstants可以簡(jiǎn)單理解為Native發(fā)出的事件在js代碼里的哪個(gè)function觸發(fā)
getCommandsMap這個(gè)方法一般和下面的receiveCommand方法聯(lián)合使用,可以簡(jiǎn)單理解在js里觸發(fā)一個(gè)操作,比如下拉刷新,這個(gè)操作怎么從js傳遞到Native
其他幾個(gè)方法相信看過(guò)上一篇文章的已經(jīng)知道是什么意思,這里就拿setDataSource來(lái)在此說(shuō)明下,這個(gè)方法用于設(shè)置輪播圖數(shù)據(jù),其上的@ReactProp(name = "dataSource")注解表示這個(gè)方法是js來(lái)觸發(fā)的,觸發(fā)的屬性叫做dataSource
ok,先簡(jiǎn)單理解這么多吧

接下來(lái),慣例把自定義的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;
    }
}

最后,就是,寫(xiě)一個(gè)輪播圖的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

到此,我們已經(jīng)把一個(gè)原生輪播控件暴露給了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);

我把改動(dòng)的代碼截取出來(lái)

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都依次對(duì)應(yīng)著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對(duì)應(yīng)著getExportedCustomDirectEventTypeConstants里的onSelectedItem,上文已經(jīng)簡(jiǎn)單介紹過(guò)了,再次回顧下,當(dāng)原生代碼觸發(fā)PageClickEvent事件后,會(huì)回調(diào)到Carousel.js里onSelectedItem方法,進(jìn)而調(diào)用到_onSelectedItem方法,參數(shù)通過(guò)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()
  }

這個(gè)的意思是在js里去調(diào)用reloadData,我們截取YRNBannerManager部分代碼,我們發(fā)現(xiàn)這個(gè)reloadData與getCommandsMap里的reloadData對(duì)應(yīng),并且其會(huì)觸發(fā)COMMAND_RELOAD_DATA命令,這個(gè)命令由receiveCommand接收到并最終處理bannerView.start()來(lái)開(kāi)始輪播

@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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,140評(píng)論 25 708
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,262評(píng)論 8 265
  • 別離十年之際,重拾年華;作鄙詩(shī),憶過(guò)往,敘方今,而自?shī)省?晚風(fēng)拂夜靜,抬首思遐霄。 池畔十年柳,又入綺夢(mèng)中。 綺夢(mèng)...
    朱大餅閱讀 355評(píng)論 1 0
  • 可以從一個(gè)人的穿著來(lái)判斷他的性格,首先,我們可以通過(guò)款式以及搭配判斷,其次是顏色。同樣的人穿不同顏色的衣服,給人的...
    一半一半zyh閱讀 173評(píng)論 0 2
  • 無(wú)論是對(duì)待陌生人還是朋友,真誠(chéng)相待很重要。別人能感受到你的真心,當(dāng)然,也能分辨出虛情假意。 今日話(huà)題:五月目標(biāo)完成...
    sharemy的簡(jiǎn)書(shū)閱讀 143評(píng)論 0 0

友情鏈接更多精彩內(nèi)容