React?Native?Modal?的封裝與使用實(shí)例詳解
背景
在使用 React Native(以下簡稱 RN ,使用版本為 0.59.5) 開發(fā) App 的過程中,有許許多多使用到彈窗控件的場景,雖然 RN 自帶了一個 Modal 控件,但是在使用過程中它有一些不太好的體驗(yàn)和問題。
- Android 端的 Modal 控件無法全屏,也就是內(nèi)容無法從狀態(tài)欄處開始布局。
- ios 端的 Modal 控件的層級太高,是基于 window 的,如果在 Modal 中打開一個新的 ViewController 界面的時(shí)候,將會被 Modal 控件給覆蓋住,同時(shí) ios 的 Modal 控件只能彈出一個。
針對上面所發(fā)現(xiàn)的問題,我們需要對 RN 的 Modal 控件整體做一個修改和封裝,以便于在使用中可以應(yīng)對各種不同樣的業(yè)務(wù)場景。
Android FullScreenModal 的封裝使用
針對第一個問題,查看了 RN Modal 組件在 Android 端的實(shí)現(xiàn),發(fā)現(xiàn)它是對 Android Dialog 組件的一個封裝調(diào)用,那么假如我能實(shí)現(xiàn)一個全屏展示的 Dialog,那么是不是在 RN 上也就可以實(shí)現(xiàn)全屏彈窗了。
Android 原生實(shí)現(xiàn)全屏 Dialog
FullScreenDialog 主要實(shí)現(xiàn)代碼如下
public class FullScreenDialog extends Dialog {
private boolean isDarkMode;
private View rootView;
public void setDarkMode(boolean isDarkMode) {
this.isDarkMode = isDarkMode;
}
public FullScreenDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, themeResId);
}
@Override
public void setContentView(@NonNull View view) {
super.setContentView(view);
this.rootView = view;
}
@Override
public void show() {
super.show();
StatusBarUtil.setTransparent(getWindow());
if (isDarkMode) {
StatusBarUtil.setDarkMode(getWindow());
} else {
StatusBarUtil.setLightMode(getWindow());
}
AndroidBug5497Workaround.assistView(rootView, getWindow());
}
}在這里主要起作用的是 StatusBarUtil.setTransparent(getWindow()); 方法,它的主要作用是將狀態(tài)欄背景透明,并且讓布局內(nèi)容可以從 Android 狀態(tài)欄開始。
/**
* 使?fàn)顟B(tài)欄透明,并且是從狀態(tài)欄處開始布局
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static void transparentStatusBar(Window window) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
View decorView = window.getDecorView();
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(option);
window.setStatusBarColor(Color.TRANSPARENT);
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}這里需要注意的是,該方法只有在 Android 4.4 以上才會有效果,不過如今已經(jīng)是 9012 年了,主流 Android 用戶使用的版本應(yīng)該沒有低于 Android 4.4 了吧。
封裝給 RN 進(jìn)行相關(guān)的調(diào)用
Android 原生部分實(shí)現(xiàn)
有了 FullScreenDialog ,下一步就是封裝組件給 RN 進(jìn)行調(diào)用了,這里主要的步驟就是參考 RN Modal 的 Android 端實(shí)現(xiàn),然后替換其中的 Dialog 為 FullScreenDialog,最后封裝給 RN 進(jìn)行調(diào)用。
public class FullScreenModalManager extends ViewGroupManager<FullScreenModalView> {
@Override
public String getName() {
return "RCTFullScreenModalHostView";
}
public enum Events {
ON_SHOW("onFullScreenShow"),
ON_REQUEST_CLOSE("onFullScreenRequstClose");
private final String mName;
Events(final String name) {
mName = name;
}
@Override
public String toString() {
return mName;
}
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
MapBuilder.Builder builder = MapBuilder.builder();
for (Events event : Events.values()) {
builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
}
return builder.build();
}
@Override
protected FullScreenModalView createViewInstance(ThemedReactContext reactContext) {
final FullScreenModalView view = new FullScreenModalView(reactContext);
final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class);
view.setOnRequestCloseListener(new FullScreenModalView.OnRequestCloseListener() {
@Override
public void onRequestClose(DialogInterface dialog) {
mEventEmitter.receiveEvent(view.getId(), Events.ON_REQUEST_CLOSE.toString(), null);
}
});
view.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
mEventEmitter.receiveEvent(view.getId(), Events.ON_SHOW.toString(), null);
}
});
return view;
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
return new FullScreenModalHostShadowNode();
}
@Override
public Class<? extends LayoutShadowNode> getShadowNodeClass() {
return FullScreenModalHostShadowNode.class;
}
@Override
public void onDropViewInstance(FullScreenModalView view) {
super.onDropViewInstance(view);
view.onDropInstance();
}
@ReactProp(name = "autoKeyboard")
public void setAutoKeyboard(FullScreenModalView view, boolean autoKeyboard) {
view.setAutoKeyboard(autoKeyboard);
}
@ReactProp(name = "isDarkMode")
public void setDarkMode(FullScreenModalView view, boolean isDarkMode) {
view.setDarkMode(isDarkMode);
}
@ReactProp(name = "animationType")
public void setAnimationType(FullScreenModalView view, String animationType) {
view.setAnimationType(animationType);
}
@ReactProp(name = "transparent")
public void setTransparent(FullScreenModalView view, boolean transparent) {
view.setTransparent(transparent);
}
@ReactProp(name = "hardwareAccelerated")
public void setHardwareAccelerated(FullScreenModalView view, boolean hardwareAccelerated) {
view.setHardwareAccelerated(hardwareAccelerated);
}
@Override
protected void onAfterUpdateTransaction(FullScreenModalView view) {
super.onAfterUpdateTransaction(view);
view.showOrUpdate();
}
}在這里有幾點(diǎn)需要注意的
- 由于 RN Modal 已經(jīng)存在了 onShow 和 onRequestClose 回調(diào),這里不能再使用這兩個命名,所以這里改成了 onFullScreenShow 和 onFullScreenRequstClose,但是在 js 端還是重新命名成 onShow 和 onRequestClose ,所以在使用過程中還是沒有任何變化
- 增加了 isDarkMode 屬性,對應(yīng)上面的狀態(tài)欄字體的顏色
- 增加了 autoKeyboard 屬性,根據(jù)該屬性判斷是否需要自動彈起軟件盤
其他的一些屬性和用法也就跟 RN Modal 的一樣了。
JS 部分實(shí)現(xiàn)
在 JS 部分,我們只需要 Android 的實(shí)現(xiàn)就好了,ios 還是沿用原來的 Modal 控件。這里參照 RN Modal 的 JS 端實(shí)現(xiàn)如下
import React, {Component} from "react";
import {requireNativeComponent, View} from "react-native";
const FullScreenModal = requireNativeComponent('RCTFullScreenModalHostView', FullScreenModalView)
export default class FullScreenModalView extends Component {
_shouldSetResponder = () => {
return true;
}
render() {
if (this.props.visible === false) {
return null;
}
const containerStyles = {
backgroundColor: this.props.transparent ? 'transparent' : 'white',
};
return (
<FullScreenModal
style={{position: 'absolute'}} {...this.props}
onStartShouldSetResponder={this._shouldSetResponder}
onFullScreenShow={() => this.props.onShow && this.props.onShow()}
onFullScreenRequstClose={() => this.props.onRequestClose && this.props.onRequestClose()}>
<View style={[{position: 'absolute', left: 0, top: 0}, containerStyles]}>
{this.props.children}
</View>
</FullScreenModal>
)
}
}
使用 RootSiblings 封裝 Modal
針對第二個問題,一種方法是通過 ios 原生去封裝實(shí)現(xiàn)一個 Modal 控件,但是在 RN 的開發(fā)過程中,發(fā)現(xiàn)了一個第三方庫 react-native-root-siblings , 它重寫了系統(tǒng)的 AppRegistry.registerComponent 方法,當(dāng)我們通過這個方法注冊根組件的時(shí)候,替換根組件為我們自己的實(shí)現(xiàn)的包裝類。包裝類中監(jiān)聽了目標(biāo)通知 siblings.update,接收到通知就將通知傳入的組件視圖添加到包裝類頂層,然后進(jìn)行刷新顯示。通過 RootSiblings 也可以實(shí)現(xiàn)一個 Modal 組件,而且它的層級是在當(dāng)前界面的最上層的。
實(shí)現(xiàn)界面 Render 相關(guān)
由于 RootSiblings 的實(shí)現(xiàn)是通過將組件添加到它注冊到根節(jié)點(diǎn)中的,并不直接通過 Component 的 Render 進(jìn)行布局,而 RN Modal 的顯示隱藏是通過 visible 屬性進(jìn)行控制,所以在 componentWillReceiveProps(nextProps) 中根據(jù) visible 進(jìn)行相關(guān)的控制,部分實(shí)現(xiàn)代碼如下
render() {
if (this.props.visible) {
this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings());
}
return null;
}
componentWillReceiveProps(nextProps) {
const { onShow, animationType, onDismiss } = this.props;
const { visible } = nextProps;
if (!this.RootSiblings && visible === true) { // 表示從沒有到要顯示了
this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => {
if (animationType === 'fade') {
this._animationFadeIn(onShow);
} else if (animationType === 'slide') {
this._animationSlideIn(onShow);
} else {
this._animationNoneIn(onShow);
}
});
} else if (this.RootSiblings && visible === false) { // 表示顯示之后要隱藏了
if (animationType === 'fade') {
this._animationFadeOut(onDismiss);
} else if (animationType === 'slide') {
this._animationSlideOut(onDismiss);
} else {
this._animationNoneOut(onDismiss);
}
}
}實(shí)現(xiàn) Modal 展示動畫相關(guān)
RN Modal 實(shí)現(xiàn)了三種動畫模式,所以這里在通過 RootSiblings 實(shí)現(xiàn) Modal 組件的時(shí)候也實(shí)現(xiàn)了這三種動畫模式,這里借助的是 RN 提供的 Animated 和 Easing 進(jìn)行相關(guān)的實(shí)現(xiàn)
- ‘none’ 這種不必多說,直接進(jìn)行展示,沒有動畫效果
- ‘fade’ 淡入淺出動畫,也就是透明度的一個變化,這里使用了 Easing.in 插值器使得效果更加平滑
- ‘slide’ 幻燈片的滑入畫出動畫,這是是組件 Y 方向位置的一個變化,這里使用了 Easing.in 插值器使得效果更加平滑
完整的一個 使用 RootSiblings 封裝 Modal 實(shí)現(xiàn)代碼如下
import React, { Component } from 'react';
import {
Animated, Easing, Dimensions, StyleSheet,
} from 'react-native';
import RootSiblings from 'react-native-root-siblings';
const { height } = Dimensions.get('window');
const animationShortTime = 250; // 動畫時(shí)長為250ms
export default class ModalView extends Component {
constructor(props) {
super(props);
this.state = {
animationSlide: new Animated.Value(0),
animationFade: new Animated.Value(0),
};
}
render() {
if (this.props.visible) {
this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings());
}
return null;
}
componentWillReceiveProps(nextProps) {
const { onShow, animationType, onDismiss } = this.props;
const { visible } = nextProps;
if (!this.RootSiblings && visible === true) { // 表示從沒有到要顯示了
this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => {
if (animationType === 'fade') {
this._animationFadeIn(onShow);
} else if (animationType === 'slide') {
this._animationSlideIn(onShow);
} else {
this._animationNoneIn(onShow);
}
});
} else if (this.RootSiblings && visible === false) { // 表示顯示之后要隱藏了
if (animationType === 'fade') {
this._animationFadeOut(onDismiss);
} else if (animationType === 'slide') {
this._animationSlideOut(onDismiss);
} else {
this._animationNoneOut(onDismiss);
}
}
}
renderRootSiblings = () => {
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>
);
}
_animationNoneIn = (callback) => {
this.state.animationSlide.setValue(1);
this.state.animationFade.setValue(1);
callback && callback();
}
_animationNoneOut = (callback) => {
this._animationCallback(callback);
}
_animationSlideIn = (callback) => {
this.state.animationSlide.setValue(0);
this.state.animationFade.setValue(1);
Animated.timing(this.state.animationSlide, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 1,
}).start(() => callback && callback());
}
_animationSlideOut = (callback) => {
this.state.animationSlide.setValue(1);
this.state.animationFade.setValue(1);
Animated.timing(this.state.animationSlide, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 0,
}).start(() => this._animationCallback(callback));
}
_animationFadeIn = (callback) => {
this.state.animationSlide.setValue(1);
this.state.animationFade.setValue(0);
Animated.timing(this.state.animationFade, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 1,
}).start(() => callback && callback());
}
_animationFadeOut = (callback) => {
this.state.animationSlide.setValue(1);
this.state.animationFade.setValue(1);
Animated.timing(this.state.animationFade, {
easing: Easing.in(),
duration: animationShortTime,
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,
},
});使用 View 封裝 Modal
上面兩種 Modal 的封裝已經(jīng)能夠滿足絕大部分業(yè)務(wù)場景的需求,但是如果在 Modal 中需要打開新的界面(不創(chuàng)建新的 ViewController 和 Acticity ),并且 Modal 不進(jìn)行隱藏的話,比如使用 react-navigation 跳轉(zhuǎn)頁面,那么上面實(shí)現(xiàn)的 Modal 層級會太高了。所以這里通過 View 去實(shí)現(xiàn)類似的一個 Modal 控件。它的實(shí)現(xiàn)代碼類似于上面的 RootSiblings 的實(shí)現(xiàn)。在 Render 中進(jìn)行展示就好了,動畫也可以使用上面的實(shí)現(xiàn)
render() {
return this._renderView()
}
_renderView = () => {
if (this.state.visible) {
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>
);
} else {
return null
}
}整體 Modal 控件的封裝
相對于 RN Modal ,上面新增了三種 Modal 的實(shí)現(xiàn),為了整合整體的使用,所以需要對它們進(jìn)行一個整體的封裝使用,通過制定 modalType 的方式,來指定我們需要它內(nèi)部的實(shí)現(xiàn),先上代碼
/**
* @Author: linhe
* @Date: 2019-05-12 10:11
*
* 因?yàn)閕os端同時(shí)只能存在一個Modal,并且Modal多次顯示隱藏會有很奇怪的bug
*
* 為了兼容ios的使用,這里需要封裝一個ModalView
*
* Android 依舊使用 React Native Modal 來進(jìn)行實(shí)現(xiàn)
* ios 的話采用 RootSiblings 配合進(jìn)行使用
*
* 這個是因?yàn)橛械膍odal里面還需要跳轉(zhuǎn)到其他界面
* 這個時(shí)候主要要將該View放到最外邊的層級才可以
*
* modalType:1 //表示使用Modal進(jìn)行實(shí)現(xiàn)
* 2 //表示使用RootSiblings進(jìn)行實(shí)現(xiàn)
* 3 //表示使用View進(jìn)行實(shí)現(xiàn)
* 注意:默認(rèn)情況下 Android 使用的是1,ios使用的是2
*
* 同時(shí)采用與 React Native Modal 相同的API
*/
'use strict';
import React, {Component} from "react";
import {Animated, BackHandler, Platform, Easing, StyleSheet, Dimensions, Modal} from "react-native";
import PropTypes from 'prop-types'
import RootSiblings from 'react-native-root-siblings';
import FullScreenModal from './FullScreenModal/FullScreenModal'
const {height} = Dimensions.get('window')
const animationShortTime = 250 //動畫時(shí)長為250ms
const DEVICE_BACK_EVENT = 'hardwareBackPress';
export default class ModalView extends Component {
static propTypes = {
isDarkMode: PropTypes.bool, // false 表示白底黑字,true 表示黑底白字
autoKeyboard: PropTypes.bool, // 未知原因的坑,modal中的edittext自動彈起鍵盤要設(shè)置這個參數(shù)為true
useReactModal: PropTypes.bool, // 是否使用 RN Modal 進(jìn)行實(shí)現(xiàn)
modalType: PropTypes.number // modalType 類型,默認(rèn) android 為 1,ios 為 2
};
static defaultProps = {
isDarkMode: false,
autoKeyboard: false,
useReactModal: false,
modalType: (Platform.OS === 'android' ? 1 : 2) // 默認(rèn) android 為1,ios 為2
};
constructor(props) {
super(props);
this.state = {
visible: false,
animationSlide: new Animated.Value(0),
animationFade: new Animated.Value(0)
};
}
render() {
const {modalType} = this.props
if (modalType === 1) { //modal實(shí)現(xiàn)
return this._renderModal()
} else if (modalType === 2) { //RootSiblings實(shí)現(xiàn)
this.RootSiblings && this.RootSiblings.update(this._renderRootSiblings())
return null
} else { //View的實(shí)現(xiàn)
return this._renderView()
}
}
_renderModal = () => {
const ModalView = this.props.useReactModal ? Modal : FullScreenModal
return (
<ModalView
transparent={true}
{...this.props}
visible={this.state.visible}
onRequestClose={() => {
if (this.props.onRequestClose) {
this.props.onRequestClose()
} else {
this.disMiss()
}
}}>
{this.props.children}
</ModalView>
)
}
_renderRootSiblings = () => {
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>
);
}
_renderView = () => {
if (this.state.visible) {
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>
);
} else {
return null
}
}
show = (callback) => {
if (this.isShow()) {
return
}
const {modalType, animationType} = this.props
if (modalType === 1) { //modal
this.setState({visible: true}, () => callback && callback())
} else if (modalType === 2) { //RootSiblings
this.RootSiblings = new RootSiblings(this._renderRootSiblings(), () => {
if (animationType === 'fade') {
this._animationFadeIn(callback)
} else if (animationType === 'slide') {
this._animationSlideIn(callback)
} else {
this._animationNoneIn(callback)
}
});
// 這里需要監(jiān)聽 back 鍵
this._addHandleBack()
} else { //view
if (animationType === 'fade') {
this.setState({visible: true}, () => this._animationFadeIn(callback))
} else if (animationType === 'slide') {
this.setState({visible: true}, () => this._animationSlideIn(callback))
} else {
this.setState({visible: true}, () => this._animationNoneIn(callback))
}
// 這里需要監(jiān)聽 back 鍵
this._addHandleBack()
}
}
disMiss = (callback) => {
if (!this.isShow()) {
return
}
const {modalType, animationType} = this.props
if (modalType === 1) { //modal
this.setState({visible: false}, () => callback && callback())
} else { //RootSiblings和View
if (animationType === 'fade') {
this._animationFadeOut(callback)
} else if (animationType === 'slide') {
this._animationSlideOut(callback)
} else {
this._animationNoneOut(callback)
}
// 移除 back 鍵的監(jiān)聽
this._removeHandleBack()
}
}
isShow = () => {
const {modalType} = this.props
if (modalType === 1 || modalType === 3) { //modal和view
return this.state.visible
} else { //RootSiblings
return !!this.RootSiblings
}
}
_addHandleBack = () => {
if (Platform.OS === 'ios') {
return
}
// 監(jiān)聽back鍵
this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => {
const {onRequestClose} = this.props
if (onRequestClose) {
onRequestClose()
} else {
this.disMiss()
}
return true
});
}
_removeHandleBack = () => {
if (Platform.OS === 'ios') {
return
}
this.handleBack && this.handleBack.remove()
}
_animationNoneIn = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
callback && callback()
}
_animationNoneOut = (callback) => {
this._animationCallback(callback);
}
_animationSlideIn = (callback) => {
this.state.animationSlide.setValue(0)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationSlide, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 1,
}).start(() => callback && callback());
}
_animationSlideOut = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationSlide, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 0,
}).start(() => this._animationCallback(callback));
}
_animationFadeIn = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(0)
Animated.timing(this.state.animationFade, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 1,
}).start(() => callback && callback());
}
_animationFadeOut = (callback) => {
this.state.animationSlide.setValue(1)
this.state.animationFade.setValue(1)
Animated.timing(this.state.animationFade, {
easing: Easing.in(),
duration: animationShortTime,
toValue: 0,
}).start(() => this._animationCallback(callback));
}
_animationCallback = (callback) => {
if (this.props.modalType === 2) {//RootSiblings
this.RootSiblings && this.RootSiblings.destroy(() => {
callback && callback()
this.RootSiblings = undefined
})
} else { //view
this.setState({visible: false}, () => callback && callback())
}
}
}
const styles = StyleSheet.create({
root: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
}
});這里主要通過 useReactModal 和 modalType 兩個屬性來控制我們所需要的實(shí)現(xiàn)
- modalType 表示 modal 內(nèi)部實(shí)現(xiàn)方式,1 表示使用層級較高的實(shí)現(xiàn),2 表示使用 RootSiblings 進(jìn)行實(shí)現(xiàn),3 表示使用 View 進(jìn)行實(shí)現(xiàn),當(dāng)不進(jìn)行指定的時(shí)候,默認(rèn) Android 為 1,ios 為 2
- useReactModal 主要針對 Android 端并且 modalType 為 1 的時(shí)候使用,true 表示使用 RN Modal,false 表示使用 FullScreenModal ,默認(rèn)為 false
- 對外提供 show,disMiss,isShow 方法分別表示顯示彈窗,隱藏彈窗和判斷當(dāng)前彈窗的狀態(tài),同時(shí)在 show 和 disMiss 方法調(diào)用的時(shí)候還添加了 callback 回調(diào)
其他
Android Back 鍵的注意
當(dāng) Modal 組件使用 RootSiblings 或者 View 實(shí)現(xiàn)的時(shí)候,它并沒有處理一個 Android 的返回鍵,所以對于這兩種實(shí)現(xiàn)的時(shí)候,要額外處理一個 Back 鍵的操作,這里借助了 RN BackHandler 這個了,如果 modalType 不為 1 的話,需要通過 BackHandler 去實(shí)現(xiàn)一個返回鍵的監(jiān)聽,然后通過 onRequestClose 屬性進(jìn)行返回
const DEVICE_BACK_EVENT = 'hardwareBackPress';
_addHandleBack = () => {
if (Platform.OS === 'ios') {
return
}
// 監(jiān)聽back鍵
this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => {
const {onRequestClose} = this.props
if (onRequestClose) {
onRequestClose()
} else {
this.disMiss()
}
return true
});
}
_removeHandleBack = () => {
if (Platform.OS === 'ios') {
return
}
this.handleBack && this.handleBack.remove()
}View 封裝 Modal 時(shí)候的注意
如果是 View 實(shí)現(xiàn)的 Modal 控件,那必須要注意它的一個層級,必須滿足它所處于整個界面布局的最外層,否則它可能會被其他組件所擋住,同時(shí)它的最大的顯示范圍取決于它的父 View 的顯示范圍。
最后
當(dāng)然在最后還是要附上實(shí)現(xiàn)效果圖


再一次附上 Demo 地址:https://github.com/hzl123456/ModalViewDemo
到此這篇關(guān)于React Native Modal 的封裝與使用 的文章就介紹到這了,更多相關(guān)React Native Modal 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中的useRef 和 useState介紹
這篇文章主要給大家分享的是 JavaScript中的useRef 和 useState介紹,下列文章,我們將學(xué)習(xí) useRef 和 useState hook是什么,它們的區(qū)別以及何時(shí)使用哪個。 這篇文章中的代碼示例將僅涉及功能組件,但是大多數(shù)差異和用途涵蓋了類和功能組件,需要的朋友可以參考一下2021-11-11
React Native之prop-types進(jìn)行屬性確認(rèn)詳解
本篇文章主要介紹了React Native之prop-types進(jìn)行屬性確認(rèn)詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12
React render核心階段深入探究穿插scheduler與reconciler
這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-11-11
React Form組件的實(shí)現(xiàn)封裝雜談
這篇文章主要介紹了React Form組件的實(shí)現(xiàn)封裝雜談,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
react 原生實(shí)現(xiàn)頭像滾動播放的示例
這篇文章主要介紹了react 原生實(shí)現(xiàn)頭像滾動播放的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
react使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能(完整代碼)
最近在做一個后臺管理項(xiàng)目,涉及到react相關(guān)知識,項(xiàng)目需求需要在表單中帶附件提交,怎么實(shí)現(xiàn)這個功能呢?下面小編給大家?guī)砹藃eact使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能,一起看看吧2021-06-06
React如何利用Antd的Form組件實(shí)現(xiàn)表單功能詳解
這篇文章主要給大家介紹了關(guān)于React如何利用Antd的Form組件實(shí)現(xiàn)表單功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
React?Flux與Redux設(shè)計(jì)及使用原理
這篇文章主要介紹了React?Flux與Redux設(shè)計(jì)及使用,Redux最主要是用作應(yīng)用狀態(tài)的管理。簡言之,Redux用一個單獨(dú)的常量狀態(tài)樹(state對象)保存這一整個應(yīng)用的狀態(tài),這個對象不能直接被改變2023-03-03

