Skip to content

自定义组件案例:单选组件

RN 中,官方并没有提供单选组件,如果应用开发中涉及到单选功能,就需要开发者使用第三方开源库或者自己封装单选组件。

通常,一个正常的单选功能会包含若干个子选项,每个子选项的前面有一个标识勾选状态的圆环,当某个选项被选中时圆环会变成实心,表示选中状态。如下图所示:

image-20220620132355185 image-20220620132411214

要完成自定义单选功能,首先需要自定义一个单选按钮组件。通过分析可以发现,单选按钮的左边是图片,右边是描述文字,按钮的图片有选中和未选中两种状态。基于此,在开发的时候可以定义一个状态变量 selected 来记录按钮的选中状态,为 true 时表示选中,为 false 时表示未选中。

state = {
selected: this.props.selected, // 状态由外部传入
}

为了保证自定义组件的通用性,除了状态是由外部传入的,JSX 中要渲染的图片和文字以及按钮的样式也需要由外部传入,因此可以定义如下一些必要的参数或属性供外部传入。

const { text, drawablePadding, style } = this.props

当单选按钮接收到外部传入的属性后,就可以执行渲染操作了。同时,当改变按钮的选中状态之后,还需要将状态传递出去,此时就需要使用默认属性。自定义单选按钮的示例如下:

import React, { PureComponent } from 'react'
import { View, Pressable, Text, Image, StyleSheet } from 'react-native'
let selectedImage = require('../assets/radio_selted.png')
let unSelectedImage = require('../assets/radio_select.png')
export default class RadioButton extends PureComponent {
static defaultProps = {
selectedChanged: false,
selectedTextColor: '#F83D2B',
unSelectedTextColor: '#333333',
}
state = {
selected: this.props.selected,
}
constructor(props) {
super(props)
this.selectedChanged = props.selectedChanged
}
// 每个按钮的点击事件
pressHandle() {
this.selectedChanged(this.state.selected)
this.setState({
selected: !this.state.selected,
})
}
render() {
const { text, drawablePadding } = this.props
const { selected } = this.state
return (
<Pressable onPress={this.pressHandle.bind(this)}>
<View style={styles.radioStyle}>
{/* 左边图片 */}
<Image
style={styles.image}
source={selected ? selectedImage : unSelectedImage}
/>
{/* 右边文字 */}
<Text
style={{
color: selected
? this.props.selectedTextColor
: this.props.unSelectedTextColor,
marginLeft: drawablePadding,
fontSize: 18,
}}
>
{text}
</Text>
</View>
</Pressable>
)
}
setSelectedState(state) {
this.setState({
selected: state,
})
}
}
const styles = StyleSheet.create({
radioStyle: {
flexDirection: 'row',
alignItems: 'center',
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
},
image: {
width: 22,
height: 22,
},
text: {
flexDirection: 'row',
alignItems: 'center',
},
})

上面的代码完成了一个自定义单选按钮的功能,并不能真正的实现单选功能。因为一个正常的单选功能会包含若干子选项,而且只能有一个选项被选中。

因此,要完成自定义单选功能,还需要定义一个容器组件,这个容器组件会包含多个单选按钮,并且只有一个能选中。通过分析,自定义的单选组件的数据源、排列方向和默认选中项都需要由外界传入,因此自定义的单选组件至少需要提供如下一些属性供使用方传入。

const { data, orientation, defaultValue, drawablePadding } = this.props

当然,除了上面的一些必要属性,开发者还可以根据实际需要自由定制。下面是自定义单选按钮的容器组件示例代码:

import React, { PureComponent } from 'react'
import { View } from 'react-native'
import RadioButton from './RadioButton'
export default class RadioGroup extends PureComponent {
constructor(props) {
super(props)
this.state = {
currentIndex: -1, // 当前选中的索引
dataArray: [],
itemChange: props.itemChange,
}
}
render() {
// 获取参数
const { data, orientation, defaultValue, drawablePadding } = this.props
return (
<View style={{ flexDirection: orientation }}>
{data.map((radioData, index) => {
return (
<RadioButton
index={index}
selected={index === defaultValue ? true : false}
key={index}
ref={(radioButton) => this.state.dataArray.push(radioButton)}
text={radioData.text}
oritation={orientation}
drawablePadding={drawablePadding}
selectedChanged={() => {
this.change(index)
}}
/>
)
})}
</View>
)
}
change(index) {
this.state.currentIndex = index
console.log(this.state.dataArray)
this.state.dataArray.map((refer, index2) => {
if (refer !== null) {
refer.setSelectedState(index2 === this.state.currentIndex)
}
})
this.state.itemChange(this.state.currentIndex)
}
}

在上面的自定义单选按钮容器组件中,当某个子选项被选中后,会调用 change 方法改变选中按钮的状态,进而通知组件进行视图更新。

至此,自定义单选组件就算基本开发完成了。使用时,只需要根据要求传入必要的属性即可。

<RadioGroup
orientation="row"
data={data}
defaultValue={0}
drawablePadding={8}
itemChange={(index) => {
alert(index)
}}
></RadioGroup>

下面是我们在根组件中进行测试的完整代码:

import React, { PureComponent } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import RadioGroup from './components/RadioGroup'
export default class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
data: [{ text: '个人' }, { text: '单位' }, { text: '其他' }],
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>发票抬头</Text>
<RadioGroup
orientation="column"
data={this.state.data}
defaultValue={0} //默认选中的值
drawablePadding={10} //图片与文字的间距
itemChange={(index) => {
alert(index)
}}
></RadioGroup>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
marginTop: 35,
},
title: {
height: 30,
fontSize: 20,
borderColor: 'black',
marginLeft: 15,
fontWeight: 'bold',
},
})