React Native for Android 的发布,对一个 Android 开发者来说还是有相当的吸引力的。通过前面这篇博客:React Native for Android 入门老虎好不容易入了门了,然后想找一个简单的项目,来练练手。一方面来熟悉一下 RN(React Native, 后面都做此简写),另一方面来验证使用 RN 来实现一个相对完整的项目的可行性。

平时用的最多的客户端之一:知乎日报,这个 APP 相对简单,而且也找到了有人分析的知乎日报 API 分析。就选择它了:实现一个 React Native 版的知乎日报客户端,目标是尽量实现官方客户端一致的效果。

这篇文章主要讲使用 RN 来实现知乎日报客户端的可行性和实现方法。整个项目开源在 GitHub 上:ZhiHuDaily-React-Native,欢迎 Star 和 PR。

基本概念

这里以我的个人理解,快速过一下 React Native 中一些基本概念。如果和官方的理解有些偏差,还请指出。

1. 组件

React Native 主要是通过 Virtual Dom 来实现显示页面或者页面中的模块。可以通过 React.createClass() 来创建自己的 Dom,在 React 中称之为组件(Component)。创建之后,就可以直接像 HTML 标准标签一样使用了。如下:

var MyCustomComponent = React.createClass({  ...
});// 然后就可以这样使用
<MyCustomComponent />

到底什么是一个组件?我的理解就是页面上的一个逻辑单元。组件可以小到一个按钮,也可以大到整个页面,组件嵌套组合,就成了各种复杂的界面了。

2. 组件生命周期

类似于 Android 中的一个 View,它也有自己的生命周期,有自己的状态。React 组件的数据保存在自己内部的 state 变量中。每个组件都有自己的生命周期,每个生命周期都有对应的回调,这个和 Android 中的 View 非常类似:

  • getInitialState:获得初始化组件状态,只调用一次
  • componentWillMount:组件将要加载,只调用一次
  • componentDidMount:组件加载完成并显示出来了,也就是完成了一次绘制,只调用一次
  • render:绘制组件,可能调用多次。

具体要写自己的页面的话,要从哪里入手呢?我们这里就要来看一下 React.createClass() 是什么的。这个方法可以辅助你创建一个组件,其中传入创建组件的参数,就是自定义组件需要的内容。一个基本的自定义组件写法如下:

var MyCustomComponent = React.createClass({  // 这里返回一个对象,设置组件的初始化状态,// 后面就可以通过 this.state 来获得这个对象getInitialState: function() {return {key1: data1,key2: data2,...};},// 这里一般做一些和界面显示无关的初始化操作componentWillMount: function() {},// 这里一般做加载数据的操作componentDidMount: function() {},// 这是最重要的函数,用来绘制界面,// 所有的自定义组件,这个函数是必须提供的render: function() {return(<View>...</View>);},
});

一个自定义组件基本上就是上面那样定义了。只有 render 函数是必须的,其他都是可选的。

3. 组件的数据

绘制界面部分,一般情况下会根据组件的状态 state 来绘制动态页面,例如下面一个最简单的例子:

render: function() {  return(<Text>{this.state.key1}</Text>);
}

这里就是直接把状态中的 key1 的值用 Text 组件直接显示出来。

另外,React 组件中最重要的一个概念就是 state -- 组件的状态。除了前面的使用 getInitialState 方法来设置初始化状态外。在界面逻辑处理或者事件交互的过程中,可以调用 this.setState(...) 方法来修改组件的状态值。

如果在代码中直接修改 state,React 就会把旧状态和新状态做一个 diff,找到变化的部分,然后对应找到和这个变化的值关联的界面部分,请求重新绘制这个部分。例如刚才的例子中,如果调用:

this.setState({key1: 'Hello world!'});

界面上的 Text 内容马上就会显示出 Hello world!

组件中还有一种数据:属性(Property),这种数据可以通过 this.props 来直接获取,例如非常常见的

<View style={{flex: 1}}>

这里的 style 就是 View 这个组件的一个属性。

那么属性(props)和状态(state)两种数据有什么区别呢?一般 属性 表示静态的数据,组件创建后,就基本不变的内容,状态 是动态数据。

4. React Native 布局

关于 React Native 的布局,实用的是 FlexBox 实现,类似网页的 CSS 布局方法,具体可以参考官方推荐的 A Complete Guide to Flexbox 和官方文档 Flexbox。关于布局说起原理比较简单,但是要很灵活的写出你想要的样式,还是需要慢慢积累经验。

另外,值得一提的是,React Native 中的样式长度单位,是逻辑单位,概念和 Android 中的 dp 一样。

以上就是 React Native 的基本逻辑,有了这些概念,我们就可以开始写 APP 了。

APP 开发实践

我们要实现的知乎日报的 APP 的主页面是一个文章列表,左边可以滑动出来抽屉,账号信息和显示主题列表。选择主题列表,可以在列表页更新对应主题的文章列表。点击文章列表进入文章详情。还有评论,点赞,登录等功能初期并不计划做。

1. 抽屉的实现

庆幸的是,官方提供了 DrawerLayoutAndroid 组件,这个组件其实就是对 Android 中的 DrawerLayout 的封装。可以参考官方文档,使用过 Native 版本的 DrawerLayout话,很容易上手这个组件。主要代码如下:

render: function() {  ...return (<DrawerLayoutAndroidref={(drawer) => { this.drawer = drawer; }}drawerWidth={Dimensions.get('window').width - DRAWER_WIDTH_LEFT}keyboardDismissMode="on-drag"drawerPosition={DrawerLayoutAndroid.positions.Left}renderNavigationView={this._renderNavigationView}><View style={styles.container}>...{content}</View></DrawerLayoutAndroid>);
}

其中 renderNavigationView 属性,表示抽屉里面显示的内容。本项目的实现,可以参考:ListScreen.js。

2. 主页文章列表

文章列表在 Android 可以用 ListView 实现,React Native 也很贴心提供了对应的组件 ListView。实用方法和 Android 原生的也类似,需要提供一个数据源 dataSource 和一个基本的绘制每行界面的函数。借用官方的一个代码片段:

getInitialState: function() {  var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});return {dataSource: ds.cloneWithRows(['row 1', 'row 2']),};
},render: function() {  return (<ListViewdataSource={this.state.dataSource}renderRow={(rowData) => <Text>{rowData}</Text>}/>);
},

这是一个最简的 ListView 使用的例子。其实,React Native 提供的 ListView 比原生还要强大一些,提供了列表的 Section 的支持,列表可以分节,可以显示每节的头部,这个和 iOS 的 UITableView 类似。

因为知乎日报的文章列表是按照日期分 Section 的。具体的使用方法在官方的例子 UIExplorer 中有例子。问项目中可以参考这个文件: ListScreen.js。

3. 详情页的实现

知乎日报的文章详情页是使用一个 WebView 显示内容的。遗憾的是,React Native 官方在 Android 上并没有提供 WebView 的支持。好在 React Native 很容易集成原生的组件:Native UI Components。我就按照官方文档,导出一个 React 的 WebView 组件。Java 端的代码如下:

public class ReactWebViewManager extends SimpleViewManager<WebView> {public static final String REACT_CLASS = "RCTWebView";@UIProp(UIProp.Type.STRING)public static final String PROP_URL = "url";@UIProp(UIProp.Type.STRING)public static final String PROP_HTML = "html";@UIProp(UIProp.Type.STRING)public static final String PROP_CSS = "css";@Overridepublic String getName() {return REACT_CLASS;}@Overrideprotected WebView createViewInstance(ThemedReactContext reactContext) {return new WebView(reactContext);}@Overridepublic void updateView(final WebView webView, CatalystStylesDiffMap props) {super.updateView(webView, props);if (props.hasKey(PROP_URL)) {webView.loadUrl(props.getString(PROP_URL));}if (props.hasKey(PROP_HTML)) {String html = props.getString(PROP_HTML);if (props.hasKey(PROP_CSS)) {String css = props.getString(PROP_CSS);html = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + css + "\" />" + html;}webView.loadData(html, "text/html; charset=utf-8", "UTF-8");}}
}

这里我导出了一个简单的 WebView,并暴露了 urlhtmlcss 三个属性。url 表示网页要显示的网页地址,html 表示要加载的 HTML 字符串, css 表示网页样式链接。还要注册这个 ReactWebViewManager 到 ReactInstanceManager 中。具体代码可以看 MyReactPackage.java 和 MainActivity.java

在 JS 端,需要做对应的封装:

class ObservableWebView extends React.Component {  ...render() {return <RCTWebView {...this.props} onChange={this._onChange} />;}
}ObservableWebView.propTypes = {  url: PropTypes.string,html: PropTypes.string,css: PropTypes.string,onScrollChange: PropTypes.func,
};var RCTWebView = requireNativeComponent('RCTWebView', ObservableWebView, {  nativeOnly: {onChange: true}
});module.exports = ObservableWebView;

然后就可以在 React 中使用了,如下:

var MyWebView = require('./WebView');render: function() {  return (<View style={styles.container}><MyWebViewstyle={styles.content}html={this.state.detail.body}css={this.state.detail.css[0]}/></View>);
}

这样就能直接显示了网页内容,挺出乎我意料的简单。

还有一个细节,官方客户端,随着 WebView 的滑动,头部的 Image 也跟着往上收起来。这里我们就要监听 WebView 的滑动事件,然后来设置头部的 Image 的跟随移动。还好,官方文档也提供了一个可以方便从 Native 往 React 传递事件的方法:Events。跟着文档来,实现了一个 ObservableWebView,继承于原生的 WebView,同时把滑动事件上报给 React:

// ObservableWebView.java
public class ObservableWebView extends WebView {  ...@Overrideprotected void onScrollChanged(final int l, final int t, final int oldl, final int oldt){super.onScrollChanged(l, t, oldl, oldt);WritableMap event = Arguments.createMap();event.putInt("ScrollX", l);event.putInt("ScrollY", t);ReactContext reactContext = (ReactContext)getContext();reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topChange", event);}
}

这里在 onScrollChanged() 回调中,就是往 React 中报 topChange 事件。事件中包含 ScrollX 和 ScrollY 两个值。这里的 topChange 被映射到了 JS 的 onChange 事件。在 React 层就能这样用了: <MyWebView onChange={onChangeCallback}/>,这里的 onChangeCallback 是一个自定义的回调函数。WebView 滑动的时候,就会回调到这个函数中来。为了实用方便,这里还可以做一些封装,把 topChange 封装为我们关心的滑动事件 onScrollChange

class ObservableWebView extends React.Component {  constructor() {super();this._onChange = this._onChange.bind(this);}_onChange(event: Event) {if (!this.props.onScrollChange) {return;}this.props.onScrollChange(event.nativeEvent.ScrollY);}render() {return <RCTWebView {...this.props} onChange={this._onChange} />;}
}

详情可以参考:WebView.js。

这时,我们就可以在 React 组件中的 onScrollChange 事件回调中实现滑动详情页的头部图片的效果:

onWebViewScroll: function(event) {  // 这里移动头部的 Image
},
render: function() {  return (<View style={styles.container}><MyWebView...onScrollChange={this.onWebViewScroll}/><Imageref={REF_HEADER}source={{uri: this.state.detail.image}}style={styles.headerImage} />{toolbar}</View>);
},

这里的写起来也很简单。关键看一下 onWebViewScroll 函数的实现。最简单的实现方法就是,通过 ScrollY 来设置组件的 state,来让 React 自动触发重绘。因为事件上报非常频繁,就会触发大量的重绘,会带来严重的性能问题。

React Native 提供了 Direct Manipulation,也就是直接操作组件,这种方式不会触发重绘,效率会高很多。

onWebViewScroll: function(event) {  // 像素转为 React 中的大小单元var scrollY = -event / PIXELRATIO;var nativeProps = precomputeStyle({transform: [{translateY: scrollY}]});// 直接操作组件的属性this.refs[REF_HEADER].setNativeProps(nativeProps);
},

到这里,实现这个 React Native 版的知乎日报客户端所涉及的技术点,基本都讲完了。还有很多细节请参考源码:ZhiHuDaily-React-Native,欢迎一起交流,和发 pull request 来一起完善这个项目。

总结

这篇文章几百字就写完了,看起来实现这个客户端并不复杂。其实,这里有远超过我想象的坑,后面我应该还会写一篇文章,来总结这个项目中遇到的坑。总体来说,React Native for Android 作为初期的版本,实现一个简单 APP 已经可行。但是它并不完善,如果想用在实际项目中,还需要慎重考量。

最后,大家可以关注这个项目:ZhiHuDaily-React-Native。希望能对开始关注 React Native 的同学有些帮助。

React Native for Android 实践 — 实现知乎日报客户端相关推荐

  1. React Native在Android当中实践(五)——常见问题

    React Native在Android当中实践(一)--背景介绍 React Native在Android当中实践(二)--搭建开发环境 React Native在Android当中实践(三)--集 ...

  2. React Native在Android当中实践(一)——背景介绍

    React Native在Android当中实践(一)--背景介绍 React Native在Android当中实践(二)--搭建开发环境 React Native在Android当中实践(三)--集 ...

  3. React Native和Android整合详解

    前言 按照React Native的迭代速度,使用官网的文档,已经不能很顺利的实现React Native和Android的有效整合.React Native最新版本 已经是0.39.为了更好的讲解R ...

  4. Android方法调用实体类的值,React Native调用Android原生方法和传值

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 创建react native 项目:react-native init callAndroidProject cd ca ...

  5. 20分钟理解React Native For Android原理

    原址:http://doslin.com/2017/03/15/react-native-source-code-analysis/ 前言 文中所有 RN 缩写指代React Native For A ...

  6. react native开发Android 篇——集成自定义的字体

    react native开发Android 篇--集成自定义的字体 第一种:link添加自定义字体 第二种:直接复制字体到`android/app/src/main/assets/fonts`目录下 ...

  7. React Native调用Android接口

    由于工作需要近期研究了下React Native调用Android接口,该文章将介绍自己在RN环境搭建和封装第三方SDK接口以及RN调用Android接口的趟坑过程.(第一次写博客,写的不好请大家勿喷 ...

  8. react native开发Android 篇——APP名称、图标、启动页

    react native开发Android 篇--APP名称.图标.启动页 设置APP名称 设置APP图标 设置启动页 隐藏启动页 设置APP名称 编辑 android/app/src/main/re ...

  9. android版知乎日报客户端应用源码

    如你所见,这是一个知乎日报客户端,我给它起名为:知乎小报,大家可以下载安装体验一下,见附件.IOS版本源码:http://code.662p.com/view/9004.html 因为有大把的时间需要 ...

最新文章

  1. P3806 【模板】点分治1
  2. 制度缺陷还是人性不堪
  3. 2018-2019年度 AI方向Top10 综述
  4. 断点续传和下载原理分析
  5. Spring :Sprin体系
  6. 为一路通(16tone)开博
  7. 【jenkins】jenkins+maven+gitlab+testng,jenkins配置
  8. Ubuntu18.04修改主机名和用户名
  9. 存到mysql的中文乱码_web项目存数据到数据库,中文乱码,解决过程
  10. 《伯克毕生发展心理学3》
  11. android 加载第三方so文件,Uni-app 以Module方式开发Android插件,引入第三方资源包so文件,但无法读取...
  12. [转载] 老鼠夹的寓言
  13. 网络篇 思科设备的登陆方式07
  14. route 命令的使用
  15. Structure of Heap
  16. DDR3内存参数配置
  17. 对接完 115 家医院的需求后,复盘下如何用产品的思路进行募捐
  18. linux所有信号,【转载】Linux 信号列表
  19. spring 事务应用误区总结:那些导致事务不回滚的坑
  20. C++学习32:侯捷C++11,14新特性(标准库部分)

热门文章

  1. java怎么对接支付
  2. AndroidAPK逆向分析
  3. excel转ORACLE数据库,excel数据出入Oracle数据库(转)
  4. PrivacyIN Week1课程回顾 | 张宇鹏博导开讲零知识证明密码学基础研究导论
  5. 基于ijkplayer 0.8.8编译的完整so. libijkffmpeg.so等,支持ssl h265, rm, rmvb
  6. 银行、金融业固定资产管理软件
  7. ChatExcel 来了,太酷炫了!
  8. js 调用window.print()方法打印
  9. 判断一个数,是否是质素
  10. IBMMQ window版安装(二)