728x90

구현간에 어처구니없는 이슈로

삽질을 1시간이나해서 남기는 기록...

 

목표

1. animation 처리할 박스의 크기를 구한다.

2. 박스 크기 측정이 완료되면 Collapsible 느낌으로 나타나게한다.

* 조건: 처리할 박스 하단에는 다른 컴포넌트는 없음

 

어떻게 처리할지 고민이 되었다.

박스의 크기를 구하려면

이미 박스가 렌더링 되어있어야하는데

그러면 Collapsible 느낌을 낼수가 없다는 역설

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc() {
  return (
    <>
      <View>
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

Higher Order Component를 만들어 원할때

언제든지 쓰게할것이다

따라서 이름을 AnimateHoc라고 지어보았다.

 

그럼 박스의 크기를 일단 구해본다.

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout}> <-- 박스크기 측정
        { children }
      </View>
    </>
  )
}

 

이러면 쉽게 박스의 크기를 구할수있는데,

이것이 사용자에게 노출되면 안된다.

꼼수로 opacity로 투명도를 낮춰주자.

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

투명도를 낮추더라도 공간은 차지하니

아 아래에 다른 무언가가 있다면 이 HOC는

쓰기에 적합하지 않다. 다른 방법을 찾아야한다.

아니면 약간의 수정을

 

 

다음은, children이 서서히 나타나며 보이게하는 부분이다

(마치, Collapsible 처럼)

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true); <-- 굳이 init이라는 장치를 한번 더 적용한 이유는 잘못된 리렌더링을 방지하기 위함
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- 서서히 나타나야될 부분
        style={[{ height: 0 }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      { <-- 크기 측정을 위해서 opacity: 0 상태로 렌더링하다가 측정완료되면 렌더링 하지않음
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
          { children }
        </View>
      }
    </>
  )
}

 

이제는 완벽하다 생각했다

문제없이 동작할것이라 생각했는데

duration이 없기라도한듯

한번에 animated view가 나타났다

 

왜지, 아무리 구글링을해도

Docs를 살펴봐도

라이브러리 사용면에서는 문제는 없어보였다.

 

그래서 animated view에  backgroundColor를 넣어서

정상적으로 서서히 크기가 커지나 살펴보니

아주 정상적이었다!

다만, 그 내용물이 한번에 나타났을뿐!

박스는 천천히, 내용물은 한번에...

 

그래서overflow hidden을 설정해줌으로써 완벽히 정리되었다.

최종 코드

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true);
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- overflow 멈춰!!!
        style={[{ height: 0, overflow: 'hidden' }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      {
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}>
          { children }
        </View>
      }
    </>
  )
}

 

이제 이걸 활용한다면 이렇게 할수있을것이다.

// 기본 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc>
      <View>
        <Text>1. 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)

// 시간 조절 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc duration={ 1500 }>
      <View>
        <Text>1. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 천천히 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)
728x90
반응형