自定义React Native的RadioButton和RadioGroup(单选按钮)

开始学习React Native也有5天了,在项目时发现RN没有给提供RadioButton和RadioGroup这两个组件,只有CheckBox组件,但是项目中确实有好处需要使用到RadioButton和RadioGroup,既然没有,那就自己写吧。

效果图:


1.RadioButton

需求分析:

1)可以指定选中状态和未选中状态的图片、图片的大小:selectedImg、unSelectedImg、imgSize

2)可以指定选中状态和未选中状态的文字、文字大小、文字颜色 text,textSize,selectedTextColor,unSelectedTextColor

3)可以指定文字距离图片的距离:drawablePadding

4)可以指定整体RadioButton的间距,就像使用系统组件一样,这个用style来接收

5)如果使用RadioGroup来创建RadioButton,还可以设置不同的RadioButton是横向排列还是纵向排列,如果是横向排列那么2个RadioButton之间的间距是多少,如果是纵向排列,那么纵向的间距又是多少,这些我们用2个变量来控制:oritation和margin,oritation用来控制横纵向的排列,margin代表2个Button之间的距离。

需求有了之后,其实写起来很简单,无非就是一张图片和文字再加一些样式而已,在RN中控制一个View动态改变需要使用到state,这里定义一个state变量selected来记录RadioButton是否被选中,为true为选中状态,false为未选中状态:

state = {
 // 让当前的变量为传递过来的是否选中的状态,在使用RadioButton时是可以指定属性selected,在这里接收
 selected: this.props.selected,
};

state变量和动态控制一个组件的改变,但是组件改变之前仍然可能会显示一些东西,这些东西用props来控制,而且可以用这些props定义一些默认的属性,例如我们可以定义默认的文字颜色等:

// 默认属性
static defaultProps = {
   selectedChanged: false,
   selectedTextColor: '#333333',
   unSelectedTextColor: '#333333',
};

下面就是在render方法中渲染图片和文字,以及控制一些style属性,首先获取到传递过来的各种属性值以及当前的状态值:

const {
    selectedImg, unSelectedImg, imgSize, text, selectedTextColor, oritation,
    unSelectedTextColor, textSize, drawablePadding, margin, style,
} = this.props;
const {selected} = this.state;

因为在渲染时多个不同的RadioButton排列方向可以是横纵方向,所以这个时候就需要注意传递过来的margin是marginLeft还是marginTop,所以先定义一个变量style,根据方向的不同,来决定到底是marginLeft还是marginTop:

let marginStyle = {};
if (oritation === 'row') {
   marginStyle = {
       flexDirection: 'row',
       alignItems: 'center',
       marginLeft: margin,
   }
} else {
   marginStyle = {
       flexDirection: 'row',
       alignItems: 'center',
       marginTop: margin,
   }
}

然后返回一个整体的View即可:这里需要注意,在使用时我们可能会给这个RadioButton添加style属性,例如marginLeft,marginTop等,这个是在外面设置的,在内部我们同样会设置style属性,这个style是用来控制图片和文字这2个元素为横向排列,所以需要把外面传递过来的属性和内部的属性合到一起,多个属性使用数组就行,例如下面的:

style={[marginStyle, style]},然后就是根据state以及传入过来的属性设置具体的值。

return (
   <View style={[marginStyle, style]}>
      <Image
            style={{width: imgSize, height: imgSize}}
            source={selected ? selectedImg : unSelectedImg}/>
      <Text style={{
            color: selected ? selectedTextColor : unSelectedTextColor,
            fontSize: textSize, marginLeft: drawablePadding
              }}>{text}
      </Text>
  </View>
);

这样就可以自定义图片、文字等属性了,有了这些之后,还需要一个点击事件来改变样式的显示,并且提供给外面一个监听,让外面能够知道改变了,首先声明一个成员变量代表监听事件:

selectedChanged;

然后在构造方法中给这个变量赋值:

constructor(props) {
    super(props);
    this.selectedChanged = props.selectedChanged;
}

然后给最外层的View添加TouchableOpacity组件,添加点击事件:

return (
            <TouchableOpacity onPress={() => {
                if (this.selectedChanged) { // 这个是判断外界是否传入了这个监听事件,如果没有传入,则直接调用this.selectedChanged会出错
                    // this.selectedChanged就是调用了我们刚才生命的成员变量,可以代表一个方法,这里传入一个现在的选中状态的值和上一次状态值                    
                    this.selectedChanged(selected, !selected);
                }
                // 点击之后改变state值,该值一旦改变之后就会重新调用render方法进行绘制
                this.setState({
                    selected: !selected,
                })
            }}>
                <View
                    ...
                </View>
            </TouchableOpacity>
);

在外界使用:

<RadioButton
     selected={true}
     selectedImg={Images.InvoiceInfo.RadioButtonSelected}
     unSelectedImg={Images.InvoiceInfo.RadioButtonUnSelected}
     imgSize={20}
     text='个人'
     textSize={12}
     drawablePadding={8}
     selectedChanged={(oldState, newState) => {
        console.log(oldState + "--" + newState);
}}/>

2.RadioGroup

使用RadioButton大部分情况是多个共同使用,而且只能有一个被选中,android中就有这样的组件,但是在RN中没有找到,其实这个也很容易实现,原理是通过RadioGroup来生成多个RadioButton并且持有这些RadioButton的引用,当一个被选中的时候把其他的置为不选中,使用RadioGroup时给这个RadioGroup传递一个数组即可,然后RadioGroup通过数组来创建RadioGroup,因为同样要指定RadioButton的样式,所以在外部使用时直接把style的各种样式和属性一并传递给RadioGroup,RadioGroup在创建RadioButton时把这些样式属性再传递给RadioButton即可,原理很简单,直接上代码:


import React, {Component} from 'react';
import {
    View,
} from 'react-native';
import RadioButton from "./RadioButton";

/**
 * RadioGroup:
 * 里面只能存放RadioButton,生成多少个RadioButton,需要传递data数组,不管生成多少个RadioButton,只能有一个被选中

 使用示例:
 <RadioGroup
 style={{marginLeft: 20, marginTop: 23}} // 指定RadioGroup的样式
 oritation='column'     // 里面RadioButton是横向排列还是纵向排列:'column','row'
 margin={15}  // 如果oritation='column',则代表多个RadioButton横向间距,为'row',则代表多个RadioButton纵向间距
 data={[{"text": "个人"}, {"text": "单位"}, {"text": "其他"}]} // data需要传递一个数组
 selectedImg={Images.InvoiceInfo.RadioButtonSelected} // 被选状态时的图片
 unSelectedImg={Images.InvoiceInfo.RadioButtonUnSelected} // 未被选状态时的图片
 imgSize={20} // 图片的大小(宽高一样)
 text='单位' // 文字
 textSize={12} // 文字大小
 drawablePadding={8} // 文字和图片之间的距离
 itemChange={(index) => { // 某一个item被选中时的事件监听,会返回当前选中的item的索引位置
                        ToastAndroid.show("" + index, ToastAndroid.SHORT);
                    }}
 />
 */
export default class RadioGroup extends Component {

    // 当前选中的item索引
    currentIndex = -1;
    // 存放所有的RadioButton的引用
    referencesArray = [];
    // 选中某一个item的回调监听
    itemChange;

    constructor(props) {
        super(props);
        this.itemChange = props.itemChange;
    }

    render() {
        // 清空数组,避免多次render重复向数组中添加
        this.referencesArray = [];
        const {
            data, selectedImg, unSelectedImg, imgSize, selectedTextColor, oritation,
            unSelectedTextColor, textSize, drawablePadding, margin, style,
        } = this.props;
        return (
            <View style={[style, {flexDirection: oritation}]}>
                {
                    data.map((radioData, index) => {
                        return (
                            <RadioButton
                                key={index}
                                ref={radioButton => this.referencesArray.push(radioButton)}
                                text={radioData.text}
                                selectedImg={selectedImg}
                                unSelectedImg={unSelectedImg}
                                imgSize={imgSize}
                                selectedTextColor={selectedTextColor}
                                unSelectedTextColor={unSelectedTextColor}
                                textSize={textSize}
                                oritation={oritation}
                                drawablePadding={drawablePadding}
                                margin={index === 0 ? null : margin}
                                selectedChanged={() => {
                                    this.change(index);
                                }}
                            />
                        );
                    })
                }
            </View>
        );
    }

    /**
     * 某一个item选中后的事件
     * @param index 当前选中的item的索引
     */
    change(index) {
        this.currentIndex = index;
        // 遍历引用数组,通知每一个RadioButton改变状态
        this.referencesArray.map((refer, index2) => {
            if (refer !== null) {
                refer.setSelectedState(index2 === this.currentIndex);
            }
        });
        // 调用回调监听
        this.itemChange(this.currentIndex);
    }
}

把RadioButton的全部代码一起附上:

import React, {Component} from 'react';
import {
    Text,
    View,
    TouchableOpacity,
    Image,
} from 'react-native';
import Images from "../Images";

/**
 *
 <RadioButton
 selected={true}
 selectedImg={Images.InvoiceInfo.RadioButtonSelected}
 unSelectedImg={Images.InvoiceInfo.RadioButtonUnSelected}
 imgSize={20}
 text='个人'

 textSize={12}
 drawablePadding={8}
 selectedChanged={(oldState, newState) => {
                        console.log(oldState + "--" + newState);
                    }}/>
 */
export default class RadioButton extends Component {

    selectedChanged;

    constructor(props) {
        super(props);
        this.selectedChanged = props.selectedChanged;
    }

    state = {
        selected: this.props.selected,
    };

    // 默认属性
    static defaultProps = {
        selectedChanged: false,
        selectedTextColor: '#333333',
        unSelectedTextColor: '#333333',
        // selectedImg: Images.InvoiceInfo.RadioButtonSelected,
        // unSelectedImg: Images.InvoiceInfo.RadioButtonUnSelected
    };

    render() {
        let marginStyle = {};
        const {
            selectedImg, unSelectedImg, imgSize, text, selectedTextColor, oritation,
            unSelectedTextColor, textSize, drawablePadding, margin, style,
        } = this.props;
        const {selected} = this.state;

        // 这个oritation只是RadioGroup用来告诉RadioButton使用marginLeft还是marginTop,单独使用RadioButton时不需要使用
        if (oritation === 'row') {
            marginStyle = {
                flexDirection: 'row',
                alignItems: 'center',
                marginLeft: margin,
            }
        } else {
            marginStyle = {
                flexDirection: 'row',
                alignItems: 'center',
                marginTop: margin,
            }
        }
        return (
            <TouchableOpacity onPress={() => {
                if (this.selectedChanged) {
                    this.selectedChanged(selected, !selected);
                }
                this.setState({
                    selected: !selected,
                })
            }}>
                <View
                    style={[marginStyle, style]}>
                    <Image
                        style={{width: imgSize, height: imgSize}}
                        source={selected ? selectedImg : unSelectedImg}/>
                    <Text style={{
                        color: selected ? selectedTextColor : unSelectedTextColor,
                        fontSize: textSize, marginLeft: drawablePadding
                    }}>{text}
                    </Text>
                </View>
            </TouchableOpacity>
        );
    }

    /**
     * 设置选中与否的状态:true  false
     */
    setSelectedState(selectedState) {
        this.setState({
            selected: selectedState,
        });
    }
}
学习RN时间还很短,还不到一个星期,写的效果也许不好,但是目前只能达到这个水平,等熟练了之后会载来改进。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值