Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- llm
- exoplayer cache
- 시행착오
- android ktor
- map
- ExoPlayer
- ListAdapter
- video caching
- kotlin list
- ChatGPT
- android exoplayer
- list map
- ktor api call
- ListAdapter DiffUtil
- 유튜브
- android
- kotlin collection
- 스피너
- 안드로이드
- android custom view
- ktor client
- 독서
- Python
- DiffUtil.ItemCallback
- AWS EC2
- doc2vec
- FastAPI
- getChangePayload
- build with ai
- Zsh
Archives
- Today
- Total
버튼 수집상
[안드로이드] 커스텀 뷰 Custom View 만드는 법 본문
배경
Compose를 아직 도입하지 않은 xml 베이스의 프로젝트에서도 코드로 뷰를 생성해서 쓸 때가 있다.
그럴 때 커스텀뷰를 만들면 반복되는 코드를 은닉하면서 코드 가독성이 좋아진다는 장점이 있다.
커스텀뷰를 만들면서 겪었던 시행착오들과 기억해야할 사항들을 정리해보겠다.
뷰 생성자를 오버라이딩 해준다.
class CustomDropDown @JvmOverloads constructor(
mContext: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : LinearLayout(mContext, attrs, defStyle) {
//...
}
@JvmOverloads 어노테이션을 붙이면 mContext, attrs, defStyle로 만들 수 있는 생성자들을 자동으로 만들어준다.
이 때 클래스 인자에 디폴트 값을 넣어줘도 반영되지 않는다.
class CustomDropDown @JvmOverloads constructor(
mContext: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.style.CustomDropDown // ❌ 반영 안 됨!
) : LinearLayout(mContext, attrs, defStyle) {
//...
}
style을 적용시키는 두 가지 방법이 있다.
1. xml 사용처에서 선언
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.app.views.CustomDropDown
android:id="@+id/dropDown"
style="@style/CustomDropDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 아래로 생략 -->
보통 style의 이름은 뷰와 같은 이름으로 지정한다.
2. 코드로 생성할 때 ContextThemeWrapper 인자로 넘겨줌
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val dropDown = CustomDropDown(ContextThemeWrapper(this, R.style.CustomDropDown)).apply {
id = View.generateViewId() // ConstraintLayout에 add할 때 필요
}
binding.root.addView(dropDown)
// ... 생략
}
}
ContextThemeWrapper는 스타일을 적용시키면서 뷰에 Context로 전달된다.
뷰 UI를 초기화할 때 두 가지 방법이 있다.
1. 코드로 바로 생성
CustomDropDown.kt
// 예시 데이터
val itemList = listOf("item1", "item2", "item3", "item4")
class CustomDropDown @JvmOverloads constructor(
val mContext: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : LinearLayout(mContext, attrs, defStyle) {
// UI 초기화 로직
init {
itemList.forEachIndexed { index, name ->
// 0번째 텍스트만 스타일 다르게 적용
val style = if (index == 0) R.style.ItemSelected else R.style.ItemDefault
addView(TextView(ContextThemeWrapper(mContext, style)).apply {
text = name
if (index != 0) post { updateLayoutParams<MarginLayoutParams> { topMargin = 24.toPx } }
})
}
}
}
위 코드의 TextView에 적용된 style
<style name="ItemText" parent="Widget.AppCompat.TextView">
<item name="android:textSize">16dp</item>
</style>
<style name="ItemSelected" parent="ItemText">
<item name="android:textStyle">bold</item>
<item name="android:textColor">#000000</item>
</style>
<style name="ItemDefault" parent="ItemText">
<item name="android:textColor">#929292</item>
</style>
2. xml로 생성한 뷰를 바인딩
bottom_bar_button_view.xml
<?xml version="1.0" encoding="utf-8"?>
<merge 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"
android:layout_width="match_parent"
android:layout_height="56dp"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/iconImg"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="9dp"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/iconImg"
android:layout_marginTop="2dp"
android:ellipsize="none"
android:maxLines="1"
android:textColor="#929292"
android:textSize="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iconImg"
tools:text="Category"/>
</merge>
BottomBarButtonView.kt
class BottomBarButtonView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
private var binding: BottomBarButtonViewBinding
init {
val view = inflate(context, R.layout.bottom_bar_button_view, this)
binding = BottomBarButtonViewBinding.bind(view)
}
// ...
}
728x90
'TIL - 안드로이드' 카테고리의 다른 글
[안드로이드] AI 어시스턴트 스튜디오 봇 Studio Bot 소개글 번역 (0) | 2023.08.25 |
---|---|
[안드로이드] 상용 앱을 운영하면서 느낀 점 (0) | 2023.08.18 |
[안드로이드] ExoPlayer 비디오 캐싱하기 (0) | 2023.08.09 |
[안드로이드] 그림자에 색깔 있는 카드뷰 만들기-1 Custom CardView with shadowColor attribute (0) | 2023.08.01 |
[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 3 (0) | 2023.07.28 |