RecyclerView的局部刷新居然这么简单

RecyclerView的局部刷新

面试时经常被问到Android列表控件RecyclerView,无非就是深入源码与ListView进行对比,四层缓存和局部刷新。而今天的重点就是局部刷新

使用场景

我们在使用RecyclerView的局部刷新时,往往使用notify相关方法
RecyclerView的局部刷新居然这么简单_第1张图片
确实可以解决大部分问题,但是这种局部刷新需要我们精准的指定具体的position。这种局限性使他只能适用于小范围的内容修改。

但我们的数据往往是从livedata获取到的,难道为了实现局部刷新,还要我们自己对比list发生改变的下标吗。可能大家都不会花时间做出回报率低的优化,直接使用notifyDataSetChanged()进行全局刷新(一个字,快)。

但官方早就预判到了大家的懒惰机智,推出了DiffUtil,AsyncListDiffer,ListAdapter等局部刷新大礼包,可以自动分析oldList和newList的差异,自动实现局部刷新。

代码部分

使用

package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.databinding.AdapterListBinding
import com.example.myapplication.databinding.FragmentListBinding
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread

class ListFragment : Fragment() {
    private lateinit var binding: FragmentListBinding
    private val adapter = MyListAdapter()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentListBinding.inflate(inflater, container, false)
        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
        binding.recyclerView.adapter = adapter
		// 提交数据,可以在主线程中执行
        adapter.submitList(
            Array(3) {
                User(id = it, name = it.toString())
            }.toList()
        )
        // 过2秒后刷新,观察到动画效果
        post(2) {
            adapter.submitList(
                // 为了与原来不同,new的新数据,模拟从数据库中读取的新数据,部分与原来相同
                Array(5) {
                    User(id = it, name = it.toString())
                }.toList()
            )
        }
        return binding.root
    }

    private fun post(delaySeconds: Long, run: () -> Unit) {
        thread {
            TimeUnit.SECONDS.sleep(delaySeconds)
            requireActivity().runOnUiThread {
                run()
            }
        }
    }
}

data class User(val id: Int, val name: String)

private class MyListAdapter : ListAdapter<User, MyListAdapter.MyViewHolder>(
    object : DiffUtil.ItemCallback<User>() {
        // 两个Item是否相同,是粗判断
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        // Item的具体内容是否相同,细判断,推测类似与equal和hashcode
        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
) {
    class MyViewHolder(binding: AdapterListBinding) : RecyclerView.ViewHolder(binding.root) {
        val text = binding.textView

        // 数据绑定
        fun bindTo(user: User) {
            text.text = user.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        // 使用的ViewBinding
        return MyViewHolder(
            AdapterListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }
}

分析源码

知道大家不喜欢看源码,没用的就替大家删了

观察到与我们开发有关,只有adapter.submitList()方法,进入

public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {    
    final AsyncListDiffer<T> mDiffer;
   	// 发现是直接调用AsyncListDiffer里的方法,进入
	public void submitList(@Nullable List<T> list) {
        mDiffer.submitList(list);
    }
}
public class AsyncListDiffer<T> {
    public void submitList(@Nullable final List<T> newList) {
        // 调用内部的方法
        submitList(newList, null);
    }
    
    // 注释描述计算新旧列表差异,计算在后台线程运行,更新在UI线程
    @SuppressWarnings("WeakerAccess")
    public void submitList(@Nullable final List<T> newList,
            @Nullable final Runnable commitCallback) {
        // 新旧一样,啥都不做
        if (newList == mList) {
            return;
        }
        // 旧的List
        final List<T> previousList = mReadOnlyList;
		// 空的,全部都删了
        if (newList == null) {
            int countRemoved = mList.size();
            // 这里的mList和mReadOnlyList,都指的是旧List
            // 旧List清空
            mList = null;
            mReadOnlyList = Collections.emptyList();
           	// 和数据更新有关,内部就是RecyclerAdapter
            mUpdateCallback.onRemoved(0, countRemoved);
            return;
        }
		// 旧List是空的,相当于第一次提交,直接更新全部
        if (mList == null) {
            mList = newList;
            mReadOnlyList = Collections.unmodifiableList(newList);
            // 和数据更新有关,内部就是RecyclerAdapter
            mUpdateCallback.onInserted(0, newList.size());
            return;
        }
        // 在后台线程执行,计算新旧List差异
        final List<T> oldList = mList;
        // getBackgroundThreadExecutor就是Executors.newFixedThreadPool(2)
        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                    // 这里面代码特别多,都是DiffUtil的计算差异的模板代码,没啥看的,知道在后台就ok
                }
                // UI线程,没找到,应该是获取到mainLooper构造handler                                     
                mMainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        if (mMaxScheduledGeneration == runGeneration) {
                            // 可以点进去
                            latchList(newList, result, commitCallback);
                        }
                    }
                });
            }
        });
    }
    
    void latchList(
            @NonNull List<T> newList,
            @NonNull DiffUtil.DiffResult diffResult,
            @Nullable Runnable commitCallback) {
        // 更新旧List
        final List<T> previousList = mReadOnlyList;
        mList = newList;
        mReadOnlyList = Collections.unmodifiableList(newList);
        // 内部通过差分算法,快速计算出差异
        diffResult.dispatchUpdatesTo(mUpdateCallback);
    }       
}

总结

ListAdapter封装了AsyncListDiffer,内部通过DiffUtil计算新旧List差异。计算自动切换线程,不用担心卡顿,放心食用。本质还是调用Adaper.notify之类的方法

后续

差分算法是啥,以后看到了再写吧

你可能感兴趣的:(android)