Make animated collapsible card component, with initial props to show or hide

10,423

Made a brand new one for you. Simple and works fine.

Note: no state required for this component. fewer state, better performance.

Maybe you could modify your own style on top of this =)

class Card extends Component {
    anime = {
        height: new Animated.Value(),
        expanded: false,
        contentHeight: 0,
    }

    constructor(props) {
        super(props);

        this._initContentHeight = this._initContentHeight.bind(this);
        this.toggle = this.toggle.bind(this);

        this.anime.expanded = props.expanded;
    }

    _initContentHeight(evt) {
        if (this.anime.contentHeight>0) return;
        this.anime.contentHeight = evt.nativeEvent.layout.height;
        this.anime.height.setValue(this.anime.expanded ? this._getMaxValue() : this._getMinValue() );
    }

    _getMaxValue() { return this.anime.contentHeight };
    _getMinValue() { return 0 };

    toggle() {
        Animated.timing(this.anime.height, {
            toValue: this.anime.expanded ? this._getMinValue() : this._getMaxValue(),
            duration: 300,
        }).start();
        this.anime.expanded = !this.anime.expanded;
    }

    render() {
        return (
            <View style={styles.titleContainer}>
                <View style={styles.title}>
                    <TouchableHighlight underlayColor="transparent" onPress={this.toggle}>
                        <Text>{this.props.title}</Text>
                    </TouchableHighlight>
                </View>

                <Animated.View style={[styles.content, { height: this.anime.height }]} onLayout={this._initContentHeight}>
                    {this.props.children}
                </Animated.View>
            </View>
        );
    }
}

Usage:

<Card title='Customized Card 1' expanded={false}>
    <Text>Hello, this is first line.</Text>
    <Text>Hello, this is second line.</Text>
    <Text>Hello, this is third line.</Text>
</Card>

Visual result: (only second card start with expanded={true}, others with expanded={false})

enter image description here

Share:
10,423
wuno
Author by

wuno

Director of Software Engineer (CEH)

Updated on August 21, 2022

Comments

  • wuno
    wuno almost 2 years

    Background

    Using React Native I was able to make collapsible card component. On Icon click the card slides up hiding its content, or expands showing its content. I would think setting the default value would be as easy as setting expanded to false or true, but I think the problem here is that when it is toggled an animation is triggered which changes the height of the card.

    Example

    class CardCollapsible extends Component{
      constructor(props){
        super(props);
    
        this.state = {
          title: props.title,
          expanded: true,
          animation: new Animated.Value(),
          iconExpand: "keyboard-arrow-down",
        };
      }
    
      _setMaxHeight(event){
          this.setState({
              maxHeight   : event.nativeEvent.layout.height
          });
      }
    
      _setMinHeight(event){
          this.setState({
              minHeight   : event.nativeEvent.layout.height
          });
    
          this.toggle = this.toggle.bind(this);
      }
    
      toggle(){
        let initialValue    = this.state.expanded? this.state.maxHeight + this.state.minHeight : this.state.minHeight,
            finalValue      = this.state.expanded? this.state.minHeight : this.state.maxHeight + this.state.minHeight;
    
        this.setState({
          expanded : !this.state.expanded
        });
    
        if (this.state.iconExpand === "keyboard-arrow-up") {
          this.setState({
            iconExpand : "keyboard-arrow-down"
          })
        } else {
          this.setState({
            iconExpand : "keyboard-arrow-up"
          })
        }
        this.state.animation.setValue(initialValue);
        Animated.spring( this.state.animation, {
            toValue: finalValue
          }
        ).start();
      }
    
      render(){
    
        return (
          <Animated.View style={[styles.container,{height: this.state.animation}]}>
              <View style={styles.titleContainer} onLayout={this._setMinHeight.bind(this)}>
                <CardTitle>{this.state.title}</CardTitle>
                <TouchableHighlight
                  style={styles.button}
                  onPress={this.toggle}
                  underlayColor="#f1f1f1">
                  <Icon
                    name={this.state.iconExpand}
                    style={{ fontSize: 30 }}/>
                </TouchableHighlight>
              </View>
              <Separator />
              <View style={styles.card} onLayout={this._setMaxHeight.bind(this)}>
                {this.props.children}
              </View>
          </Animated.View>
        );
      }
    }
    
    var styles = StyleSheet.create({
      container: {
        backgroundColor: '#fff',
        margin:10,
        overflow:'hidden'
        },
      titleContainer: {
        flexDirection: 'row'
        },
      card: {
        padding: 10
      }
    });
    
    export { CardCollapsible };
    

    Open

    enter image description here

    Closed

    enter image description here

    Question

    My goal is to allow a person calling the component to set the initial state of the component to expanded or open. But when I try changing the expanded state to false it does not render closed.

    How would I go about allowing the user calling the component to select whether it is expanded or closed on initial component render?