RecycleView配合SwipeRefreshLayout实现轻量级上拉刷新下拉加载,外加牛X的Adapter

简介: 看过我这边文章的RecyclerView.Adapter的优化与封装的真的想说声抱歉,其实不需要继承BaseBean,只需要继承Object就可以了,而且更灵活了,当时不知道咋了,脑袋抽风了,多次了这一举。好了,今天来一个更牛逼的神器RefreshLayout效果图:实现了上拉刷新和下拉加载功能封装好的BaseAdapter和BaseViewHolder

看过我这边文章的RecyclerView.Adapter的优化与封装的真的想说声抱歉,其实不需要继承BaseBean,只需要继承Object就可以了,而且更灵活了,当时不知道咋了,脑袋抽风了,多次了这一举。


好了,今天来一个更牛逼的神器RefreshLayout

效果图:

这里写图片描述

  • 实现了上拉刷新和下拉加载功能
  • 封装好的BaseAdapter和BaseViewHolder,将adapter交给Activity来管理
  • 实现了单击事件和长单击事件

类结构图:

这里写图片描述


这个demo我把之前的BaseAdapter和BaseViewHolder的封装都重新整理了一边,然后把BaseAdapter抽象给Activity来管理,因为我发现我在之前的博文中封装的BaseAdapter,然后让其他的adapter去继承他去操作,这个adapter根本就不需要多少的操作,而且有点冗余,好多的adapter,烦,所以我打算把BaseAdapter的管理交给Activity去实现,这样,整个项目只需要一个BaseAdapter和BaseViewHolder去操作就行了,真的是万能的适配器啊,项目瞬间清爽了起来。好了,今天先来说说实现思路:


因为下拉加载SwipeRefreshLayout大家都知道的,官方的,没什么好讲,但是在处理上拉加载的时候这个下拉在后面还需要去处理,所以,先来讲上拉加载更多。

上拉加载更多需要哪些实现呢?

  • 计算所有的RecycleView的item数量(即layoutManager.getItemCount())
  • 获取当前页面底部可见的item的position位置(即LayoutManager.findLastVisibleItemPosition())
  • 判断这个可见的position是否大于等于RecycleView的item数量减1(因为position是从0开始的)
  • 我们还需要一个getChildCount辅助方法,获取当前整个屏幕显示的item数量,目的是判断有无item,来处理item不满一屏幕的时候处理。
  • 当数据不满一屏幕的时候,SwipeRefreshLayout下拉的时候会触发我在onScrollStateChanged里面做的判断,然而这个判断是满足上拉加载更多的,会出现上拉加载刷新和下拉加载更多同时调用,然后看见数据突然的闪一下恢复到原来的数据,为了解决数据不满一屏的操作的bug,我在onTouch里面做了一个手势的处理,如果是向下拉,则给上拉加载更多的判断设置个flag为false,不让他去处理,亲测完美解决。

上代码,来看看RefreshRecycleView.java

/**
 * Created by wangqi on 2016/11/8.
 */
public class RefreshRecycleView extends RecyclerView {
    public RefreshRecycleView(Context context) {
        super(context);
        initView(context);
    }

    public RefreshRecycleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public RefreshRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }

    private ProgressDialog progressDialog;

    private void initView(Context context) {
        progressDialog = new ProgressDialog(context);
        progressDialog.setMessage("加载更多...");
    }


    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        //获取在各个不同的LayoutManager中可见的position位置
        if (state == RecyclerView.SCROLL_STATE_IDLE && loadMoreListner != null && loadingMoreEnabled) {
            LayoutManager layoutManager = getLayoutManager();
            int lastVisibleItemPosition;
            if (layoutManager instanceof GridLayoutManager) {
                lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
                ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
                lastVisibleItemPosition = findMax(into);
            } else {
                lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
            }
            //触发加载更多事件
            if (flag && loadingMoreEnabled && layoutManager.getChildCount() > 0 && lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() >= layoutManager.getChildCount()) {
                progressDialog.show();
                loadMoreListner.onLoad();
            }
        }
    }

    public boolean flag;


    float startY = 0;
    float moveY = 0;

    /**
     *
     * @param e
     * @return  处理数据不满一屏的手势判断
     */
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = e.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = e.getRawY();
                if (moveY > startY)
                    flag = false;
                else
                    flag = true;
                startY = moveY;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onTouchEvent(e);
    }

    public void setLoadMoreComplete() {
        progressDialog.dismiss();
    }

    //设置不给上拉加载
    public void setLoadingMoreEnabled(boolean loadingMoreEnabled) {
        this.loadingMoreEnabled = loadingMoreEnabled;
    }

    private int findMax(int[] lastPositions) {
        int max = lastPositions[0];
        for (int value : lastPositions) {
            if (value > max) {
                max = value;
            }
        }
        return max;
    }


    boolean loadingMoreEnabled = true;
    LoadMoreListner loadMoreListner;

    public void setLoadMoreListner(LoadMoreListner loadMoreListner) {
        this.loadMoreListner = loadMoreListner;
    }

    public interface LoadMoreListner {
        void onLoad();
    }

}

代码特别简单,

  • 在滑动事件里面去判断当前上否是滑动状态,然后判断上拉加载更多是否可用,当前的回调是否已经注册。
  • 获取RecycleView在不同LayoutManager的时候返回的底部可见的position的位置
  • layoutManager.getChildCount()是获取当前可见界面的item数量
  • lastVisibleItemPosition获取item可见的最底部的position位置
  • layoutManager.getItemCount()是获取整个RecycleView的item的总数
  • 然后对当前进行判断,
  • falg是处理onTouch返回的数据不满一屏幕的判断,
  • 其他的判断大家看看就懂的,不难
  • 然后显示dialog,回调load接口,给别人去处理

好了,这时候来看看包装RefreshRecycleView的类RefreshLayout.java了

看看xml就懂了

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/refresh_swip"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.e8net.myapplication.RefreshRecycleView
        android:id="@+id/refresh_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

RefreshLayout只是对RecycleView的一种包装,专门处理上拉刷新和下拉加载更多,将所有事件的处理通过这个辅助类传递给Activity的调用者,外者只需要调用,不需要管理实现原理,简洁了代码。来看看代码

/**
 * Created by wangqi on 2016/11/8.
 */
public class RefreshLayout extends LinearLayout {

    RefreshRecycleView refreshRecycleView;
    SwipeRefreshLayout swipeRefreshLayout;

    public RefreshLayout(Context context) {
        super(context);
        initView(context);
    }


    public RefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }


    private void initView(final Context context) {
        View v = LayoutInflater.from(context).inflate(R.layout.refresh_view, this, false);
        refreshRecycleView = (RefreshRecycleView) v.findViewById(R.id.refresh_recycler);
        swipeRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.refresh_swip);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        this.addView(v, params);

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                onRefreshListner.refresh();
            }
        });

        //加载更多
        refreshRecycleView.setLoadMoreListner(new RefreshRecycleView.LoadMoreListner() {
            @Override
            public void onLoad() {
                onRefreshListner.loadMore();
            }
        });
    }

    public void setLoadingMoreEnabled(boolean flag) {
        refreshRecycleView.setLoadingMoreEnabled(flag);
    }

    public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
        refreshRecycleView.setLayoutManager(layoutManager);
    }

    public void setAdapter(RecyclerView.Adapter<BaseViewHolder> adapter) {
        refreshRecycleView.setAdapter(adapter);
    }


    public void setRefreshComplete() {
        swipeRefreshLayout.setRefreshing(false);
    }

    public void setLoadMoreComplete() {
        refreshRecycleView.setLoadMoreComplete();
    }

    private RefreshListner onRefreshListner;

    public void setOnRefreshListner(RefreshListner onRefreshListner) {
        this.onRefreshListner = onRefreshListner;
    }


    public interface RefreshListner {
        void refresh();

        void loadMore();
    }
}
  • 初始化xml,将view界面inflater到RefreshLayout
  • 给下拉刷新和上拉加载进行注册
  • 然后设置下拉刷新完成和上拉加载完成,这个大家应该都懂的,全是包装
  • 然后注册个接口,将下拉和上拉的触发交个回调者来处理

这时候包装都结束了,最后来看看Activity来调用看看

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        refreshLayout = (RefreshLayout) findViewById(R.id.refresh_layout);
        list = new ArrayList<>();
        addList();
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

        refreshLayout.setLayoutManager(layoutManager);


        myRecycleAdapter = new BaseRecycleAdapter(this, R.layout.recycle_item, list);


        refreshLayout.setAdapter(myRecycleAdapter);

        refreshLayout.setOnRefreshListner(new RefreshLayout.RefreshListner() {
            @Override
            public void refresh() {
                refreshLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        list.clear();
                        addList();
                        myRecycleAdapter.notifyDataSetChanged();
                        refreshLayout.setRefreshComplete();
                    }
                }, 2000);
            }

            @Override
            public void loadMore() {
                refreshLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        addList();
                        myRecycleAdapter.notifyDataSetChanged();
                        refreshLayout.setLoadMoreComplete();
                    }
                }, 2000);
            }
        });
      }
         public void addList() {
        for (int i = 0; i < 10; i++)
            list.add(""); }

下拉和上拉就这么搞定了,哈哈,不对不对,你这个adapter还没讲呢,对吼,那赶紧的跟上步伐


说说我这个牛X的adapter,所有项目的RecycleView都只需要这一个adapter就可以搞定了,如果item需要复杂的处理,可以拓展BaseViewHolder方法,主要思路就是,将数据交给adapter,adapter不做处理,只是对数据的size进行排版item,数据的设置回调给Activity去实现,Activity拿到数据去BaseViewHolder里面去设置界面显示数据,adapter里面实现了单击和长单击事件,和我之前那篇博文一样的思路,那来看看代码吧

BaseRecycleAdapter.java

/**
 * Created by wangqi on 2016/7/16.
 */
public class BaseRecycleAdapter extends RecyclerView.Adapter<BaseViewHolder> {
    private int layoutId;
    private List<? extends Object> data;
    public Context context;
    private OnItemClickListner onItemClickListner;//单击事件
    private OnItemLongClickListner onItemLongClickListner;//长按单击事件
    private boolean clickFlag = true;//单击事件和长单击事件的屏蔽标识


    /**
     * @param context  //上下文
     * @param layoutId //布局id
     * @param data     //数据源
     */
    public BaseRecycleAdapter(Context context, int layoutId, List<? extends Object> data) {
        this.layoutId = layoutId;
        this.data = data;
        this.context = context;
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(context).inflate(layoutId, parent, false);
        final BaseViewHolder holder = new BaseViewHolder(v, context);
        //单击事件回调
        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onItemClickListner == null)
                    return;
                if (clickFlag) {
                    onItemClickListner.onItemClickListner(v, holder.getLayoutPosition());
                }
                clickFlag = true;
            }
        });
        //单击长按事件回调
        v.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (onItemLongClickListner == null)
                    return false;
                onItemLongClickListner.onItemLongClickListner(v, holder.getLayoutPosition());
                clickFlag = false;
                return false;
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        if (mCallBack != null)
            mCallBack.convert(holder, data.get(position), position);

    }


    @Override
    public int getItemCount() {
        return data.size();
    }

    public void setOnItemClickListner(OnItemClickListner onItemClickListner) {
        this.onItemClickListner = onItemClickListner;
    }

    public void setOnItemLongClickListner(OnItemLongClickListner onItemLongClickListner) {
        this.onItemLongClickListner = onItemLongClickListner;
    }

    public interface OnItemClickListner {
        void onItemClickListner(View v, int position);
    }

    public interface OnItemLongClickListner {
        void onItemLongClickListner(View v, int position);
    }


    CallBack mCallBack;

    public void setCallBack(CallBack CallBack) {
        this.mCallBack = CallBack;
    }

    public interface CallBack {
        <T extends Object> void convert(BaseViewHolder holder, T bean, int position);
    }

}

这一次adapter的修改,主要是对当初的抽象onBindViewHolder方法变成了接口回调,当初需要继承这个Base,然后实现这个convert方法来对数据进行操作,但这个时候感觉继承这个adapter的使用者,完全不需要那么多的操作,只是对数据操作了下,大功能基本上是在BaseViewHolder处理,这一次整改,将这个设置器交给Activity来处理,还有就是数据的泛型,上一遍博文让所有的Model都继承BaseBean,这个真是多此一举啊,哎,失败,这里我给改成Object,方便传递,其他的和我上篇博文一样,可以看看,文章开头给了链接地址。

然后来看看BaseViewHolder.java

/**
 * Created by wangqi on 2016/7/16.
 */
public class BaseViewHolder extends RecyclerView.ViewHolder {
    View convertView;
    Context context;

    public BaseViewHolder(View itemView, Context context) {
        super(itemView);
        this.convertView = itemView;
        this.context = context;
    }

    public View getItemView() {
        return convertView;
    }

    public void setText(int id, String text) {
        TextView tx = (TextView) convertView.findViewById(id);
        tx.setText(text);
    }

    public void setText(int id, String text, final OnClickListener onClickListener) {
        TextView tx = (TextView) convertView.findViewById(id);
        tx.setText(text);
        tx.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.onClickListner(v);
            }
        });
    }


    public void setImageListner(int id, final OnClickListener onClickListener) {
        ImageView img = (ImageView) convertView.findViewById(id);
        img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.onClickListner(v);
            }
        });

    }

    public void setImageResource(int id, int resouceId) {
        ImageView img = (ImageView) convertView.findViewById(id);
        img.setImageResource(resouceId);
    }

    public void setImageResource(int id, int resouceId, final OnClickListener onClickListener) {
        ImageView img = (ImageView) convertView.findViewById(id);
        img.setImageResource(resouceId);
        img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onClickListener.onClickListner(v);
            }
        });
    }


    public interface OnClickListener {
        void onClickListner(View v);
    }

}

这里面的方法是需要开发者根据需求自己去定制添加方法的,我这里只是列举了一些常见的,大家可以自己扩展,大家可能看到我给的回调,如果你当前界面不需要设置监听,你完全可以直接调用设置数据源的方法,如果你需要item的子View触发事件,也可以调用有回调接口的方法,这个回调也是交给Activity去处理

来看看完整的MainActivity的代码

public class MainActivity extends AppCompatActivity {
    private RefreshLayout refreshLayout;
    List<String> list;
    BaseRecycleAdapter myRecycleAdapter;


    public void addList() {
        for (int i = 0; i < 10; i++)
            list.add("");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        refreshLayout = (RefreshLayout) findViewById(R.id.refresh_layout);
        list = new ArrayList<>();
        addList();
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

        refreshLayout.setLayoutManager(layoutManager);


        myRecycleAdapter = new BaseRecycleAdapter(this, R.layout.recycle_item, list);


        refreshLayout.setAdapter(myRecycleAdapter);

        refreshLayout.setOnRefreshListner(new RefreshLayout.RefreshListner() {
            @Override
            public void refresh() {
                refreshLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        list.clear();
                        addList();
                        myRecycleAdapter.notifyDataSetChanged();
                        refreshLayout.setRefreshComplete();
                    }
                }, 2000);
            }

            @Override
            public void loadMore() {
                refreshLayout.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        addList();
                        myRecycleAdapter.notifyDataSetChanged();
                        refreshLayout.setLoadMoreComplete();
                    }
                }, 2000);
            }
        });


        myRecycleAdapter.setOnItemClickListner(new BaseRecycleAdapter.OnItemClickListner() {
            @Override
            public void onItemClickListner(View v, int position) {
                Toast.makeText(MainActivity.this, "click---" + position, Toast.LENGTH_SHORT).show();
            }
        });

        myRecycleAdapter.setOnItemLongClickListner(new BaseRecycleAdapter.OnItemLongClickListner() {
            @Override
            public void onItemLongClickListner(View v, int position) {
                Toast.makeText(MainActivity.this, "longClick---" + position, Toast.LENGTH_SHORT).show();
            }
        });

        myRecycleAdapter.setCallBack(new BaseRecycleAdapter.CallBack() {
            @Override
            public <T> void convert(BaseViewHolder holder, T bean, final int position) {
                holder.setText(R.id.txt, "明天会更好" + position, new BaseViewHolder.OnClickListener() {
                    @Override
                    public void onClickListner(View v) {
                        Toast.makeText(MainActivity.this, "明天会更好" + position, Toast.LENGTH_SHORT).show();
                    }
                });

                holder.setImageListner(R.id.img, new BaseViewHolder.OnClickListener() {
                    @Override
                    public void onClickListner(View v) {
                        Toast.makeText(MainActivity.this, "img" + position, Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }
}

看完后有没有为之一振的感觉呢,Activity不去管那些复杂的操作,只做一些简单结果处理,确实有那么点意思,adapter也只需要给xml布局和数据源,不再去管理复杂操作,想怎么玩就怎么玩,所有项目的RecycleView都可以用这一个adapter,Activity立马解决数据操作,神器,绝对的神器,为了实现效果早点装逼,代码质量还需要一些规范分类(关键是懒,哈哈),里面用了好多的接口回调,也是醉了,不过这种结果处理,真是屡试不爽啊,好咯,下午马上就要上实验课了,作业还一大堆,不会操作就只会下位来装逼指导学生,每天上课之前ppt放映到”系主任”几个字的那个页面,真是受够了——————————愤青少年


最后附上github项目链接来star吧

目录
相关文章
|
10月前
|
Android开发
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X6 webview(一)
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X6 webview
|
10月前
|
Android开发
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X6 webview(二)
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X6 webview
|
10月前
|
Android开发
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X5 webview
【原理篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X5 webview
|
Android开发
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
264 0
安卓使用RecycleView+SmartRefreshLayout+CommonAdapter实现最简单上拉刷新,下拉加载
|
Android开发
同一页面实现recycleView三种布局【recycleView + adapter】
同一页面实现recycleView三种布局【recycleView + adapter】
138 0
同一页面实现recycleView三种布局【recycleView + adapter】
|
vr&ar 图形学
【Unity3D 灵巧小知识点】☀️ | Unity UGUI组件Scroll View禁止 左右 或 上下 滑动
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
【Unity3D 灵巧小知识点】☀️ | Unity UGUI组件Scroll View禁止 左右 或 上下 滑动
|
Android开发
关于DialogFragment全屏方案
DialogFragment默认情况下就算给内部布局设置match_parent也不是全屏。于是网上就有很多的方案,讲讲我所遇到的坑。 1.给window设置高度 很多地方都是用这样的写法 Window dialogWindow = getDialog().
1698 0
|
iOS开发
iOS开发实战 - 完美解决UIScrollView嵌套滑动手势冲突
我们应该都有用过这个功能,你的朋友微信给你分享了一个淘宝里面的商品链接,然后当你复制这个链接打开淘宝APP的时候,就会弹出一个弹窗,像这样: example.PNG 这个功能想必大家都挺熟悉,受这个启发我们产品也想在我们APP上添加这样一个功能,与这个不一样的是,当我们复制一段网址的时候打开我们的APP会弹出框填一些信息后上传到我们的“资源库”。
4260 0
|
数据库
ListView结合xutils3仿微信实现下拉加载更多
前言:最近涉及到和QQ打交道,定义所有的好友一共只能有300条消息,如果一次性从数据库读取300条或者更多,界面会有细微的卡顿.所以考虑了下分页,第一次进来只显示20条(仿微信),当用户滑到第一条后,如果数据库有消息,则再加载20条.
1060 0