(RunWithMe 리팩토링) 5. CustomView로 재사용성 향상
안녕하세요
RunWithMe 리팩토링 프로젝트에서 팀장과 안드로이드 개발을 맡은 서경원입니다.
RunWithMe 프로젝트를 리팩토링하면서 공부한 내용에 대해서 설명해보려합니다.
이해가 안되는 내용이나 제가 잘못 적은 부분이 있다면 꼭 댓글 남겨주세요.
이전에 ‘우리집’이라는 프로젝트를 Compose로 진행했던 경험이 있습니다.
컴포즈를 사용하면서 제일 좋았던 점은 Component 재사용성이 뛰어나다는 점이었습니다.
xml에서도 custom view를 활용해 view 재사용성을 향상시키고자 했습니다.
다른 뷰는 재사용하는 부분이 거의 없어 가장 많이 사용하는 Toolbar에 CustomView를 적용하였습니다.
Toolbar는 총 4가지 형태로 존재합니다.
- 메인 로고만 있는 툴바
- 백 버튼과 화면 정보가 있는 툴바
- 백 버튼과 화면 정보, 그리고 옵션 버튼 1개가 있는 툴바
- 백 버튼과 화면 정보, 그리고 옵션 버튼 2개가 있는 툴바
옵션 버튼에는 flag 값을 두어 사용하지 않는 경우에는 사용하지 않도록 하여 하나의 Custom View로 4가지 형태를 모두 사용할 수 있도록 했습니다.
아래는 Custom View를 적용하기 위한 코드입니다.
// rwm_toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="@dimen/elevation_small">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/tb_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_back_button"
android:layout_width="?attr/actionBarSize"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:src="@drawable/baseline_arrow_back_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="200dp"
android:layout_height="?attr/actionBarSize"
android:layout_marginStart="@dimen/toolbar"
android:gravity="center|start"
android:textColor="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_back_button"
app:layout_constraintTop_toTopOf="parent"
style="@style/toolbar_text"
tools:text="툴바 제목" />
<ImageView
android:id="@+id/iv_option_two"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="20dp"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_option_one"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_launcher_foreground" />
<ImageView
android:id="@+id/iv_option_one"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="20dp"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/baseline_search_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
</layout>
재사용할 xml을 만들어줍니다.
// values에 생성
// attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomToolbar">
<attr name="title" format="string"/>
<attr name="back_image" format="reference|integer"/>
<attr name="uses_option_one" format="boolean"/>
<attr name="option_one_image" format="reference|integer"/>
<attr name="uses_option_two" format="boolean"/>
<attr name="option_two_image" format="reference|integer"/>
</declare-styleable>
</resources>
xml에 binding할 속성을 입력합니다.
// CustomToolbar.kt
package com.side.runwithme.component
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.LayoutInflater
import com.google.android.material.appbar.AppBarLayout
import com.side.runwithme.R
import com.side.runwithme.databinding.RwmToolbarBinding
class CustomToolbar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppBarLayout(context, attrs, defStyleAttr) {
private var binding = RwmToolbarBinding.inflate(LayoutInflater.from(context), this, true)
init {
attrs?.let {
getAttrs(it)
}
}
private fun getAttrs(attrs: AttributeSet) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar)
setTypeArray(typedArray)
}
private fun setTypeArray(typedArray: TypedArray) {
binding.apply {
val backImage = typedArray.getResourceId(
R.styleable.CustomToolbar_back_image,
R.drawable.baseline_arrow_back_24
)
setBackImage(backImage)
val usesOptionOne =
typedArray.getBoolean(R.styleable.CustomToolbar_uses_option_one, false)
if (usesOptionOne) {
val optionImage = typedArray.getResourceId(
R.styleable.CustomToolbar_option_one_image,
R.drawable.ic_launcher_foreground
)
setOptionImage(optionImage, 1)
}
val usesOptionTwo =
typedArray.getBoolean(R.styleable.CustomToolbar_uses_option_two, false)
if (usesOptionTwo) {
val optionImage = typedArray.getResourceId(
R.styleable.CustomToolbar_option_two_image,
R.drawable.ic_launcher_foreground
)
setOptionImage(optionImage, 2)
}
val title = typedArray.getString(R.styleable.CustomToolbar_title)
setTitle(title!!)
}
// 데이터를 캐싱해두어 가비지컬렉션에서 제외시키도록 하는 함수
// typedArray 사용 후 호출해야하나, 커스텀뷰가 반복 사용되는 것이 아니라면 호출하지 않아도 무방하다.
typedArray.recycle()
}
fun setBackImage(image: Int) {
binding.ivBackButton.setImageResource(image)
}
fun setOptionImage(image: Int, flag: Int) {
when (flag) {
1 -> {
binding.ivOptionOne.setImageResource(image)
}
2 -> {
binding.ivOptionTwo.setImageResource(image)
}
}
}
fun setTitle(title: String) {
binding.tvTitle.text = title
}
fun setBackButtonClickEvent(onClick: () -> Unit) {
binding.ivBackButton.setOnClickListener {
onClick()
}
}
fun setOptionButtonClickEvent(flag: Int, onClick: () -> Unit ) {
when (flag) {
1 -> {
binding.ivOptionOne.setOnClickListener {
onClick()
}
}
2 -> {
binding.ivOptionTwo.setOnClickListener {
onClick()
}
}
}
}
}
attrs에서 정의한 속성을 binding해줍니다.
usesOptionOne과 usesOptionTwo가 옵션 메뉴를 사용하기 위한 flag 값입니다.
// CustomToolbar를 사용해줄 xml
<com.side.runwithme.component.CustomToolbar
android:id="@+id/toolbar_challenge_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/rv_challenge_list"
app:layout_constraintTop_toTopOf="parent"
app:uses_option_one="true"
app:option_one_image="@drawable/baseline_search_24"
app:title="모집 중인 챌린지" />
위와 같이 xml에 CustomToolbar에 속성을 적용해 사용해주면 됩니다.
'Android 일지 > 리팩토링' 카테고리의 다른 글
3. 경로 최적화로 좌표 데이터 약 73% 감소 (2) | 2023.10.17 |
---|---|
6. SharedPreference에서 DataStore로 변경하여 데이터 일관성 문제 해결하기 (0) | 2023.05.30 |
4. EventFlow 도입 (0) | 2023.05.26 |
2. bindService를 적용하여 메모리 누수 방지하기 (1) | 2023.05.25 |
1. LifecycleService와 Service의 차이점 (0) | 2023.05.25 |