不管是Android还是ios,Button控件都在这两个原生开发中都已经被封装好了,我们可以直接使用。但是在RN中并没有直接提供这种组件给我们,而是给我们提供了一个可点击的组件:Touchable系列(如TouchableOpacity, TouchableHighlight等)。那么今天我们就一起来学习封装属于我们自己的Button。看一下我们的效果图
上面的效果图中有三个Button,若不封装的话,我们就肯定会写很多重复代码,这对于一个面向对象的程序员来说肯定是不能接受的。好了,那我们就一起来实现封装的代码吧。若不熟悉Touchable系列组件的,可以先看看我之前写的文章。
实现步骤
-
step 1 先封装一个可点击的button
_renderTouchableHighlight(selectedColor, type,style) { return ( <TouchableHighlight underlayColor={selectedColor} onPress={this._onPress} style={[styles.container, type,style, this.state.disable && {backgroundColor: this.props.disableColor}]} disabled={this.state.disable} > <Text style={this.props.textStyle}>{this.props.text}</Text> </TouchableHighlight> ); } _renderTouchableOpacity(type,style) { return ( <TouchableOpacity onPress={this._onPress} style={[styles.container, type, style]} disabled={this.state.disable} > <Text style={this.props.textStyle}>{this.props.text}</Text> </TouchableOpacity> ); }
这里用两个方法来渲染不同类型的button,其实它们的不同之处在于:当Button被点击的时候,button需要呈现出什么样的状态来进行视觉交互。RN已经给Opacity类型的button设置了selected状态,但我们若需要自己定义selected按钮状态的话,就需要使用TouchableHighlight类型的。
方法中的selectedColor用来判断使用者是选择何种类型的button,若传来了selectedColor,那么就作为TouchableHighlight的underlayColor。对于整个button长什么样,由使用者去定制,不过我们可肯定的是,button的文字肯定是居中的,所以设置了styles.container,代码如下:
container: {
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden' //这个属性定义溢出元素内容区的内容会如何处理,内容会被修剪,并且其余内容是不可见的。
},
方法中第二个参数type是用来决定用户需要什么样的button,是实心,空心或者仅是text。
static _setDifferentButtonStyle(buttonColor, buttonRadius, buttonType, borderWidth) {
if (buttonType == "normal") {
return CustomButton._setDifferentStyle(buttonColor, buttonRadius, buttonColor);
} else if (buttonType == 'stroke') {
return CustomButton._setDifferentStyle('transparent', buttonRadius, buttonColor, borderWidth);
} else if (buttonType == 'text') {
return CustomButton._setDifferentStyle('transparent', 0, 'transparent');
}
}
static _setDifferentStyle(backgroundColor, borderRadius, borderColor, borderWidth) {
return {
backgroundColor: backgroundColor,
borderRadius: borderRadius,
borderColor: borderColor,
borderWidth: borderWidth
};
}
上面代码中,可以根据使用者传过来的buttonType类型来返回对应的button样式;至于第三个参数style,是使用者设置button时传过来的具体的样式,我们可以直接拿来用。
- step 2 传递点击事件
Touchable系列中有个onPress方法,这是用来处理点击事件的。我们不可能把外面传过来的事件在这个类中去处理,而是需要使用者自己处理。那如何做了?也就是事件的传递了,另外一种说法就是回调。在最上面代码中,有看到onPress={this._onPress},那么这个this._onPress到底是谁了?
_onPress() {
if (this.props.onPress) {
this.props.onPress();
}
}
从上面的代码中可以看出,最终是调到传过来的onPress方法。不过我们若直接这样调的话,肯定会报错。为什么?因为这个方法是我们自己定义的,但没有像组件的生命周期方法一样,在一个组件被创建时就已经被初始化了。所以,我们需要将我们自己定义的方法与初始时进行绑定。初始化操作我们一般放在构造方法中进行。
constructor(props) {
super(props);
this._onPress = this._onPress.bind(this);
}
- step 3 防重复点击
网络请求数据一般是耗时操作,为了防止用户多次点击button去请求数据,我们还需要设置在做耗时操作时,不能让button变得可点击,并且给出视觉交互。那如何来实现了?我们可以通过状态的改变来决定是否可以点击。有两种实现方式:
1、对外提供两个方法,让使用者通过拿到我们自定义button的实例来调用这个暴露出去的方法,从而达到点击与不可点击的切换
2、我们可以将某个改变点击状态的方法传给使用者进行回调,让使用者决定什么时候可改变button的点击状态。
第一种相当来说比较简单,我们来使用第二种方式。
constructor(props) {
super(props);
this._onPress = this._onPress.bind(this);
this._enable = this._enable.bind(this);
this._disable = this._disable.bind(this);
this.state = {
disable: false
}
}
_onPress() {
if (this.props.onPress) {
this._disable();
this.props.onPress(this._enable);
}
}
_enable() {
this.setState({
disable: false
});
};
_disable() {
this.setState({
disable: true
});
};
我们通过一个状态值来保存button的可点击状态,在button被点击时,马上将这个button置为不可点击,至于什么时候可以点击,我们将enable方法回调给了使用者,由使用者决定。如上面所说,我们自定义的方法都必须先在构造方法中进行初始化。
- step 4 设置属性类型和默认值
我们自定义的属性需要什么类型,使用者并不知道,所以我们需要声明我们自定义属性的类型,可以通过PropTypes,并且还可以强制用户必须传哪些属性。
//属性类型
CustomButton.propTypes = {
text: PropTypes.string.isRequired,
textStyle: Text.propTypes.style,
buttonType: PropTypes.oneOf(['normal', 'stroke', 'text']).isRequired,
selectedColor: PropTypes.string,
onPress: PropTypes.func,
buttonColor:PropTypes.string,
buttonRadius:PropTypes.number,
borderWidth:PropTypes.number,
};
//属性默认值
CustomButton.defaultProps = {
borderWidth: 1
};
最后是整个类的渲染
render() {
//这里是将props中属性进行解构,es6语法,可查看阮一峰的《ES6标准与入门》
let {selectedColor, buttonColor, buttonRadius, buttonType, borderWidth, style}=this.props;
let type = CustomButton._setDifferentButtonStyle(buttonColor, buttonRadius, buttonType, borderWidth);
if (selectedColor) {
{
return this._renderTouchableHighlight(selectedColor, type, style);
}
} else {
{
return this._renderTouchableOpacity(type, style);
}
}
}
-
step 5 进行测试
<View style={styles.container}> <CustomButton text="确定" buttonColor="red" buttonRadius={20} buttonType="normal" textStyle={styles.textStyle} style={styles.customButton} selectedColor="green" disableColor="yellow" onPress={(callback)=> { setTimeout(()=> { callback(); }, 3000); }} /> <CustomButton text="确定" buttonColor="red" buttonRadius={20} buttonType="stroke" textStyle={styles.textStyle} style={styles.customButton} selectedColor="green" disableColor="yellow" onPress={(callback)=> { setTimeout(()=> { callback(); }, 3000); }} /> <CustomButton text="确定" buttonColor="red" buttonRadius={20} buttonType="text" textStyle={styles.textStyle} selectedColor="green" disableColor="yellow" style={{marginTop:20}} onPress={(callback)=> { setTimeout(()=> { callback(); }, 3000); }} /> </View>
好了,自定义button就封装完了以及学习了自定义一个组件需要做哪些事。这里面稍微有一点难度的就是方法的传递进行回调。在java中是不允许方法作为参数传递的。不过,在java中不能干的事,在js中可以干是非常常见的。我们今天做的button主要是文字,其实还可以对其进行拓展,那就是这个button为image时,那个比较简单,有兴趣的朋友可以进一步进行封装。
本人目前对于RN也还是处于学习的阶段,若在写文章时出现了错误或者代码可以优化时,请各位朋友不吝告知啊!