博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
可能是最in的React Native使用原生自定义下拉刷新组件
阅读量:4087 次
发布时间:2019-05-25

本文共 7386 字,大约阅读时间需要 24 分钟。

在 2016 年移动端跨平台开发是几个最热的技术之一,相信在 2017 年这股热潮将持续发酵。为什么这么说呢,因为随着业务的爆发式增长,传统的原生开发模式有点显得跟不上节奏了,这也促使各个公司希望寻找到一个更加高效的开发方案,当下可以被选择的方案中, React Native 及Weex 都是不错的技术方案。在年前团队内部的一场 React Native vs Weex 的技术对垒中本来我选择的是 Weex 的阵营,但当时在多维的技术指标中新生的 Weex 还是不敌 React Native ,团队内部最终敲定了采用 React Native 跨平台方案。http://www.tuicool.com/articles/ERjyyiU

1、概述

闲话不多说,这里的主要目的是跟大家聊聊 React Native 在 Android 平台使用原生自定义 View ,这里默认大家对 React Native 已经有一定的了解, React Native 中的组件都是基于 iOS/Android 的官方组件进行封装,所以在一些特别的场景下并不能很好的满足需求。正如标题中的下拉刷新组件, React Native 在 Android 平台采用的是 android.support.v4.widget.SwipeRefreshLayout ,一些 iOS 设计优先的团队(譬如我司)而言对于 Android 开发人员简直就是灾难。在众多开源的 React Native 项目中大家也不会再这些细节上较真,但是公司的 UED 这关可不好过。

听说流行有图有真相,那先来个在 iOS 端经典的菊花图的 Android reac-native 版:

2、Android 端的支持实现

适配 Android 平台的原生组件可以参看官方文档  ,如果网络不方便的话也可以参看翻译版  。

2.1 自定义下拉刷新控件

这里就不讲如何自定义 Android 控件,假设你是一位有一定经验的开发人员。

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

2.2 创建 ViewManager 的实现类

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

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

到这里一个简单的 ViewGroupManager 就实现了。

2.3 给 ViewManager 添加事件监听

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

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

public class SwipeRefreshViewManager extends ViewGroupManager
{ ... @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
getExportedCustomDirectEventTypeConstants() { return MapBuilder.
builder() .put("topRefresh", MapBuilder.of("registrationName", "onRefresh")) .build(); } ...}

我们将事件封装为 PtrRefreshEvent 。

public class PtrRefreshEvent extends Event
{ 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 部分的代码。

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

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

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

2.5 将 ViewManager 注册到应用

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

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

到这里 Android 端的实现已经全部完成了。

3、React/JS 端的组件实现及使用

接下来我们来聊一聊使用 React 实现下拉刷新的组件,当然在这之前期望你对 jsx/es6 的语法及 react/react-native 的 API 有一定的了解。

3.1 实现下拉刷新组件

还记得吗,在 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 (            
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};

3.2 下拉刷新组件的使用

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

class Demo1 extends Component {    ...    render() {        return (            
} />
) }}

我就在想既然可以通过 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},          
{contentContainer}
); } } return ( ... );})

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

class Demo2 extends Component {    ...    render() {        return (            
:
} />
) }}

希望你能有所收获,本文完!

转载地址:http://pphni.baihongyu.com/

你可能感兴趣的文章
HTML5项目笔记7:使用HTML5 WebStorage API构建与.NET对应的会话机制
查看>>
[NOI2004]郁闷的出纳员
查看>>
CentOS 7 系统的初始划配置
查看>>
系统性能瓶颈会存在那几个方面?如何分析
查看>>
IE6开发调试插件:IE Developer Toolbar
查看>>
最简单的JAVA解析XML字符串方法
查看>>
spring 依赖注入到直接new 对象
查看>>
四则运算结对作业
查看>>
重启outlook的bat脚本
查看>>
2018-2019-2 网络对抗技术 20165329 Exp4 恶意代码分析
查看>>
ASP.NET 文件后缀名详解
查看>>
laravel中redis队列的使用
查看>>
编程之美资格题目2 : 大神与三位小伙伴
查看>>
解决Axis2在webservice中遇到特殊字符的无法传输的缺陷(<CDATA>数据类型)
查看>>
VS打包方法(安装和部署简介)——内含大量图片,密症慎入!
查看>>
JS获取radio的那些事
查看>>
Python面向对象编程(二)
查看>>
字典dict的深入学习(item() / items() 一致的)
查看>>
spring的bean的注解配置
查看>>
闲扯加班
查看>>