Android RecyclerView + Paging Library 添加头部刷新会自动滚动的问题分析及解决

本文主要是介绍Android RecyclerView + Paging Library 添加头部刷新会自动滚动的问题分析及解决,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、前言
最近在做一款应用,因为 api 涉及到分页的,所以选择用 RecyclerView + Paging 去做,能省去向下滚动时去处理加载下一页的数据的逻辑,Paging 会自动去加载下一页的内容,首页要做个 Banner, 所以把 Banner 作为 RecyclerView 的 头添加进去,可是添加后下面的数据加载完就会自动滚到下面去,如下图,一下拉刷新,加载完数据后就滚到下面去了

二、问题分析

当我去掉头部刷新,一切是正常的,

所以原因应该就是添加了 Header,导致 position 不准确, RecyclerView 不知道有 Header 的存在,一刷新插入、删除等操作是从 0 开始的,但 Header 在前面霸占着位置了(陈独秀同学在前面站着不坐下),所以插入、删除等操作不要动到 Header 的位置,使其操作正确的 position, 那怎么让其不去动到 Heasder 的位置呢?只有看看源码能不能找到解决的办法了, RecyclerView + Paging Library 用的 Adapter 用的是继承 PagedListAdapter 的,

PagedListAdapter 是使用了 DiffUtil 的,DiffUtil 里的插入、删除等操作是用到 AdapterListUpdateCallback 这个类

AdapterListUpdateCallback 这个类里插入、删除等操作调用的是 RecyclerView.Adapter mAdapter 的,

/*** ListUpdateCallback that dispatches update events to the given adapter.** @see DiffUtil.DiffResult#dispatchUpdatesTo(RecyclerView.Adapter)*/
public final class AdapterListUpdateCallback implements ListUpdateCallback {@NonNullprivate final RecyclerView.Adapter mAdapter;/*** Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter.** @param adapter The Adapter to send updates to.*/public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {mAdapter = adapter;}/** {@inheritDoc} */@Overridepublic void onInserted(int position, int count) {mAdapter.notifyItemRangeInserted(position, count);}/** {@inheritDoc} */@Overridepublic void onRemoved(int position, int count) {mAdapter.notifyItemRangeRemoved(position, count);}/** {@inheritDoc} */@Overridepublic void onMoved(int fromPosition, int toPosition) {mAdapter.notifyItemMoved(fromPosition, toPosition);}/** {@inheritDoc} */@Overridepublic void onChanged(int position, int count, Object payload) {mAdapter.notifyItemRangeChanged(position, count, payload);}
}



RecyclerView.Adapter 里用的是 mObservable 的插入、删除等操作,

这个 mObservable 是 AdapterDataObservable 类型的,

AdapterDataObservable 是继承 Observable 的

static class AdapterDataObservable extends Observable<AdapterDataObserver> {public boolean hasObservers() {return !mObservers.isEmpty();}public void notifyChanged() {// since onChanged() is implemented by the app, it could do anything, including// removing itself from {@link mObservers} - and that could cause problems if// an iterator is used on the ArrayList {@link mObservers}.// to avoid such problems, just march thru the list in the reverse order.for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onChanged();}}public void notifyItemRangeChanged(int positionStart, int itemCount) {notifyItemRangeChanged(positionStart, itemCount, null);}public void notifyItemRangeChanged(int positionStart, int itemCount,@Nullable Object payload) {// since onItemRangeChanged() is implemented by the app, it could do anything, including// removing itself from {@link mObservers} - and that could cause problems if// an iterator is used on the ArrayList {@link mObservers}.// to avoid such problems, just march thru the list in the reverse order.for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);}}public void notifyItemRangeInserted(int positionStart, int itemCount) {// since onItemRangeInserted() is implemented by the app, it could do anything,// including removing itself from {@link mObservers} - and that could cause problems if// an iterator is used on the ArrayList {@link mObservers}.// to avoid such problems, just march thru the list in the reverse order.for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onItemRangeInserted(positionStart, itemCount);}}public void notifyItemRangeRemoved(int positionStart, int itemCount) {// since onItemRangeRemoved() is implemented by the app, it could do anything, including// removing itself from {@link mObservers} - and that could cause problems if// an iterator is used on the ArrayList {@link mObservers}.// to avoid such problems, just march thru the list in the reverse order.for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);}}public void notifyItemMoved(int fromPosition, int toPosition) {for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);}}
}

而 Observable 是个典型的观察者模式被观察者的写法

/*** Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}.** This abstract class is intended to be subclassed and specialized to maintain* a registry of observers of specific types and dispatch notifications to them.** @param T The observer type.*/
public abstract class Observable<T> {/*** The list of observers.  An observer can be in the list at most* once and will never be null.*/protected final ArrayList<T> mObservers = new ArrayList<T>();/*** Adds an observer to the list. The observer cannot be null and it must not already* be registered.* @param observer the observer to register* @throws IllegalArgumentException the observer is null* @throws IllegalStateException the observer is already registered*/public void registerObserver(T observer) {if (observer == null) {throw new IllegalArgumentException("The observer is null.");}synchronized(mObservers) {if (mObservers.contains(observer)) {throw new IllegalStateException("Observer " + observer + " is already registered.");}mObservers.add(observer);}}/*** Removes a previously registered observer. The observer must not be null and it* must already have been registered.* @param observer the observer to unregister* @throws IllegalArgumentException the observer is null* @throws IllegalStateException the observer is not yet registered*/public void unregisterObserver(T observer) {if (observer == null) {throw new IllegalArgumentException("The observer is null.");}synchronized(mObservers) {int index = mObservers.indexOf(observer);if (index == -1) {throw new IllegalStateException("Observer " + observer + " was not registered.");}mObservers.remove(index);}}/*** Remove all registered observers.*/public void unregisterAll() {synchronized(mObservers) {mObservers.clear();}}
}

那么是谁及什么时候注册到这个 mObservers 呢?什么时候就是在 RecyclerView setAdapter 或 swapAdapter 的时候

/*** Swaps the current adapter with the provided one. It is similar to* {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same* {@link ViewHolder} and does not clear the RecycledViewPool.* <p>* Note that it still calls onAdapterChanged callbacks.** @param adapter The new adapter to set, or null to set no adapter.* @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing*                                      Views. If adapters have stable ids and/or you want to*                                      animate the disappearing views, you may prefer to set*                                      this to false.* @see #setAdapter(Adapter)*/
public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {// bail out if layout is frozensetLayoutFrozen(false);setAdapterInternal(adapter, true, removeAndRecycleExistingViews);processDataSetCompletelyChanged(true);requestLayout();
}/*** Set a new adapter to provide child views on demand.* <p>* When adapter is changed, all existing views are recycled back to the pool. If the pool has* only one adapter, it will be cleared.** @param adapter The new adapter to set, or null to set no adapter.* @see #swapAdapter(Adapter, boolean)*/
public void setAdapter(Adapter adapter) {// bail out if layout is frozensetLayoutFrozen(false);setAdapterInternal(adapter, false, true);processDataSetCompletelyChanged(false);requestLayout();
}/*** Replaces the current adapter with the new one and triggers listeners.* @param adapter The new adapter* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and*                               item types with the current adapter (helps us avoid cache*                               invalidation).* @param removeAndRecycleViews  If true, we'll remove and recycle all existing views. If*                               compatibleWithPrevious is false, this parameter is ignored.*/
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,boolean removeAndRecycleViews) {if (mAdapter != null) {mAdapter.unregisterAdapterDataObserver(mObserver);mAdapter.onDetachedFromRecyclerView(this);}if (!compatibleWithPrevious || removeAndRecycleViews) {removeAndRecycleViews();}mAdapterHelper.reset();final Adapter oldAdapter = mAdapter;mAdapter = adapter;if (adapter != null) {adapter.registerAdapterDataObserver(mObserver);adapter.onAttachedToRecyclerView(this);}if (mLayout != null) {mLayout.onAdapterChanged(oldAdapter, mAdapter);}mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);mState.mStructureChanged = true;
}


Adapter 的 registerAdapterDataObserver 方法

/*** Register a new observer to listen for data changes.** <p>The adapter may publish a variety of events describing specific changes.* Not all adapters may support all change types and some may fall back to a generic* {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged()* "something changed"} event if more specific data is not available.</p>** <p>Components registering observers with an adapter are responsible for* {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)* unregistering} those observers when finished.</p>** @param observer Observer to register** @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)*/
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {mObservable.registerObserver(observer);
}

那 adapter.registerAdapterDataObserver(mObserver) 这句中的 mObserver 是谁?

原来 mObserver 是 RecyclerViewDataObserver 类型的

private class RecyclerViewDataObserver extends AdapterDataObserver {RecyclerViewDataObserver() {}@Overridepublic void onChanged() {assertNotInLayoutOrScroll(null);mState.mStructureChanged = true;processDataSetCompletelyChanged(true);if (!mAdapterHelper.hasPendingUpdates()) {requestLayout();}}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount, Object payload) {assertNotInLayoutOrScroll(null);if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeInserted(int positionStart, int itemCount) {assertNotInLayoutOrScroll(null);if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeRemoved(int positionStart, int itemCount) {assertNotInLayoutOrScroll(null);if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {triggerUpdateProcessor();}}@Overridepublic void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {assertNotInLayoutOrScroll(null);if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {triggerUpdateProcessor();}}void triggerUpdateProcessor() {if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);} else {mAdapterUpdateDuringMeasure = true;requestLayout();}}
}

RecyclerViewDataObserver 是继承 AdapterDataObserver 的

/*** Observer base class for watching changes to an {@link Adapter}.* See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.*/
public abstract static class AdapterDataObserver {public void onChanged() {// Do nothing}public void onItemRangeChanged(int positionStart, int itemCount) {// do nothing}public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {// fallback to onItemRangeChanged(positionStart, itemCount) if app// does not override this method.onItemRangeChanged(positionStart, itemCount);}public void onItemRangeInserted(int positionStart, int itemCount) {// do nothing}public void onItemRangeRemoved(int positionStart, int itemCount) {// do nothing}public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {// do nothing}
}

RecyclerViewDataObserver 里用的是 mAdapterHelper 的插入、删除等操作,mAdapterHelper 是 AdapterHelper 类型的

AdapterHelper 是包访问权限的

突破口在 Adapter 的 registerAdapterDataObserver 方法,这个方法是 public 的且不是 final 的,我们可以在自己的 Adapter 覆盖 registerAdapterDataObserver 这个方法,由于 RecyclerViewDataObserver 这个类是 private 的,但 AdapterDataObserver 是 public 的,而且 registerAdapterDataObserver 要的是 AdapterDataObserver 类型的参数,我们可以写个类继承 AdapterDataObserver, 把传进 registerAdapterDataObserver 的 observer 参数和 Header 的个数传进去,在对目标 AdapterDataObserver 操作之前进行偷梁换柱,把 Header 的个数加进去,分析到这里问题就已经可以解决了。

三、问题解决
写个 AdapterDataObserverProxy 类继承 RecyclerView.AdapterDataObserver

class AdapterDataObserverProxy extends RecyclerView.AdapterDataObserver {RecyclerView.AdapterDataObserver adapterDataObserver;int headerCount;public ArticleDataObserver(RecyclerView.AdapterDataObserver adapterDataObserver, int headerCount) {this.adapterDataObserver = adapterDataObserver;this.headerCount = headerCount;}@Overridepublic void onChanged() {adapterDataObserver.onChanged();}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount) {adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount);}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload);}@Overridepublic void onItemRangeInserted(int positionStart, int itemCount) {adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount);}@Overridepublic void onItemRangeRemoved(int positionStart, int itemCount) {adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount);}@Overridepublic void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount);}
}

因为 registerAdapterDataObserver 是针对 AdapterDataObserver 进行处理的,所以这里用 AdapterDataObserverProxy 去做代理,对真正的 AdapterDataObserver 操作前把 Header 的个数加进去,所以覆盖后的 registerAdapterDataObserver 就可以这样写,

@Override
public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {super.registerAdapterDataObserver(new AdapterDataObserverProxy(observer, getHeaderCount()));
}

getHeaderCount() 是 Header 的个数,具体怎么写就看自己的情况了,当然覆盖后的 registerAdapterDataObserver 这样写的话就要在 RecyclerView setAdapter 之前先把 Header 添加到 Adapter 去,毕竟 registerAdapterDataObserver 是在 setAdapter 里调用的。

四、后记
RecyclerView + Paging Library 用于做分页虽然好,但还是有很多坑的,现在还有坑没填,用了 RecyclerView + Paging Library 想做个添加 item 和删除 item 的功能还没有头绪,PagedList 虽然继承 AbstractList,但 add 和 remove 方法没有去实现,调用会出现 UnsupportedOperationException,脑阔疼。
 

这篇关于Android RecyclerView + Paging Library 添加头部刷新会自动滚动的问题分析及解决的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/754987

相关文章

一文详解MySQL如何设置自动备份任务

《一文详解MySQL如何设置自动备份任务》设置自动备份任务可以确保你的数据库定期备份,防止数据丢失,下面我们就来详细介绍一下如何使用Bash脚本和Cron任务在Linux系统上设置MySQL数据库的自... 目录1. 编写备份脚本1.1 创建并编辑备份脚本1.2 给予脚本执行权限2. 设置 Cron 任务2

qt5cored.dll报错怎么解决? 电脑qt5cored.dll文件丢失修复技巧

《qt5cored.dll报错怎么解决?电脑qt5cored.dll文件丢失修复技巧》在进行软件安装或运行程序时,有时会遇到由于找不到qt5core.dll,无法继续执行代码,这个问题可能是由于该文... 遇到qt5cored.dll文件错误时,可能会导致基于 Qt 开发的应用程序无法正常运行或启动。这种错

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

MySQL 设置AUTO_INCREMENT 无效的问题解决

《MySQL设置AUTO_INCREMENT无效的问题解决》本文主要介绍了MySQL设置AUTO_INCREMENT无效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录快速设置mysql的auto_increment参数一、修改 AUTO_INCREMENT 的值。

关于跨域无效的问题及解决(java后端方案)

《关于跨域无效的问题及解决(java后端方案)》:本文主要介绍关于跨域无效的问题及解决(java后端方案),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录通用后端跨域方法1、@CrossOrigin 注解2、springboot2.0 实现WebMvcConfig

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

Java死锁问题解决方案及示例详解

《Java死锁问题解决方案及示例详解》死锁是指两个或多个线程因争夺资源而相互等待,导致所有线程都无法继续执行的一种状态,本文给大家详细介绍了Java死锁问题解决方案详解及实践样例,需要的朋友可以参考下... 目录1、简述死锁的四个必要条件:2、死锁示例代码3、如何检测死锁?3.1 使用 jstack3.2