Recyclerview - onCreateViewHolder вызывается для каждого элемента списка - PullRequest
0 голосов
/ 12 июля 2020

Я реализовал простой адаптер, но он заставляет RecyclerView не перерабатывать представления и вызывает onCreateViewHolder () для каждого элемента списка при прокрутке. Это вызывает рывки при прокрутке списка. Несколько пунктов, перечисленных ниже, не связаны с чрезмерным количеством вызовов onCreateViewHolder () , но я пробовал их, чтобы улучшить производительность прокрутки и избежать рывков. Вещи, которые я пробовал до сих пор:

  1. recyclerView.setHasFixedSize (true)
  2. recyclerView.recycledViewPool.setMaxRecycledViews (1, 10) с recyclerView.setItemViewCacheSize (10)
  3. recyclerView.setDrawingCacheEnabled (true) с recyclerView.setDrawingCacheQuality (View.DRAWING_CACHE_QUALITY_HIGH)
  4. установка высоты RecyclerView на "match_parent"
  5. Ранее использовался Kotlin Syntheti *, теперь перемещен * 10 to Android ViewBinding
  6. Переписать сложные вложенные макеты в Constraint Layout
  7. переопределить onFailedToRecycleView () , чтобы узнать, вызывается ли он, но он никогда не вызывался

Вот мой адаптер:

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.suppstore.R
import com.example.suppstore.common.Models.Brand
import com.example.suppstore.databinding.LiBrandBinding
import com.google.firebase.perf.metrics.AddTrace

class BrandsAdapter(list: ArrayList<Brand>, var listener: BrandClickListener?) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val VIEW_TYPE_LOADING = 0
    private val VIEW_TYPE_NORMAL = 1
    private var brandsList: ArrayList<Brand> = list

    @AddTrace(name = "Brands - onCreateViewHolder", enabled = true)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == VIEW_TYPE_NORMAL) {
            ViewHolder(
                LiBrandBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent, false
                )
            )

        } else {
            LoaderHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.li_loader, parent, false)
            )
        }
    }

    @AddTrace(name = "Brands - onBindViewHolder", enabled = true)
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is ViewHolder)
            holder.setData(brandsList[position], listener!!)
    }

    class ViewHolder(itemView: LiBrandBinding) : RecyclerView.ViewHolder(itemView.root) {
        private val binding: LiBrandBinding = itemView

        @AddTrace(name = "Brands - ViewHolder-setData", enabled = true)
        fun setData(brand: Brand, listener: BrandClickListener) {
            binding.cardItem.setOnClickListener { listener.onItemClick(brand) }
            binding.tvBrandName.text = brand.name
            binding.tvCount.text = brand.count.toString() + " Products"
        }
    }

    class LoaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView.rootView) {

    }

    @AddTrace(name = "Brands - addLoader", enabled = true)
    fun addLoader() {
        brandsList.add(Brand())
        notifyItemInserted(brandsList.size - 1)

    }

    @AddTrace(name = "Brands - setData", enabled = true)
    fun setData(newList: ArrayList<Brand>) {
        this.brandsList = newList
        notifyDataSetChanged()

    }

    @AddTrace(name = "Brands - removeLoader", enabled = true)
    fun removeLoader() {
        if (brandsList.size == 0)
            return
        val pos = brandsList.size - 1
        brandsList.removeAt(pos)
        notifyItemRemoved(pos)

    }

    override fun getItemViewType(position: Int): Int {
        return if (brandsList.get(position).count == -1) {
            VIEW_TYPE_LOADING
        } else
            VIEW_TYPE_NORMAL

    }

    interface BrandClickListener {
        fun onItemClick(brand: Brand)
    }

    override fun getItemCount(): Int {
        return brandsList.size
    }
}

Вот элемент списка (li_brand):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cardItem"
    android:layout_width="match_parent"
    android:layout_height="85dp"
    android:background="@color/app_black">

    <TextView
        android:id="@+id/tvBrandName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:textColor="@color/app_yellow"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@id/tvCount"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <TextView
        android:id="@+id/tvCount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginTop="2dp"
        android:textColor="@color/app_grey"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tvBrandName" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:layout_gravity="center_vertical"
        android:layout_marginEnd="15dp"
        android:src="@drawable/ic_baseline_arrow_forward_ios_24"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:background="@color/app_bg"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Вот связанные функции во фрагменте

class BrandsFragment : Fragment() {
    private val adapter = BrandsAdapter(ArrayList(), brandClickListener())
    fun brandClickListener(): BrandsAdapter.BrandClickListener {
        return object : BrandsAdapter.BrandClickListener {
            override fun onItemClick(brand: Brand) {
                activityViewModel?.setSelectedBrand(brand)
            }
        }
    }

    fun setupRecyclerView() {
        val llManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        binding.recyclerView.layoutManager = llManager
        binding.recyclerView.setHasFixedSize(true)
        binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                if (dy > 0) { //check for scroll down
                    val visibleItemCount = llManager.childCount
                    val totalItemCount = llManager.itemCount
                    val firstVisibleItemPos = llManager.findFirstVisibleItemPosition()
                    if (loadWhenScrolled
                        && visibleItemCount + firstVisibleItemPos >= totalItemCount
                        && firstVisibleItemPos >= 0
                    ) {
                        //ensures that last item was visible, so fetch next page
                        loadWhenScrolled = false
                        viewModel.nextPage()
                    }
                }
            }
        })
        binding.recyclerView.adapter = adapter
    }
}

А вот и фрагмент xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_black"
    android:focusableInTouchMode="true"
    android:orientation="vertical"
    tools:context=".Brands.BrandsFragment">

    <androidx.appcompat.widget.SearchView
        android:id="@+id/searchView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:background="@drawable/bottom_line_yellow"
        android:theme="@style/SearchViewTheme"
        app:closeIcon="@drawable/ic_baseline_close_24"
        app:iconifiedByDefault="false"
        app:queryBackground="@android:color/transparent"
        app:queryHint="Atleast 3 characters to search"
        app:searchIcon="@drawable/ic_baseline_search_24" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

1 Ответ

1 голос
/ 12 июля 2020

Вы пробовали класс RecyclerView Diffutil? Надеюсь, это решит проблему плавной прокрутки и чрезмерное воссоздание элементов.

https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

"DiffUtil - служебный класс, который вычисляет разницу между двумя списками и выводит список операций обновления, преобразующий первый список во второй. "

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...