在 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
- //自定义的下拉刷新控件
- public class PullToRefreshView extends ViewGroup {
- ...
- public PullToRefreshView(Context context) {
- ...
- }
- public void setRefreshing(boolean refreshing) {
- ...
- }
- public void setOnRefreshListener(OnRefreshListener listener) {
- ...
- }
- }
官方文档中给我们的示例是创建
的实现类,但此处的下拉刷新控件是个
- 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);
- }
- ...
- }
到这里一个简单的
就实现了。
- ViewGroupManager
但我们这是一个下拉刷新控件,有一个问题是我们如何将下拉刷新的监听事件传递给
呢?官方文档中写的并不清晰,还是翻阅源码吧,果不其然在源码中寻找到了我们想要的答案。
- 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
这个事件名在 JavaScript 端映射到
- topRefresh
回调属性上,这部分我们后面会在结合 JavaScript 再解释下用法。
- onRefresh
关于组件这部分大家可以参看
的
- React Native
部分的代码。
- Android
这部分内容官方文档的介绍足够使用了,这里不再细说。
- public class SwipeRefreshViewManager extends ViewGroupManager<PullToRefreshView>{
- ...
- @ReactProp(name = "refreshing")
- public void setRefreshing(PullToRefreshView view, boolean refreshing) {
- view.setRefreshing(refreshing);
- }
- }
如果你熟悉
的
- Android
集成的话,你只需要将
- React Native
添加到
- SwipeRefreshViewManager
中即可,
- ReactPackage
- public class MainPackage implements ReactPackage {
- ...
- @Override
- public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
- return Arrays.asList(new SwipeRefreshViewManager());
- }
- ...
- }
到这里
端的实现已经全部完成了。
- Android
接下来我们来聊一聊使用
实现下拉刷新的组件,当然在这之前期望你对
- React
的语法及
- jsx/es6
的 API 有一定的了解。
- react/react-native
还记得吗,在
我们通过
- 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};
- /
说到使用就太简单了,虽然简单但仍然要说,我们知道官方提供的组件例如
中通过
- 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>
- )
- }
- }
我就在想既然可以通过
来指定刷新控制器,那我自定义的下拉刷新组件是不是也可以通过
- refreshControl
来指定呢?带着这样的疑问,我仔细读了读
- refreshControl
的源码,发现这个猜想还是蛮靠谱的,也赞叹 Facebook 的工程师们的妙笔生花。
- ListView/ScrollView
- 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 (
- ...
- );
- })
- /
基于以上的分析以及我们对于属性的封装,我们的写法也相当的原味:
- 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>
- )
- }
- }
希望你能有所收获,本文完!
最后祝大家鸡年大吉吧!
来源: