教你实现Android RecyclerView上拉加载功能

摘要

一直在用到RecyclerView时都会微微一颤,因为一直都没去了解怎么实现上拉加载,受够了每次去Github找开源引入,因为感觉就为了一个上拉加载功能而去引入一大堆你不知道有多少BUG的代码,不仅增加了项目的冗余程度,而且出现BUG的时候,你却发现很难去改,正因为这样,我就下定决心去了解如何来实现RecyclerView的上拉加载功能,相信大家和我有过同样的情况,但是我相信,只要你给自己几分钟看完这篇文章,你就会发现实现一个上拉加载是非常的简单。

什么是上拉加载

上拉加载和下拉刷新相对应,在Android API LEVEL 19(即4.4)之后,Google官方推出了SwipeRefreshLayout和RecyclerView的共同使用,为我们提供了更加便捷的列表下拉刷新功能,但是,并没有给我们提供上拉加载功能,但是在RecyclerView强大的可扩展之下,Github上面有了很多开源项目实现了上拉加载功能,即我们不会一次性将所有数据加载到列表中,当用户滑动到底部时,再向服务器请求数据,再填充数据到列表中,这样不仅可以有更好的人机交互,同时在减少

了服务器的压力的同时也对客户端的性能有了更好的提升。本篇文章主要通过介绍实现以下简单的上拉加载功能,同学们可以在掌握了最基本的实现功能之后,再通过扩展和优化,甚至可以封装成比较通用的代码,开源到Github上面。

教你实现Android RecyclerView上拉加载功能

Demo

实现思路

一、XML的实现

布局很简单,只有一个SwipeRefreshLayout包裹了一个RecyclerView,相信用过RecyclerView的都很容易看懂。如下为activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/recyclerView"

然后,我们RecyclerView的Item布局也是非常简单,只有一个TextView。如下为item.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:textSize="30sp"

看到我们效果图都知道,在我们上拉时,还有一个提示的条目,我定义为 footview.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_marginBottom="1dp"/>

二、初始化SwipeRefreshLayout

在准备好了布局文件之后,我们就把目光转到Activity中去,首先我们需要初始化SwipeRefreshLayout,初始化也是很简单,这里省去了findView操作,所以就只有设置转动的颜色,还有设置刷新的监听事件:

private void initRefreshLayout() {
 refreshLayout.setRefreshing(true); // 重置adapter的数据源为空

三、定义RecyclerView的Adapter

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List<String> datas; // 数据源

privateint footType = 1; // 第二种ViewType,底部的提示Viewprivateboolean hasMore = true; // 变量,是否有更多数据privateboolean fadeTips = false; // 变量,是否隐藏了底部的提示private Handler mHandler = new Handler(Looper.getMainLooper()); //获取主线程的Handlerpublic MyAdapter(List<String> datas, Context context, boolean hasMore) { // 初始化变量this.datas = datas; this.context = context; this.hasMore = hasMore; } // 获取条目数量,之所以要加1是因为增加了一条footView@Overridepublic int getItemCount() { return datas.size() + 1; } // 自定义方法,获取列表中数据源的最后一个位置,比getItemCount少1,因为不计上footView

public int getRealLastPosition() { return datas.size(); } // 根据条目位置返回ViewType,以供onCreateViewHolder方法内获取不同的Holder@Overridepublic int getItemViewType(int position) { if (position == getItemCount() - 1) { return footType; } else { return normalType; } } // 正常item的ViewHolder,用以缓存findView操作class NormalHolder extends RecyclerView.ViewHolder { private TextView textView; public NormalHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.tv); } } // // 底部footView的ViewHolder,用以缓存findView操作class FootHolder extends RecyclerView.ViewHolder { private TextView tips; public FootHolder(View itemView) { super(itemView); tips = (TextView) itemView.findViewById(R.id.tips); } } @Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 根据返回的ViewType,绑定不同的布局文件,这里只有两种if (viewType == normalType) { returnnew NormalHolder(LayoutInflater.from(context).inflate(R.layout.item, null)); } else { returnnew FootHolder(LayoutInflater.from(context).inflate(R.layout.footview, null)); } } @Overridepublic void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { // 如果是正常的imte,直接设置TextView的值if (holder instanceof NormalHolder) { ((NormalHolder) holder).textView.setText(datas.get(position)); } else { // 之所以要设置可见,是因为我在没有更多数据时会隐藏了这个footView ((FootHolder) holder).tips.setVisibility(View.VISIBLE); // 只有获取数据为空时,hasMore为false,所以当我们拉到底部时基本都会首先显示“正在加载更多...”if (hasMore == true) { // 不隐藏footView提示 fadeTips = false; if (datas.size() > 0) {// 如果查询数据发现增加之后,就显示正在加载更多 ((FootHolder) holder).tips.setText("正在加载更多..."); } } else { if (datas.size() > 0) { // 如果查询数据发现并没有增加时,就显示没有更多数据了 ((FootHolder) holder).tips.setText("没有更多数据了"); // 然后通过延时加载模拟网络请求的时间,在500ms后执行 mHandler.postDelayed(new Runnable() { @Overridepublic void run() { // 隐藏提示条 ((FootHolder) holder).tips.setVisibility(View.GONE); // 将fadeTips设置true fadeTips = true; // hasMore设为true是为了让再次拉到底时,会先显示正在加载更多 hasMore = true; } }, 500); } } } } // 暴露接口,改变fadeTips的方法public boolean isFadeTips() { return fadeTips; } // 暴露接口,下拉刷新时,通过暴露方法将数据源置为空public void resetDatas() { datas = new ArrayList<>(); } // 暴露接口,更新数据源,并修改hasMore的值,如果有增加数据,hasMore为true,否则为falsepublic void updateList(List<String> newDatas, boolean hasMore) { // 在原有的数据之上增加新数据if (newDatas != null) { datas.addAll(newDatas); } this.hasMore = hasMore; notifyDataSetChanged(); }

}

四、初始化RecyclerView

private void initRecyclerView() { // 初始化RecyclerView的Adapter

// 实现上拉加载重要步骤,设置滑动监听器,RecyclerView自带的ScrollListener recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 在newState为滑到底部时if (newState == RecyclerView.SCROLL_STATE_IDLE) { // 如果没有隐藏footView,那么最后一个条目的位置就比我们的getItemCount少1,自己可以算一下if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Overridepublic void run() { // 然后调用updateRecyclerview方法更新RecyclerView updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); }// 如果隐藏了提示条,我们又上拉加载时,那么最后一个条目就要比getItemCount要少2if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Overridepublic void run() { // 然后调用updateRecyclerview方法更新RecyclerView updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } } } @Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 在滑动完成后,拿到最后一个可见的item的位置 lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); }

 });

所以,Activity的完整代码如下:

 
  
public class MainActivity extends AppCompatActivity implements
        SwipeRefreshLayout.OnRefreshListener {
    private SwipeRefreshLayout
            refreshLayout;
    private RecyclerView recyclerView;
    private
    List<String> list;

    privateint lastVisibleItem = 0;
    privatefinalint PAGE_COUNT = 10;
    private GridLayoutManager mLayoutManager;
    private MyAdapter adapter;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    @Overrideprotected
    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        findView();
        initRefreshLayout();
        initRecyclerView();
    }

    private void initData() {
        list = new ArrayList<>();
        for (int i = 1; i <= 40; i++) {
            list.add("条目" + i);
        }
    }

    private void findView() {
        refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refreshLayout);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    }

    private void initRefreshLayout() {
        refreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light);
        refreshLayout.setOnRefreshListener(this);
    }

    private void initRecyclerView() {
        adapter = new MyAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false);
        mLayoutManager = new GridLayoutManager(this, 1);
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setAdapter(adapter);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Overridepublic
            void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) {
                        mHandler.postDelayed(new Runnable() {
                            @Overridepublic
                            void run() {
                                updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
                            }
                        }, 500);
                    }
                    if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) {
                        mHandler.postDelayed(new Runnable() {
                            @Overridepublic
                            void run() {
                                updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT);
                            }
                        }, 500);
                    }
                }
            }

            @Overridepublic
            void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
            }
        });
    }

    private List<String> getDatas(final int firstIndex, final int lastIndex) {
        List<String> resList = new ArrayList<>();
        for (int i = firstIndex; i < lastIndex; i++) {
            if (i < list.size()) {
                resList.add(list.get(i));
            }
        }
        return resList;
    }

    private void updateRecyclerView(int fromIndex, int toIndex) {
        List<String> newDatas = getDatas(fromIndex, toIndex);
        if (newDatas.size() > 0) {
            adapter.updateList(newDatas, true);
        } else {
            adapter.updateList(null, false);
        }
    }

    @Overridepublic
    void onRefresh() {
        refreshLayout.setRefreshing(true);
        adapter.resetDatas();
        updateRecyclerView(0, PAGE_COUNT);
        mHandler.postDelayed(new Runnable() {
            @Overridepublic
            void run() {
                refreshLayout.setRefreshing(false);
            }
        }, 1000);

    }
 
  

后话

以上代码我是考虑到了更多的边界条件,所以在代码上会稍微多了一点,但是也不影响观看。大家也可以通过改变数据源的数量和PAGE_COUNT等来测试,每个人在具体使用上都会有不同的要求,所以基本代码我摆了出来,众口难调,更多的细节需要大家来优化,例如footView可以设置一个动画条,下拉刷新用其他样式替换原生的样式等,我想,这些对于学习完这篇文章的你来说,都会是简单的问题了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值