สวัสดีทุกคน. ฉันตัดสินใจยกหัวข้อรายการที่ซับซ้อนใน Android หัวข้อนี้ทำให้ฉันงงในตอนแรก ดูเหมือนยากสำหรับฉันมาก แต่จริงๆ แล้วทุกอย่างง่ายกว่า และฉันคิดว่าใครก็ตามที่ประสบปัญหานี้อยู่จะพบว่าบทความนี้มีประโยชน์ ตัวอย่างทั้งหมดเขียนด้วยภาษา Kotlin ฉันพยายามเขียนความคิดเห็นทุกที่และทำให้ชัดเจนที่สุดสำหรับผู้ที่เขียนด้วยภาษา Java นี่คือโครงสร้างชั้นเรียนของเรา: รายการที่ซับซ้อนเป็นเรื่องง่าย - 1MainActivity- นี่คือชั้นเรียนหลัก ListAdapter- ที่นี่เราผูกรายการของเราเพื่อดูองค์ประกอบ ViewHolder(มีหลายรายการ) - นี่คือมาร์กอัปของเราสำหรับองค์ประกอบแต่ละประเภท คลาสไอเท็มคือข้อมูลสำหรับรายการ (pojo) ไฟล์ XML คือมาร์กอัป ( activity_mainสำหรับหน้าจอหลัก ส่วนที่เหลือสำหรับองค์ประกอบรายการแต่ละประเภท) สิ่งแรกที่เราต้องทำคือเพิ่มการขึ้นต่อกันเพื่อให้เราสามารถใช้รายการได้ (ด้วยเหตุผลบางประการ การขึ้นต่อกันนี้หายไปเมื่อสร้างโปรเจ็กต์) ในไฟล์build.gradleในบล็อกdependenciesเราเพิ่มบรรทัด:
implementation "androidx.recyclerview:recyclerview:1.1.0"
ต่อไปเราจะค้นหาไฟล์activity_main.xml: รายการที่ซับซ้อนเป็นเรื่องง่าย - 2ที่นี่มีเพียงองค์ประกอบของเราสำหรับรายการ - RecyclerView. เราเพิ่มการเยื้องและการจัดรูปแบบตามรสนิยม BaseViewHolderต่อไปเราจะ สร้างคลาส มันจะเป็นนามธรรม เนื่องจากคลาสทั้งหมดของเราจะสืบทอดจากViewHolderมัน ค่อนข้างเป็นไปได้ที่จะทำโดยไม่มีมัน แต่ฉันยังคงแนะนำให้ตรวจสอบให้แน่ใจว่าคลาสที่สืบทอดทั้งหมดนั้นมีโครงสร้างเหมือนกัน (ใน Kotlin extendsและimplementถูกใช้ แทน :. เช่นเดียวกับใน Java คุณสามารถสืบทอดจากคลาสเดียวและใช้งานอินเทอร์เฟซจำนวนมากได้) ต่อไป เราจะสร้างคลาสข้อมูลของเรา: ItemTitle, ItemGoogleและItemApple. ใน Java คลาสเหล่านี้เป็นคลาสธรรมดาที่มี Constructor และ Getters
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รายการที่ซับซ้อนเป็นเรื่องง่าย - 3list_item_google.xmlรายการที่ซับซ้อนทำได้ง่าย - 4list_item_apple.xmlรายการที่ซับซ้อนทำได้ง่าย - 5ตอนนี้เรามาดูส่วนที่ยากที่สุดกันดีกว่า - 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เราจะต้องแทนที่สามวิธี: onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder- ที่นี่เราเริ่มต้นชั้นเรียนของViewHolderเรา getItemCount— วิธีการนี้จะรับผิดชอบขนาดของรายการ onBindViewHolder- ที่นี่เราส่งองค์ประกอบรายการไปยังชั้นเรียนของViewHolderเรา สำหรับรายการปกติที่มีองค์ประกอบประเภทเดียว นี่ก็เพียงพอแล้ว แต่สำหรับประเภทต่างๆ เรายังจำเป็นต้องกำหนดวิธีการใหม่getItemViewType(สำหรับสิ่งนี้ เราใช้ค่าคงที่ที่อยู่ด้านบนสุดของคลาสอะแดปเตอร์ของเรา ใน Java คุณสามารถใช้finalตัวแปรสำหรับสิ่งนี้) นอกจากนี้ใน Java 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นี่คือที่ของเรา และ เริ่มListAdapterต้น นี่เป็นโค้ดสำเร็จรูปที่หลายๆ คนเคยเจอ ในวิธีการนี้stubDataฉันกรอกรายการด้วยข้อมูล (ดังที่คุณเห็น ข้อมูลที่มีองค์ประกอบต่างกัน) และส่งรายการนี้ไปยังอะแดปเตอร์ ต่อไป เราเปิดตัวแอปพลิเคชันของเรา และเราควรเห็นสิ่งนี้บนหน้าจอของเรารายการที่ซับซ้อนเป็นเรื่องง่าย - 6อย่างที่คุณเห็น มีองค์ประกอบที่แตกต่างกันในรายการเดียว ซึ่งเป็นเป้าหมายของเรา ป.ล. ลืมบอกไปว่า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