cekiasoo's blog Android Coder

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

2018-08-24
cekiasoo

阅读:


一、前言

最近在做一款应用,因为 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 {
    @NonNull
    private 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} */
    @Override
    public void onInserted(int position, int count) {
        mAdapter.notifyItemRangeInserted(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    /** {@inheritDoc} */
    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    /** {@inheritDoc} */
    @Override
    public 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 frozen
    setLayoutFrozen(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 frozen
    setLayoutFrozen(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() {
    }
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
        processDataSetCompletelyChanged(true);
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public 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;
    }
    @Override
    public void onChanged() {
        adapterDataObserver.onChanged();
    }
    @Override
    public void onItemRangeChanged(int positionStart, int itemCount) {
        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount);
    }
    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload);
    }
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount);
    }
    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount);
    }
    @Override
    public 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。


Similar Posts

Comments