1.背景
在文章例子中的 RN(以下用 RN 表示 React Native )版本是 0.55.0。在项目的开发中,会遇到很多全屏弹窗的使用需求,而 RN 官方也提供了一个这样的组件 Modal。但是在实际使用中,在 ios 端如果显示一个 Modal 的时候去打开一个新的 Modal 将无法打开。同时在一个页面里面存在两个以上 Modal 控件的时候,打开第三个 Modal 的时候页面会卡主。
2.使用 RootSiblings 封装自己的 Modal
解决上面的问题,可以采用的一种方法就是使用一个全屏的 View 去代替使用 RN 的 Modal 控件,但是有个弊端就是这个 View 的父布局需要是全屏的并且需要考虑这个 View 的布局位置必须在最外层,否则会被其他的控件挡住。这里有其他的方法去解决这个问题,就是采用 RootSiblings 去封装自己的 Modal。
2.1 RootSiblings 使用介绍
这里说的 RootSiblings 指的是第三方库 react-native-root-siblings ,实现原理就是重写了系统的 AppRegistry.registerComponent 方法,当我们通过这个方法注册根组件的时候,替换根组件为我们自己的实现的包装类。包装类中监听了目标通知 siblings.update,接收到通知就将通知传入的组件视图添加到包装类顶层,然后进行刷新显示。这样来看就可以解决上面使用 View 实现全屏弹窗的问题。
2.2 使用 RootSiblings 封装自己的 Modal 控件
在这里创建 ModalView 组件类进行封装使用。对于 Android 平台下,我们依旧使用 RN 的 Modal 组件来实现全屏弹窗效果。而对于 ios 平台,我们就是使用今天的重点 RootSiblings 来进行封装使用。ModalView 完整代码如下
/**
* 作者:请叫我百米冲刺 on 2018/4/3 下午4:24
* 邮箱:mail@hezhilin.cc
*
* 因为ios端同时只能存在一个 Modal,并且显示第三个 Modal 界面的时候有奇怪的 bug
*
* 为了兼容 ios 的使用,这里需要封装一个 ModalView
*
* Android 依旧使用 React Native Modal 来进行实现
* ios 的话采用 RootSiblings 配合进行使用
*
* 同时采用与 React Native Modal 相同的API
*/
'use strict';
import React, {Component} from "react";
import {Modal, Animated, Platform, Easing, StyleSheet, Dimensions} from "react-native";
import RootSiblings from 'react-native-root-siblings';
const {height} = Dimensions.get('window')
var isAndroid = Platform.OS == 'android'
export default class ModalView extends Component {
constructor(props) {
super(props);
this.state = {
visible: false, //给android式的modal进行使用的
animationSlide: new Animated.Value(0),
animationFade: new Animated.Value(0)
};
//ios也可以指定使用android的实现方式
if (this.props.useAndroid) {
isAndroid = true
}
}
render() {
this.RootSiblings && this.RootSiblings.update(this.renderIos())
return isAndroid ? this.renderAndroid() : null
}
renderAndroid = () => {
return (
<Modal {...this.props}
transparent={true}
visible={this.state.visible}
onRequestClose={() => {
if (this.props.onRequestClose) {
this.props.onRequestClose()
} else {
this.disMiss()
}
}}>
{this.props.children}
</Modal>
)
}
renderIos = () => {
return (
<Animated.View style={[styles.root,
{opacity: this.state.animationFade},
{
transform: [{
translateY: this.state.animationSlide.interpolate({
inputRange: [0, 1],
outputRange: [height, 0]
}),
}]
}]}>
{this.props.children}
</Animated.View>
);
}
show = (callback) => {
if (this.isShow()) {
return
}
if (isAndroid) {
this.setState({visible: true}, () => callback && callback())
} else {
this.RootSiblings = new RootSiblings(this.renderIos(), () => {
if (this.props.animationType == 'fade') {
this.animationFadeIn(callback)
} else if (this.props.animationType == 'slide') {
this.animationSlideIn(callback)
} else {
this.animationNoneIn(callback)
}
});
}
}
disMiss = (callback) => {
if (!this.isShow()) {
return
}
if (isAndroid) {
this.setState({visible: false}, () => callback && callback())
} else {
if (this.props.animationType == 'fade') {
this.animationFadeOut(callback)
} else if (this.props.animationType == 'slide') {
this.animationSlideOut(callback)
} else {
this.animationNoneOut(callback)
}
}
}
isShow = () => {
if (isAndroid) {
return this.state.visible
} else {
return this.RootSiblings ? true : false
}
}
animationNoneIn = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
callback && callback()
}
animationNoneOut = (callback) => {
this.animationCallback(callback);
}
animationSlideIn = (callback) => {
this.setState({visible: true}, () => {
this.state.animationSlide.setValue(0)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationSlide, {
easing: Easing.linear(),
duration: 300,
toValue: 1,
}).start(() => callback && callback());
})
}
animationSlideOut = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationSlide, {
easing: Easing.linear(),
duration: 300,
toValue: 0,
}).start(() => this.animationCallback(callback));
}
animationFadeIn = (callback) => {
this.setState({visible: true}, () => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(0)
Animated.timing(this.state.animationFade, {
easing: Easing.linear(),
duration: 300,
toValue: 1,
}).start(() => callback && callback());
})
}
animationFadeOut = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationFade, {
easing: Easing.linear(),
duration: 300,
toValue: 0,
}).start(() => this.animationCallback(callback));
}
animationCallback = (callback) => {
this.RootSiblings && this.RootSiblings.destroy(() => {
callback && callback()
this.RootSiblings = undefined
})
}
}
const styles = StyleSheet.create({
root: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
}
})
在这里我们让改 ModalView 使用 RN 的 Modal 相同的 Api,对于 ios 部分,在这里我们添加了三种启动动画模式,类似于 RN Modal 的 slide,fade动画,以及无动画 none。动画实现使用的是 Animated 和 Easing ,具体 Animated 的使用方法可以参照文档 React Native 动画
2.3 ModalView 实现步骤分析
在 ModalView 调用 show 方法的时候,这里要创建 RootSiblings 并将需要全屏显示的组件添加到 RootSiblings 之中,同时根据动画展示类型去调用相关的 Animated 动画
renderIos = () => {
return (
<Animated.View style={[styles.root,
{opacity: this.state.animationFade},
{
transform: [{
translateY: this.state.animationSlide.interpolate({
inputRange: [0, 1],
outputRange: [height, 0]
}),
}]
}]}>
{this.props.children}
</Animated.View>
);
}
this.RootSiblings = new RootSiblings(this.renderIos(), () => {
if (this.props.animationType == 'fade') {
this.animationFadeIn(callback)
} else if (this.props.animationType == 'slide') {
this.animationSlideIn(callback)
} else {
this.animationNoneIn(callback)
}
});
在 ModalView 调用 disMiss 方法的时候,要去销毁这么这个 RootSiblings 组件
animationCallback = (callback) => {
this.RootSiblings && this.RootSiblings.destroy(() => {
callback && callback()
this.RootSiblings = undefined
})
}
同时在这里要注意的是,在 RN 的界面更新之中,通过 state 去进行组件 UI 界面的更新,最终会反馈到 render() 方法中。由于 ModalView 的实现方式是通过 RootSiblings 把显示的组件直接添加到根组件的。无法通过 state 去更新界面 UI ,所以在这里我们需要主动调用 update 方法去进行界面的更新。
render() {
this.RootSiblings && this.RootSiblings.update(this.renderIos())
return isAndroid ? this.renderAndroid() : null
}
假如需要展示的弹窗页面没有使用 state 进行界面 UI 的更新,那么就不需要调用 RootSiblings.update 方法。
2.4 ModalView 简单展示
ios.gifandroid.gif
动图上展示了带动画的弹窗展示以及多个 Modal 进行展示
3.最后
要使用上面的 ModalView ,只需要导入第三方库 react-native-root-siblings ,然后把 ModalView 拷贝到你的项目中就可以了。最后附上上面的 demo 的地址:https://github.com/hzl123456/ModalViewDemo
网友评论