上篇博客,笔者介绍了如何封装Android原生模块供rn端调用,一共分为四个步骤。这篇我们来讲解下如何封装Android原生UI供rn端调用。
官方文档,请参考下面链接:https://reactnative.cn/docs/native-components-android。如果有些同学懒得看官方文档或是看完之后有些模糊不清的地方,可以参考下本篇博客列举的几个案例。
我们通过两个案例来讲解如何封装原生UI给rn端调用
- 封装TextView
- 封装Webview
封装TextView
第一步,集成SimpleViewManager,如下所示:
package com.example.demo.reactnative.base.viewManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import org.w3c.dom.Text;
import java.util.Map;
/**
* Created by brett.li
* on 2022/10/18
*/
//SimpleViewManager<T>,T可以是Android原生的view如:TextView、WebView或者是Android原生的自定义view
public class MyTextviewManager extends SimpleViewManager<TextView> {
private ReactApplicationContext context;
public MyTextviewManager(ReactApplicationContext reactApplicationContext){
context = reactApplicationContext;
}
@NonNull
@Override
public String getName() {
return "RCTTextView";
}
@NonNull
@Override
protected TextView createViewInstance(@NonNull ThemedReactContext reactContext) {
TextView textView = new TextView(context);
textView.post(new Runnable() {
@Override
public void run() {
ViewGroup.LayoutParams lp = textView.getLayoutParams();
lp.width = 100;
lp.height = 100;
textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
});
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
//原生事件通知rn,我们可以在原生中处理点击事件,如点击TextView,我们直接弹出一个Toast,但是如何想要在rn端响应这个事件,那么可以通过下面这种方式
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onClick",event);
}
});
return textView;
}
@Override
public void onDropViewInstance(@NonNull TextView view) {
super.onDropViewInstance(view);
Log.e("RCTTextView","onDropViewInstance view is "+view);
}
@ReactProp(name = "text")
public void setText(TextView view,String text){
Log.e("RCTTextView","text is "+text);
view.setText(text);
view.setTextSize(10);
Log.e("RCTTextView","layoutParams is "+view.getLayoutParams());
}
}
第二步,把MyTextviewManager添加到ReactPackage中:
package com.example.demo.reactnative.base.hook;
import androidx.annotation.NonNull;
import com.example.demo.reactnative.base.module.RNBaseReactModule;
import com.example.demo.reactnative.base.module.StorageModule;
import com.example.demo.reactnative.base.module.ToastModule;
import com.example.demo.reactnative.base.viewManager.MyTextviewManager;
import com.example.demo.reactnative.base.viewManager.MyWebViewManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by brett.li
* on 2022/10/17
*/
public class RNBasePackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
//modules.add(RNBaseReactModule.getInstance(reactContext).setReactApplicationContext(reactContext));
// modules.add(new ToastModule(reactContext));
// modules.add(new StorageModule(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
List<ViewManager> modules = new ArrayList<>();
modules.add(new MyTextviewManager(reactContext));
return modules;
}
}
第三步,将RNBasePackage添加到ReactNativeHost,这一步更封装原生模块一样。
package com.example.demo;
import android.app.Application;
import android.content.Context;
import com.facebook.soloader.SoLoader;
/**
* Created by Brett.li on 2021/9/21.
*/
public class MyReactApplication extends Application implements ReactApplication {
@Override
public ReactNativeHost getReactNativeHost() {
return new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages= new PackageList(this).getPackages();
packages.add(new RNBasePackage());
return packages;
}
@Nullable
@Override
protected String getBundleAssetName() {
//就是我们打包出来的bundle的名字,不能写错,不然就加载不到bundle
return "main.bundle";//bundle的名字,默认是index.android.bundle
}
@Override
protected String getJSMainModuleName() {
//即打包脚本中--entry-file后面的参数名。不能写错
return "index";
}
};
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
//application中需要做两件事
//1.实现getReactNativeHost接口
//2.添加SoLoader.init(this, /* native exopackage */ false);这句代码
第四步,rn端调用
import React from "react";
import {
DeviceEventEmitter,
NativeEventEmitter,
NativeModules,
Platform,
requireNativeComponent,
StyleSheet,
Text, ToastAndroid,
TouchableOpacity,
View
} from "react-native";
//1.添加该代码,确保rn端能够找到该原生ui
const RCTTextView = (Platform.OS === 'android') ? requireNativeComponent('RCTTextView') : null;
const NWebview = (Platform.OS === 'android') ? requireNativeComponent('WebView') : null;
const styles = StyleSheet.create({
container: {
// flex: 1,
justifyContent: 'center',
alignItems:'center'
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});
interface Props{
test:string
}
interface State{
test:string
}
export class HelloWorld extends React.Component<Props,State> {
constructor(props:Props) {
super(props);
this.state={
test:props.test
}
//响应原生端事件,“onClick”需要和原生的emit方法的第一个参数对应
DeviceEventEmitter.addListener("onClick",(msg)=>{
ToastAndroid.show(msg.message,ToastAndroid.SHORT);
})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>{
NativeModules.CustomToast.show('Brett', NativeModules.CustomToast.SHORT);
}}>
<Text style={styles.hello}>{this.state.test}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={()=>{
this.setDataFromStorage("brett","1")
// NativeModules.BaseStorageModule.setStringData("brett","1")
}}>
<Text>{"setData"}</Text>
</TouchableOpacity>
//2.注意,必须为原生ui添加width,height不然是显示不出来的,
//style是每个rn的view都有的属性,如果是自定义的原生ui,编译器可能没有提示,但是还是可以使用style属性的。text属性则是我们在原生中@ReactProp(name = "text")的那么对应的值
<RCTTextView style={{width:100,height:100}} text={"Hello!!!"}/>
<NWebview style = {{width:500,height:500}} url={"https://www.baidu.com"}/>
</View>
);
}
}
完成上面这四步,运行程序则可以看到我们自定义的view了。
小提示:如果编译出错,找不到该原生ui。首先,确认上面这四步是否有遗漏,如果没有那么请删掉node_modules文件夹,重新yarn下载依赖,并且原生程序sync一下。
封装Webview
其实,笔者认为封装原生ui这个功能最大的好处是可以用于webview中。因为现在的app很大一部分都是混合开发的,通过webview加载一些前端的页面,通过webview客户端可以注入一些事件给前端,如果原生已经完成了这一套逻辑,难道rn端还要在实现一套吗?当然rn端有提供一个成熟的webview控件,但是如果我们自己封装webview那么就可以省去重新编写js事件这部分代码,直接复用原生的。
例如:我们可以在rn端控制webview加载的url、大小等,原生则是自己进行webview的一些设置、WebViewClient的监听等,监听完成需要发送什么事件则通过RCTDeviceEventEmitte通知rn端,在rn端实现相关的功能。有相关需求的同学可以参考下笔者的思路。
package com.example.demo.reactnative.base.viewManager;
import android.util.Log;
import android.view.ViewGroup;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* Created by brett.li
* on 2022/10/18
*/
public class MyWebViewManager extends SimpleViewManager<WebView> {
private ReactApplicationContext context;
public MyWebViewManager(ReactApplicationContext context){
this.context = context;
}
@NonNull
@Override
public String getName() {
return "WebView";
}
@NonNull
@Override
protected WebView createViewInstance(@NonNull ThemedReactContext reactContext) {
WebView webView = new WebView(reactContext);
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return webView;
}
@ReactProp(name = "url")
public void setUrl(WebView webView,String url){
Log.e("WebView","url is "+url);
initWebViewSetting(webView);
webView.loadUrl(url);
}
private void initWebViewSetting(WebView webView){
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.startsWith("http://") || url.startsWith("https://")) {
view.loadUrl(url);
return true;
}
return false;
}
});
}
}
这里就只贴出原生的代码,剩下的步骤更笔者上面封装TextView的一样的。
最后
其实,笔者上面提供的案例都是封装view的,并没有封装viewgroup。后面笔者会专门抽出一个章节来讲解如何封装一个瀑布流控件。