ReactNative自定义控件之 RefreshLayout

来源:互联网 发布:ubuntu怎么安装wps 编辑:程序博客网 时间:2024/05/18 02:57

ReactNative自定义控件之 RefreshLayout

1 自定义下拉刷新控件

    //自定义的下拉刷新控件public class PullToRefreshView extends ViewGroup {    ...    public PullToRefreshView(Context context) {        ...    }      public void setRefreshing(boolean refreshing) {        ...    }    public void setOnRefreshListener(OnRefreshListener listener) {        ...    }}

2 创建 ViewManager 的实现类

官方文档中给我们的示例是创建 SimpleViewManager 的实现类,但此处的下拉刷新控件是个 ViewGroup ,所以此处实现类应继承 ViewManager 的另一个子类 ViewGroupManager

public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{    @Override    public String getName() {        return "PtrLayout";    }    @Override    protected PullToRefreshView createViewInstance(ThemedReactContext reactContext) {        return new PullToRefreshView(reactContext);    }    ...}

3 给 ViewManager 添加事件监听

但我们这是一个下拉刷新控件,有一个问题是我们如何将下拉刷新的监听事件传递给 JavaScript 呢?官方文档中写的并不清晰,还是翻阅源码吧,果不其然在源码中寻找到了我们想要的答案。

覆写 addEventEmitters 函数将事件监听传递给 JavaScript 。

    public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{    ...    @Override    protected void addEventEmitters(ThemedReactContext reactContext, PullToRefreshView view) {        view.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {            @Override            public void onRefresh() {                reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()                        .dispatchEvent(new PtrRefreshEvent(view.getId()));            }        });    }    @Nullable    @Override    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {        return MapBuilder.<String, Object>builder()                .put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))                .build();    }    ...}//我们将事件封装为 PtrRefreshEvent 。public class PtrRefreshEvent extends Event<PtrRefreshEvent>{    protected PtrRefreshEvent(int viewTag) {        super(viewTag);    }    @Override    public String getEventName() {        return "topRefresh";    }    @Override    public void dispatch(RCTEventEmitter rctEventEmitter) {        rctEventEmitter.receiveEvent(getViewTag(),getEventName(),null);    }}细心地你肯定发现了 getExportedCustomDirectEventTypeConstants 这个函数,这里先说明一下,覆写该函数,将 topRefresh 这个事件名在 JavaScript 端映射到 onRefresh 回调属性上,这部分我们后面会在结合 JavaScript 再解释下用法。关于组件这部分大家可以参看 React Native 的 Android 部分的代码。

4 使用@ReactProp 注解导出属性的设置方法

这部分内容官方文档的介绍足够使用了,这里不再细说。

public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{    ...    @ReactProp(name = "refreshing")    public void setRefreshing(PullToRefreshView view, boolean refreshing) {        view.setRefreshing(refreshing);    }}

5 将 ViewManager 注册到应用

如果你熟悉 Android 的 React Native 集成的话,你只需要将 SwipeRefreshViewManager 添加到 ReactPackage 中即可

public class MainPackage implements ReactPackage {    ...    @Override    public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {        return Arrays.asList(new SwipeRefreshViewManager());    }    ...}

6 实现下拉刷新组件

还记得吗,在 Android 我们通过 SwipeRefreshViewManager 中 getName 返回的控件名称,将会在这里用于引用这个原生控件。

'use strict';import React, {Component, PropTypes} from 'react';import {View, requireNativeComponent} from 'react-native';import NativeMethodsMixin from 'react/lib/NativeMethodsMixin';import mixin from 'react-mixin';//引用原生下拉刷新控件const NativePtrView = requireNativeComponent('PtrLayout', PtrView);//封装一个react组件,该组件中引用了原生控件的实现class PtrView extends Component {    static propTypes = {        ...View.propTypes,        onRefresh: PropTypes.func,        refreshing: PropTypes.bool.isRequired    };    _nativeRef = (null: ?PtrView);    _lastNativeRefreshing = false;    constructor(props) {        super(props);    }    componentDidMount() {        this._lastNativeRefreshing = this.props.refreshing;    }    componentDidUpdate(prevProps = {refreshing: false}) {        if (this.props.refreshing !== prevProps.refreshing) {            this._lastNativeRefreshing = this.props.refreshing;        } else if (this.props.refreshing !== this._lastNativeRefreshing) {            this._nativeRef.setNativeProps({refreshing: this.props.refreshing});            this._lastNativeRefreshing = this.props.refreshing;        }    }    //渲染原生下拉刷新控件,这里onRefresh就是在ViewManager::getExportedCustomDirectEventTypeConstants     //这个函数中 topRefresh 的映射属性。     render() {        return (            <NativePtrView                {...this.props}                ref={ref => this._nativeRef = ref}                onRefresh={this._onRefresh.bind(this)}/>        )    }    _onRefresh() {        this._lastNativeRefreshing = true;        this.props.onRefresh && this.props.onRefresh();        this.forceUpdate();    }}mixin.onClass(PtrView, NativeMethodsMixin);export {PtrView};

7 下拉刷新组件的使用

说到使用就太简单了,虽然简单但仍然要说,我们知道官方提供的组件例如 ListView 中通过 refreshControl 来指定刷新控制器,用法是这样的:

class Demo1 extends Component {    ...    render() {        return (            <View style={{flex: 1}}>                <ListView                     ...                    refreshControl={                        <RefreshControl                            refreshing={this.state.refreshing}                            onRefresh={this._refresh.bind(this)} />                    }                />            </View>        )    }}

8 我就在想既然可以通过 refreshControl 来指定刷新控制器,那我自定义的下拉刷新组件是不是也可以通过 refreshControl 来指定呢?带着这样的疑问,我仔细读了读 ListView/ScrollView 的源码,发现这个猜想还是蛮靠谱的,也赞叹 Facebook 的工程师们的妙笔生花。

const ScrollView = React.createClass({    let ScrollViewClass;    if (Platform.OS === 'ios') {      ScrollViewClass = RCTScrollView;    } else if (Platform.OS === 'android') {      if (this.props.horizontal) {        ScrollViewClass = AndroidHorizontalScrollView;      } else {        ScrollViewClass = AndroidScrollView;      }    }    ...    const refreshControl = this.props.refreshControl;    if (refreshControl) {      if (Platform.OS === 'ios') {        ...      } else if (Platform.OS === 'android') {        // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.        // Since the ScrollView is wrapped add the style props to the        // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.        // 此处就是重点,通过 cloneElement 创建一个新的 ReactElement,而 refreshControl 是通过 props 指定而来并没有写死,Good!        return React.cloneElement(          refreshControl,          {style: props.style},          <ScrollViewClass {...props} ref={this._setScrollViewRef}>            {contentContainer}          </ScrollViewClass>        );      }    }    return (      ...    );})

9 基于以上的分析以及我们对于属性的封装,我们的写法也相当的原味:

class Demo2 extends Component {    ...    render() {        return (            <View style={{flex: 1}}>                <ListView                     ...                    refreshControl={                         //这里为了保证只在Android平台上使用该组件,如果iOS端也有原生控件的实现,                         //那就不必考虑平台了。                         Platform.OS === 'android' ?                          <PtrView                              refreshing={this.state.refreshing}                              onRefresh={this._refresh.bind(this)} />                         :                         <RefreshControl                              refreshing={this.state.refreshing}                              onRefresh={this._refresh.bind(this)} />                    }                />            </View>        )    }}
0 0
原创粉丝点击