如何正确地使用RecyclerView.ListAdapter

默认是在一个fragment中实现RecyclerView.

private inner class CrimeAdapter() :
        ListAdapter<Crime, CrimeHolder>(mDiffCallback) {

        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): CrimeHolder {//这个parent就是RecyclerView;在这个方法中绑定子项视图
                    val view = layoutInflater.inflate(
                        R.layout.fragment_crime_list_item,
                        parent,
                        false
                    )//负责创建要显示的视图,并封装到ViewHolder里
                    return CrimeHolder(view)
                }

        override fun onBindViewHolder(
            holder: CrimeHolder,
            position: Int
        ) {//负责将数据集里指定位置的crime数据发送给指定的ViewHolder
            val crime = currentList[position]
            holder.bind(crime)//把viewholder和adapter的处理逻辑分开,只需调用viewholder的方法
        }

    }
  1. 构造函数里也不需要传一个list了,在onBindViewHolder里原来是list[position],现在可以用currentList。这个字段是ListAdapter里面的。之前我的一个bug就是这个B弄的。
  2. 继承自ListAdapter, <模型类, 自定义holder>, 参数需要一个DiffUtil.ItemCallback<Crime>()实例,可以在这个fragment里以伴随类的方式定义。
    companion object {
            val mDiffCallback = object : DiffUtil.ItemCallback<Crime>() {//作为ListAdapter的参数
                override fun areItemsTheSame(oldItem: Crime, newItem: Crime): Boolean {
                    return oldItem.id == newItem.id
                }
    
                override fun areContentsTheSame(oldItem: Crime, newItem: Crime): Boolean {
                    return oldItem.title == newItem.title
                }
            }
    

    必须实现这两个方法,第一个方法检测item是否相同,第二个检测item内容是否相同。这两个方法都很重要。第二个方法如果比较的item的属性不对,更新会有延迟!!比如说比较的是id,那我更改了title,我回到列表界面时,他判断我的id没变,所以内容没变,结果他不给我更新我操他妈的。上面那个写法也是不完整的,只比较了title,那我更改了其他信息,回到主界面也是不会立即更新的,会有一个延迟。

  3. 重头戏!!!那如何提交recycleview列表的更新呢?首先在fragment字段中定义一个adapter, 然后让recyclerview的adapter字段指向这个adapter, 然后因为传进去的列表是livedata,所以我们用observe检测数据变化,一有变化就用ListAdapter里的方法——submitList来提交,到这里就明白了,我们取消了原来以参数的方式传列表进去,改为submitList.(更删改查都用它,反正有变化就用它)
    private var mAdapter: CrimeAdapter = CrimeAdapter()
    
    override fun onCreateView(//onCreateview方法里不用做什么和本文相关的事,看下面
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            _binding = FragmentCrimeListBinding.inflate(inflater, container, false)
            binding.crimeRecyclerview.layoutManager =
                LinearLayoutManager(context)//把加载的recyclerview视图的布局设为线性布局
            return binding.root
        }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)        binding.crimeRecyclerview.adapter = mAdapter//
            crimeListViewModel.crimeListLiveData.observe(
                viewLifecycleOwner,
                { crimes ->
                    crimes?.let {
                        mAdapter.submitList(crimes)//有更新就提交给adapter
                    }
                }
            )
        }
    
  4. 注意!!传给submitList的列表不能是同一个列表引用,如果是同一个列表引用的话要拷贝一份,
    新拿到的数据根本没有更新到列表中来。问题可能出在 submitList() 方法上:
    
    public void submitList(@Nullable List<T> list) {
        mDiffer.submitList(list);
    }
    
    
    
    最终调用的是 AsyncListDiffer 类中的 submitList() 方法:
    
    public void submitList(@Nullable final List<T> newList,
            @Nullable final Runnable commitCallback) {
        // incrementing generation means any currently-running diffs are discarded when they finish
        final int runGeneration = ++mMaxScheduledGeneration;
        if (newList == mList) {
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }
        final List<T> previousList = mReadOnlyList;
        // fast simple remove all
        if (newList == null) {
            //noinspection ConstantConditions
            int countRemoved = mList.size();
            mList = null;
            mReadOnlyList = Collections.emptyList();
            // notify last, after list is updated
            mUpdateCallback.onRemoved(0, countRemoved);
            onCurrentListChanged(previousList, commitCallback);
            return;
        }
        // fast simple first insert
        if (mList == null) {
            mList = newList;
            mReadOnlyList = Collections.unmodifiableList(newList);
            // notify last, after list is updated
            mUpdateCallback.onInserted(0, newList.size());
            onCurrentListChanged(previousList, commitCallback);
            return;
        }
        final List<T> oldList = mList;
       // 省略一些代码。
    }
    
       
    
    第一次我们调用 submitList() 方法时,把成员变量集合 list 传递过来,那么 newList 参数就不为 null,在第 25 行,会进入 if 语句,把 newList 赋值给 mList,也就是把 list 赋值给 mList。
    
    第二次我们调用submitList() 方法时,仍是把成员变量集合 list 传递过来,这时 mList 就是指向的 list,所以第 5 行的 if (newList == mList) 成立,进而直接 return 掉 submitList() 方法。新的数据并未更新到列表中。
    

    因为livedata的observe传给我们的不是同一个列表引用,所以可以直接放进去submitList.

最后加上一份官方的示例代码

A complete usage pattern with Room would look like this:
 * <pre>
 * {@literal @}Dao
 * interface UserDao {
 *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
 *     public abstract LiveData&lt;List&lt;User>> usersByLastName();
 * }
 *
 * class MyViewModel extends ViewModel {
 *     public final LiveData&lt;List&lt;User>> usersList;
 *     public MyViewModel(UserDao userDao) {
 *         usersList = userDao.usersByLastName();
 *     }
 * }
 *
 * class MyActivity extends AppCompatActivity {
 *     {@literal @}Override
 *     public void onCreate(Bundle savedState) {
 *         super.onCreate(savedState);
 *         MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
 *         RecyclerView recyclerView = findViewById(R.id.user_list);
 *         UserAdapter&lt;User> adapter = new UserAdapter();
 *         viewModel.usersList.observe(this, list -> adapter.submitList(list));/直接用行了
 *         recyclerView.setAdapter(adapter);
 *     }
 * }
 *
 * class UserAdapter extends ListAdapter&lt;User, UserViewHolder> {
 *     public UserAdapter() {
 *         super(User.DIFF_CALLBACK);
 *     }
 *     {@literal @}Override
 *     public void onBindViewHolder(UserViewHolder holder, int position) {
 *         holder.bindTo(getItem(position));
 *     }
 *     public static final DiffUtil.ItemCallback&lt;User> DIFF_CALLBACK =
 *             new DiffUtil.ItemCallback&lt;User>() {
 *         {@literal @}Override
 *         public boolean areItemsTheSame(
 *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
 *             // User properties may have changed if reloaded from the DB, but ID is fixed
 *             return oldUser.getId() == newUser.getId();
 *         }
 *         {@literal @}Override
 *         public boolean areContentsTheSame(
 *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
 *             // NOTE: if you use equals, your object must properly override Object#equals()
 *             // Incorrectly returning false here will result in too many animations.
 *             return oldUser.equals(newUser);
 *         }
 *     }
 * }</pre>
### 回答1: 这个错误信息表示在 Android 应用程序中,你试图在一个空对象引用上调用 `ListView.setAdapter(ListAdapter)` 方法,从而导致了空指针异常。 出现这个错误的原因可能是在调用 `ListView.setAdapter(ListAdapter)` 方法之前,你没有正确地初始化或者找到对应的 `ListView` 对象。要解决这个问题,你需要检查代码中关于 `ListView` 对象的初始化部分,确保它们被正确地初始化并且不为 null。 你也可以在代码中加入一些调试语句,打印出 `ListView` 对象的引用值,以便更好地排查问题所在。 ### 回答2: 这个错误是由于在程序运行时,尝试对一个空对象引用进行虚拟方法调用,造成了程序崩溃的情况。具体来说,是在使用ListView控件时,没有先进行初始化或设置Adapter,而直接进行其他操作导致的错误。 解决这个问题需要检查代码中ListView的初始化和Adapter的设置,确保它们都不为空。如果ListView为空,需要先进行初始化,可以使用findViewById方法获取ListView控件的引用,然后再进行其他操作。如果Adapter为空,则需要先创建一个Adapter对象,并将其设置给ListView。同时,还需要注意在设置Adapter时,不能传入空对象,否则仍会出现空指针异常。 另外,为了避免出现这种错误,编写代码时应注意检查对象的是否为空,避免使用null引用进行操作。同时,对于一些关键的操作,也可以使用try-catch语句进行异常处理,以避免程序崩溃。最好的方法是在开发过程中,养成良好的编码习惯,以提高代码的质量和可靠性。 ### 回答3: 这个错误的意思是在尝试调用一个空对象的方法。一般来说,当我们用到“.”点操作符去调用某个对象的方法时,如果该对象为空,就会出现这个错误。 在 Android 中,一般会出现这个错误的情况是:我们在试图将一个空的 ListView 控件绑定到一个适配器(ListAdapter)上。这时候,我们必须要先实例化 ListView 对象,然后才能去绑定适配器。 解决这个问题的方法很简单,我们只需要在绑定适配器之前进行一次非空判断即可,代码可以写成这样: ListView listView = findViewById(R.id.list_view); if (listView != null) { listView.setAdapter(adapter); } 这样,就可以确保 listView 对象非空,避免出现空指针异常。 除此之外,我们还可以使用调试工具来帮助我们找出错误发生的位置和原因,并对代码进行优化。例如,我们可以使用 Logcat 来输出调试信息,还可以使用 Android Studio 自带的调试工具进行代码调试。这些工具都能够帮助我们更加有效地定位并解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值