¿Cómo animas la altura en reaccionar nativo cuando no sabes el tamaño del contenido?

11 minutos de lectura

Avatar de usuario de Poyan
Poyán

En react-native, ¿cómo animas el tamaño de una vista cuando no sabes el tamaño de su contenido?

Digamos que la altura del contenido de la Vista puede estar entre 0 y 400 puntos, y el contenido puede crecer y reducirse dinámicamente, y desea animar cualquier cambio en la altura.

Básicamente, estoy buscando reproducir el comportamiento de LayoutAnimation sin usar LayoutAnimation pero usando Animado.

Creo que lo que se me escapa es que no sé cómo animar hacia una altura de destino, ya que no sé la altura del contenido.

  • animas usando valores de traducción, consulta stackoverflow.com/questions/55339044/…

    – Yathavan

    8 de enero de 2021 a las 6:13

yo suelo LayoutAnimation para eso, justo antes del cambio de estado que hace que cambie la altura de su componente, agregue:

LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);

Puede utilizar diferentes ajustes preestablecidos:

  • LayoutAnimation establecerá una animación global en todo, este método es solo si tiene una aplicación muy simple en la que se lleva a cabo 1 acción a la vez.

    – ericjam

    8 jun 2018 a las 22:32

  • LayoutAnimation todavía tiene muchos problemas fatales tanto en iOS como en Android. Si está considerando admitir varios dispositivos móviles, sugiero API animada todo el tiempo.

    – Samitha Nanayakkara

    3 de abril de 2020 a las 9:32

  • LayoutAnimation tiene muchos errores en Android y ha provocado varios bloqueos en la producción de mis aplicaciones. Tuve que reemplazarlo (es decir Platform.OS === 'ios' ? LayoutAnimation.configureNext(LayoutAnimation.create(50, 'easeInEaseOut', 'opacity')) : null;) y reemplácelo con llamadas solo de iOS.

    –Walter Monecke

    21 de abril de 2020 a las 9:10


  • He hecho una respuesta a continuación para cualquier persona que experimente una situación similar a la mía en el manejo de la animación. Podria manejar la altura del contenido y usando Animated componente. Siéntete libre de echar un vistazo ;D ¡Saludos!

    – Ala Choy

    12 de noviembre de 2020 a las 4:45

Avatar de usuario de Wing Choy
ala choy

Hola a todos, espero que no sea demasiado tarde…

para cualquiera que esté lidiando con la animación React Native en la altura de la vista.
Sé que es muy molesto como:

✖️ React Native animación parece no admite estilos de diseño (por ejemplo, ancho y alto)
✖️ LayoutAnimation parece complicado de investigar
✖️ Desea usar una forma oficial de animar en lugar de instalar un paquete de terceros
✖️ A veces, el contenido puede ser grande para romper sus estilos de vista

así que aquí está mi solución para ti (forma de componente de clase):

Primero, establezca el valor animado en el estado:

state = { height: new Animated.Value(0) };

A continuación, configure su vista animada max height con interpolación de animación:

const maxHeight = this.state.height.interpolate({ 
  inputRange: [0, 1], 
  outputRange: [0, 2000]  // <-- any value larger than your content's height
});
return (<Animated.View style={[styles.box, { maxHeight: maxHeight }]} />); 
// any other fixed styles in styles.box

Después de eso, configura la animación dentro del function usted llamó,
o componentDidMount si desea que se muestre tan pronto como se represente:

// or in any function that users interact
componentDidMount() {
  Animated.timing(this.state.formHeight, {
    toValue: 1,
    duration: 500,           // <-- animation duration
    easing: Easing.linear,   // <-- or any easing function
    useNativeDriver: false   // <-- need to set false to prevent yellow box warning
  }).start();
}

Sé consciente de no establezca useNativeDriver a true ya que no es compatible con los estilos de diseño.


Muestra

Así que a continuación hay una muestra para que interactúes,
no dude en copiar y pegar para usted React Native proyecto para probar:

import React, { PureComponent } from 'react';
import { Animated, Button, Easing, View, Text, StyleSheet } from 'react-native';

class AnimateBox extends PureComponent {
  state = { opacity: new Animated.Value(0), height: new Animated.Value(0) };

  showContent = () => {
    const { opacity, height } = this.state;

    Animated.timing(height, {
      toValue: 1,
      duration: 500,
      easing: Easing.linear,
      useNativeDriver: false  // <-- neccessary
    }).start(() => {
      Animated.timing(opacity, {
        toValue: 1,
        duration: 500,
        easing: Easing.linear,
        useNativeDriver: false  // <-- neccessary
      }).start();
    });
  };

  render() {
    const { opacity, height } = this.state;
    const maxHeight = height.interpolate({ 
      inputRange: [0, 1], 
      outputRange: [0, 1000]  // <-- value that larger than your content's height
    });

    return (
      <View style={styles.box}>
        <Animated.View style={{ opacity: opacity, maxHeight: maxHeight }}>
          <Text style={styles.content}>
            Lorem Ipsum is simply a dummy text of the printing and typesetting industry.
            Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
            when an unknown printer took a galley of type and scrambled it to make a type specimen book.
            It has survived not only five centuries, but also the leap into electronic typesetting,
            remaining essentially unchanged. It was popularised in the 1960s with the release of
            Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
          </Text>
        </Animated.View>
        <View style={styles.spacing}>
          <Button title="Show content" onPress={this.showContent} />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  box: {
    backgroundColor: '#fff',
    marginHorizontal: 15,
    paddingHorizontal: 15
  },
  spacing: {
    paddingVertical: 10
  },
  content: {
    fontSize: 16,
    lineHeight: 30,
    color: '#555'
  }
});


export default AnimateBox;

Codificación feliz 🙂


Más explicación (Editar el 03 de febrero de 2021)

Como se menciona en la documentación de React Native:
https://reactnative.dev/docs/animations#advertencias

No todo lo que puede hacer con Animated actualmente es compatible con el controlador nativo. La principal limitación es que solo puede animar propiedades que no sean de diseño: cosas como la transformación y la opacidad funcionarán, pero las propiedades de posición y Flexbox no.

Deshabilitar useNativeDriver puede animar estilos que no sean opacity y transformpero de hecho aumenta la carga de trabajo en el subproceso JS ya que el subproceso JS necesita calcular la interfaz de usuario en cada cuadro.

Y aquí en el blog de React Native 0.62:
https://reactnative.dev/blog/2020/03/26/version-0.62#deprecaciones

Ahora es necesario configurar useNativeDriver para poder cambiar el valor predeterminado en el futuro.

Debes tener que configurar el useNativeDriver en opciones de animación en React native v0.62 o superior para evitar cualquier advertencia.

  • ¿De verdad? por qué dice “La propiedad de estilo ‘altura’ no es compatible con el módulo animado nativo” para mí. quiero llorar

    – Charitha Goonewardena

    3 de febrero de 2021 a las 2:36

  • Hola @CharithaGoonewardena, creo que es posible que hayas configurado el useNativeDriver a verdadero, lo que conduce a esa advertencia. He agregado alguna explicación a la respuesta. No dude en echar un vistazo o copiar el código de muestra en su proyecto para editarlo. ¡Salud!

    – Ala Choy

    3 de febrero de 2021 a las 7:35


  • Esto funciona, pero realmente no es necesario interpolar. Simplemente puede animar a 2000 en lugar de 1 y establecer maxHeight en this.state.formHeight

    – John Kendall

    19 de febrero de 2021 a las 15:54

  • Sí, por supuesto, @Johnkendall 🙂 La interpolación solo se usa para manejar maxHeight y opacidad con el mismo valor animado en mi muestra. Depende de usted definir el valor máximo si no tiene otros estilos animados.

    – Ala Choy

    21 de febrero de 2021 a las 6:44

  • En la animación de Android no hay transición en absoluto. Es simplemente ir a valorar directamente. ¿Alguien se encontró con esto? iOS funciona bien

    – Necmettin Sargin

    13 de julio de 2021 a las 9:38

Avatar de usuario de Tierna D. Jack Lawless
Tierna D. Jack Lawless

Tendrá que agregar algún tipo de escala de tamaño, probablemente con porcentajes para obtener el mejor efecto.

Lo primero es lo primero que necesitarías usar Animated.View en vez de View.

A continuación, deberá aplicar una transformación al estilo de la vista, digamos que se ve a continuación. Esta es la parte que actualiza, cambia y crea el movimiento.

        <Animated.View style={[ styles.someStyleYouMade,
          {
            transform: [
              // scaleX, scaleY, scale, theres plenty more options you can find online for this.
              {  scaleX: ViewScaleValue } // this would be the result of the animation code below and is just a number.
            ]
          } 
        ]}
        >

La siguiente parte es básicamente un ejemplo de API animada, escribirías algo como esto (personalizado como quieras) y cuando se llame a este script, se animará de la forma que especifiques.

  Animated.timing(                    // Animate over time
    this.state.ViewScale,             // The animated value to drive, this would be a new Animated.Value(0) object.
    {
      toValue: 100,                   // Animate the value
      duration: 5000,                 // Make it take a while
    }
  ).start();

Por último, probablemente querrá aplicar una interpolación al valor para que parezca lo más personalizado posible.

(esto entrará en su render() función pero antes de la return(). la ViewScaleValue entrará en el Animated.View transformar)

const ViewScaleValue = this.state.ViewScale.interpolate({
  inputRange: [0, 25, 50, 75, 100],
  outputRange: [0, .5, 0.75, 0.9, 1]
});

todo este código haría ViewScaleValueun número simple, anime de 0 a 100, vaya rápido y luego lento (debido a la interpolación) y aplique cada iteración de la animación al Animated.View transformar.

Lea la API animada junto con esto para comprenderlo bien.

  • Usando este método, ¿cómo puedo ajustar la altura sin escalar? Parece que solo hay opciones de escala disponibles.

    – CyberMew

    14 de marzo de 2019 a las 14:33

  • cuando la vista animada tiene valor cero, entonces es simplemente invisible … aún así, está tomando el lugar que requerirá cuando el contenido sea visible

    – Samiksha Jagtap

    16 oct 2019 a las 6:49

  • Por qué %? ¿Puede explicar cómo el porcentaje ayudará a OP a lograr su objetivo?

    – png

    30 de agosto de 2021 a las 21:38

avatar de usuario de sbearben
sbearben

El método que he tomado es gastar pases de diseño para obtener la altura del componente “truncado” y la altura del componente de “tamaño completo” (necesita una forma de que la altura truncada sea determinista, generalmente sabiendo cómo renderizar un “fila” de contenido). Esencialmente, antes de tener esos valores, los representa como dos vistas separadas que están ocultas:

hidden: {
  position: 'absolute',
  left: 0,
  top: 0,
  opacity: 0,
},

Usa onLayout para capturar sus alturas:

const onLayoutTruncated = ({nativeEvent}: LayoutChangeEvent) => {
  if (!doneProcessing) {
    truncatedHeight = nativeEvent.layout.height;
    checkIfDoneProcessingLayout();
  }
};

const onLayoutFull = ({nativeEvent}: LayoutChangeEvent) => {
  if (!doneProcessing) {
    fullHeight = nativeEvent.layout.height;
    checkIfDoneProcessingLayout();
  }
};

checkIfDoneProcessingLayout() comprobará si ambos truncatedHeight y fullHeight están configurados y hacen un cambio de estado si ambos lo están (doneProcessing = true).

Desde allí, debe mostrar la vista truncada y poder animar entre ambos valores de altura usando un Animated.Value e interpolación:

const expandingHeight = animatedValue.interpolate({
  inputRange: [0, 1],
  outputRange: [truncatedHeight, fullHeight],
});

Active la animación de expansión/contracción al hacer clic usando Animated.timing

Animated.timing(animatedValue, {toValue: isExpanded ? 0 : 1, easing: SOME_EASING_FUNCTION, duration: SOME_DURATION}).start();

Yo uso propio componente con @react-spring/native

¿Cómo animas la altura en reaccionar nativo cuando no sabes el tamaño del contenido?

algo como esto

import React from 'react';
import {View, ViewProps} from 'react-native';

interface GetDimensionsOfThisContainerProps extends ViewProps {
  children: React.ReactChild[];
  onDimensions: ({width, height}: {width: Number; height: Number}) => void;
}

export const GetDimensionsOfThisContainer: React.FC<
  GetDimensionsOfThisContainerProps
> = ({children, onDimensions, ...props}: GetDimensionsOfThisContainerProps) => {
  return (
    <View
      onLayout={event =>
        onDimensions({
          width: Math.round(event.nativeEvent.layout.width),
          height: Math.round(event.nativeEvent.layout.height),
        })
      }
      style={{position: 'absolute', width: '100%'}}
      {...props}>
      {children}
    </View>
  );
};

      <GetDimensionsOfThisContainer
        onDimensions={({height: _height}) => {
          if (dynamicHeight !== _height) setDynamicHeight(_height);
        }}>
       {children}
      </GetDimensionsOfThisContainer>

fuente: https://gist.github.com/sturmenta/af790331f6bd27322ecd73ee723c8c60

Avatar de usuario de Joachim Feltkamp
Joachim Feltkamp

Basado en la solución de @Wing Choi, quiero compartir mi solución mejorada. Esto debería funcionar plug & play.

  • En lugar de usar maxHeight (no tan bueno para las animaciones), usé onLayout-Event para tener siempre la altura exacta. (Se disparará en todos los cambios internos y externos de la capa)
  • En lugar de simplemente abrir la capa con un botón, agregué una propiedad “abrir” (bool) para controlar la capa desde un componente principal.
import React, { PureComponent } from 'react';
import { Animated, Easing, View } from 'react-native';
import PropTypes from "prop-types";

class AnimateBox extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      init: false,
      opacity: (this.props.open) ? 1 : 0,
      height: 1000
    };
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (this.state.init && prevState.open !== this.props.open) {
      this.showContent();
    }
  }

  adjustHeight(layout) {
    const height = Math.round(layout.height);

    this.setState({
      init: true,
      opacity: new Animated.Value((this.props.open) ? 1 : 0),
      height: new Animated.Value((this.props.open) ? height : 0),
      interpol: {
        inputRange: [0, 1],
        outputRange: [0, height]
      }
    });
  }

  showContent = () => {
    const { opacity, height } = this.state;

    Animated.timing(height, {
      toValue: (this.props.open) ? 1 : 0,
      duration: 400,
      easing: Easing.out(Easing.ease),
      useNativeDriver: false
    }).start(() => {
      Animated.timing(opacity, {
        toValue: (this.props.open) ? 1 : 0,
        duration: 300,
        easing: Easing.linear,
        useNativeDriver: false
      }).start();
    });
  };

  render() {
    const { opacity, height, interpol } = this.state;
    let animHeight = height;
    if (this.state.init) {
      animHeight = height.interpolate(interpol);
    }
    return (
      <Animated.View style={{ position: 'relative', opacity: opacity, height: animHeight, overflow: 'hidden' }}>
        <View style={{position: 'absolute', top: 0, left: 0, right: 0}}
              onLayout={(event) => { this.adjustHeight(event.nativeEvent.layout)}} >
          {this.props.children}
        </View>
      </Animated.View>);
  }
}

AnimateBox.propTypes = {
  open: PropTypes.bool
};

AnimateBox.defaultProps = {
  open: false
}

export default AnimateBox;

¿Ha sido útil esta solución?