Custom Adapter Class
- AdapterView 자체를 커스텀해 특별한 기능을 부여하고 싶을 때 Adapter 클래스를 구현
- BaseAdapter 를 상속받아 getCount(), getView() 메소드를 구현 (필수 오버라이딩 메소드는 4개, 커스텀에 꼭 필요한건 이 2개)
- getCount() 메소드 : AdapterView를 통해 보여줄 항목의 개수를 반환
- getView() 메소드 : AdapterView를 통해 보여줄 항목의 View를 반환
[row.xml]
✔️ 단순히 항목을 예쁘게 화면에 표시하고 클릭하는 기능 -> ArrayAdapter/simpleAdapter Class 사용 + Custom ListView
✔️ 항목에 버튼을 달고 여러가지 기능 등을 추가 -> Custom Adapter Class 구현 + Custom ListView
getView() 메소드
- 화면에 항목이 보여질때마다 호출 (화면에 항목이 5개 보여지면 5번 호출), 항목 View를 생성해 반환
- 항목 데이터가 100개면 100개의 View 객체가 생성되고 반환되는게 원리이지만 메모리 부하 문제로 화면에 보이지 않는 View 객체를 재사용 (*recycler view 와 유사) -> 만약 재사용 가능한 뷰가 없을 경우 새로운 뷰 객체 생성
- 항목 레이아웃 (row.xml)에 커스텀한 버튼 클릭 메소드 등을 구현
- 이때 row.xml은 MainActivity 에서 관리하는 레이아웃이 아니라 ListView를 구성하고 있는 항목 view에 대한 레이아웃 이다. 따라서 항목 view 객체에 존재하는 버튼이나 텍스트뷰 같은 아이들은 따로 1)주소값을 찾아주거나 2) row.xml 에 대한 뷰바인딩이 이루어져야 사용할 수 있다.
강좌에서는 findViewById 를 사용하는 방법은 매번 사용하기 번거롭기 때문에 View.run {...} 를 사용하면 항목뷰 객체가 가지고 있는 뷰들의 주소를 모두 가져올 수 있으므로 이 안에 기능을 구현하라고 소개했다.
하지만 나는 이 방식을 사용해도 선언되지 않은 뷰를 사용했다고 오류가 났다.
해당 강좌는 안드로이드의 extension이 decreated 되기 전에 녹화한 영상이다. 이 당시에는 뭔가 자동으로 되는게 많은 것 같은데 지금은 뷰바인딩을 다 직접 해야 하기 때문에 상황이 달라진 것이 원인이라고 생각했다. 따라서 나는 이 문제를 row.xml 에 대한 뷰바인딩 선언이 제대로 이루어지지 않아서 라고 생각했다.
비슷한 이슈를 구글링 하다가 없어서 좌절하고 있었는데 강좌 커뮤니티에 누가 뷰바인딩 코드를 올려줘서 해결할 수 있었다.
var rowView = convertView as? RowBinding
if (rowView == null) { // 재사용 가능 convertView 가 없는 경우
rowView = RowBinding.inflate(layoutInflater)
}
rowView?.run{
rowTextView.text = data1[position]
rowButton1.tag = position
rowButton2.tag = position
rowButton1.setOnClickListener {
binding.textView1.text = "첫번째 버튼 클릭! : ${it.tag}"
}
rowButton2.setOnClickListener {
binding.textView1.text = "두번째 버튼 클릭! : ${it.tag}"
}
}
return rowView.root
as? 키워드를 사용해 재사용 가능항 항목뷰(convertView)를 RowBinding 객체로 형변환 해주고 만약 실패할 경우 null 로 초기화해 exception을 방지한다. null일 경우 항목뷰 객체를 생성해준다.
그리고 rowView?.run { ... } 안에서 rowView 안에 있는 뷰들을 위 코드와 같이 바로 사용할 수 있다. 만약 run {} 을 사용하지 않을 경우 rowView?.rowTextView.text = " " 이런식으로 사용하면 된다.
[MainActivity.kt 전체 코드]
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.inflate
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.androidstudy_kotlin.databinding.ActivityMainBinding
import com.example.androidstudy_kotlin.databinding.RowBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val data1 = arrayOf("데이터1","데이터2","데이터3","데이터4","데이터5")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.list1.adapter = adapter1
}
val adapter1 = object : BaseAdapter() {
// 항목의 개수를 반환
override fun getCount(): Int {
return data1.size
}
override fun getItem(p0: Int): Any? { //반환값이 null일 수 있음
return null
}
override fun getItemId(p0: Int): Long {
return 0
}
// 화면에 보여줄 항목의 뷰를 반환 (화면의 5개의 항목이 보여지면 이 메소드가 5번 호출)
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
// 스크롤하면 뷰가 계속 만들어지니까 메모리에 부담
// 화면상에 항목 5~6개만 보이게 한다고 한다면 스크롤시 안 보이게 되는 뷰를 재사용
// convertView : 더이상 화면에 보이지 않아 재사용이 가능한 항목뷰
// positon : 항목의 인덱스 번호(0부터 시작)
var rowView = convertView as? RowBinding
if (rowView == null) { // 재사용 가능 항목뷰 가 없는 경우
// 새로운 항목(row layout) 만들어줌 (리스트뷰의 항목으로 사용하므로 타 레이아웃에 붙이지 않음)
//rowView = getLayoutInflater().inflate(R.layout.row, null) //새로운 항목뷰객체를 만들때 레이아웃을 설정
rowView = RowBinding.inflate(layoutInflater)
}
//항목뷰 내부에 배치되어 있는 뷰들의 주소값을 가져옴
// (액티비티가 아니라 액티비티 안에 있는 다른 뷰(어댑터뷰,리스트뷰)가 관리하고 있는 뷰이므로 주소값 가져와야 함) -> 바인딩이 불가능 한가?..
//val text1 = rowView?.findViewById<TextView>(R.id.rowTextView)
//val btn1 = rowView?.findViewById<Button>(R.id.rowButton1)
//val btn2 = rowView?.findViewById<Button>(R.id.rowButton2)
/*
rowbinding.rowTextView.text = data1[position]
rowbinding.rowButton1.tag = position // 뷰에 태그를 저장 (태그는 object 타입이므로 아무거나 태그로 저장 가능)
rowbinding.rowButton2.tag = position
rowbinding.rowButton1.setOnClickListener {
binding.textView1.text = "첫번째 버튼 클릭! : ${it.tag}"
}
rowbinding.rowButton2.setOnClickListener {
binding.textView1.text = "두번째 버튼 클릭! : ${it.tag}"
}
*/
// findViewById 안 쓰고 rowView가 가지고 있는 모든 뷰들의 주소값을 다 가져와 뷰의 id와 동일한 이름의 변수로 선언
rowView?.run{
rowTextView.text = data1[position]
rowButton1.tag = position
rowButton2.tag = position
rowButton1.setOnClickListener {
binding.textView1.text = "첫번째 버튼 클릭! : ${it.tag}"
}
rowButton2.setOnClickListener {
binding.textView1.text = "두번째 버튼 클릭! : ${it.tag}"
}
}
//return rowView // 뷰바인딩 안할 경우
return rowView.root
}
}
}
'Android' 카테고리의 다른 글
[안드로이드/AdapterView] ViewPager (0) | 2023.02.25 |
---|---|
[안드로이드/AdapterView] Spinner (0) | 2023.02.24 |
[안드로이드/Widget] 다양한 view/widget & 사용법 (0) | 2023.02.20 |
[안드로이드/Style] 색상 지정 (0) | 2022.09.15 |
[안드로이드/AdapterView] Custom ListView (0) | 2022.09.14 |