[React Native]原生UI组件(上)

834 查看

前面两篇文章主要介绍了如何在ReactNative中集成并使用原生模块的代码,本篇文章会讲解另一个和原生模块有关的重点内容,那就是在ReactNative中使用已有的原生UI组件。

总所周知,移动App快速发展的这几年,用户对于交互体验(UI、UE)的要求越来越高,因此,在原生开发中涌现了很多优秀的UI组件。ReactNative做为一个新的技术方向,目前在UI组件的积累难免还没有原生那么丰富和强大,所以我们必须要学会如何在ReactNative中使用一些我们已有的原生UI组件,并能和这些组件进行“交流”(数据、事件等)。

本文会演示如何在ReactNative中使用“仿QQ”滑动删除消息组件SwipeMenuListView,对这个控件不熟悉的朋友请先查看官方说明,本篇文章的最终效果图如下


device-2016-06-05-204355_0-76.gif

正如上图所演示的,使用原生UI会涉及到"数据""事件"两个部分,由于篇幅较长,我们会分成上下两篇文章讲解,本篇文章会讲解"数据"部分。

那么,下面我们将一步步演示如何实现上面这个图的效果

  • 步骤一:新建ReactNative项目

使用下面的命令新建一个ReactNative项目

react-native init Demo4

让我们运行下新建的项目,效果如下


Paste_Image.png
  • 步骤二:在原生项目中引入SwipeMenuListView

使用AndroidStudio打开Demo4目录下的android文件夹,打开app目录下的build.gradle文件,在dependencies节点中加入以下部分

compile 'com.baoyz.swipemenulistview:library:1.3.0'
  • 步骤三:导出原生视图SwipeMenuListView给JS模块使用

原生视图需要被ViewManager创建和管理,这样才能被JS模块使用。ViewManager是一个抽象的类,我们可以使用它的派生类SimpleViewManager来实现,另外SimpleViewManager还可以将我们的原生视图的属性导出给JS模块,这样我们在JS模块就可以传递数据给原生视图(或变量、属性等)来改变原生视图的样式和行为。

提供原生视图的步骤如下:

  1. 创建一个SimpleViewManager的子类AppViewManager
  2. 实现createViewInstance方法
  3. 导出视图的属性:使用@ReactProp注解来导出属性的设置方法
  4. 注册视图
  5. 实现JS模块

第4步与我们前面两篇文章讲解的的原生模块比较一致,原生模块和UI都需要注册才能使用,下面我们将详细介绍这5个步骤。

1. 创建一个SimpleViewManager的子类

...
public class AppViewManager extends SimpleViewManager<SwipeMenuListView> {    
  @Override  
  public String getName() {        
    return "SwipeMenuListView";    
  }

这个例子里我们创建一个视图管理类AppViewManager,它继承自SimpleViewManager<SwipeMenuListView>SwipeMenuListView是这个视图管理类所管理的对象类型,这应当是一个自定义的原生视图,getName方法会用于JS端引用这个原生视图。

2. 实现createViewInstance方法

...
@Override
protected SwipeMenuListView createViewInstance(final ThemedReactContext reactContext) {
    SwipeMenuListView swipeMenuListView = new SwipeMenuListView(reactContext);

    // set creator
    swipeMenuListView.setMenuCreator(initMenu(reactContext));

    return swipeMenuListView;
}

这里,我们直接new一个SwipeMenuListView,然后使用setMenuCreator方法设置菜单,我们写了一个initMenu方法来封装菜单的实现过程,由于这里不是本文介绍的重点,这里不做分析,有兴趣的朋友可以查看文章最后的源代码。

3. 导出视图的属性:使用@ReactProp注解来导出属性的设置方法
这里我们引用官方的说明:

要导出属性给JS使用,需要申明带有@ReactProp注解的设置方法。方法的第一个参数是要修改属性的视图实例,第二个参数是要设置的属性值。方法的返回值类型必须为void,而且访问控制必须被声明为public。JavaScript所得知的属性类型会由该方法第二个参数的类型来自动决定。支持的类型有:boolean, int, float, double, String, Boolean, Integer,ReadableArray, ReadableMap。

...
/**
 * 导出属性"array"给JS模块调用
 * @param swipeMenuListView
 * @param array
 */
@ReactProp(name = "array")
public void setDataSource(SwipeMenuListView swipeMenuListView, ReadableArray array)
{
    dataSource = new ArrayList<>();
    for(int i = 0; i < array.size(); i++)
    {
        dataSource.add(array.getString(i));
    }
    adapter = new MyAdapter(mContext, dataSource);
    swipeMenuListView.setAdapter(adapter);
}

这个方法做的事情很简单,主要是将ReadableArray类型的数据转换为我们熟知的ArrayList,然后使用MyAdapterSwipeMenuListView绑定一个数据源。MyAdapter也很简单,它的布局仅有一个TextView,这里不做分析。

4. 注册视图
在Java中的最后一步就是将视图控制器注册到应用中,和[React Native]原生模块(上)介绍的一样,我们需要新建一个class:ReactPackager,并且实现ReactPackage接口。不同的是,原生模块需要添加到createNativeModules中,而原生UI则需要添加到createViewManagers中。

...
public class AppReactPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
      return Collections.emptyList();
  }

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
      return Collections.emptyList();
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
      // 注册AppViewManager
      List<ViewManager> viewManagers = new ArrayList<>();
      viewManagers.add(new AppViewManager());
      return viewManagers;
  }
}

当然,AppReactPackage需要在MainActivitygetPackages中注册并返回。

...
@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new AppReactPackage()
    );
}

5. 实现JS模块
我们需要新建一个JS文件,来描述我们在AppViewManagergetName方法返回的SwipeMenuListView

'use strict';
var {
    PropTypes
} = require('react');
var {
    requireNativeComponent, View
} = require('react-native');

var iface = {
    name: 'SwipeMenuListView',
    propTypes: {
        array: PropTypes.arrayOf(PropTypes.string),
        ...View.propTypes,// 包含默认的View的属性
    },
};

module.exports = requireNativeComponent('SwipeMenuListView', iface);

requireNativeComponent通常接受两个参数:
第一个参数AppViewManagergetName方法返回的视图名称。
第二个参数是一个描述UI组件接口的对象,这个对象通常包括两个重要的部分:

  1. name:便于调试时显示(你可以设置为任意字符串)。
  2. propTypes:用来声明@ReactProp(name = "array")注解中array参数的JS类型,这里PropTypes.arrayOf(PropTypes.string)表示字符串类型的数组。另外...View.propTypes用来声明View的默认属性,记住这一个声明是必须的,否则你将无法正确使用原生UI。

最后,我们看下如何在JS模块使用这个原生UI

...
class Demo4 extends Component {
  render() {
    return (
      <View style={styles.container}>
        <SwipeMenuListView style={styles.listView} array={["Java", "C", "C++", "C#", "Python", "PHP"
              , "Visual Basic .NET", "JavaScript", "Assembly Language", "Ruby", "Perl"
              , "Delphi", "Visual Basic", "Swift", "MATLAB", "Pascal"]}>
        </SwipeMenuListView>
      </View>
    );
  }
}
...
AppRegistry.registerComponent('Demo4', () => Demo4);

这里需要说明的是,一定要指明SwipeMenuListView的高度和宽度,否则无法显示。

本文的源码地址Demo4

下一篇文章会介绍ReactNative与原生UI组件“事件”交互的内容,感兴趣的朋友请继续阅读[React Native]原生UI组件(下)