【React-Native】自定义Vant--Drawer(抽屉)动画组件

前言:

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>

 感谢大家浏览。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值