【React Native】进阶指南之一(特定平台、图片加载、动画使用)
一、特定平台代码
React Native提供了两种方法来区分平台:
- 使用Platform模块;
- 使用特定平台扩展名;
1、Platform模块
React Native提供了一个检测当前运行平台的模块;Platform适用于对一小部分代码需要按照平台定制的情况;
import { Platform, StyleSheet } from "react-native"; const styles = StyleSheet.create({ height: Platform.OS === "ios" ? 200 : 100 });
Platform.OS 在iOS上会返回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" } }) } });
1.1 检测Android版本
在Android上,Version属性是一个数字,标识Andorid的api level;
import { Platform } from "react-native"; if (Platform.Version === 25) { console.log("Running on Nougat!"); }
1.2 检测iOS版本
在iOS上,Version属性是-[UIDevice systemVersion]的返回值,具体形式为一个表示当前系统版本的字符串。如:“10.3”
import { Platform } from "react-native"; const majorVersionIOS = parseInt(Platform.Version, 10); if (majorVersionIOS <= 9) { console.log("Work around a change in behavior"); }
2、特定平台扩展名
当不同平台的代码逻辑较为复杂时,最好是放到不同的文件里,这时候我们可以使用特定平台扩展名。React Native 会检测某个文件是否具有.ios.
或是.android.
的扩展名,然后根据当前运行的平台自动加载正确对应的文件。
***.ios.js
***.android.js
二、React Native中使用图片
1、静态图片资源
React Native提供了一个统一的方式来管理iOS和Android应用中的图片。要往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
Packager会打包所有的图片并且依据屏幕精度提供对应的资源。譬如:iPhone 7 会使用check@2x.png
,而 iPhone 7 plus 或是 Nexus 5 上则会使用check@3x.png
。如果没有图片恰好满足屏幕分辨率,则会自动选中最接近的一个图片。
这样会带来如下的一些好处:
(1)iOS和Android一致的文件系统;
(2)图片和JS代码处在相同的文件夹,这样组件就可以包含自己所用的图片而不用单独去设置;
(3)不需要全局命名。不用担心图片名字冲突问题。
(4)只有实际被用到(即require)的图片才会被打包到app。
注意:为了使新的图片资源机制正常工作,require中的图片名字必须是一个静态字符串(不能使用变量,因为require是在编译时期执行,而非运行时期执行)。通过require方式引用的图片包含图片尺寸信息,不需要重新设置width和height.
2、使用混合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}} />
注意:这些做法并没有任何安全检查。你需要自己确保图片在应用中确实存在,而且还需要指定尺寸。
3、Uri数据图片
有时候你可能拿到的是图片的 base64 数据,此时可以使用'data:'
格式来显示图片。请注意,你需要手动指定图片的尺寸
。
// 请记得指定宽高! <Image style={{ width: 51, height: 51, resizeMode: 'contain', }} source={{ uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==', }} />
4、缓存控制(仅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}} />
三、动画效果的使用
React Native 提供了两个互补的动画系统:用于创建精细的交互控制的动画Animated
和用于全局的布局动画LayoutAnimation
。
1、Animated
Animated
使得开发者可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated
旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用简单的start/stop
方法来控制动画按顺序执行。 Animated
仅封装了四个可以动画化的组件:View
、Text
、Image
和ScrollView
,不过你也可以使用Animated.createAnimatedComponent()
来封装你自己的组件。下面是一个在加载时带有淡入动画效果的视图:
import React from 'react'; import { Animated, Text, View } from 'react-native'; class FadeInView extends React.Component { state = { fadeAnim: new Animated.Value(0), // 透明度初始值设为0 } componentDidMount() { Animated.timing( // 随时间变化而执行动画 this.state.fadeAnim, // 动画中的变量值 { toValue: 1, // 透明度最终变为1,即完全不透明 duration: 10000, // 让动画持续一段时间 } ).start(); // 开始执行动画 } render() { let { fadeAnim } = this.state; return ( <Animated.View // 使用专门的可动画化的View组件 style={{ ...this.props.style, opacity: fadeAnim, // 将透明度指定为动画变量值 }} > {this.props.children} </Animated.View> ); } } // 然后你就可以在组件中像使用`View`那样去使用`FadeInView`了 export default class App extends React.Component { render() { return ( <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}> <FadeInView style={{width: 250, height: 50, backgroundColor: 'powderblue'}}> <Text style={{fontSize: 28, textAlign: 'center', margin: 10}}>Fading in</Text> </FadeInView> </View> ) } }
2、LayoutAnimation
API
LayoutAnimation
允许你在全局范围内创建
和更新
动画,这些动画会在下一次渲染或布局周期运行。它常用来更新 flexbox 布局,因为它可以无需测量或者计算特定属性就能直接产生动画。尤其是当布局变化可能影响到父节点(譬如“查看更多”展开动画既增加父节点的尺寸又会将位于本行之下的所有行向下推动)时,如果不使用LayoutAnimation
,可能就需要显式声明组件的坐标,才能使得所有受影响的组件能够同步运行动画。
尽管LayoutAnimation
非常强大且有用,但它对动画本身的控制没有Animated
或者其它动画库那样方便,所以如果你使用LayoutAnimation
无法实现一个效果,那可能还是要考虑其他的方案。
要在Android上使用 LayoutAnimation,那么目前还需要在UIManager
中启用:
// 在执行任何动画代码之前,比如在入口文件App.js中执行 UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
import React from 'react'; import { NativeModules, LayoutAnimation, Text, TouchableOpacity, StyleSheet, View, } from 'react-native'; const { UIManager } = NativeModules; UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); export default class App extends React.Component { state = { w: 100, h: 100, }; _onPress = () => { // Animate the update LayoutAnimation.spring(); this.setState({w: this.state.w + 15, h: this.state.h + 15}) } render() { return ( <View style={styles.container}> <View style={[styles.box, {width: this.state.w, height: this.state.h}]} /> <TouchableOpacity onPress={this._onPress}> <View style={styles.button}> <Text style={styles.buttonText}>Press me!</Text> </View> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, box: { width: 200, height: 200, backgroundColor: 'red', }, button: { backgroundColor: 'black', paddingHorizontal: 20, paddingVertical: 15, marginTop: 15, }, buttonText: { color: '#fff', fontWeight: 'bold', }, });