ReactNative封装Android原生UI

上篇博客,笔者介绍了如何封装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。后面笔者会专门抽出一个章节来讲解如何封装一个瀑布流控件。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值