作者:Oz
版权声明:本文图文为博主原创,转载请注明出处。
在 2016 年移动端跨平台开发是几个最热的技术之一,相信在 2017 年这股热潮将持续发酵。为什么这么说呢,因为随着业务的爆发式增长,传统的原生开发模式有点显得跟不上节奏了,这也促使各个公司希望寻找到一个更加高效的开发方案,当下可以被选择的方案中,React Native
及 Weex
都是不错的技术方案。在年前团队内部的一场 React Native vs Weex
的技术对垒中本来我选择的是 Weex
的阵营,但当时在多维的技术指标中新生的 Weex
还是不敌 React Native
,团队内部最终敲定了采用 React Native
跨平台方案。
概述 闲话不多说,这里的主要目的是跟大家聊聊 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
版:
Android 端的支持实现 适配 Android
平台的原生组件可以参看官方文档 Native UI Components ,如果网络不方便的话也可以参看翻译版原生UI组件 。
自定义下拉刷新控件 这里就不讲如何自定义 Android
控件,假设你是一位有一定经验的开发人员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class PullToRefreshView extends ViewGroup { ... public PullToRefreshView (Context context) { ... } public void setRefreshing (boolean refreshing) { ... } public void setOnRefreshListener (OnRefreshListener listener) { ... } }
创建 ViewManager 的实现类 官方文档中给我们的示例是创建 SimpleViewManager
的实现类,但此处的下拉刷新控件是个 ViewGroup
,所以此处实现类应继承 ViewManager
的另一个子类 ViewGroupManager
:
1 2 3 4 5 6 7 8 9 10 11 12 public class SwipeRefreshViewManager extends ViewGroupManager <PullToRefreshView>{ @Override public String getName () { return "PtrLayout" ; } @Override protected PullToRefreshView createViewInstance (ThemedReactContext reactContext) { return new PullToRefreshView (reactContext); } ... }
到这里一个简单的 ViewGroupManager
就实现了。
给 ViewManager 添加事件监听 但我们这是一个下拉刷新控件,有一个问题是我们如何将下拉刷新的监听事件传递给 JavaScript
呢?官方文档中写的并不清晰,还是翻阅源码吧,果不其然在源码中寻找到了我们想要的答案。
覆写 addEventEmitters
函数将事件监听传递给 JavaScript
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
部分的代码。
使用@ReactProp 注解导出属性的设置方法 这部分内容官方文档的介绍足够使用了,这里不再细说。
1 2 3 4 5 6 7 public class SwipeRefreshViewManager extends ViewGroupManager <PullToRefreshView>{ ... @ReactProp(name = "refreshing") public void setRefreshing (PullToRefreshView view, boolean refreshing) { view.setRefreshing(refreshing); } }
将 ViewManager 注册到应用 如果你熟悉 Android
的 React Native
集成的话,你只需要将 SwipeRefreshViewManager
添加到 ReactPackage
中即可:
1 2 3 4 5 6 7 8 public class MainPackage implements ReactPackage { ... @Override public List<ViewManager> createViewManagers (ReactApplicationContext reactApplicationContext) { return Arrays.asList(new SwipeRefreshViewManager ()); } ... }
到这里 Android
端的实现已经全部完成了。
React/JS 端的组件实现及使用 接下来我们来聊一聊使用 React
实现下拉刷新的组件,当然在这之前期望你对 jsx/es6
的语法及 react/react-native
的 API 有一定的了解。
实现下拉刷新组件 还记得吗,在 Android
我们通过 SwipeRefreshViewManager
中 getName
返回的控件名称,将会在这里用于引用这个原生控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 '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 );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 ; } } 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 };
下拉刷新组件的使用 说到使用就太简单了,虽然简单但仍然要说,我们知道官方提供的组件例如 ListView
中通过 refreshControl
来指定刷新控制器,用法是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Demo1 extends Component { ... render ( ) { return ( <View style ={{flex: 1 }}> <ListView ... refreshControl ={ <RefreshControl refreshing ={this.state.refreshing} onRefresh ={this._refresh.bind(this)} /> } /> </View > ) } }
我就在想既然可以通过 refreshControl
来指定刷新控制器,那我自定义的下拉刷新组件是不是也可以通过refreshControl
来指定呢?带着这样的疑问,我仔细读了读 ListView/ScrollView
的源码,发现这个猜想还是蛮靠谱的,也赞叹 Facebook 的工程师们的妙笔生花,真是应了那句话叫“不要写死”!Facebook 的工程师做到了…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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' ) { return React .cloneElement ( refreshControl, {style : props.style }, <ScrollViewClass {...props } ref ={this._setScrollViewRef} > {contentContainer} </ScrollViewClass > ); } } return ( ... ); })
基于以上的分析以及我们对于属性的封装,我们的写法也相当的原味:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 > ) } }
希望你能有所收获,本文完!
最后祝大家鸡年大吉吧!