1. 기술 면접 연습
Set과 Map의 차이에 대해서 설명해주세요.
Set은 데이터만 저장할 뿐 인덱스를 부여하지 않으며, 중복되는 데이터 값을 허용하지 않는 컬렉션의 종류이며, Map은 반대로 데이터와 더불어 인덱스에 해당하는 키(key) 값을 직접 입력해야 하는 컬렉션의 종류입니다.
2. 최종 프로젝트
아코디언 UI
어제는 어떤 UI인지 살짝 맛만 봤던 아코디언 UI를, 오늘은 구현해보기로 했다.
이 아코디언 UI를 구현할 부분은 바로 산 개요와 추천 이유.
그런데 이걸 구현하려다 보니 몇 가지 문제가 있었다.
우선, 대부분의 아코디언 UI가 효과적으로 사용되는 경우는 RecyclerView다.
그리고 이런 RecyclerView를 사용할 때 가장 중요한 것 중 하나가 바로 data class, 내지는 객체설정이다. 실제로 대부분의 경우에는 아코디언 UI를 사용할 때 data class에 객체를 따로 설정하는 것이 많았고.
하지만, 현재 산 개요와 추천 이유에는 RecyclerView를 전혀 사용하지 않고 있다.
거기다 지금 산 개요와 추천 이유의 글들을 전부 Firestore에 있는 DB에서 받아오고 있는 상황이라 data class를 따로 만들기도 이상하고, 그렇다고 현재 쓰고 있는 DB에 추가하자니 이거 하나 쓰자고 100개의 산에다가 일일이 추가하는 데에 드는 시간이 꽤나 아까운 상황.
그래서 이건 어쩔 수 없이 data class를 쓰지 않고 아코디언 UI를 적용시켜야 했다.
아코디언 UI를 구현시키는 방법에는 여러가지가 있었지만, 그 중에서 나는 ToggleAnimation 이라는 클래스를 따로 만들어서 적용시키는 방법을 사용하였다.
class ToggleAnimation {
companion object {
// 클릭 시 토글 아이콘 회전
fun toggleArrow(view: View, isExpanded: Boolean): Boolean {
if (isExpanded) {
view.animate().setDuration(200).rotation(180f)
return true
} else {
view.animate().setDuration(200).rotation(0f)
return false
}
}
// 확장하기
fun expand(view: View) {
val animation = expandAction(view)
view.startAnimation(animation)
}
private fun expandAction(view: View) : Animation {
view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val actualHeight = view.measuredHeight
view.layoutParams.height = 0
view.visibility = View.VISIBLE
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
view.layoutParams.height = if (interpolatedTime == 1f) ViewGroup.LayoutParams.WRAP_CONTENT
else (actualHeight * interpolatedTime).toInt()
view.requestLayout()
}
}
animation.duration = (actualHeight / view.context.resources.displayMetrics.density).toLong()
view.startAnimation(animation)
return animation
}
// 축소하기
fun collapse(view: View) {
val actualHeight = view.measuredHeight
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
view.visibility = View.GONE
} else {
view.layoutParams.height = (actualHeight - (actualHeight * interpolatedTime)).toInt()
view.requestLayout()
}
}
}
animation.duration = (actualHeight / view.context.resources.displayMetrics.density).toLong()
view.startAnimation(animation)
}
}
}
우선 첫 번째 함수 toggleArrow는 펼치고 접을 때마다 토글을 회전시키는 애니메이션을 나타내는 것이다.
expand는 펼칠 때 적용시키는 애니메이션을, collapse는 접을 때 적용시키는 애니메이션을 나타낸다.
그리고 Activity에서 직접 적용시켜야 하므로 바로 적어봤다.
private fun setMoreView(sanlist: SanDetailDTO) = with(binding) {
tvIntroInfo.text = sanlist.summary
tvRecommendInfo.text = sanlist.recommend
var isExp = false
// 산 개요 옆 화살표 토글
ivIntroToggle.setOnClickListener {
viewMoreText(it, !isExp, clIntroAccordian)
// 확대 & 축소 전환
isExp = if(clIntroAccordian.visibility == View.VISIBLE) true
else false
}
// 추천 이유 옆 화살표 토글
ivRecommendToggle.setOnClickListener {
viewMoreText(it, !isExp, clRecommendAccordian)
// 확대 & 축소 전환
isExp = if(clRecommendAccordian.visibility == View.VISIBLE) true
else false
}
}
private fun viewMoreText(view: View, isExpanded: Boolean, layoutExpand: ConstraintLayout) {
ToggleAnimation.toggleArrow(view, isExpanded)
if(isExpanded) {
ToggleAnimation.expand(layoutExpand)
} else {
ToggleAnimation.collapse(layoutExpand)
}
}
위에서도 말했다시피, 이거는 원래 객체를 따로 만들어서 관리하는 것이 더 깔끔하지만, 그러기에는 시간이 너무나도 촉박해서 그냥 여기서 자체적으로 true, false를 스위칭시켜서 펼치고 접힐 수 있게 해두었다.
그리고 이것은 그 결과다.
뭐랄까, 이게 아코디언 UI인가 하기에는 좀 이상하지만, 그래도 좀 다듬으면 꽤 괜찮은 UI가 나올 것 같다.
다만, 여기서 몇 가지 문제가 있었는데
- 너무 긴 글은 접기를 시도할 때, 반응속도가 너무 느리다. 토글 애니메이션은 벌써 적용됐는데 글은 한참 지나고 나서야 접힌다.
- 펼쳤다가 접은 다음, 다시 펼치려고 할 때에 한 번 씹힌다. 즉, 두 번 클릭해야 다시 펼치기가 적용되는 문제가 있다.
'[TIL][내일배움캠프]' 카테고리의 다른 글
[내일배움캠프][TIL] 24.03.21 (목) - 최종 프로젝트 5주차 : 추가 구현 (0) | 2024.03.21 |
---|---|
[내일배움캠프][TIL] 24.03.20 (수) - 최종 프로젝트 5주차 : 추가 구현 (0) | 2024.03.20 |
[내일배움캠프][TIL] 24.03.18 (월) - 최종 프로젝트 5주차 : 추가 구현 (0) | 2024.03.18 |
[내일배움캠프][TIL] 24.03.15 (금) - 최종 프로젝트 4주차 : 추가 구현 (0) | 2024.03.15 |
[내일배움캠프][TIL] 24.03.14 (목) - 최종 프로젝트 4주차 : 추가 구현 (2) | 2024.03.14 |