【问题标题】:Recyclerview - onCreateViewHolder called for each list itemRecyclerview - 为每个列表项调用 onCreateViewHolder
【发布时间】:2020-07-12 17:39:47
【问题描述】:

我已经实现了一个简单的适配器,但它导致 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 的合成,现在转移到 Android 的 ViewBinding
  6. 将复杂的嵌套布局重写为约束布局
  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>

这里是Fragment中的相关函数

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>

【问题讨论】:

    标签: android android-recyclerview


    【解决方案1】:

    你试过 RecyclerView Diffutil 类吗?希望它能解决平滑滚动问题并压倒项目的重新创建。

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

    “DiffUtil 是一个实用程序类,它计算两个列表之间的差异,并输出一个更新操作列表,将第一个列表转换为第二个列表。”

    【讨论】:

    • 谢谢。我确实使用了 ListAdapter 并解决了这个问题。仍然很好奇为什么 RecyclerView 会这样。我注意到的一件事是,我没有在适配器的 setData() 中使用 notifyDataSetChanged(),而是使用 notifyItemRangeInserted()(因为我正在插入分页数据)并且创建了与 ListAdapter 创建的相同数量的持有者。我不知道为什么每次滚动时 notifyDataSetChanged() 都会导致创建持有人
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-05
    • 2021-08-07
    • 1970-01-01
    相关资源
    最近更新 更多