안녕하세요 여러분. 나는 안드로이드에서 복잡한 목록이라는 주제를 제기하기로 결정했습니다. 이 주제는 처음에 나를 당황하게 만들었습니다. 나에게는 매우 어려워 보였지만 실제로는 모든 것이 더 간단합니다. 그리고 나는 지금 이 문제에 직면한 누구에게나 이 기사가 유용하다고 생각할 것이라고 생각합니다. 모든 예제는 Kotlin으로 작성되었습니다. 나는 어디서나 주석을 작성하려고 노력했으며 Java로 작성하는 사람들을 위해 가능한 한 명확하게 설명했습니다. 클래스 구조는 다음과 같습니다.
MainActivity
- 이것이 메인 클래스입니다. ListAdapter
- 여기서 목록을 뷰 요소에 바인딩합니다. ViewHolder
(여러 가지가 있습니다) - 이것은 각 요소 유형에 대한 마크업입니다. 항목 클래스는 목록(pojo)에 대한 데이터입니다. XML 파일은 마크업입니다( activity_main
기본 화면의 경우 나머지는 각 목록 요소 유형에 대한 것입니다). 가장 먼저 해야 할 일은 목록을 사용할 수 있도록 종속성을 추가하는 것입니다(어떤 이유로 프로젝트를 생성할 때 이 종속성이 누락되었습니다). build.gradle
블록의 파일에 dependencies
다음 줄을 추가합니다.
implementation "androidx.recyclerview:recyclerview:1.1.0"
다음으로 파일을 찾습니다 activity_main.xml
. 여기에는 목록에 대한 요소만 있습니다 RecyclerView
. 취향에 따라 들여쓰기와 서식을 추가합니다. 다음으로 클래스를 생성합니다 BaseViewHolder
. 우리의 모든 클래스가 그것으로부터 상속받게 되므로 그것은 추상적일 것입니다 ViewHolder
. 그것 없이도 가능하지만 모든 상속 클래스의 구조가 동일한지 확인하는 것이 좋습니다. (Kotlin에서는 extends
및 가 대신 implement
사용됩니다 . Java에서와 마찬가지로 여기서는 하나의 클래스에서 상속하고 많은 인터페이스를 구현할 수 있습니다.) 다음으로 데이터 클래스인 , 및 를 :
만듭니다 . Java에서는 생성자와 게터가 있는 일반 클래스입니다. ItemTitle
ItemGoogle
ItemApple
data class ItemGoogle(
val name: String,
val product: String,
val version: String,
val isUse: Boolean
) : ListMarker
data class ItemApple(
val name: String,
val country: String,
val year: Int
) : ListMarker
data class ItemTitle(
val title: String,
val amount: Int
) : ListMarker
여기서는 인터페이스에 중점을 두겠습니다 ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
모든 데이터 클래스는 이 인터페이스를 구현하여 동일한 유형으로 변환합니다. 나중에 목록에 필요할 것입니다. 다음으로 목록의 각 요소에 대한 뷰홀더를 만듭니다. 그들 각각은 에서 상속받는다는 것을 잊지 마세요 BaseViewHolder
. view
이러한 클래스에서는 데이터 클래스의 필드에 해당하는 요소를 지정합니다 .
abstract class BaseViewHolder<t>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}
class GoogleViewHolder(view: View) : BaseViewHolder<itemgoogle>(view) {
override fun bind(item: ItemGoogle) {
itemView.tvName.text = item.name
itemView.tvProduct.text = item.product
itemView.tvVersion.text = item.version
if (item.isUse) {
itemView.tvProduct.visibility = View.GONE
} else {
itemView.tvProduct.visibility = View.VISIBLE
}
}
}
class AppleViewHolder(view: View) : BaseViewHolder<itemapple>(view) {
override fun bind(item: ItemApple) {
//можем делать так
itemView.tvName.text = item.name
itemView.tvCountry.text = item.country
itemView.tvYear.text = item.year.toString()
/*----сверху и снизу два идентичных блока----*/
//а можем сделать такой блок и не использовать в каждой строке itemView
with(itemView) {
tvName.text = item.name
tvCountry.text = item.country
tvYear.text = item.year.toString()
}
}
}
class TitleViewHolder(view: View) : BaseViewHolder<itemtitle>(view) {
override fun bind(item: ItemTitle) {
itemView.tvTitle.text = item.title
itemView.tvAmount.text = item.amount.toString()
}
}
여기에는 특별한 논리가 없으며 모든 것이 매우 간단합니다. 또한 각 뷰홀더에는 고유한 레이아웃이 있습니다. list_item_title.xml
list_item_google.xml
list_item_apple.xml
이제 가장 어려운 부분인 ListAdapter
.
class ListAdapter : RecyclerView.Adapter<baseviewholder<*>>() {
companion object {
//задаем константы для каждого типа айтема
private const val TYPE_TITLE = 0
private const val TYPE_GOOGLE = 1
private const val TYPE_APPLE = 2
}
//здесь можно использовать обычный ArrayList
//сюда добавляются все айтемы, которые реализовали интерфейс ListMarker
//How вариант можно было сделать mutableListOf<any>() и обойтись без интерфейса
private val items = mutableListOf<listmarker>()
internal fun swapData(list: List<listmarker>) {
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
return when(viewType) {
//задаем разметку для каждого типа айтема
TYPE_TITLE -> TitleViewHolder(parent.inflate(R.layout.list_item_title))
TYPE_GOOGLE -> GoogleViewHolder(parent.inflate(R.layout.list_item_google))
TYPE_APPLE -> AppleViewHolder(parent.inflate(R.layout.list_item_apple))
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is ItemTitle -> TYPE_TITLE
is ItemGoogle -> TYPE_GOOGLE
is ItemApple -> TYPE_APPLE
else -> throw IllegalArgumentException("Invalid type of item $position")
}
}
override fun getItemCount(): Int {
//этот метод определяет размер списка
return items.size
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val element = items[position]
when (holder) {
//отправляем каждый айтем к своему ViewHolder
is TitleViewHolder -> holder.bind(element as ItemTitle)
is GoogleViewHolder -> holder.bind(element as ItemGoogle)
is AppleViewHolder -> holder.bind(element as ItemApple)
else -> throw IllegalArgumentException()
}
}
}
</listmarker></listmarker></any></baseviewholder<*>
에서 클래스를 상속할 때 , , RecyclerView.Adapter
세 가지 메서드를 재정의해야 합니다 . - 여기서는 클래스를 초기화합니다 . — 이 방법은 목록 크기를 담당합니다. - 여기서는 목록 요소를 클래스에 전달합니다 . 하나의 요소 유형을 가진 일반 목록의 경우 이것으로 충분합니다. 그러나 다른 유형의 경우 여전히 메소드를 재정의해야 합니다 (이를 위해 어댑터 클래스의 최상위에 있는 상수를 사용합니다. Java에서는 이를 위해 변수를 사용할 수 있습니다). 또한 Java에서는 표현식 대신 일반 . 그리고 마지막으로 메인 클래스인 으로 넘어가겠습니다 . onCreateViewHolder
getItemCount
onBindViewHolder
onCreateViewHolder
ViewHolder
getItemCount
onBindViewHolder
ViewHolder
getItemViewType
final
onBindViewHolder
when
if
MainActivity
class MainActivity : AppCompatActivity() {
private val adapter = ListAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRecyclerView()
}
private fun initRecyclerView() {
rvList.layoutManager = LinearLayoutManager(this)
//здесь мы задаем разделитель между айтемами, чтоб они не сливались друг с другом
val divider = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
rvList.addItemDecoration(divider)
rvList.adapter = adapter
stubData()
}
private fun stubData() {
val list = mutableListOf<listmarker>()
list.add(ItemTitle("title1", 4))
list.add(ItemGoogle("android", "product1", "17.0v", true))
list.add(ItemGoogle("no name", "product2", "3.1v", false))
list.add(ItemApple("macOs", "USA", 2005))
list.add(ItemApple("iOs", "China", 2007))
list.add(ItemTitle("title2", 2))
list.add(ItemGoogle("map", "product3", "23.0v", true))
list.add(ItemApple("car", "England", 2018))
list.add(ItemTitle("title3", 0))
//отправляем все данные в адаптер
adapter.swapData(list)
}
}
</listmarker>
RecyclerView
이것이 우리의 and 가 초기화되는 곳입니다 ListAdapter
. 이것은 많은 사람들이 접했던 꽤 상용구 코드입니다. 이 방법에서는 stubData
목록을 데이터(보시다시피 다양한 요소가 포함된 데이터)로 채우고 이 목록을 어댑터에 전달했습니다. 다음으로 애플리케이션을 실행하면 화면에 다음과 같은 내용이 표시됩니다. 보시다시피 하나의 목록에 다양한 요소가 있는데 이것이 바로 우리의 목표였습니다. 추신: Extension
. 다음과 같이 보입니다.
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate instead of длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
이것은 클래스가 아니라 파일이므로 내부에 이름이 없습니다. inflate
하지만 이제 긴 구조 대신 간단히 어댑터에서 사용할 수 있습니다 . 아쉽게도 Java에서는 제공되지 않으므로 LayoutInflater.from(context).inflate
. 그게 다야. 다음 시간까지 :) GitHub 링크
GO TO FULL VERSION