easyrecyclerview 刷新加载功能代码分析(填坑之旅)

想选一个刷新加载 又可以添加各种header 的列表控件,挑来挑去也就easyrecyclerview 最好用了,
可是刷新加载 却也有bug

  • 1.刷新的时候不能加载,加载的时候不能刷新,解决刷新的时候不能加载(我的方案给个变量isRefreshing 刷新的时候为true
    加载回掉接口的时候,如果是true就不让他加载),解决加载的 时候不能刷新(弹出进度对话框)这两种解决方案比较恶心,需要
    写在界面的代码里,这是我不愿意看到的

  • 第二个bug,我在项目里算是bug,每次刷新数据的时候,都会自动去加载,每次进入界面不光调用刷新的接口,也会调用加载的
    接口。

  • 3.第三个缺陷,不能定制刷新的样式,和加载的样式,这个我后面讲怎么去改它
    EasyRecyclerView继承FrameLayout,

填充了下面的布局代码
<com.jude.easyrecyclerview.swipe.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ptr_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical|horizontal"
        android:clickable="true"/>
    <FrameLayout
        android:id="@+id/empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        />

    <FrameLayout
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        />

    <FrameLayout
        android:id="@+id/error"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        />

</com.jude.easyrecyclerview.swipe.SwipeRefreshLayout>
  • 所以你可以设置刚进入前面时候的布局id=progress
    数据为空的时候的布局 id = empty
    发生错误的时候布局 id = error
    这个控件的功能简直和它的名字一样感人easy
public void setAdapterWithProgress(RecyclerView.Adapter adapter) {
        mRecycler.setAdapter(adapter);
        adapter.registerAdapterDataObserver(new EasyDataObserver(this));
        //只有Adapter为空时才显示ProgressView
        if (adapter instanceof RecyclerArrayAdapter){
            if (((RecyclerArrayAdapter) adapter).getCount() == 0){
                showProgress();
            }else {
                showRecycler();
            }
        }else {
            if (adapter.getItemCount() == 0){
                showProgress();
            }else {
                showRecycler();
            }
        }
    }

如果数据为0的时候加载showProgress()方法
如果数据不是0 显示recyclerview

下面先介绍一下刷新的原理
刷新是通过 swiperefreshlayout,这个应该support v4包下面的一个控件
通过serRefresh方法设置它是否刷新和刷新完毕
我们通过在EasyRecyclerView类里面找到这个方法,基本就可以跟踪到它是怎么刷新的了,结果你会发现你只找到了一处调用setrefresh(false)的方法

 private void hideAll(){
        mEmptyView.setVisibility(View.GONE);
        mProgressView.setVisibility(View.GONE);
        mErrorView.setVisibility(GONE);
        mPtrLayout.setRefreshing(false);
        mRecycler.setVisibility(View.INVISIBLE);
    }

没有调用setrefresh(true)的方法 wtf,没关系继续跟踪这个方法的类swipefreshlayou
你会发现一个方法

public boolean dispatchTouchEvent(MotionEvent ev) {
        return mPtrLayout.dispatchTouchEvent(ev);
 }
 easyrecylerview的事件传递给了swiperefreshlayou了,所以推断setrefresh(true)
 应该是它自己根据滑动事件去处理的。
 我们接下来在swperefreshlayout里面去找setrefresh(true)的方法
 发现了被这个方法调用:
 private void finishSpinner(float overscrollTop) {
        if (overscrollTop > mTotalDragDistance) {
            setRefreshing(true, true /* notify */);
        } else {
            // cancel refresh
            mRefreshing = false;
            mProgress.setStartEndTrim(0f, 0f);
            AnimationListener listener = null;
            if (!mScale) {
                listener = new AnimationListener() {

                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (!mScale) {
                            startScaleDownAnimation(null);
                        }
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }

                };
            }
            animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
            mProgress.showArrow(false);
        }
    }

    跟踪finishSpinner的方法发现两处调用onTouchEvent 和onStopNestedScroll
    证明猜想正确,别问我为什么不分析这些事件代码,因为我还没有改它的需求,所以我就避开了这个麻烦。
    基本确定setrefresh(true)根据滑动事件主动触发

    下面我们去看看setrefresh(false)方法什么时候调用呢
     上面说了hidall 接着就去跟踪hideall方法,

    被这两个方法调用
      public void showProgress() {
        log("showProgress");
        if (mProgressView.getChildCount()>0){
            hideAll();
            mProgressView.setVisibility(View.VISIBLE);
        }else {
            showRecycler();
        }
    }


    public void showRecycler() {
        log("showRecycler");
        hideAll();
        mRecycler.setVisibility(View.VISIBLE);
    }

     public void showEmpty() {
        log("showEmpty");
        if (mEmptyView.getChildCount()>0){
            hideAll();
            mEmptyView.setVisibility(View.VISIBLE);
        }else {
            showRecycler();
        }
    }

 接着你会发现这两个方法会被setAdapterWithProgress这个方法 调用,这个是设置adapter的方法 ,怎么可能,我可是想要寻求停止刷新的
 方法,这个只会在初始化的时候调用一次啊,说明还有其他地方调用了,我们看看findUsages 找找。

你会发先easydataObserver类里面有一个update方法
 //自动更改Container的样式
    private void update() {
        int count;
        if (recyclerView.getAdapter() instanceof RecyclerArrayAdapter) {
            count = ((RecyclerArrayAdapter) recyclerView.getAdapter()).getCount();
        } else {
            count = recyclerView.getAdapter().getItemCount();
        }
        if (count == 0) {
            recyclerView.showEmpty();
        } else {
            recyclerView.showRecycler();
        }
    }

    class EasyDataObserver extends RecyclerView.AdapterDataObserver 
    这个类继承了 RecyclerView.AdapterDataObserver ,此时你需要弄清一个原理
    recyclerView 和listview一样数据填充都是通过adapter,adapter的实现通过观察者模式,数据更新的时候会发一个通知
    注册了这个通知的类就会收到数据更新的通知。

    我们跟踪EasyDataObserver 这个类看看,
     public void setAdapter(RecyclerView.Adapter adapter) {
        ...
        adapter.registerAdapterDataObserver(new EasyDataObserver(this));
        ...
    }

    /**
     * 设置适配器,关闭所有副view。展示进度条View
     * 适配器有更新,自动关闭所有副view。根据条数判断是否展示EmptyView
     *
     * @param adapter
     */
    public void setAdapterWithProgress(RecyclerView.Adapter adapter) {
        mRecycler.setAdapter(adapter);
        adapter.registerAdapterDataObserver(new EasyDataObserver(this));
        ...
    }

    看见了吧设置adapter的时候 会注册一个数据更新的监听器

    所以每次adapter 添加数据的时候都会通知EasyDataObserver,然后调用update
    再去调用showempty或者showrecycer方法 他们里面会有一个hideall方法 调用setrefres(false)去取消刷新
    我们看看RecyclerArrayAdapter 里面添加数据的方法  他们总会调用一个方法notifyItemInserted 去给EasyDataObserver
    发送通知
      public void add(T object) {
        ...
        if (mNotifyOnChange) notifyItemInserted(headers.size()+getCount());
        ...
    }

    public void addAll(Collection<? extends T> collection) {
        ...
        if (mNotifyOnChange) notifyItemRangeInserted(headers.size()+getCount()-dataCount,dataCount);
        ...

    }


    public void addAll(T[] items) {
        ....
        if (mNotifyOnChange) notifyItemRangeInserted(headers.size()+getCount()-dataCount,dataCount);
        ...
    }

    到这里刷新的 原理就搞定了,


    下面我介绍加载的原理
     在介绍之前我得说一个加载的时候的bug 之前提到的 bug2
     当你的数据条数没有填充全屏的时候,就会调用一次加载接口,第二次填充的数据没有填充全屏,继续调用加载的回调
     这个bug看你的需求,反正对我来说是很恶心的东西,

     加载是完全通过RecyclerArrayAdapter去控制的,所以加载刷新是两个相互独立的东西,这就造成了刷新的时候是可以加载的,
     加载的时候可以刷新的时候bug

     你要实现加载功能需要调用下面方法
     public void setMore(final int res, final OnLoadMoreListener listener){
        getEventDelegate().setMore(res, new OnMoreListener() {
            @Override
            public void onMoreShow() {
                listener.onLoadMore();
            }

            @Override
            public void onMoreClick() {

            }
        });
    }
    你会发先onLoadMoreListener 传递给了EventDelegate 一个代理的类
    我们在看一下getEventDelegate方法
     EventDelegate getEventDelegate(){
        if (mEventDelegate == null)mEventDelegate  = new DefaultEventDelegate(this);
        return mEventDelegate;
    }
    这个代理的类有个默认的实现,也可以自己去拓展实现它的接口,不过你必须按照它规定的原理去实现所以我们去找
    DefaultEventDelegate 的setMore方法

     @Override
    public void setMore(int res, RecyclerArrayAdapter.OnMoreListener listener) {
        this.footer.setMoreViewRes(res);
        this.onMoreListener = listener;
        hasMore = true;
        // 为了处理setMore之前就添加了数据的情况
        if (adapter.getCount()>0){
            addData(adapter.getCount());
        }
        log("setMore");
    }
    你会发先一个方法addData()
     @Override
    public void addData(int length) {
        log("addData" + length);
        if (hasMore){
            if (length == 0){
                //当添加0个时,认为已结束加载到底
                if (status==STATUS_INITIAL || status == STATUS_MORE){
                    footer.showNoMore();
                    status = STATUS_NOMORE;
                }
            }else {
                //当Error或初始时。添加数据,如果有More则还原。
                footer.showMore();
                status = STATUS_MORE;
                hasData = true;
            }
        }else{
            if (hasNoMore){
                footer.showNoMore();
                status = STATUS_NOMORE;
            }
        }
        isLoadingMore = false;
    }
    这个作者是根据每次填充数据的多少来判定加载状,如果你添加数据是0 则认为你加载完成了不需要去加载数据了
    添加数据个数>0 则认为还有数据就会自动去调用footer.showMore 方法
        public void showMore(){
            Log.d("addData", "showMore: count:"+adapter.getItemCount());
            flag = ShowMore;
            if (adapter.getItemCount()>0)
                adapter.notifyItemChanged(adapter.getItemCount()-1);
        }
        在这里会发送一个通知出去
        发送通知后就会调用DefaultEventDelegate 类的OnBindView方法 你跟踪onBindView方法 会发它在
        recyclerarrayAdapter   OnBindView方法里被调用
          public final void onBindViewHolder(BaseViewHolder holder, int position) {
            ...
           if (footers.size()!=0 && i>=0){
               footers.get(i).onBindView(holder.itemView);
               return ;
          }
          ....
        }
        也就是说每次发送通知会先调用 recyclerarrayAdapter 的onBindViewHolder方法,然后在调用DefaultEventDelegate
        onBindView方法 通过这个方法去设置状态 因为加载更多 就会调用onMoreViewShowed方法

          @Override
        public void onBindView(View headerView) {
            Log.d("addData", "onBindView: notifyed: flag"+flag);
            headerView.post(new Runnable() {
                @Override
                public void run() {
                    switch (flag){
                        case ShowMore:
                            onMoreViewShowed();//
                            break;
                        case ShowNoMore:
                            if (!skipNoMore)onNoMoreViewShowed();skipNoMore = false;
                            break;
                        case ShowError:
                            if (!skipError) onErrorViewShowed();skipError = false;
                            break;
                    }
                }
            });
        }

         onMoreViewShowed 就会调用 onMoreListener.onMoreShow();
         public void onMoreViewShowed() {
            log("onMoreViewShowed");
            if (!isLoadingMore&& onMoreListener !=null){
                isLoadingMore = true;
                onMoreListener.onMoreShow();
            }
        }

        onMoreListener是什么东西呢?
        RecyclerArrayAdapter.OnMoreListener onMoreListener;
       也就是RecyclerArrayAdapter  类里面的 public void setMore(int res, RecyclerArrayAdapter.OnMoreListener listener) {


        至此我们来分析一下刚才的bug,每次RecyclerArrayAdapter 调用add方法的 时候,
           public void addAll(Collection<? extends T> collection) {
                if (mEventDelegate!=null)mEventDelegate.addData(collection == null ? 0 : collection.size());
                ....

            }
            就会调用mEventDelegate.addData方法
            addData方法 根据你添加的个数来判段加载的状态是0的时候则没有数据,
            如果不是0的时候 就会调用加载更多的回调方法,footer.showMore
          public void showMore(){
            Log.d("addData", "showMore: count:"+adapter.getItemCount());
            flag = ShowMore;
            if (adapter.getItemCount()>0)
                adapter.notifyItemChanged(adapter.getItemCount()-1);
          }

          这个方法会发送通知,调用了OnBindView方法继而去回调onLoadMore方法让你调分页的接口
          你会不会发先一个问题,第一次刷新数据data>0 就会调加载的接口,data>0 继续调用接口,只要有数据就会不断的
          调加载的接口,这个我测试了,不会发生的,加载的数据没有超出屏幕就会调用加载接口,超出屏幕后就不会调用了,
          也就是说你每次翻页的时候,就会自动调用加载数据的接口,所以每次调用刷新接口,数据少的话,没有超出屏幕,就会
          继续调用加载接口,直到数据超出屏幕。


          如果你想实现第一次进去的时候,不要调用加载更多,你就改代码 addData(int length,boolean isLoading)
          自己手动去控制它什么时候加载更多,什么时候不去加载

          还有这个控件不能拓展刷新的界面和加载界面你可以使用superswiperefreshlayout 把swiperefreshlayout
          给换掉哦

          这个控件尽管不是很完美,但是毕竟比较好用,所以我宁可去改也不愿意去换它。
          只要搞懂它的原理,一切都不是事情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要使用 EasyRecyclerView,你需要按照以下步骤进行: 1. 在你的项目中引入 EasyRecyclerView 库。可以通过在项目的 build.gradle 文件中添加以下依赖来引入: ```groovy implementation 'com.jude:easyrecyclerview:4.5.3' ``` 2. 在你的布局文件中添加 EasyRecyclerView 控件。例如,在一个 activity_main.xml 文件中添加以下代码: ```xml <com.jude.easyrecyclerview.EasyRecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 在你的 Java 代码中进行初始化和配置 EasyRecyclerView。例如,在 MainActivity.java 文件中添加以下代码: ```java import com.jude.easyrecyclerview.EasyRecyclerView; import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; public class MainActivity extends AppCompatActivity { private EasyRecyclerView recyclerView; private MyAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new MyAdapter(this); recyclerView.setAdapter(adapter); // 设置下拉刷新和上拉加载更多的回调 adapter.setMore(R.layout.view_more, new RecyclerArrayAdapter.OnMoreListener() { @Override public void onMoreShow() { // 加载更多数据 } @Override public void onMoreClick() { // 点击加载更多的操作 } }); adapter.setNoMore(R.layout.view_no_more); // 设置下拉刷新的回调 recyclerView.setRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // 刷新数据 } }); } } ``` 4. 创建并配置你自己的适配器(MyAdapter)。EasyRecyclerView 需要一个适配器来提供数据和管理列表项的视图。你可以继承 RecyclerArrayAdapter 来实现自定义的适配器。 ```java import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; public class MyAdapter extends RecyclerArrayAdapter<String> { public MyAdapter(Context context) { super(context); } @Override public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { // 创建并返回自定义的 ViewHolder return new MyViewHolder(parent); } // 自定义 ViewHolder private class MyViewHolder extends BaseViewHolder<String> { private TextView textView; public MyViewHolder(ViewGroup parent) { super(parent, R.layout.item_layout); textView = $(R.id.textView); } @Override public void setData(String data) { // 设置列表项的数据 textView.setText(data); } } } ``` 以上是一个简单的 EasyRecyclerView 的使用示例。你可以根据你的需求进一步定制和配置 EasyRecyclerView,例如添加头部和尾部视图、设置分割线和装饰等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值