Expandable Card in React Native


在 React Native 中实现可扩展卡片

本文介绍如何在 react native 中自定义一个可以通过点击展开收起的容器,这里将其称为可扩展卡片。

实现方式

说一下实现思路。

首先,这个卡片应当有两个状态,即展开和收起状态,那么一定有一个描述状态的变量,点一下按钮可以切换变量的值。

然后,在收起状态下,子组件应当不显示,展开状态下子组件显示。要达成这样的效果有很多种方法。比如{isHidden ? null : children},或者改变组件高度。这样做的效果就是卡片展开与收起时没有动画效果。

那么如何实现动画效果呢?这也有很多种方法。先介绍一种简单的,逐步改变高度。只需要在useEffect中设置一个定时器,定时增加或者减少子组件的高度,然后令子组件的overflow="hidden"就可以实现简易的动画效果。具体步骤就不讲了。另一种是利用Animated组件。

以下为源码,关键步骤看注释。

import { Text, View, TouchableOpacity, Animated } from "react-native";
import { useState, useRef } from "react";

const Header = () => {
  return <Text>toggle</Text>;
};

const ExpandableCard = ({ duration = 500, header = <Header />, ...props }) => {
  const animation = useRef(new Animated.Value(0)).current;
  const [isHidden, setHidden] = useState(true);
  const [childHeight, setChildHeight] = useState(0);

  const toggleHiddenState = () => {
    setHidden(!isHidden);
    Animated.timing(animation, {
      toValue: isHidden ? 1 : 0,
      duration: duration,
      useNativeDriver: false,
    }).start();
  };

  return (
    <View>
      <TouchableOpacity
        onPress={() => {
          toggleHiddenState();
        }}
        activeOpacity={1}
      >
        {
          // 这里是卡片的header,也就是点击的地方
          // 默认就是前面的Header
          header
        }
      </TouchableOpacity>
      {
        // 动画效果
      }
      <Animated.View
        style={{
          height: animation.interpolate({
            // 输入 输出
            // 0    1
            // 1    子组件高度
            // 这里输入的0和1是上面Animated.timing中设置的
            // 一开始的高度如果设置为0,则子组件的高度一直为0
            // 设置为1,子组件的高度会慢慢增加,最终达到真实高度
            // 如果想知道具体过程,可以在下面的onLayout中的函数中打印height
            inputRange: [0, 1],
            outputRange: [1, childHeight],
          }),
          overflow: "hidden",
        }}
      >
        <View
          onLayout={(event) => {
            const { height } = event.nativeEvent.layout;
            setChildHeight(height);
          }}
        >
          {
            // 这里用一个高度为1的空组件把初始高度填上,防止子组件顶部露出来
            // 可能你会想到通过将初始高度设置为0.001等极小值来解决该问题,但实际操作中这样的方案是不稳定的
          }
          <View style={{ height: 1, width: "100%" }} />
          {
            // 这里是传入的子组件
          }
          {props.children}
        </View>
      </Animated.View>
    </View>
  );
};

export default ExpandableCard;

以上只是一个简单实现,原理已经完全体现了,想要更多的功能(比如向右侧展开)可以自己修改。

使用

用法很简单,可以传入自定义的头部,也可以设置动画时间。

<ExpandableCard header={CustomHeader} duration={1000}>
  childComponent
</ExpandableCard>

文章作者: niuiic
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 niuiic !
评论
  目录