在使用 React Native(如下簡稱 RN ) 開發移動App時,會碰到不少彈窗的場景,雖然 RN自帶了一個 Modal 組件能夠實現這一效果,可是因爲Android和iOS平臺的差別性,使得使用同一個組件開發出來的效果會略有差別。好比,Modal組件在iOS平臺,彈框是全屏的,可是在Android平臺卻不是,會有狀態欄,以下效果。
之因此這樣,是由於Android 端的Modal 控件使用的Dialog,內容沒法從狀態欄處開始佈局。而iOS是基於 Window 的,因此是覆蓋在視圖上面的。若是要讓雙端的樣式同樣,那麼須要對Android進行特殊處理。react
因爲RN的Modal 組件在Android中是使用Dialog實現的,因此若是要實現一個全屏的彈框,那麼就須要自定義一個全屏展現的Dialog。android
首先,咱們新建一個繼承自Dialog的自定義組件FullModal,代碼以下:ios
package com.cgv.cn.movie.modal; import android.app.Dialog; import android.content.Context; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.StyleRes; public class FullModal extends Dialog { private boolean isDarkMode; private View rootView; public void setDarkMode(boolean isDarkMode) { this.isDarkMode = isDarkMode; } public FullModal(@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()); } AndroidWorkaround.assistView(rootView, getWindow()); } }
在上面的代碼中,StatusBarUtil.setTransparent(getWindow())
方法的主要做用就是將狀態欄背景透明,而且讓佈局內容能夠從 Android 狀態欄開始。而後咱們看一下setTransparent()方法的實現。app
@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); } }
須要說明的是,transparentStatusBar()方法只在 Android 4.4 以上纔會有效果,Android 4.4以前要實現沉浸式狀態欄須要使用其餘的方式(好比反射)。不過如今都已是9012年了, Android 4.4版本的應該不多了吧。ide
自定義的原生組件開發完成以後,接下來就是按照RN和原生交互的規則進行RN的封裝。首先,新建一個FullModalManager類,該類的主要做用就是RN的Android和Js的通訊橋樑,代碼以下。佈局
public class FullModalManager extends ViewGroupManager<FullModalView> { @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 FullModalView createViewInstance(ThemedReactContext reactContext) { final FullModalView view = new FullModalView(reactContext); final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class); view.setOnRequestCloseListener(new FullModalView.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(FullModalView view) { super.onDropViewInstance(view); view.onDropInstance(); } @ReactProp(name = "autoKeyboard") public void setAutoKeyboard(FullModalView view, boolean autoKeyboard) { view.setAutoKeyboard(autoKeyboard); } @ReactProp(name = "isDarkMode") public void setDarkMode(FullModalView view, boolean isDarkMode) { view.setDarkMode(isDarkMode); } @ReactProp(name = "animationType") public void setAnimationType(FullModalView view, String animationType) { view.setAnimationType(animationType); } @ReactProp(name = "transparent") public void setTransparent(FullModalView view, boolean transparent) { view.setTransparent(transparent); } @ReactProp(name = "hardwareAccelerated") public void setHardwareAccelerated(FullModalView view, boolean hardwareAccelerated) { view.setHardwareAccelerated(hardwareAccelerated); } @Override protected void onAfterUpdateTransaction(FullModalView view) { super.onAfterUpdateTransaction(view); view.showOrUpdate(); } }
FullModalManager類最重要的createViewInstance()方法。而且,在事件通訊中,RN的Modal 已經存在了onShow()和 onRequestClose()回調,可是這裏不能再使用這兩個命名,因此這裏改爲了 onFullScreenShow 和 onFullScreenRequstClose,可是在Js端仍是從新命名成 onShow 和 onRequestClose ,因此在使用過程當中仍是沒有任何變化。flex
在RN的Js部分,咱們只須要處理 Android 的實現便可,而對於iOS部分則不須要處理。爲了方便在RN代碼中進行引用,咱們能夠參考RN自定義組件的方式新建FullModal.android.js和FullModal.ios.js兩個文件,其中FullModal.android.js的源碼以下。ui
const FullScreenModal = requireNativeComponent('RCTFullScreenModalHostView', null); export default class FullModalViewAndroid extends Component { _shouldSetResponder = () => { return true; } static propTypes = { isDarkMode: PropTypes.bool, // false 表示白底黑字,true 表示黑底白字 autoKeyboard: PropTypes.bool, // 未知緣由的坑,modal中的edittext自動彈起鍵盤要設置這個參數爲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> ); } }
接下來,咱們就能夠在業務代碼中進行使用了,以下所示。this
const ModalView = tools.isIos ? Modal : FullModal return ( <ModalView transparent={false} visible={targetShow} onRequestClose={() => { }}> <View style={{ flex: 1 }}> ...//省略其餘代碼 </View> </ModalView> )
附,相關源碼spa