前言
React Native封装了大部分最常见的组件,但有时我们需要一些特殊的组件是React Native没有提供的。该文章将细说如何在React Naitve应用程序中封装和植入已有的原生UI组件。
步骤
- 创建一个ViewManager的子类
- 实现createViewInstance方法
- 导出视图的属性设置器:使用@ReactProp(或@ReactPropGroup)注解
- 把这个视图管理类注册到应用程序包的createViewManagers里
- 实现JavaScript模块
具体代码实现
我们将自定义一个可以显示圆形、圆角矩形和椭圆形图片的ImageView组件
- 在Android中自定义该控件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148public class RoundImageView extends ImageView {
private Paint mPaint;
private int mWidth;
private int mRadius;
private RectF mRect;
private int mRoundRadius;
private BitmapShader mBitmapShader;
private Matrix mMatrix;
private int mType = 1;
public static final int TYPE_CIRCLE = 1;
public static final int TYPE_ROUND_RECTANGLE = 2;
public static final int TYPE_OVAL = 3;
public static final int DEFAUT_RADIUS = 5;
public RoundImageView(Context context) {
this(context, null);
}
public RoundImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mMatrix = new Matrix();
mRoundRadius = DEFAUT_RADIUS;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mType == TYPE_CIRCLE) {
mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
mRadius = mWidth / 2;
setMeasuredDimension(mWidth, mWidth);
}
}
@Override
protected void onDraw(Canvas canvas) {
if (null == getDrawable()) {
return;
}
initBitmapShaderByType();
switch (mType) {
case TYPE_CIRCLE:
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
break;
case TYPE_ROUND_RECTANGLE:
mPaint.setColor(Color.RED);
canvas.drawRoundRect(mRect, mRoundRadius, mRoundRadius, mPaint);
break;
case TYPE_OVAL:
canvas.drawOval(mRect, mPaint);
break;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRect = new RectF(0, 0, getWidth(), getHeight());
}
private void initBitmapShaderByType() {
Drawable drawable = getDrawable();
if (null == drawable) {
return;
}
Bitmap bitmap = drawableToBitmap(drawable);
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
float scale = 1.0f;
switch (mType) {
case TYPE_CIRCLE:
int bSize = Math.min(bitmap.getWidth(), bitmap.getHeight());
scale = mWidth * 1.0f / bSize;
break;
case TYPE_ROUND_RECTANGLE:
case TYPE_OVAL:
scale = Math.max(getWidth() * 1.0f / bitmap.getWidth(), getHeight() * 1.0f / bitmap.getHeight());
break;
}
mMatrix.setScale(scale, scale);
mBitmapShader.setLocalMatrix(mMatrix);
mPaint.setShader(mBitmapShader);
}
public void setImageName(String name) {
this.setImageResource(getResourceId(name));
}
public void setType(String typeStr) {
int type = 1;
switch (typeStr) {
case "circle":
type = 1;
break;
case "round":
type = 2;
break;
case "oval":
type = 3;
break;
}
if (this.mType != type) {
this.mType = type;
invalidate();
}
}
public void setRoundRadius(int mRoundRadius) {
if (this.mRoundRadius != mRoundRadius) {
this.mRoundRadius = mRoundRadius;
invalidate();
}
}
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
return bitmapDrawable.getBitmap();
}
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
private int getResourceId(String imageName) {
int resId = getResources().getIdentifier(imageName, "mipmap", getContext().getPackageName());
if (resId == 0) {
resId = getResources().getIdentifier(imageName, "drawable", getContext().getPackageName());
}
return resId;
}
}
在上述的RoundImageView中对外提供了三个公共方法,用于后续js设置属性使用:
- setImageName(String name)
- setType(String typeStr)
- setRoundRadius(int mRoundRadius)
创建ViewManager子类,实现方法createViewInstance,通过@ReactProp(或@ReactPropGroup)注解来导出属性的设置方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class RoundImageViewManager extends SimpleViewManager<RoundImageView> {
public static final String COMPONENT_NAME = "RoundImageView";
@Override
public String getName() {
return COMPONENT_NAME;
}
@Override
protected RoundImageView createViewInstance(ThemedReactContext reactContext) {
return new RoundImageView(reactContext);
}
@ReactProp(name = "name")
public void setPath(RoundImageView view, String name) {
view.setImageName(name);
}
/**
* 设置图片类型:圆形、圆角矩形、椭圆形
*
* @param mType
*/
@ReactProp(name = "type")
public void setType(RoundImageView view, String mType) {
view.setType(mType);
}
/**
* 设置圆角大小
*
* @parammRoundRadius
*/
@ReactProp(name = "roundradius")
public void setRoundRadius(RoundImageView view, int mRoundRadius) {
view.setRoundRadius(mRoundRadius);
}
}注册ViewManager,然后在Application的mReactNativeHost中返回RoundImageViewReactPackage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class RoundImageViewReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new RoundImageViewManager());
}
}
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RoundImageViewReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};实现对应的JavaScript模块
1
2
3
4
5
6
7
8
9
10
11
12
13import { PropTypes } from 'prop-types';
import { requireNativeComponent, View } from 'react-native';
const RoundImageView = requireNativeComponent('RoundImageView', {
propTypes: {
name: PropTypes.string,
type: PropTypes.oneOf(['circle', 'round', 'oval']),
roundradius: PropTypes.number,
...View.propTypes // 包含默认的View的属性
},
});
export default RoundImageView;在js中使用该组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import RoundImageView from './RoundImageView';
export default class RoundImageViewTest extends Component {
render() {
return (
<View style={styles.container}>
<RoundImageView style={{width: 100, height: 100}} name="icon_rectangle" type="circle" />
<RoundImageView style={{width: 100, height: 100}} name="icon_rectangle" type="round" roundradius={30} />
<RoundImageView style={{width: 80, height: 100}} name="icon_rectangle" type="oval" roundradius={45} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
},
});
图片
