Table of Contents
编译并运行 React Native 应用
跟着文章配置环境,首先需要有一个科学上网工具。此外,碰到了几个问题(这里记录的是安卓环境,mac os环境的在另一篇文章里):
- Cannot run program “npx.cmd” ……: CreateProcess error=267
对于这个错误,原因未知。解决的过程如下:
1.1 配置环境变量:CLASSPATH,值为:%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;
1.2 启动 Android Studio 中的安卓模拟器
到底哪个是决定性因素,未知。 - Could not download protobuf-java.jar
这个属于无法下载相关包,需要翻墙的问题。网上有很多解决方法,可惜都没解决我的问题。比如说:
2.1 修改项目 jcenter 配置// 项目/android/build.gradle …… jcenter() { url "http://jcenter.bintray.com/" } ……
2.2 修改 android studio 代理
2.3 进入C:\Users\用户\AppData.gradle,打开gradle.properties(不存在就新建一个),修改
``` systemProp.https.proxyHost=You https proxy systemProp.https.proxyPort=You https proxyPort systemProp.http.proxyHost=You proxy systemProp.http.proxyPort=You proxyPort ``` // 很可惜,我连 .gradle 文件夹都没有。当然,可以尝试新建,但我没尝试。
2.4 目前有效的方法:将 2.3 中要添加的代码,添加到项目文件夹下 /android/gradle.properties 里面
- Android Studio 找不到 SDK
安装Android Studio提示找不到JDK解决方法 - could not connect to development server …
图就不上了,总之就是连接不上 10.0.2.2:8081 的问题。网上解决方法很多,比如这个: React—Native开发之 Could not connect to development server(Android)解决方法。可惜并没有解决我的问题。
StackOverflow 上也有很多方法,比如这个:https://stackoverflow.com/questions/42064283/react-nativecould-not-connect-to-development-server-on-android。修改配置文件。可依然无效。
最后,我尝试从模拟器的浏览器里访问,10.0.2.2:8081 这个地址,先确定模拟器自己能不能访问自己的地址。结果是,不能。那么是不是 10.0.2.2:8081 这个地址有问题呢?这里找到一个答案: 连接到10.0.2.2而不是localhost的React-native android调试器。将地址模拟器里连接的地址改成 localhost:8081,然后重新运行 npm run start 和 npm run android 两条命令,模拟器里终于出现了预期的页面:Welcome to React。
样式
- 实际开发中组件的样式会越来越复杂,我们建议使用StyleSheet.create来集中定义组件的样式。
import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default class LotsOfStyles extends Component { render() { return ( <View> <Text style={styles.red}>just red</Text> <Text style={styles.bigBlue}>just bigBlue</Text> <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text> <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text> </View> ); } } const styles = StyleSheet.create({ bigBlue: { color: 'blue', fontWeight: 'bold', fontSize: 30, }, red: { color: 'red', }, });
style 属性可以是一个对象,也可以是一个对象数组。
宽度与高度
- 使用 flex 实现弹性布局
import React, { Component } from 'react'; import { View } from 'react-native'; export default class FlexDimensionsBasics extends Component { render() { return ( // 试试去掉父View中的`flex: 1`。 // 则父View不再具有尺寸,因此子组件也无法再撑开。 // 然后再用`height: 300`来代替父View的`flex: 1`试试看? <View style={{flex: 1}}> <View style={{flex: 1, backgroundColor: 'powderblue'}} /> <View style={{flex: 2, backgroundColor: 'skyblue'}} /> <View style={{flex: 3, backgroundColor: 'steelblue'}} /> </View> ); } }
react-native 好像默认 flex-direction 为 column。为了实现水平方向的划分,可以加上 flexDirection: ‘row’ 的样式。
使用 Flexbox 布局
- 一般来说,使用 flexDirection、alignItems 和 justifyContent 三个样式属性就已经能满足大多数布局需求。React Native 中的 Flexbox 的工作原理和 web 上的 CSS 基本一致,当然也存在少许差异。首先是默认值不同:flexDirection 的默认值是 column 而不是 row,而 flex 也只能指定一个数字值。
- Justify Content
在组件的 style 中指定justifyContent可以决定其子元素沿着主轴的排列方式。子元素是应该靠近主轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end、space-around、space-between以及space-evenly。 - Align Items
在组件的 style 中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end以及stretch。注意:要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。 - Align Self
alignSelf 与 alignItems 作用一样,只不过 alignSelf 是用在子元素上的,并且优先级比 alignItems 高。 - 其他的 Align Content, Flex Wrap, Flex Basis, Grow, and Shrink 与 web 端差不多。
- 也可以通过 position 布局
处理文本输入
TextInput
是一个允许用户输入文本的基础组件。它有一个名为onChangeText
的属性,此属性接受一个函数,而此函数会在文本变化时被调用。另外还有一个名为onSubmitEditing
的属性,会在文本被提交后(用户按下软键盘上的提交键)调用。
处理触摸事件
- Touchable 系列组件
这个组件的样式是固定的。所以如果它的外观并不怎么搭配你的设计,那就需要使用TouchableOpacity
或是TouchableNativeFeedback
组件来定制自己所需要的按钮,视频教程如何制作一个按钮讲述了完整的过程。或者你也可以在 github.com 网站上搜索 ‘react native button’ 来看看社区其他人的作品。
具体使用哪种组件,取决于你希望给用户什么样的视觉反馈:- 一般来说,你可以使用TouchableHighlight来制作按钮或者链接。注意此组件的背景会在用户手指按下时变暗。
- 在 Android 上还可以使用TouchableNativeFeedback,它会在用户手指按下时形成类似墨水涟漪的视觉效果。
- TouchableOpacity会在用户手指按下时降低按钮的透明度,而不会改变背景的颜色。
- 如果你想在处理点击事件的同时不显示任何视觉反馈,则需要使用TouchableWithoutFeedback。
onLongPress
属性来实现。
使用滚动视图
ScrollView
是一个通用的可滚动的容器,你可以在其中放入多个组件和视图,而且这些组件并不需要是同类型的。ScrollView 不仅可以垂直滚动,还能水平滚动(通过horizontal
属性来设置)。
import React, { Component } from 'react' import { ScrollView, Image, Text } from 'react-native' export default class HelloWorldApp extends Component { render() { return ( <ScrollView horizontal> <Text style={{ fontSize: 96 }}>Scroll me plz</Text> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Text style={{ fontSize: 96 }}>If you like</Text> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Text style={{ fontSize: 96 }}>Scrolling down</Text> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> <Image style={{ width: 100, height: 50 }} source={{ uri: 'https://enjoy-reading.tony93.top/static/img/logo.7d372522.png' }}></Image> </ScrollView> ) } }
注意,网络图片只有设置了宽高才能显示。
- ScrollView适合用来显示数量不多的滚动元素。放置在ScrollView中的所有组件都会被渲染,哪怕有些组件因为内容太长被挤出了屏幕外。如果你需要显示较长的滚动列表,那么应该使用功能差不多但性能更好的FlatList组件。
使用长列表
- FlatList
FlatList
组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同。
FlatList
更适于长列表数据,且元素个数可以增删。和ScrollView
不同的是,FlatList
并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。
FlatList
组件必须的两个属性是data
和renderItem
。data
是列表的数据源,而renderItem
则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。 - SectionList
如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么SectionList将是个不错的选择 - FlatList 属性:data、renderItem;SectionList 属性:sections、renderItem、renderSectionHeader、keyExtractor
网络
使用 fetch
- fetch 返回的是一个 Promise。
使用其他的网络库
- axios。不能使用 jQuery,因为 jQuery 依赖 DOM
WebSocket 支持
示例教程:电影列表
其他参考资源
组件和 API
基础组件
- View
搭建用户界面的最基础组件。 - Text
显示文本内容的组件。 - Image
显示图片内容的组件。 - TextInput
文本输入框。 - ScrollView
可滚动的容器视图。 - StyleSheet
提供类似CSS样式表的样式抽象层。
交互控件
列表视图
- FlatList
高性能的滚动列表组件。 - SectionList
类似FlatList
,但是多了分组显示。
ios 独有的组件和 API
- ActionSheetIOS
从设备底部弹出一个显示一个ActionSheet弹出框选项菜单或分享菜单。 - AlertIOS
弹出一个提示对话框,还可以带有输入框。 - DatePickerIOS
显示一个日期/时间选择器。 - ImagePickerIOS
插入图片。 - ProgressViewIOS
渲染一个UIProgressView
进度条。 - PushNotificationIOS
管理推送通知,包括权限处理和应用角标数字。 - SegmentedControlIOS
渲染一个UISegmentedControl
顶部选项卡布局 - TabBarIOS
渲染一个UITabViewController
底部选项卡布局。需要和TabBarIOS.Item搭配使用。
Android 独有的组件和 API
- BackHandler
监听并处理设备上的返回按钮。 - DatePickerAndroid
打开日期选择器。 - DrawerLayoutAndroid
渲染一个DrawerLayout
抽屉布局。 - PermissionsAndroid
对Android 6.0引入的权限模型的封装。 - ProgressBarAndroid
渲染一个ProgressBar
进度条。 - TimePickerAndroid
打开时间选择器。 - ToastAndroid
弹出一个Toast提示框。 - ToolbarAndroid
在顶部渲染一个Toolbar
工具栏。 - ViewPagerAndroid
可左右翻页滑动的视图容器。
其他
- ActivityIndicator
显示一个圆形的正在加载的符号。 - Alert
弹出一个提示框,显示指定的标题和信息。 - Animated
易于使用和维护的动画库,可生成流畅而强大的动画。 - CameraRoll
访问本地相册。 - Clipboard
读写剪贴板内容。 - Dimensions
获取设备尺寸。 - KeyboardAvoidingView
一种视图容器,可以随键盘升起而自动移动。 - Linking
提供了一个通用的接口来调起其他应用或被其他应用调起。 - Modal
一种简单的覆盖全屏的模态视图。 - PixelRatio
可以获取设备的像素密度。 - RefreshControl
此组件用在ScrollView
及其衍生组件的内部,用于添加下拉刷新的功能。 - StatusBar
用于控制应用顶部状态栏样式的组件。 - WebView
在原生视图中显示Web内容的组件。
特定平台代码
在编写跨平台的应用时,我们肯定希望尽可能多地复用代码。但是总有些时候我们会碰到针对不同平台编写不同代码的需求。
React Native 提供了两种方法来区分平台:
- 使用
Platform
模块. - 使用特定平台扩展名.
Platform 模块
- Platform.OS
其值为 ‘ios’ 或 ‘android’ - Platform.select
从传入的对象中返回对应平台的值
import { Platform, StyleSheet } from "react-native"; const styles = StyleSheet.create({ container: { flex: 1, ...Platform.select({ ios: { backgroundColor: "red" }, android: { backgroundColor: "blue" } }) } }); // 可以这样引用不同平台的组件 const Component = Platform.select({ ios: () => require("ComponentIOS"), android: () => require("ComponentAndroid") })().default; <Component />;
- 检测 Android 版本
在 Android 上,Platform.Version属性是一个数字,表示 Android 的 api level - 检测 iOS 版本
在 iOS 上,Version属性是-[UIDevice systemVersion]的返回值,具体形式为一个表示当前系统版本的字符串。比如可能是”10.3″
特定平台扩展名
- React Native 会检测某个文件是否具有.ios.或是.android.的扩展名,然后根据当前运行的平台自动加载正确对应的文件。
// 有如下文件 BigButton.ios.js BigButton.android.js // 引入代码如下 import BigButton from './BigButton';
- 如果你还希望在 web 端复用 React Native 的代码,那么还可以使用.native.js的扩展名。此时 iOS 和 Android 会使用BigButton.native.js文件,而 web 端会使用BigButton.js。(注意目前官方并没有直接提供 web 端的支持,请在社区搜索第三方方案)。
使用导航器跳转页面
React Navigation
- React Navigation 提供了简单易用的跨平台导航方案,在 iOS 和 Android 上都可以进行翻页式、tab 选项卡式和抽屉式的导航布局。
- React Navigation 中的视图是原生组件,同时用到了运行在原生线程上的Animated动画库,因而性能表现十分流畅。此外其动画形式和手势都非常便于定制。
- 要想详细了解 React Navigation的具体用法,请访问其官方网站,网站右上角有中文翻译,但内容可能会有所滞后。
- Demo
4.1 安装依赖
``` yarn add react-navigation yarn add react-native-gesture-handler yarn add react-navigation-stack ```
4.2 Demo 代码
``` // App.js import React, { Component } from 'react' import { createAppContainer } from 'react-navigation' import { createStackNavigator } from 'react-navigation-stack' import HomeScreen from './components/HomeScreenDemo' import ProfileScreen from './components/ProfileScreenDemo' const MainNavigator = createStackNavigator({ Home: { screen: HomeScreen }, Profile: { screen: ProfileScreen } }) const App = createAppContainer(MainNavigator) export default App // HomeScreenDemo.js import React, { Component } from 'react' import { Button } from 'react-native' class HomeScreen extends Component { static navigationOptions = { title: 'Welcome' } render() { const { navigate } = this.props.navigation return ( <Button title="Go to Jane`s profile" onPress={() => navigate('Profile', { name: 'Jane' })} ></Button> ) } } export default HomeScreen // ProfileScreenDemo import React, { Component } from 'react' import { View, Button, Text } from 'react-native' class ProfileScreen extends Component { static navigationOptions = { title: 'Profile' } render() { const { navigate, state } = this.props.navigation const { name } = state.params return ( <View> <Text>Hi, I am {name}</Text> <Button title="Back to Home" onPress={() => navigate('Home')} ></Button> </View> ) } } export default ProfileScreen ```
图片
静态图片资源
- 要往 App 中添加一个静态图片,只需把图片文件放在代码文件夹中某处,然后像下面这样去引用它:
<Image source={require('./my-icon.png')} />
如果你有 my-icon.ios.png 和 my-icon.android.png,Packager 就会根据平台而选择不同的文件。
你还可以使用@2x,@3x这样的文件名后缀,来为不同的屏幕精度提供图片。比如下面这样的代码结构:
. ├── button.js └── img ├── check.png ├── check@2x.png └── check@3x.png
并且button.js里有这样的代码:
<Image source={require('./img/check.png')} />
Packager 会打包所有的图片并且依据屏幕精度提供对应的资源。譬如说,iPhone 7 会使用check@2x.png,而 iPhone 7 plus 或是 Nexus 5 上则会使用check@3x.png。如果没有图片恰好满足屏幕分辨率,则会自动选中最接近的一个图片。
注意:为了使新的图片资源机制正常工作,require 中的图片名字必须是一个静态字符串(不能使用变量!因为 require 是在编译时期执行,而非运行时期执行!)。
静态的非图片资源
- 上面描述的
require
语法也可以用来静态地加载你项目中的声音、视频或者文档文件。大多数常见文件类型都支持,包括.mp3
,.wav
,.mp4
,.mov
,.htm
和.pdf
等(完整列表请看 packager defaults)。 - 你也可以在metro(即packager)配置文件中添加
assetExts
配置项来支持其他类型的文件。 - 需要注意的是视频必须指定尺寸而不能使用
flex
样式,因为我们目前还不能从非图片资源中获取到尺寸信息。对于直接链接到 Xcode 或者 Android 资源文件夹的视频,则不会有这个限制。
使用混合 App 的图片资源
- 如果你在编写一个混合 App(一部分 UI 使用 React Native,而另一部分使用平台原生代码),也可以使用已经打包到 App 中的图片资源(以拖拽的方式放置在 Xcode 的 asset 类目中,或是放置在 Android 的 drawable 目录里)。注意此时只使用文件名,不带路径也不带后缀:
<Image source={{uri: 'app_icon'}} style={{width: 40, height: 40}} />
对于放置在 Android 的 assets 目录中的图片,还可以使用asset:/ 前缀来引用:
<Image source={{uri: 'asset:/app_icon.png'}} style={{width: 40, height: 40}} />
注意:这些做法并没有任何安全检查。你需要自己确保图片在应用中确实存在,而且还需要指定尺寸。
网络图片
- 网络图片需要指定大小,否则不会显示。
- 最好是 https 链接
- 网络图片的请求参数
你可以在 Image 组件的 source 属性中指定一些请求参数,如下面的示例:
<Image source={{ uri: 'https://facebook.github.io/react/logo-og.png', method: 'POST', headers: { Pragma: 'no-cache', }, body: 'Your Body goes here', }} style={{width: 400, height: 400}} />
Uri 数据图片
- Image 可以加载 base64 图片,同样需要指定宽高。
- 缓存控制(仅 iOS)
在某些情况下你可能仅仅想展示一张已经在本地缓存的图片,例如一个低分辨率的占位符,直到高分辨率的图片可用。又或者你无所谓图片是否过时,而且也不在乎显示过时的图片,节省带宽相对更重要。缓存资源属性提供给了你控制网络层与缓存交互的方式。- default: 使用原生平台默认策略。
- reload: URL 的数据将从原始地址加载。不使用现有的缓存数据。
- force-cache: 现有的缓存数据将用于满足请求,忽略其期限或到期日。如果缓存中没有对应请求的数据,则从原始地址加载。
- only-if-cached: 现有的缓存数据将用于满足请求,忽略其期限或到期日。如果缓存中没有对应请求的数据,则不尝试从原始地址加载,并且认为请求是失败的。
<Image source={{ uri: 'https://facebook.github.io/react/logo-og.png', cache: 'only-if-cached', }} style={{width: 400, height: 400}} />
本地文件系统中的图片
- 最合适的相册图片
iOS 会为同一张图片在相册中保存多个不同尺寸的副本。为了性能考虑,从这些副本中挑出最合适的尺寸显得尤为重要。对于一处 200×200 大小的缩略图,显然不应该选择最高质量的 3264×2448 大小的图片。如果恰好有匹配的尺寸,那么 React Native 会自动为你选好。如果没有,则会选择最接近的尺寸进行缩放,但也至少缩放到比所需尺寸大出 50%,以使图片看起来仍然足够清晰。这一切过程都是自动完成的,所以你不用操心自己去完成这些繁琐且易错的代码。
为什么不在所有情况下都自动指定尺寸呢?
- 本地图片在加载时就可以知道尺寸,而网络图片则不可以
资源属性是一个对象(object)
- 在 React Native 中,另一个值得一提的变动是我们把src属性改为了source属性,而且并不接受字符串,正确的值是一个带有uri属性的对象。
<Image source={{uri: 'something.jpg'}} />
深层次的考虑是,这样可以使我们在对象中添加一些元数据(metadata)。也是考虑了未来的扩展性。
背景图片与嵌套写法
- ImageBackground 组件
return ( <ImageBackground source={...} style={{width: '100%', height: '100%'}}> <Text>Inside</Text> </ImageBackground> );
ImageBackground 实质就是对图片使用了绝对定位。
iOS 边框圆角的注意事项
- 请注意下列边框圆角样式目前在 iOS 的图片组件上还不支持:
- borderTopLeftRadius
- borderTopRightRadius
- borderBottomLeftRadius
- borderBottomRightRadius
在主线程外解码图片
- 图片解码有可能会需要超过一帧的时间。在 web 上这是页面掉帧的一大因素,因为解码是在主线程中完成的。然而在 React Native 中,图片解码则是在另一线程中完成的。在实际开发中,一般对图片还没下载完成时的场景都做了处理(添加 loading 等),而图片解码时显示的占位符只占用几帧时间,并不需要你改动代码去额外处理。
动画
- React Native 提供了两个互补的动画系统:用于创建精细的交互控制的动画
Animated
和用于全局的布局动画LayoutAnimation
。
Animated
Animated
使得开发者可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated
旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用简单的start/stop
方法来控制动画按顺序执行。Animated
仅封装了6个可以动画化的组件:View
、Text
、Image
、ScrollView
、FlatList
和SectionList
,不过你也可以使用Animated.createAnimatedComponent()
来封装你自己的组件。
配置动画
- 动画拥有非常灵活的配置项。自定义的或预定义的 easing 函数、延迟、持续时间、衰减系数、弹性常数等都可以在对应类型的动画中进行配置。
Animated
提供了多种动画类型,其中最常用的要属Animated.timing()
。timing 默认使用 easeInOut 曲线运行动画。开发者可以通过 easing 属性自定义动画函数。此外,开发者还可以指定 duration 和 delay 属性。具体配置参见配置动画章节
组合动画
- 多个动画可以通过parallel(同时执行)、sequence(顺序执行)、stagger和delay来组合使用。它们中的每一个都接受一个要执行的动画数组,并且自动在适当的时候调用start/stop。
- 默认情况下,如果任何一个动画被停止或中断了,组内所有其它的动画也会被停止。Parallel 有一个stopTogether属性,如果设置为false,可以禁用自动停止。具体配置参见组合动画一节
合成动画值
- 你可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值。
插值
- 每个属性都可以通过插值实现动画。插值可以将输入范围映射到输出范围。一般情况,插值是线性的,当然也可以是其他曲线函数。
style={{ opacity: this.state.fadeAnim, // Binds directly transform: [{ translateY: this.state.fadeAnim.interpolate({ inputRange: [0, 1], outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0 }), }], }}
interpolate()
还支持定义多个区间段落,常用来定义静止区间等。- interpolate()还支持到字符串的映射,从而可以实现颜色以及带有单位的值的动画变换。例如你可以像下面这样实现一个旋转动画:
value.interpolate({ inputRange: [0, 360], outputRange: ["0deg", "360deg"] });
interpolate()
还支持任意的渐变函数,其中有很多已经在Easing
类中定义了,包括二次、指数、贝塞尔等曲线以及 step、bounce 等方法。interpolation
还支持限制输出区间outputRange
。你可以通过设置extrapolate
、extrapolateLeft
或extrapolateRight
属性来限制输出区间。默认值是extend
(允许超出),不过你可以使用clamp
选项来阻止输出值超过outputRange
。
跟踪动态值
- 动画中所设的值还可以通过跟踪别的值得到。你只要把 toValue 设置成另一个动态值而不是一个普通数字就行了。
跟踪手势
Animated.event
是 Animated 中与输入有关的部分,允许手势或其它事件直接绑定到动态值上。它通过一个结构化的映射语法来完成,使得复杂事件对象中的值可以被正确的解开。第一层是一个数组,允许同时映射多个值,然后数组的每一个元素是一个嵌套的对象。
响应当前的动画值
- 你可能会注意到这里没有一个明显的方法来在动画的过程中读取当前的值——这是出于优化的角度考虑,有些值只有在原生代码运行阶段中才知道。如果你需要在 JavaScript 中响应当前的值,有两种可能的办法:
- spring.stopAnimation(callback)会停止动画并且把最终的值作为参数传递给回调函数callback——这在处理手势动画的时候非常有用。
- spring.addListener(callback)会在动画的执行过程中持续异步调用callback回调函数,提供一个最近的值作为参数。这在用于触发状态切换的时候非常有用,譬如当用户拖拽一个东西靠近的时候弹出一个新的气泡选项。不过这个状态切换可能并不会十分灵敏,因为它不像许多连续手势操作(如旋转)那样在 60fps 下运行。
启用原生动画驱动
Animated
的 API 是可序列化的(即可转化为字符串表达以便通信或存储)。通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。在动画中启用原生驱动非常简单。只需在开始动画之前,在动画配置中加入一行useNativeDriver: true
,如下所示:
Animated.timing(this.state.animatedValue, { toValue: 1, duration: 500, useNativeDriver: true // <-- 加上这一行 }).start();
- 动画值在不同的驱动方式之间是不能兼容的。因此如果你在某个动画中启用了原生驱动,那么所有和此动画依赖相同动画值的其他动画也必须启用原生驱动。
其余的见 react-native 文档
无障碍功能
详见 react-native 文档
改进用户体验
配置文本输入
- 由于触屏手机的小屏幕和软键盘,使得在手机中输入文本成为一件具有挑战的事情。 但是你可以基于你需要的数据配置文本输入让这个过程变得简单。
- 自动对焦( focus )第一个文本域
- 使用placeholder 作为预想的输入格式
- 启用 或者 禁用 自动大写、自动校正
- 选择键盘类型 【例如 email, 数字(numeric)】
- 确保回车按钮对焦到下一个域或者提交表单
- 查看
TextInput
文档 了解更多配置信息
键盘隐藏时的布局管理
- 软键盘几乎占用将近一半的手机屏幕。 如果你有会被软键盘覆盖的交互式组件,请使用[KeyboardAvoidingView 组件]以确保他们可以在打开键盘时可以被访问。
放大可触控区域
- 在手机上精准的点击一个按钮是很困难的一件事。 确保所有交互式元素大于等于44×44。 常见的撑大尺寸的做法有:使用
padding
,minWidth
和minHeight
样式。 或者, 可以使用hitSlop
属性 无需影响布局来增加可交互区域。
列表配置优化
英文不好,学习不了
定时器
定时器
- setTimeout, clearTimeout
- setInterval, clearInterval
- setImmediate, clearImmediate
- requestAnimationFrame, cancelAnimationFrame
- requestAnimationFrame(fn)和setTimeout(fn, 0)不同,前者会在每帧刷新之后执行一次,而后者则会尽可能快的执行(在 iPhone5S 上有可能每秒 1000 次以上)。
- setImmediate则会在当前 JavaScript 执行块结束的时候执行,就在将要发送批量响应数据到原生之前。注意如果你在setImmediate的回调函数中又执行了setImmediate,它会紧接着立刻执行,而不会在调用之前等待原生代码。
- Promise的实现就使用了setImmediate来执行异步调用。
InteractionManager
- 原生应用感觉如此流畅的一个重要原因就是在互动和动画的过程中避免繁重的操作。在 React Native 里,我们目前受到限制,因为我们只有一个 JavaScript 执行线程。不过你可以用InteractionManager来确保在执行繁重工作之前所有的交互和动画都已经处理完毕。
- 应用可以通过以下代码来安排一个任务,使其在交互结束之后执行:
InteractionManager.runAfterInteractions(() => { // ...需要长时间同步执行的任务... });
我们来把它和之前的几个任务安排方法对比一下:
- requestAnimationFrame(): 用来执行在一段时间内控制视图动画的代码
- setImmediate/setTimeout/setInterval(): 在稍后执行代码。注意这有可能会延迟当前正在进行的动画。
- runAfterInteractions(): 在稍后执行代码,不会延迟当前进行的动画。
触摸处理系统会把一个或多个进行中的触摸操作认定为’交互’,并且会将runAfterInteractions()的回调函数延迟执行,直到所有的触摸操作都结束或取消了。
- InteractionManager 还允许应用注册动画,在动画开始时创建一个交互“句柄”,然后在结束的时候清除它。
const handle = InteractionManager.createInteractionHandle(); // 执行动画... (`runAfterInteractions`中的任务现在开始排队等候) // 在动画完成之后 InteractionManager.clearInteractionHandle(handle); // 在所有句柄都清除之后,现在开始依序执行队列中的任务
调试
chrome 连接模拟器调试
- 打开地址:
image.png - sources 面板打开 rn 项目目录:
image.png - 连接模拟器:
image.png - 可以通过 console 或者打断点调试,但是无法看到网络请求。
性能
详见 react-native 文档
直接操作
- 有时候我们需要直接改动组件并触发局部的刷新,但不使用 state 或是 props。譬如在浏览器中使用 React 库,有时候会需要直接修改一个 DOM 节点,而在手机 App 中操作 View 时也会碰到同样的情况。在 React Native 中,setNativeProps就是等价于直接操作 DOM 节点的方法。
setNativeProps 与 TouchableOpacity
- TouchableOpacity这个组件就在内部使用了
setNativeProps
方法来更新其子组件的透明度:
setOpacityTo(value) { // Redacted: animation related code this.refs[CHILD_REF].setNativeProps({ opacity: value }); },
复合组件与 setNativeProps
- 对于复合组件,需要将 setNativeProps 一层层传递给原生组件。详见文档复合组件与 setNativeProps
- 与之相对的是TouchableHighlight组件,它本身是由原生视图构成,因而只需要我们实现setNativeProps。