사용자 맞춤형 운동 루틴 AI생성 서비스인 'fitnee' 프로젝트 진행 중 나를 막히게 한 UI 구현이 있었다.
처음에는 피그마로 정적인 이미지만 확인하고 구현을 시도했는데, 아래가 그 이미지이다.
구현해야 하는 기능
운동 중에는 타이머가 진행되며, 세트 완료 버튼을 누르면 오른쪽의 체크박스가 회색 → 민트색으로 변해 운동이 완료된다. 완료한 세트는 위쪽으로 올라가 사라진다.
그와 동시에 아래에서는 새로운 세트가 올라오며, 다음으로 진행할 세트에는 더 진한 색으로 변화를 줘서 사용자에게 알려준다. 이 상태를 0.3초 유지해 이를 더 쉽게 인지할 수 있도록 한다.
이러한 애니메이션이 진행됨과 동시에 바로 오른쪽의 ‘휴식 시간’ 페이지가 나온다.
사용자는 최대 30초의 휴식 시간을 갖을 수 있고, 만약 ‘바로 시작하기’ 버튼을 누르면 휴식 시간은 즉시 끝나며 운동 화면으로 돌아온다.
그리고 다시 새로운 세트를 진행하며 이는 마지막 세트까지 반복된다. 마지막 세트의 경우에는 다음 세트가 없으므로 현재 세트만 진하게 표시되며 아래에는 아무것도 없어야한다.
애니메이션 구현에 익숙하지 않았고 이런 기능을 주로 어떤 방식으로 구현해야하는지 몰랐던 나는 이를 구현하기 위해 구글링을 계속하며 레퍼런스들을 찾았다. 하지만 이런 기능을 부르는 용어도 따로 없었고, 실제 앱 레퍼런스를 전혀 찾아볼 수 없어서 정말 막막했다.
처음에 접근한 구현 방식
처음에는 단순하게 리스트 2개를 보여주는 방식으로 구현했다. 피그마를 통해 정적인 UI를 접한 상태로 구현을 시도하다 보니,
단순히 아래처럼 데이터를 담는 Box를 만들어 2개를 띄우고, 버튼을 누르면 박스 내부 데이터를 다음 세트 운동 정보로 바꾸는 방식을 사용했다.
하지만 구현해서 사용해보니 보기에 어색하고 사용자에게 ‘다음 세트로 넘어간다’라는 느낌을 전혀 줄 수 없었다. 이를 통해 디자이너에게 회의를 요청해 함께 상의해본 결과, 실제 박스를 통째로 위로 넘기며 동시에 아래에서 박스가 올라오는 UI 구현이 필요함을 깨달았다.
구현을 위한 고민
그동안은 기능을 개발할 때 구글링을 통해 비슷한 기능이나 레퍼런스를 찾고, 이를 응용해서 구현한 경우가 많았다. 하지만 이런 움직이는 동적 UI는 어떤식으로 검색해야할지 감이 안 잡혔고, 나름대로 검색했지만 전혀 찾을 수가 없었다. 그래서 처음으로 직접 고민해서 구현해보기로 했다!
혼자 생각하며 리액트 네이티브의 animated 라이브러리 사용을 고민했다. 이미 운동 시작 부분에서 시작 버튼을 누르면 ‘3, 2, 1’ 숫자가 순차적으로 나오며 화면이 넘어가는 애니메이션을 animated를 이용해 구현한 경험이 있기 때문에 무조건 애니메이션으로 구현해야 한다는 생각에 갖혀있었다.
그래도 마땅한 아이디어가 떠오르지 않아서 주변 개발자들이나 팀원들과 상의했다. 심지어 개발자가 아닌 그냥 친구에게도 아이디어를 물어봤다. 상의하던 과정에서, 현재 운동 정보와 다음 운동 정보, 이 두 가지만 컨테이너에 넣지말고, 모든 데이터를 다 담은 후, 컨테이너 사이즈에 제한을 줘서 두개의 박스만 보이도록 하는게 어떻냐는 의견을 받았다. 이 이야기를 들으니 저절로 애니메이션 구현 방법도 쉽게 생각해 낼 수 있었다..!!
협업의 중요성을 깨닫음과 동시에 역시 함께 자유롭게 다양한 의견을 이야기해보는게 최고구나.. 생각했다.
실제 구현 방식
결론적으로 animated 라이브러리를 사용하기보다는
1. 이 모든 데이터를 스크롤뷰에 넣고,
2. 버튼을 누르면 스크롤이 일정 길이 만큼 이동하도록 하면!
아주 쉽게 해결할 수 있겠다…! 라는 생각을 하게 되었다.
며칠 내내 막혀서 고민이 많았지만, 한번 아이디어가 확립되니 순조롭게 개발할 수 있었다. (🥹🥹🥹)
우선 리액트 네이티브의 ‘ScrollView’ 라이브러리와 ‘FlatList’ 라이브러리 중 FlatList를 선택했다.
그 이유는 참고 ⬇️
[react-native] ScrollView와 FlatList 중 어떤 것을 쓰는게 좋을까?
리액트 네이티브에는 리액트와 다르게 'ScrollView'와 'FlatList'라는 것이 있다. 리액트에서는 단순히{ overflow : auto } 를 사용해주면 되는 일이지만, 리액트 네이티브에서는 react-native의 라이브러리
2wlslog.tistory.com
그리고 이 FlatList 컨테이너의 사이즈를 박스가 딱 2개가 들어갈 수 있도록 조정한 후, showsVerticalScrollIndicator={false}
를 줘서 스크롤바가 안 보이도록 제거해줬다. 그리고 컨테이너에 운동 정보를 담은 박스를 모두 FlatList의 renderItem에 넣어줬다.
박스는 현재 운동에만 색깔을 주고, 이후의 운동은 회색으로 현재 운동이 강조 될 수 있도록 색을 위한 삼항연산자를 작성해줬다.
(이미 진행한 운동 : 글자, 체크박스에 모두 진한 색 / 현재 운동 : 글자는 진한색, 체크박스는 회색 / 나머지 운동 : 글자, 체크박스 모두 회색)
심지어 { Idx === 24 || Idx === 25 }는 유산소 운동이라 kg 대신 km로 표시해줘야해서 그것도 함께 처리해줬다.
가장 중요한 스크롤 이동 함수는
//delay 동안 쉬도록
setIsPlaying(false) //타이머 play용 변수
setKey((prevKey) => prevKey + 1) //타이머 리셋
setIsTimerRunning(true) //타이머 켜기
setTimeout(() => {
setIsPlaying(true)
}, 300)
계획했던 대로 0.3초를 쉬고 진행하도록 setTimeout을 사용해줬고
if (flatListRef.current) {
flatListRef.current.scrollToIndex({
animated: true,
index: boxNumber,
})
scrollPosition.current = boxNumber
//console.log('scroll', scrollPosition)
}
}, 1200)
이런식으로 플랫리스트의 scrollToIndex함수와 scrollPosition.current, 그리고 내가 만든 변수인 boxNumber를 이용해서 스크롤 위치를 조정해줬다.
.
.
.
그리고 최종 FlatList 코드
<BoxList>
<FlatList
data={exerciseData}
renderItem={renderItem}
keyExtractor={(item) => item.set}
showsVerticalScrollIndicator={false}
ref={flatListRef}
onEndReached={goToCompleteExercise}
scrollEnabled={false}
getItemLayout={getItemLayout}
initialScrollIndex={scrollPosition.current}
/>
</BoxList>
최종 구현한 애플리케이션 모습
원하는대로 애플리케이션을 완벽하게 구현할 수 있었다. 스스로 해결해서 아주 만족..!!!!
'React-Native' 카테고리의 다른 글
[React-Native][Android] 안드로이드 인앱 결제 시 결제 테스트 라이선스 등록 (2) | 2024.10.12 |
---|---|
[React-Native][Expo] IOS Deploy 오류해결 (2) | 2024.10.08 |
[React-Native] React-Native CLI / Expo EAS CLI Deploy, Build (3) | 2024.08.05 |
[React-Native] Expo CLI란? (0) | 2024.07.31 |
[React-Native] ScrollView와 FlatList 중 어떤 것을 쓰는게 좋을까? (4) | 2024.06.21 |