假設(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()));
}
}
附上截圖