ReactNative调用android原生View

RN开发过程中,React Native是将原生控件封装桥接成JS组件来使用的,这保证了其性能的高效性。但是有时候官方封装的常用组件不能满足需求,就需要结合原生UI使用,例如:对原生实现的UI复用;复杂UI仍然需要原生自定义View实现。接下来就简单记录下RN开发过程中调用原生UI的流程。

例如下面这个UI效果,就需要用到Android原生自定义View实现:


1.png

以上图实现效果为例,分别从Android端和RN端说明。

Android端

1. 创建自定义控件CircleMenu;

public class CircleMenu extends View {
    private Context mContext;

    /** 点击外面 */
    public static final int DL_TOUCH_OUTSIDE = -2;
    /** 点击中间点 */
    public static final int DL_TOUCH_CENTER = -1;

    /** 中心点的坐标X */
    private float mCoreX;
    /** 中心点的坐标Y */
    private float mCoreY;
    /** 是否有中心按钮 */
    private boolean mHasCoreMenu;
    /** 中心按钮的默认背景 */
    private int mCoreMenuNormalBackgroundColor;
    /** 中间按钮的描边颜色 */
    private int mCoreMenuStrokeColor;
    /** 中间按钮的描边边框大小 */
    private float mCoreMenuStrokeSize;
    /** 中间按钮选中时的背景颜色 */
    private int mCoreMenuSelectedBackgroundColor;
    /** 中心按钮圆形半径 */
    private float mCoreMenuRoundRadius;
    /** 菜单数量 */
    private int mRoundMenuNumber;
    /** 菜单偏移角度 */
    private float mRoundMenuDeviationDegree;
    /** 菜单图片 */
    private ArrayList<Bitmap> mRoundMenuDrawableList = new ArrayList<>();
    /** 是否画每个菜单扇形到中心点的直线 */
    private boolean mIsDrawLineToCenter;
    /** 菜单正常背景颜色 */
    private int mRoundMenuNormalBackgroundColor;
    /** 菜单点击背景颜色 */
    private int mRoundMenuSelectedBackgroundColor;
    /** 菜单描边颜色 */
    private int mRoundMenuStrokeColor;
    /** 菜单描边宽度 */
    private float mRoundMenuStrokeSize;
    /** 菜单图片与中心点的距离 百分数 */
    private float mRoundMenuDistance;
    /** 点击状态 -2是无点击,-1是点击中心圆,其他是点击菜单 */
    private int onClickState = DL_TOUCH_OUTSIDE;
    /** 记录按下时间,超过预设时间算长按按钮 */
    private long mTouchTime;

    public CircleMenu(Context context) {
        super(context);
        init(context, null);
    }

    public CircleMenu(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public CircleMenu(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 初始化数据
     * @param context
     * @param attrs
     */
    private void init(Context context, @Nullable AttributeSet attrs) {
        mContext = context;
        // 加载默认资源
        final Resources res = getResources();
        final boolean defaultHasCoreMenu = res.getBoolean(R.bool.default_has_core_menu);
        final int defaultCoreMenuNormalBackgroundColor = res.getColor(R.color.default_core_menu_normal_background_color);
        final int defaultCoreMenuStrokeColor = res.getColor(R.color.default_core_menu_stroke_color);
        final float defaultCoreMenuStrokeSize = res.getDimension(R.dimen.default_core_menu_stroke_size);
        final int defaultCoreMenuSelectedBackgroundColor = res.getColor(R.color.default_core_menu_selected_background_color);
        final float defaultCoreMenuRoundRadius = res.getDimension(R.dimen.default_core_menu_round_radius);
        final int defaultRoundMenuNumber = res.getInteger(R.integer.default_round_menu_number);
        final int defaultRoundMenuDeviationDegree = res.getInteger(R.integer.default_round_menu_deviation_degree);
        final boolean defaultIsDrawLineToCenter = res.getBoolean(R.bool.default_is_draw_line_to_center);
        final int defaultRoundMenuNormalBackgroundColor = res.getColor(R.color.default_round_menu_normal_background_color);
        final int defaultRoundMenuSelectedBackgroundColor = res.getColor(R.color.default_round_menu_selected_background_color);
        final int defaultRoundMenuStrokeColor = res.getColor(R.color.default_round_menu_stroke_color);
        final float defaultRoundMenuStrokeSize = res.getDimension(R.dimen.default_round_menu_stroke_size);
        final float defaultRoundMenuDistance = res.getFraction(R.fraction.default_round_menu_distance, 1, 1);

        // 读取配置信息
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.DLRoundMenuView);
        mHasCoreMenu = a.getBoolean(R.styleable.DLRoundMenuView_RMHasCoreMenu, defaultHasCoreMenu);
        mCoreMenuNormalBackgroundColor = a.getColor(R.styleable.DLRoundMenuView_RMCoreMenuNormalBackgroundColor, defaultCoreMenuNormalBackgroundColor);
        mCoreMenuStrokeColor = a.getColor(R.styleable.DLRoundMenuView_RMCoreMenuStrokeColor, defaultCoreMenuStrokeColor);
        mCoreMenuStrokeSize = a.getDimension(R.styleable.DLRoundMenuView_RMCoreMenuStrokeSize, defaultCoreMenuStrokeSize);
        mCoreMenuSelectedBackgroundColor = a.getColor(R.styleable.DLRoundMenuView_RMCoreMenuSelectedBackgroundColor, defaultCoreMenuSelectedBackgroundColor);
        mCoreMenuRoundRadius = a.getDimension(R.styleable.DLRoundMenuView_RMCoreMenuRoundRadius, defaultCoreMenuRoundRadius);
        mRoundMenuNumber = a.getInteger(R.styleable.DLRoundMenuView_RMRoundMenuNumber, defaultRoundMenuNumber);
        mRoundMenuDeviationDegree = a.getInteger(R.styleable.DLRoundMenuView_RMRoundMenuDeviationDegree, defaultRoundMenuDeviationDegree);
        mIsDrawLineToCenter = a.getBoolean(R.styleable.DLRoundMenuView_RMIsDrawLineToCenter, defaultIsDrawLineToCenter);
        mRoundMenuNormalBackgroundColor = a.getColor(R.styleable.DLRoundMenuView_RMRoundMenuNormalBackgroundColor, defaultRoundMenuNormalBackgroundColor);
        mRoundMenuSelectedBackgroundColor = a.getColor(R.styleable.DLRoundMenuView_RMRoundMenuSelectedBackgroundColor, defaultRoundMenuSelectedBackgroundColor);
        mRoundMenuStrokeColor = a.getColor(R.styleable.DLRoundMenuView_RMRoundMenuStrokeColor, defaultRoundMenuStrokeColor);
        mRoundMenuStrokeSize = a.getDimension(R.styleable.DLRoundMenuView_RMRoundMenuStrokeSize, defaultRoundMenuStrokeSize);
        mRoundMenuDistance = a.getFraction(R.styleable.DLRoundMenuView_RMRoundMenuDistance, 1, 1, defaultRoundMenuDistance);
        // 释放内存,回收资源
        a.recycle();
    }

    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        // 左右减去2,留足空间给算术裁切
        setMeasuredDimension(widthSpecSize - 2, heightSpecSize - 2);
    }

    /**
     * 绘制
     * @param canvas
     */
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        // 拿到中心位置
        mCoreX = (float) getWidth() / 2;
        mCoreY = (float) getHeight() / 2;
        // 搞到一个正方形画板
        RectF rect = new RectF(mRoundMenuStrokeSize, mRoundMenuStrokeSize,
                getWidth() - mRoundMenuStrokeSize, getHeight() - mRoundMenuStrokeSize);
        // 菜单数量要大于0
        if (mRoundMenuNumber > 0) {
            // 每个菜单弧形的角度
            float sweepAngle = (float) 360 / mRoundMenuNumber;
            // 一个重要的点 0度在正X轴上,所以需要让它回到正Y轴上
            // 计算真正的偏移角度
            // -90度回到正Y轴;-每个菜单占据角度的一半,使得菜单中央回到正Y轴;再加上用户自己想修改的角度偏移
            /** 真实菜单偏移角度 */
            float mRealRoundMenuDeviationDegree = mRoundMenuDeviationDegree - (sweepAngle / 2) - 90;
            for (int i = 0; i < mRoundMenuNumber; i++) {
                // 画扇形
                Paint paint = new Paint();
                paint.setAntiAlias(true);
                paint.setColor(onClickState == i?mRoundMenuSelectedBackgroundColor:mRoundMenuNormalBackgroundColor);
                canvas.drawArc(rect, mRealRoundMenuDeviationDegree + (i * sweepAngle), sweepAngle, true, paint);
                // 画扇形描边
                paint = new Paint();
                paint.setAntiAlias(true);
                paint.setStrokeWidth(mRoundMenuStrokeSize);
                paint.setStyle(Paint.Style.STROKE);
                paint.setColor(mRoundMenuStrokeColor);
                canvas.drawArc(rect, mRealRoundMenuDeviationDegree + (i * sweepAngle), sweepAngle, mIsDrawLineToCenter, paint);
                // 画图案
                Bitmap roundMenuDrawable = mRoundMenuDrawableList.get(i);
                if (null != roundMenuDrawable){
                    Matrix matrix = new Matrix();
                    matrix.postTranslate((float) ((mCoreX + getWidth() / 2 * mRoundMenuDistance) - (roundMenuDrawable.getWidth() / 2)),
                            mCoreY - ((float) roundMenuDrawable.getHeight() / 2));
                    matrix.postRotate(mRoundMenuDeviationDegree - 90 + (i * sweepAngle), mCoreX, mCoreY);
                    canvas.drawBitmap(roundMenuDrawable, matrix, null);
                }
            }
        }

        //画中心圆圈
        if (mHasCoreMenu) {
            // 画中心圆
            RectF rect1 = new RectF(mCoreX - mCoreMenuRoundRadius, mCoreY - mCoreMenuRoundRadius,
                    mCoreX + mCoreMenuRoundRadius, mCoreY + mCoreMenuRoundRadius);
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStrokeWidth(mCoreMenuStrokeSize);
            paint.setColor(onClickState == -1?mCoreMenuSelectedBackgroundColor:mCoreMenuNormalBackgroundColor);
            canvas.drawArc(rect1, 0, 360, true, paint);
            //画描边
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStrokeWidth(mCoreMenuStrokeSize);
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(mCoreMenuStrokeColor);
            canvas.drawArc(rect1, 0, 360, true, paint);
        }
    }
}

2. 创建ReactCircleMenuManager类继承自SimpleViewManager,供RN调用;

public class ReactCircleMenuManager extends SimpleViewManager {

    @Override
    public String getName() {
        return "ReactCircleMenu";//
    }

    @Override
    protected View createViewInstance(final ThemedReactContext reactContext) {
        //final View view = LayoutInflater.from(reactContext).inflate(R.layout.shadow_layout, null);
        final CircleMenuView circleMenuView = new CircleMenuView(reactContext);
        //        circleMenuView.setOnMenuClickListener(new OnMenuClickListener() {
//            @Override
//            public void OnMenuClick(int position) {
//                Log.e("TAG", "点击了:"+position);
//                WritableMap event = Arguments.createMap();
//                event.putInt("position", position);
//                reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(circleMenuView.getId(), "onMenuClick", event);
//            }
//        });
        return circleMenuView;
    }

    /**
     * 接收传输的颜色参数
     */
    @ReactProp(name = "color")
    public void setColor(View view, String color) {
        view.setBackgroundColor(Color.parseColor(color));
    }

    /**
     * 回传点击事件到RN
     * @return
     */
//    @Nullable
//    @Override
//    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
//        return MapBuilder.<String, Object>builder()
//                .put("onMenuClick", MapBuilder.of("registrationName", "onMenuClick"))
//                .build();
//    }
}

3. 创建ReactCircleMenuPackage类并实现ReactPackage,在createViewManagers方法中返回CircleManager的实例。

public class ReactCircleMenuPackage implements ReactPackage {
    /**
     * 用户注册Native Modules
     */
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    /**
     * 用于注册Native UI Components
     * @param reactContext
     * @return
     */
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.<ViewManager>singletonList(
                new ReactCircleMenuManager()
        );
    }
}

4. 在Application中注册ReactCircleMenuPackage

public class MainApplication extends MultiDexApplication implements ReactApplication {
    private static MainApplication instance;

    public static MainApplication getInstance() {
        return instance;
    }

    private ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(),
                    new ReactCircleMenuPackage()//添加此处
            );
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }
    };

    public void setReactNativeHost(ReactNativeHost reactNativeHost) {
        mReactNativeHost = reactNativeHost;
    }

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        SoLoader.init(this, /* native exopackage */ false);
    }
   
}

ReactNative端

  1. 创建创建circleMenu.js文件,并通过requireNativeComponent创建变量ReactCircleMenu;
import React, { Component } from "react";
import PropTypes from "prop-types";
import { View, requireNativeComponent } from "react-native";

//requireNativeComponent函数中的第一个参数就是ReactCircleMenuManager.getName返回的值。
const RCTCircleMenu = requireNativeComponent("ReactCircleMenu", {
    propTypes: {
        color: PropTypes.number,//定义传输属性类型
        ...View.propTypes // 包含默认的View的属性
    }
});

export default class CircleMenu extends Component {
    render() {
        return (
            <RCTCircleMenu
                color={color.primary}//自定义颜色
                style={{ width: 175,height: 175}}
                onMenuClick={this.onMenuClick.bind(this)}
            />
        );
    }

    /**
     * 点击事件
     */
    onMenuClick(e){
        console.log(e.nativeEvent.position.toString());
    }
}
  1. 调用CircleMenu.js文件

import React, {Component} from "react";
import {View, requireNativeComponent} from "react-native";
import CircleMenu from "../../widget/CircleMenu";

export default class DevPhoneIR extends Component {
    render() {
        return (
            <CircleMenu/>
        );
    }
}

这样就实现了RN调用封装的原生组件,上边代码中可以看出,RN可以自定义属性给Android原生View,Android原生组件的点击事件也可以回传给RN。

当然也可以把原生xml布局封装为View形式使用,只需修改ReactCircleMenuManager类里createViewInstance方法的返回View即可。

@Override
    protected View createViewInstance(final ThemedReactContext reactContext) {
        final Vibrator vibrator = (Vibrator)reactContext.getSystemService(reactContext.VIBRATOR_SERVICE);

        final View view = LayoutInflater.from(reactContext).inflate(R.layout.layout, null);
        final TextView textView = view.findViewById(R.id.text);
        final Button btn = view.findViewById(R.id.btn);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG", "点击了:");
            }
        });
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG", "点击了1:");
                ConsumerIrManagerApi.getIrManager(reactContext).transmit(Constants.FREQUENCY, NecPattern.buildPattern(
                        Constants.TV_USER_CODE_H,Constants.TV_USER_CODE_L, 0x14));
                vibrator.vibrate(80);
                WritableMap event = Arguments.createMap();
                event.putInt("position", 0);
                reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(view.getId(), "onMenuClick", event);
            }
        });
        return view;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容