前言:
react-naitve组件库相对丰富,但比起web有点逊色,目前国外组件库居多,笔者在开发项目过程中需要使用到Drawer组件,由于是前端开发学习RN,对国内的组件库的抽屉组件尤为偏爱,但找了很多开源组件,没有找到比较满意的,不是文档复杂,就是动画效果不符合预期。于是打算自己封装一个简易的,能满足大部分开发需求的抽屉组件。
测试图:
具备功能api:
话不多说了,具体介绍到此,如果对您有用的话,以下代码继续浏览。
Drawer代码:
代码不多,阅读简单,复制能用,支持安卓,ios基本表现一致。
大佬可以自行增加新功能完善。
/**
* @Author: 小易很OK
* @描述: 抽屉动画
* @Date: 2022/12/12
*/
import { AntDesign } from "@expo/vector-icons";
import React, { useState, useEffect, useCallback, useRef } from "react";
import {
Animated,
StyleSheet,
Dimensions,
Modal,
View,
StyleProp,
ViewStyle,
TouchableWithoutFeedback,
Text,
} from "react-native";
import { styleAll } from "../styleAll";
const WINDOW = Dimensions.get("window");
type DrawerMenuProps = {
visible: boolean;
duration?: number;
menuPosition?: "left" | "right" | "top" | "bottom";
Height?: number | string;
hideModal: Function;
children?: JSX.Element;
isCircle?: boolean;
CircleNum?: number;
isShadowHideModal?: boolean;
title?:string
};
/**
* 抽屉组件Api
* @param visible 是否显示抽屉
* @param Height 抽屉高度,默认50%
* @param hideModal 关闭抽屉函数
* @param duration 抽屉展示动画时间 默认300
* @param isCircle 是否圆角 默认true
* @param menuPosition 抽屉打开位置 默认bottom "left" | "right" | "top" | "bottom";
* @param children 插入元素
* @param CircleNum 圆角数值 默认20
* @param isShadowHideModal 是否点击阴影关闭弹窗
* @param title 文本头部标题
*/
export const DrawerMenu = ({
visible,
Height,
hideModal,
duration,
isCircle,
menuPosition,
children,
CircleNum,
isShadowHideModal,
title,
}: DrawerMenuProps) => {
const [menuPositionStyle, setMenuPositionStyle] =
useState<StyleProp<ViewStyle>>();
const [drawerMenuBox, setDrawerMenuBox] = useState<StyleProp<ViewStyle>>();
const DrawerAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
isOpen(Number(visible));
switch (menuPosition) {
case "left":
setMenuPositionStyle({
flex: 1,
backgroundColor: "#fff",
borderTopRightRadius: isCircle ? CircleNum : 0,
borderBottomRightRadius: isCircle ? CircleNum : 0,
});
setDrawerMenuBox({
width: Height,
height: WINDOW.height,
position: "absolute",
left: 0,
top: 0,
});
break;
case "right":
setMenuPositionStyle({
flex: 1,
backgroundColor: "#fff",
borderTopLeftRadius: isCircle ? CircleNum : 0,
borderBottomLeftRadius: isCircle ? CircleNum : 0,
});
setDrawerMenuBox({
width: Height,
height: WINDOW.height,
position: "absolute",
right: 0,
top: 0,
});
break;
case "top":
setMenuPositionStyle({
flex: 1,
backgroundColor: "#fff",
borderBottomLeftRadius: isCircle ? CircleNum : 0,
borderBottomRightRadius: isCircle ? CircleNum : 0,
});
setDrawerMenuBox({
width: WINDOW.width,
height: Height,
position: "absolute",
right: 0,
top: 0,
});
break;
case "bottom":
setMenuPositionStyle({
flex: 1,
backgroundColor: "#fff",
borderTopLeftRadius: isCircle ? CircleNum : 0,
borderTopRightRadius: isCircle ? CircleNum : 0,
});
setDrawerMenuBox({
width: WINDOW.width,
height: Height,
position: "absolute",
right: 0,
bottom: 0,
});
break;
}
}, [visible]);
const isOpen = (toValue: number) => {
Animated.timing(DrawerAnim, {
toValue,
duration,
useNativeDriver: true,
}).start();
};
const handleHideModal = () => {
setTimeout(() => {
hideModal();
}, 200);
isOpen(0);
};
// 判断position从不同位置唤出抽屉
const getPosition = useCallback(() => {
switch (menuPosition) {
case "left":
return {
translateX: DrawerAnim.interpolate({
inputRange: [0, 1],
outputRange: [-WINDOW.width, 0],
}),
};
case "right":
return {
translateX: DrawerAnim.interpolate({
inputRange: [0, 1],
outputRange: [WINDOW.width, 0],
}),
};
case "top":
return {
translateY: DrawerAnim.interpolate({
inputRange: [0, 1],
outputRange: [-WINDOW.height, 0],
}),
};
case "bottom":
return {
translateY: DrawerAnim.interpolate({
inputRange: [0, 1],
outputRange: [WINDOW.height, 0],
}),
};
default:
return {
translateX: DrawerAnim.interpolate({
inputRange: [0, 1],
outputRange: [-WINDOW.width, 0],
}),
};
}
}, [DrawerAnim, menuPosition]);
return (
<Modal visible={visible} transparent animationType={"fade"}>
<TouchableWithoutFeedback
onPress={() => isShadowHideModal && handleHideModal()}
>
<View
style={{
backgroundColor: "rgba(0, 0, 0, .5)",
height: "100%",
width: "100%",
position: "absolute",
top: 0,
left: 0,
}}
></View>
</TouchableWithoutFeedback>
<View style={drawerMenuBox}>
<Animated.View
style={[
styles.container,
{
transform: [getPosition()],
opacity: DrawerAnim,
},
]}
>
<View style={menuPositionStyle}>
{
//底部不需要ios刘海适配
menuPosition !== "bottom" && <View style={styleAll.iosTop} />
}
<View style={styles.headTitle}>
<Text style={styles.title}>{title}</Text>
<AntDesign
style={{ position: "absolute", right: 15, top: 11 }}
name="close"
size={24}
color="black"
onPress={handleHideModal}
/>
</View>
{children}
</View>
</Animated.View>
</View>
</Modal>
);
};
DrawerMenu.defaultProps = {
visible: false,
duration: 200,
Height: "50%",
menuPosition: "bottom",
isCircle: true,
CircleNum: 20,
isShadowHideModal: true,
title:""
};
const styles = StyleSheet.create({
container: {
height: "100%",
width: "100%",
position: "absolute",
left: 0,
right: 0,
zIndex: 1000,
},
headTitle: {
height: 40,
width: "100%",
padding: 10,
justifyContent:'center',
alignItems:'center',
position: "relative",
},
title:{
fontSize:18
}
});
styleAll文件代码:
import { Platform, StyleSheet } from "react-native";
export const styleAll = StyleSheet.create({
iosBottom: {
paddingBottom: Platform.OS === "ios" ? 90 : 0,
},
iosTop:{
paddingTop: Platform.OS === "ios" ? 50 : 0,
}
});
测试调用组件代码:
import { DrawerMenu } from "./Drawer";
<DrawerMenu
visible={visible}
hideModal={hideModal}
menuPosition="left"
Height={"70%"}
>
<View>
//抽屉内容样式
</View>
</DrawerMenu>
感谢大家浏览。