You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android RecyclerView加载更多数据的最简实现方法

RecyclerView 上拉加载更多极简实现方案

核心逻辑只有3部分:监听列表滑动判断是否滑到底部、增加加载状态标记避免重复请求、适配器支持加载中底部提示,直接基于你现有代码修改即可落地。


第一步:修改Adapter,支持加载更多底部布局

首先给适配器增加两种条目类型:普通评论项、加载中提示项,同时增加状态控制方法,修改后完整代码如下:

public class CommentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context context;
    private List<Comments> dataList;
    // 条目类型常量
    private static final int TYPE_COMMENT = 0;
    private static final int TYPE_LOADING = 1;
    // 加载状态标记
    private boolean isLoading = false;
    private boolean isLastPage = false;

    public CommentAdapter(Context context, List<Comments> dataList) {
        this.context = context;
        this.dataList = dataList;
    }

    @Override
    public int getItemViewType(int position) {
        // 最后一项且未加载完全部数据时,显示加载中布局
        return (position == dataList.size() - 1 && !isLastPage) ? TYPE_LOADING : TYPE_COMMENT;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == TYPE_LOADING) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
        // 原有普通评论项加载逻辑不变
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_comment, parent, false);
        return new CustomViewHolder(view);
    }

    @SuppressLint("RecyclerView")
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof CustomViewHolder) {
            CustomViewHolder commentHolder = (CustomViewHolder) holder;
            commentHolder.txtTitle.setText(dataList.get(position).getComment());
            commentHolder.txttime.setText(dataList.get(position).getUpdated_at());
            commentHolder.userNametitle.setText(dataList.get(position).getuser_name());

            commentHolder.itemView.setOnClickListener(view -> {
                // 原有点击逻辑保持不变
            });
        }
        // 加载中项不需要绑定业务数据
    }

    @Override
    public int getItemCount() {
        // 未到最后一页时,多算1个位置给加载中布局
        return isLastPage ? dataList.size() : dataList.size() + 1;
    }

    public boolean isLoading() {
        return isLoading;
    }

    public void setLoading(boolean loading) {
        isLoading = loading;
    }

    // 标记全部数据加载完成,不再显示加载中布局
    public void setLastPage(boolean lastPage) {
        isLastPage = lastPage;
        notifyDataSetChanged();
    }

    // 第一页数据替换
    public void setData(List<Comments> list) {
        dataList.clear();
        dataList.addAll(list);
        notifyDataSetChanged();
    }

    // 分页数据追加
    public void addData(List<Comments> newData) {
        int startPosition = dataList.size();
        dataList.addAll(newData);
        notifyItemRangeInserted(startPosition, newData.size());
        isLoading = false;
    }

    // 原有评论项ViewHolder保持不变
    static class CustomViewHolder extends RecyclerView.ViewHolder {
        TextView txtTitle, txttime, userNametitle;
        public CustomViewHolder(@NonNull View itemView) {
            super(itemView);
            // 原有控件绑定逻辑不变
            txtTitle = itemView.findViewById(R.id.txt_title);
            txttime = itemView.findViewById(R.id.txt_time);
            userNametitle = itemView.findViewById(R.id.user_name_title);
        }
    }

    // 加载中项ViewHolder
    static class LoadingViewHolder extends RecyclerView.ViewHolder {
        public LoadingViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}

加载中布局item_loading.xml可以直接用以下代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:padding="16dp"
    android:orientation="horizontal">
    <ProgressBar
        android:layout_width="24dp"
        android:layout_height="24dp"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载中..."
        android:layout_marginStart="8dp"/>
</LinearLayout>

第二步:修改页面逻辑,增加分页加载控制

不要重复创建适配器和布局管理器,初始化一次即可,增加滑动监听触发加载下一页,修改CommentPost.java代码如下:

// 分页参数
private int currentPage = 1;
private static final int PAGE_SIZE = 10;
private boolean hasMore = true;
private ProgressDialog progressDoalog;
private RecyclerView recyclerView;
private CommentAdapter adapter;
private LinearLayoutManager layoutManager;
private RetrofitService service;
private String audioPath;
// 如果做前端假分页,用这个变量存全量数据
// private List<Comments> allCommentList = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_comment_post);
    // 原有初始化逻辑
    service = RetrofitInstance.getRetrofitInstance().create(RetrofitService.class);
    audioPath = getIntent().getStringExtra("audio_path");
    initRecyclerView();
}

private void initRecyclerView() {
    recyclerView = findViewById(R.id.recyclerView);
    // 初始化空适配器
    adapter = new CommentAdapter(this, new ArrayList<>());
    layoutManager = new LinearLayoutManager(CommentPost.this);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(adapter);

    // 滑动监听,判断滑到底部触发加载
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            // 只处理向下滑动的场景
            if (dy <= 0) return;
            int totalItemCount = layoutManager.getItemCount();
            int lastVisiblePos = layoutManager.findLastVisibleItemPosition();
            // 满足3个条件才加载:没在加载中、还有更多数据、已经滑到最后一项
            if (!adapter.isLoading() && hasMore && lastVisiblePos == totalItemCount - 1) {
                currentPage++;
                loadCommentData();
            }
        }
    });

    // 初始加载第一页
    loadCommentData();
}

private void loadCommentData() {
    // 第一页显示全屏加载框,后续分页靠底部加载项提示
    if (currentPage == 1) {
        progressDoalog = new ProgressDialog(CommentPost.this);
        progressDoalog.setMessage("Loading....");
        progressDoalog.show();
    } else {
        adapter.setLoading(true);
    }

    // 后端支持分页就传页码和每页条数参数,不支持的话看下面注释的前端假分页逻辑
    Call<List<Comments>> call = service.getComment(audioPath, currentPage, PAGE_SIZE);
    call.enqueue(new Callback<List<Comments>>() {
        @Override
        public void onResponse(Call<List<Comments>> call, Response<List<Comments>> response) {
            if (currentPage == 1) {
                progressDoalog.dismiss();
            }
            List<Comments> result = response.body();
            if (result == null) result = new ArrayList<>();
            
            // 判断是否还有更多:返回数据小于每页条数说明到最后一页了
            if (result.size() < PAGE_SIZE) {
                hasMore = false;
                adapter.setLastPage(true);
            }

            if (currentPage == 1) {
                // 第一页直接替换数据
                adapter.setData(result);
                // 原有滚到最新评论的逻辑
                recyclerView.scrollToPosition(adapter.getItemCount() - 1);

                // 前端假分页的话,第一次拿到全量数据后替换成以下逻辑
                // allCommentList = result;
                // if (allCommentList.size() <= PAGE_SIZE) {
                //     hasMore = false;
                //     adapter.setLastPage(true);
                //     adapter.setData(allCommentList);
                // } else {
                //     adapter.setData(allCommentList.subList(0, PAGE_SIZE));
                // }
            } else {
                // 追加分页数据
                adapter.addData(result);

                // 前端假分页的话,追加数据替换成以下逻辑
                // int start = (currentPage -1)*PAGE_SIZE;
                // int end = Math.min(currentPage*PAGE_SIZE, allCommentList.size());
                // adapter.addData(allCommentList.subList(start, end));
            }
        }

        @Override
        public void onFailure(Call<List<Comments>> call, Throwable t) {
            if (currentPage == 1) {
                progressDoalog.dismiss();
            } else {
                adapter.setLoading(false);
                // 加载失败页码回退,支持重试
                currentPage--;
            }
            Toast.makeText(CommentPost.this, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show();
        }
    });
}

注意事项

  • 如果你的后端分页参数用的是偏移量offset而不是页码,只需要把currentPage换成offset变量,每次加载成功后offset += PAGE_SIZE即可,其他逻辑不变。
  • 不要在每次加载数据时重新创建Adapter和LayoutManager,否则会出现滚动位置错乱、重复触发加载的问题。
  • 如果后端返回的分页结构带总条数字段,判断是否最后一页可以用已加载条数 >= 总条数的逻辑,比判断返回列表长度更准确。

内容的提问来源于stack exchange,提问作者Md. Shahinur Islam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:15:43