JavaRush /Java Blog /Random-JA /複雑なリストも簡単に
Paul Soia
レベル 26
Kiyv

複雑なリストも簡単に

Random-JA グループに公開済み
こんにちは、みんな。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 と同様に、ここでも 1 つのクラスを継承し、多くのインターフェイスを実装できます) 次に、データ クラス、 、ItemTitleおよびItemGoogleを作成しますItemApple。Java では、これらはコンストラクターとゲッターを備えた通常のクラスです。
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
すべてのデータ クラスは、同じ型にキャストするためにこのインターフェイスを実装しています。これは後でリストに必要になります。次に、リストの各要素のビューホルダーを作成します。それぞれが から継承していることを忘れないでくださいBaseViewHolderviewこれらのクラスでは、どの要素がデータ クラスのフィールドに対応するかを 指定します。
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の 3 つのメソッドをオーバーライドする必要があります。 - ここでクラスを初期化します。 — このメソッドはリストのサイズを担当します。 - ここではリスト要素をクラスに渡します。1 つの要素タイプを持つ通常のリストの場合は、これで十分です。ただし、型が異なる場合は、メソッドを再定義する必要があります(このために、アダプター クラスの先頭にある定数を使用します。Java では、これに変数を使用できます)。また、Java では、式の代わりに通常の. そして最後に、メインクラスに移りましょう - 。 onCreateViewHoldergetItemCountonBindViewHolderonCreateViewHolderViewHoldergetItemCountonBindViewHolderViewHoldergetItemViewTypefinalonBindViewHolderwhenifMainActivity
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ご覧のとおり、1 つのリストにさまざまな要素が含まれています。これが私たちの目標でした。PS について言及するのを忘れていました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へのリンク
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION