【问题标题】:How to dynamically set height of a component whose height is an animated value post-animation?如何动态设置高度为动画后动画值的组件的高度?
【发布时间】:2020-06-14 02:59:40
【问题描述】:

目标:有一个下拉视图,随着时间的推移动画高度扩展。需要注意的是:一旦视图展开,它需要能够动态处理是否存在额外的视图数据。如果存在,将呈现几个额外的文本组件。

问题:就目前而言,动画父级的高度为固定高度。所以当additionalContent 被渲染时,它超出了父级的边界,其高度是固定的。我不想明确设置父级的高度,因为那样我就无法按照我想要的方式为该方面设置动画。我想按原样保持高度动画,并在存在additionalContent 时动态调整父级以包含子级

const ListItem = (props) => {
    const [checkInModal, setCheckInModal] = useState(false);
    const [animatedHeight, setAnimatedHeight] = useState(new Animated.Value(0))
    const [animatedOpacity] = useState(new Animated.Value(0))
    const [dynamicHeight, setDynamicHeight] = useState(0);
    const [expanded, setExpanded] = useState(false);

    const toggleDropdown = () => {
        if (expanded == true) {
            // collapse dropdown
            Animated.timing(animatedHeight, {
              toValue: 0,
              duration: 200,
            }).start()

        } else {
            // expand dropdown
             Animated.timing(animatedHeight, {
              toValue: 100,
              duration: 200,
            }).start()
        }
        setExpanded(!expanded)
    }

    const renderAdditionalContent = () => {
      setDynamicHeight(75);

      if (someVariable == true) {
        return (
          <View> <Text> Some Content </Text> </View>
        )
      }
    }

    const interpolatedHeight = animatedHeight.interpolate({
        inputRange: [0, 100],
        outputRange: [75, 225]
    })

    const interpolatedOpacity = animatedOpacity.interpolate({
        inputRange: [0, 100],
        outputRange: [0.0, 1.0]
    })

    return (
        <Animated.View
            style={[styles.container, { height: interpolatedHeight + dynamicHeight }]}
        >
            <View style={{ flexDirection: 'row', justifyContent: 'space-between', }}>
                <View style={styles.leftContainer}>
                    <View style={{ flexDirection: 'row', alignItems: 'center' }}>
                        <Text style={styles.title}>{props.title}</Text>
                    </View>
                    <Text style={styles.subtitle}>{time()}</Text>
                </View>

                <View style={styles.rightContainer}>
                    <TouchableOpacity onPress={() => toggleDropdown()} style={styles.toggleBtn}>
                        <Image source={require('../assets/img/chevron-down.png')} resizeMode={'contain'} style={styles.chevron} />
                    </TouchableOpacity>
                </View>
            </View>

            {expanded == true ? (
                <Animated.View style={[styles.bottomContainer, { opacity: interpolatedOpacity }]}>
                    <Components.BodyText text="Subject:" style={{ fontFamily: Fonts.OPENSANS_BOLD }} />

                    <Components.BodyText text={props.subject} />

                    { renderAdditionalContent() }

                </Animated.View>
            ) : null}
        </Animated.View>
    );
};

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#fff',
        borderRadius: 25,
        width: width * 0.95,
        marginBottom: 5,
        marginHorizontal: 5,
        paddingVertical: 15,
        paddingHorizontal: 15
    },
    leftContainer: {
        justifyContent: 'space-between',
    },
    rightContainer: {
        flexDirection: 'row',
        alignItems: 'center'
    },
    title: {
        fontFamily: Fonts.OPENSANS_BOLD,
        fontSize: 20,
        color: '#454A66'
    },
    subtitle: {
        color: '#454A66',
        fontSize: 14
    },
    typeIcon: {
        height: 25,
        width: 25
    },
    chevron: {
        height: 15,
        width: 15
    },
    toggleBtn: {
        borderWidth: 1,
        borderColor: Colors.PRIMARY_DARK,
        borderRadius: 7,
        paddingTop: 4,
        paddingBottom: 2.5,
        paddingHorizontal: 4,
        marginLeft: 10
    },
    bottomContainer: {
        marginVertical: 20
    },
    buttonContainer: {
        flexDirection: 'row',
        width: 250,
        justifyContent: 'space-between',
        alignSelf: 'center',
        marginVertical: 20
    },
    noShadow: {
        elevation: 0,
        shadowOffset: {
            width: 0,
            height: 0
        },
        shadowRadius: 0,
    }
});

export default ListItem;

如何做到这一点?到目前为止,我尝试创建一个状态变量 dynamicHeight 并将其设置在呈现其他内容的函数中,但这没有奏效。

这是小吃:https://snack.expo.io/P6WKioG76

澄清编辑:renderAdditionalContent 函数渲染附加内容(显然),该内容可以是从一行字符到多行的任何位置。无论字符数如何,组件的主父容器都需要将所有子容器都放在其范围内。就目前而言,如果附加内容渲染的内容过多,则内容会溢出组件的主父容器的边框,这是必须避免的。显然,这可以通过简单地不给主组件容器指定高度来完成。但我们的想法是拥有动画高度并正确包装子内容

有什么建议吗?

【问题讨论】:

  • 你能创建一个工作演示吗?那么我们可以为您提供更多帮助
  • @VivekDoshi 检查零食^
  • 展开点击时抛出错误
  • @VivekDoshi 现在看。部分错误是我问这个问题的原因。我注释掉了将具有动态大小内容的部分
  • 好的,那么预期的行为是什么?

标签: reactjs react-native react-animated


【解决方案1】:

已编辑:简单易行的方法

您也可以应用高度100%,这样我们就不需要计算内部内容的高度,它会根据提供的内容自动调整

const interpolatedHeight = animatedHeight.interpolate({
   inputRange: [0, 100],
   outputRange: ["0%", "100%"] // <---- HERE
})

为了实现这一点,我在&lt;Animated.View&gt; 周围添加了&lt;View&gt; 标记,以使100% 的高度正确且css 更改很少,这看起来更优雅,并为您的问题提供了完美的解决方案。

WORKING DEMO


所以,你可以使用 onLayout

计算完布局后立即触发此事件

第一步:

// setHeight 
const setHeight = (height) => {
    setDynamicHeight(prev => prev + height);
}

<View onLayout={(event) => {
    var { x, y, width, height } = event.nativeEvent.layout;
    setHeight(height); // <--- get the height and add it to total height
}}>
    <Text>Subject</Text>

    <Text>Subject Content</Text>

    {renderAdditionalContent()}
</View>

第二步:

useEffect(() => {
    // trigger if only expanded
    if (expanded) {
        // trigger height animation , whenever there is change in height
        Animated.timing(animatedHeight, {
            toValue: dynamicHeight, // <--- animate to the given height
            duration: 200,
        }).start();
    }
}, [dynamicHeight]); // <--- check of height change

WORKING DEMO(可以通过添加删除文本来测试)

【讨论】:

  • 这是最好的答案,我无法让 ref 正常工作,这是错误的选项
  • 这在 FlatList 上不起作用。输入height: '100%' 是行不通的。
【解决方案2】:

这是您的代码的一个工作点心,具有动态下拉高度:https://snack.expo.io/4mT5Xj6qF

您可以通过更改thisIsAValue 常量来更改高度。

老实说,您的代码非常接近,只是缺少一些点点滴滴:

  • 当你制作高度动画时,你不需要使用interpolate,你可以让&lt;Animated.View&gt;直接向上计算高度。如果您想要动画,例如旋转,则需要插值,并且您需要计算元素必须移动的度数。
  • 您需要将动态高度作为to: 值传递给动画
  • 我设置了一个简单的 useEffect 挂钩检查 someVariabledynamicHeight 属性的变化

此解决方案可让您通过道具动态设置高度。 但是,如果您想根据 View 中存在的元素计算高度,您可能需要查看 @vivek-doshi 的答案

【讨论】:

    【解决方案3】:

    你可以使用 React refs。这允许您直接访问组件。

    const el = React.useRef(null);
    

    将 el 分配给您拥有的任何内容的容器 div 的 ref。然后使用可见性样式隐藏/显示 div:

    <div style={{ visibility: !expanded && "hidden" }} ref={el}>
       <View /* snip */>
          { renderAdditionalContent() }
          /* -- snip -- */
       </View>
    </div>
    

    然后在 toggleDropdown 函数中,您可以通过 .current.offsetHeight 属性访问组件的高度

    Animated.timing(animatedHeight, {
      toValue: el.current.offsetHeight,
      duration: 200,
    }).start()
    

    Refs 可用于任何 html 元素,并允许您访问元素的原始属性。动画可能是它们最常见的用例之一。

    演示:https://snack.expo.io/heLsrZNpz

    您可以在这里阅读更多内容:https://reactjs.org/docs/refs-and-the-dom.html

    【讨论】:

    • 嗨迪伦!您使用了 &lt;div&gt; 元素并指向 ReactJS 文档,但问题是关于 React Native。除此之外,使用 useRef 钩子似乎是个好主意!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    • 2015-03-05
    • 1970-01-01
    • 2014-12-15
    • 2013-09-25
    • 1970-01-01
    相关资源
    最近更新 更多