自定义组件案例 1:弹框组件
在使用 RN 进行跨平台应用开发时,官方提供的组件往往是有限的,并且很多组件并不是多平台通用的,有些只针对特别的平台。此时,要想在应用开发上保持页面样式的一致性,除了直接选择第三方开源库以外,另一个有效的手段就是自定义组件。
本小节我们来自定义一个弹框组件。
首先我们简单复习一下 ES6 中导入导出模块的相关知识。
// 组件导出
export default class App extends Component{
...
}
// 组件导入
import App from './App'
除了组件外,变量和常量也支持导入和导出。
// 变量和常量导出
var name = "张三";
const age = "18";
export {name, age}
// 变量和常量的导入
import {name, age} from "./App"
方法的导入以及导出,和变量、常量的导入导出类似。
// 方法导出
export function sum(a, b){
return a + b
}
// 方法导入
import {sum} from "./Util"
另外,我们还会使用到 propTypes。通常,通用组件需要使用自定义属性的方式接收外界传入的值,如果是必须要传入的值,可以使用 isRequired 关键字。例如:
static propTypes = {
title: PropTypes.string.PropTypes.func.isRequired,
content: PropTypes.string,
}
需要注意的是,由于 propTypes 在 15.0.0 版本中已经被移除掉了,所以在 15.5.0 以及之后的版本中,需要使用新的方式引入。
import PropTypes from 'prop-types';
好了,前置内容介绍完毕后,接下来我们就来封装第一个自定义组件——弹框组件。
上图是封装好之后的效果,可以看到,整个弹框由 4 个部分组成,分别是图片、标题、内容、确认按钮以及关闭按钮。其中图片、标题、内容、确认按钮内容都是应该在使用组件时传递进去的。
完整的封装组件代码如下:
import React, { Component } from "react";
import PropTypes from "prop-types";
import {
View,
Text,
TouchableOpacity,
Image,
StyleSheet,
ImageBackground,
Dimensions,
} from "react-native";
const { width } = Dimensions.get("window");
export default class FreeDialog extends Component {
static propTypes = {
isShow: PropTypes.bool.isRequired,
title: PropTypes.string,
content: PropTypes.string,
buttonContent: PropTypes.string,
closeDialog: PropTypes.func.isRequired,
imageSource: PropTypes.string.isRequired,
};
closeDialogHandle() {
this.props.closeDialog();
}
render() {
if (!this.props.isShow) {
return null;
} else {
return (
<View style={styles.containerBg}>
<View style={[styles.dialogBg]}>
<Image source={this.props.imageSource} style={styles.logoStyle} />
<Text style={styles.titleStyle}>{this.props.title}</Text>
<Text style={styles.contentStyle}>{this.props.content}</Text>
<TouchableOpacity>
<ImageBackground
resizeMode="stretch"
source={require("../images/commen_btn.png")}
style={styles.buttonStyle}
>
<Text style={styles.btnContentStyle}>
{this.props.buttonContent}
</Text>
</ImageBackground>
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.btnCloseStyle}
onPress={this.closeDialogHandle.bind(this)}
>
<Image
source={require("../images/ic_close.png")}
style={{ height: 38, width: 38 }}
/>
</TouchableOpacity>
</View>
);
}
}
}
const styles = StyleSheet.create({
containerBg: {
backgroundColor: "rgba(0, 0, 0, 0.7)",
position: "absolute",
left: 0,
right: 0,
bottom: 0,
top: 0,
overflow: "hidden",
justifyContent: "center",
alignItems: "center",
},
dialogBg: {
width: width - 100,
backgroundColor: "#ffffff",
borderRadius: 10,
overflow: "hidden",
alignItems: "center",
},
logoStyle: {
height: ((width - 100) * 258) / 400,
width: width - 100,
},
titleStyle: {
marginTop: 14,
color: "#333333",
fontSize: 18,
fontWeight: "600",
},
contentStyle: {
marginTop: 5,
color: "#333333",
fontSize: 14,
fontWeight: "400",
},
buttonStyle: {
height: ((width - 135) * 88) / 480,
width: width - 180,
marginTop: 36,
marginBottom: 22,
alignItems: "center",
justifyContent: "center",
},
btnContentStyle: {
fontSize: 16,
color: "white",
textAlign: "center",
fontWeight: "600",
},
btnCloseStyle: {
padding: 10,
marginTop: 33,
alignItems: "center",
},
});
在 App.js 根组件中使用测试:
import React, { PureComponent } from "react";
import {
TouchableOpacity,
StyleSheet,
Text,
View,
Dimensions,
} from "react-native";
import FreeDialog from "./components/FreeDialog";
const { width } = Dimensions.get("window");
export default class DialogPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShowDialog: false,
};
}
renderDialog() {
return (
<FreeDialog
isShow={this.state.isShowDialog}
closeDialog={this.closeDialog.bind(this)}
title={"年终大促"}
content={"您有新的新年礼品,请查收!"}
buttonContent={"新年礼品请查收"}
imageSource={require("./images/dialog_bg.png")}
/>
);
}
showDialog() {
this.setState({
isShowDialog: true,
});
}
closeDialog() {
this.setState({
isShowDialog: false,
});
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
style={styles.btnContainer}
onPress={this.showDialog.bind(this)}
>
<Text style={styles.textStyle}>免费咨询医生</Text>
</TouchableOpacity>
{this.renderDialog()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F5FCFF",
},
btnContainer: {
marginTop: 15,
marginLeft: 10,
marginRight: 10,
backgroundColor: "#EE7942",
height: 38,
width: width - 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
},
textStyle: {
color: "#ffffff",
fontSize: 18,
},
});
-EOF-
自定义组件案例 2:单选组件
在 RN 中,官方并没有提供单选组件,如果应用开发中涉及到单选功能,就需要开发者使用第三方开源库或者自己封装单选组件。
通常,一个正常的单选功能会包含若干个子选项,每个子选项的前面有一个标识勾选状态的圆环,当某个选项被选中时圆环会变成实心,表示选中状态。如下图所示:
要完成自定义单选功能,首先需要自定义一个单选按钮组件。通过分析可以发现,单选按钮的左边是图片,右边是描述文字,按钮的图片有选中和未选中两种状态。基于此,在开发的时候可以定义一个状态变量 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",
},
});
-EOF-