JavaRush /Blog Java /Random-PL /Złożone listy stają się proste
Paul Soia
Poziom 26
Kiyv

Złożone listy stają się proste

Opublikowano w grupie Random-PL
Cześć wszystkim. Postanowiłem poruszyć temat złożonych list w Androidzie. Na początku ten temat mnie zaintrygował. Wydawało mi się to bardzo trudne, ale tak naprawdę wszystko jest prostsze. Myślę, że każdy, kto stanie teraz przed tym problemem, uzna ten artykuł za przydatny. Wszystkie przykłady są napisane w Kotlinie. Starałem się pisać komentarze wszędzie i uczynić to tak jasnym, jak to możliwe dla tych, którzy piszą w Javie. Oto nasza struktura klas: Złożone listy stają się proste — 1MainActivity- To jest klasa główna. ListAdapter- tutaj wiążemy naszą listę z elementami widoku. ViewHolder(jest ich kilka) - to jest nasz znacznik dla każdego typu elementu. Klasy elementów są danymi dla listy (pojo). Pliki Xml to znaczniki ( activity_maindla ekranu głównego, reszta dla każdego typu elementu listy). Pierwszą rzeczą, którą musimy zrobić, to dodać zależność, abyśmy mogli korzystać z list (z jakiegoś powodu tej zależności brakuje podczas tworzenia projektu). W pliku build.gradlew bloku dependenciesdodajemy linię:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Następnie znajdujemy plik activity_main.xml: Złożone listy stają się proste — 2Tutaj znajduje się tylko nasz element dla list - RecyclerView. Dodajemy dowolne wcięcia i formatowanie według własnego uznania. Następnie tworzymy klasę BaseViewHolder. Będzie to abstrakcyjne, ponieważ wszystkie nasze klasy będą po nim dziedziczyć ViewHolder. Można się bez tego obejść, ale nadal zalecam upewnienie się, że wszystkie klasy dziedziczące mają tę samą strukturę. (w Kotlinie extendsi zamiast tego implementużywane jest :. Podobnie jak w Javie, tutaj można dziedziczyć z jednej klasy i implementować wiele interfejsów) Następnie tworzymy nasze klasy danych: ItemTitle, ItemGooglei ItemApple. W Javie są to zwykłe klasy z konstruktorem i modułami pobierającymi.
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
Skupmy się tutaj na interfejsie ListMarker.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Wszystkie nasze klasy danych implementują ten interfejs, aby rzutować je na ten sam typ. Będziemy potrzebować tego później do naszej listy. Następnie tworzymy naszych widzów dla każdego elementu listy. Nie zapominaj, że każdy z nich dziedziczy po BaseViewHolder. W tych klasach określamy, który element viewodpowiada polu z klas danych.
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()
    }
}
Nie ma tu żadnej szczególnej logiki, wszystko jest niezwykle proste. Ponadto każdy widz ma swój własny układ: list_item_title.xmlZłożone listy stają się proste — 3list_item_google.xmlZłożone listy stają się proste — 4list_item_apple.xmlZłożone listy stają się proste — 5teraz przejdźmy do najtrudniejszej części - 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
    //Jak вариант можно было сделать 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<*>
Kiedy dziedziczymy naszą klasę z RecyclerView.Adapter, będziemy musieli zastąpić trzy metody: onCreateViewHolder, getItemCount, onBindViewHolder. onCreateViewHolder- tutaj inicjujemy nasze klasy ViewHolder. getItemCount— ta metoda odpowiada za rozmiar listy. onBindViewHolder- tutaj przekazujemy elementy listy naszym klasom ViewHolder. W przypadku zwykłej listy z jednym typem elementu to by wystarczyło. Jednak w przypadku różnych typów nadal musimy przedefiniować metodę getItemViewType(w tym celu używamy stałych, które znajdują się na górze naszej klasy adaptera. W Javie można finaldo tego używać zmiennych). Również w Javie onBindViewHolderzamiast wyrażenia whenmożna użyć zwykłego if. I na koniec przejdźmy do naszej głównej klasy - 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>
W tym miejscu inicjowane jest nasze RecyclerViewi ListAdapter. Jest to ładny szablonowy kod, z którym wielu się zetknęło. W metodzie stubDatawypełniłem listę danymi (jak widać danymi różnymi elementami) i przekazałem tę listę do adaptera. Następnie uruchamiamy naszą aplikację i na ekranie powinniśmy zobaczyć coś takiego: Złożone listy stają się proste — 6Jak widać na jednej liście znajdują się różne elementy, co było naszym celem. PS Zapomniałem wspomnieć o Extension. Oto jak to wygląda:
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate zamiast длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
    LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
To nie jest klasa, ale plik, dlatego nie ma w nim nazwy. Ale teraz możemy go używać w adapterze po prostu inflatezamiast długiej konstrukcji. Niestety nie jest to dostępne dla Java, więc napisz LayoutInflater.from(context).inflate. To wszystko. Do następnego razu :) Link do GitHuba
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION