美文网首页
ReactNative调用android原生View

ReactNative调用android原生View

作者: Huangrong_000 | 来源:发表于2019-08-26 17:51 被阅读0次

    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;
        }
    

    相关文章

      网友评论

          本文标题:ReactNative调用android原生View

          本文链接:https://www.haomeiwen.com/subject/csmdectx.html