ReactNative调用android原生View
16lz
2021-01-26
RN开发过程中,React Native是将原生控件封装桥接成JS组件来使用的,这保证了其性能的高效性。但是有时候官方封装的常用组件不能满足需求,就需要结合原生UI使用,例如:对原生实现的UI复用;复杂UI仍然需要原生自定义View实现。接下来就简单记录下RN开发过程中调用原生UI的流程。
例如下面这个UI效果,就需要用到Android原生自定义View实现:
以上图实现效果为例,分别从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 getExportedCustomDirectEventTypeConstants() { // return MapBuilder.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端
- 创建创建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()); }}
- 调用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; }
更多相关文章
- Android延时启动效果+轮播图+点击进入+沉浸式状态栏+按钮点击颜
- Android自定义GridView之仿支付宝首页可拖动、可删除的九宫格
- Android-小巫新闻客户端底部菜单切换实现
- Android(安卓)L中水波纹点击效果的实现
- android DownloadManager广播事件:下载完成、通知栏点击事件
- Android实现加载网页,获取网页上图以及点击图片预览图片
- Android使用RadioButton结合ListView显示对话框单选按钮列表
- Android的自定义图片按钮ImageButton【第一篇】
- android button 背景样式