首页 > 解决方案 > 为滚动视图的动态高度设置动画

问题描述

我有一个类似于下图的 UI:

动画用户界面

标题部分(深蓝色部分)应该是固定的,而正文部分是可滚动的。

标题部分的高度将是动态的,因为可以从服务器检索到 4 - 5 个项目符号点。

我的任务是当用户开始向上滚动以阅读下面的内容时开始降低标题部分的高度,直到用户只能阅读标题中的标题。此外,当用户开始向下滚动到他/她可以看到标题Body的点时,我应该开始增加高度,以便他可以再次开始看到所有项目符号点。

此功能更像是在滚动条上隐藏/显示标题。但是,标题将始终可见。

我编写了以下编写的代码来实现相同的目的:

import React from 'react';
import {
  View,
  Text,
  ScrollView,
  TouchableOpacity,
  Linking,
  Image,
  Animated,
} from 'react-native';

class App extends React.PureComponent<Props, States> {
  constructor() {
    super();

    this.state = {
      headerHeight: 0,
      headerTitleHeight: 0,
    };

    this.scrollY = new Animated.Value(0);
  }

  render() {
    const { navigation, pending, macroFeed } = this.props;
    const { headerHeight, headerTitleHeight } = this.state;

    const { themeDetails } = navigation.state.params;
    const {
      title, bullet1, bullet2, bullet3,
    } = themeDetails;

    let headerStyle = {};

    if (headerHeight) {
      headerStyle = {
        height: this.scrollY.interpolate({
          inputRange: [0, headerHeight - headerTitleHeight],
          outputRange: [headerHeight, headerTitleHeight],
          extrapolate: 'clamp',
        }),
      };
    }

    const sortedMacroFeed = _.sortBy(macroFeed, (o) => moment(o.date).format('YYYYMMDD')).reverse().slice(0, 5);

    if (pending) {
      return (
        <View style={styles.maxFlex}>
          <LoadingSpinner
            size="large"
            containerStyle={styles.loadingSpinner}
          />
        </View>
      );
    }

    return (
      <View
        style={styles.maxFlex}
      >
        <Animated.View
          style={[styles.headerWrapper, headerStyle]}
          onLayout={(event) => {
            this.setState({
              headerHeight: event.nativeEvent.layout.height,
            });
          }}
        >
          <View style={styles.macroBgWrapper}>
            <Image source={themeDetails.imgUrl} style={styles.macroBg} />
            <View style={styles.macroBgOverlay} />
          </View>

          <View 
            style={styles.header}
          onLayout={(event) => {
            this.setState({
              headerTitleHeight: event.nativeEvent.layout.height,
            });
          }}
          >
            <TouchableOpacity onPress={() => navigation.goBack()}>
              <View>
                <Icon name="ios-arrow-back" size={32} style={styles.backIcon} />
              </View>
            </TouchableOpacity>

            <View style={styles.titleWrap}>
              <Text style={styles.headerTitle}>
                {title}
              </Text>
            </View>
          </View>

          <View style={styles.bulletWrapper}>
            {
              !!bullet1 && (
                <View style={styles.column}>
                  <View style={styles.row}>
                    <View style={styles.bullet}>
                      <Text style={styles.buttetListText}>
                        {'\u2022'}
                        {' '}
                      </Text>
                    </View>
                    <View style={styles.bulletText}>
                      <Text style={styles.buttetListText}>
                        {bullet1}
                      </Text>
                    </View>
                  </View>
                </View>
              )
            }

            {
              !!bullet2 && (
                <View style={styles.column}>
                  <View style={styles.row}>
                    <View style={styles.bullet}>
                      <Text style={styles.buttetListText}>
                        {'\u2022'}
                        {' '}
                      </Text>
                    </View>
                    <View style={styles.bulletText}>
                      <Text style={styles.buttetListText}>
                        {bullet2}
                      </Text>
                    </View>
                  </View>
                </View>
              )
            }

            {
              !!bullet3 && (
                <View style={styles.column}>
                  <View style={styles.row}>
                    <View style={styles.bullet}>
                      <Text style={styles.buttetListText}>
                        {'\u2022'}
                        {' '}
                      </Text>
                    </View>
                    <View style={styles.bulletText}>
                      <Text style={styles.buttetListText}>
                        {bullet3}
                      </Text>
                    </View>
                  </View>
                </View>
              )
            }

            {
              !bullet1 && !bullet2 && !bullet3 && (
                <View style={styles.noBulletWrapper}>
                  <Text style={styles.noBulletPoints}>
                    No description found.
                  </Text>
                </View>
              )
            }
          </View>
        </Animated.View>

        <ScrollView
          style={styles.maxFlex}
          showsVerticalScrollIndicator={false}
          onScroll={Animated.event([
            { nativeEvent: { contentOffset: { y: this.scrollY } } },
          ])}
          scrollEventThrottle={16}
        >
          <View style={[styles.section, styles.wrapGutter]}>
            <Text style={styles.sectionTitle}>
              Recent Headlines
            </Text>

            {
              sortedMacroFeed.map((feed) => (
                <View key={feed.id} style={styles.newsSection}>
                  <Text style={styles.newsHours}>
                    {moment(feed.date).fromNow()} | {feed.author}
                  </Text>

                  <Text style={styles.newsTitle}>
                    {feed.title}

                    <Text onPress={() => this.openWebView(feed.url)}>
                      <EvilIcons name="external-link" size={16} style={styles.externalLinkIcon} />
                    </Text>
                  </Text>
                </View>
              ))
            }
          </View>

          <View style={styles.section}>
            <View style={[styles.wrapGutter, styles.sectionTitleWrap]}>
              <Text style={styles.sectionTitle}>
                Exposure
              </Text>

              <View style={styles.totalNavWrap}>
                <Text style={styles.totalNav}>
                  $467M
                </Text>

                <Text style={styles.totalNavLabel}>
                  Total NaV (all portfolios)
                </Text>
              </View>
            </View>

            <Tabs
              tabsData={TABS_DATA}
              renderTabContent={this.renderTabContent}
              tabName="title"
              hideIfOneTab
            />
          </View>
        </ScrollView>
      </View>
    );
  }
}

如果您观察到我正在尝试使用onLayout.

此代码仅以一种方式起作用,即当我向上滚动时。标题部分的高度降低到只能看到标题并且项目符号点被隐藏的程度。但在那之后,我无法向下滚动。高度永久降低。

现在,如果我更改下面给出的代码:

let headerStyle = {};

if (headerHeight) {
  headerStyle = {
    height: this.scrollY.interpolate({
      inputRange: [0, headerHeight - headerTitleHeight],
      outputRange: [headerHeight, headerTitleHeight],
      extrapolate: 'clamp',
    }),
  };
}

const headerStyle = {
    height: this.scrollY.interpolate({
      inputRange: [0, 240],
      outputRange: [300, 60],
      extrapolate: 'clamp',
    }),
};

一切似乎都很好。基本上,如果我停止动态检索高度值并提供像 240 之类的静态值,一切似乎都正常。

但是,如果我接受动态高度,滚动动画就会停止。任何解决此问题的帮助将不胜感激。感谢期待。

标签: javascriptreactjsreact-native

解决方案


我发现了这个问题。所以,如果你观察这条线:

        <Animated.View
          style={[styles.headerWrapper, headerStyle]}
          onLayout={(event) => {
              this.setState({
                headerHeight: event.nativeEvent.layout.height,
              });
          }}
        >

onLayout 用于在 headerHeight 值在滚动时更改时立即设置状态。因此,用于减少和向下滚动内容的标题高度是不可能的。

我将代码更改为:

        <Animated.View
          style={[styles.headerWrapper, headerStyle]}
          onLayout={(event) => {
            if (!this.state.headerHeight) {
              this.setState({
                headerHeight: event.nativeEvent.layout.height,
              });
            }
          }}
        >

现在,一切正常。


推荐阅读