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

如何使用RxJava实现RecyclerView的无限滚动功能?

解决Android中用RxJava实现RecyclerView无限滚动的问题

Hey,我来帮你搞定这个无限滚动的问题!首先得明确:你现在的代码是一次性拉取所有数据,没有分页逻辑,而且take算子本身是用来限制事件数量的,并不适合处理滚动触发的分页请求。咱们需要把滚动到底部的事件和分页API请求用RxJava的流串联起来,一步步实现需求。

第一步:给API请求加上分页参数

首先得让你的SearchLoansInput支持分页,这样每次请求都能指定拉取第几页、每页多少条数据:

public class SearchLoansInput {
    // 原有参数
    private String userName;
    // 新增分页参数
    private int page;
    private int pageSize;

    // Getter & Setter
    public void setPage(int page) {
        this.page = page;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
}

对应的Presenter里的请求方法,也要改成接收分页参数,去请求对应页的数据。

第二步:用RxJava构建分页流逻辑

我们用PublishSubject来发送「加载下一页」的事件,然后把这个事件流和API请求流结合,同时处理加载状态、防止重复请求:

先在Activity里添加核心成员变量

public class SalesDashboardActivity2 extends BaseActivity implements SalesDashboardView {
    private List<DashboardStatusBean> inProgressList = new ArrayList<>();
    // 分页控制参数
    private int currentPage = 1;
    private final int PAGE_SIZE = 10;
    private boolean isLoading = false; // 标记是否正在加载,防止重复请求
    private boolean hasMoreData = true; // 标记是否还有更多数据可加载
    // 用来发送加载下一页的事件流
    private PublishSubject<Integer> loadNextPageSubject = PublishSubject.create();
    // 订阅的Disposable,用于页面销毁时取消订阅,避免内存泄漏
    private Disposable loadDisposable;

    // ... 你的原有代码
}

初始化RxJava分页流

在onCreate里初始化这个流,同时触发首次加载:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_sales_dashboard2);
    ButterKnife.bind(this);
    setSupportActionBar(mToolbar);
    mSalesDashboardPresenter.attachView(this);
    
    // 初始化分页流逻辑
    initPagingFlow();
    // 首次加载第一页数据
    loadNextPageSubject.onNext(currentPage);
    // 设置RecyclerView滚动监听
    setupRecyclerViewScrollListener();
}

private void initPagingFlow() {
    loadDisposable = loadNextPageSubject
            // 过滤无效请求:正在加载时/没有更多数据时,不处理新的加载事件
            .filter(page -> !isLoading && hasMoreData)
            .doOnNext(page -> isLoading = true) // 标记开始加载
            .flatMap(page -> {
                // 构建分页请求参数
                SearchLoansInput input = new SearchLoansInput();
                input.setUserName(sessionManager.getMobileNumber());
                input.setPage(page);
                input.setPageSize(PAGE_SIZE);
                
                // 调用Presenter的请求,这里建议把Presenter的回调改成返回Observable
                return mSalesDashboardPresenter.getSalesInquiryObservable(input)
                        .onErrorReturn(throwable -> {
                            // 处理请求错误
                            runOnUiThread(() -> displayToastMessage("加载失败,请重试"));
                            isLoading = false;
                            return null;
                        });
            })
            .filter(output -> output != null && output.isSuccess())
            .subscribe(output -> {
                // 解析新数据
                List<DashboardStatusBean> newPageData = parseDashboardData(output);
                // 累加数据(不是替换原有列表!)
                inProgressList.addAll(newPageData);
                // 更新RecyclerView
                mAdapter.notifyDataSetChanged();
                
                // 判断是否还有更多数据:如果返回的数据量小于每页大小,说明已加载完所有数据
                hasMoreData = newPageData.size() == PAGE_SIZE;
                // 标记加载完成
                isLoading = false;
                // 更新当前页码
                currentPage++;
            });
}

// 把你原来的数据解析逻辑提取成单独方法,更清晰
private List<DashboardStatusBean> parseDashboardData(SearchLoansOutput searchLoansOutputData) {
    List<DashboardStatusBean> newList = new ArrayList<>();
    SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy");
    for (LoanDetail loanDetail : searchLoansOutputData.getLoanDetailList()) {
        DashboardStatusBean dashboardStatusBean = new DashboardStatusBean();
        
        // 日期格式化
        String createdDate = loanDetail.getCreatedDate() != null ? sdf.format(loanDetail.getCreatedDate()) : null;
        String approveDate = loanDetail.getApprovedDate() != null ? sdf.format(loanDetail.getApprovedDate()) : null;
        
        // 赋值其他字段(和你原来的代码一致)
        dashboardStatusBean.setCreatedDate(createdDate);
        dashboardStatusBean.setApproveDate(approveDate);
        dashboardStatusBean.setLoanNumber(loanDetail.getLoanNumber());
        dashboardStatusBean.setFirstName(loanDetail.getFirstName());
        dashboardStatusBean.setLastName(loanDetail.getLastName());
        dashboardStatusBean.setMobileNumber(loanDetail.getMobileNumber());
        dashboardStatusBean.setStatus(loanDetail.getStatus().getDisplayName());
        dashboardStatusBean.setLoanAmount(String.valueOf(loanDetail.getLoanAmount().setScale(0, BigDecimal.ROUND_UP)));
        dashboardStatusBean.setLoanAppId(loanDetail.getId());
        dashboardStatusBean.setProductAmount(loanDetail.getTotalProductAmount());
        dashboardStatusBean.setDownPayment(loanDetail.getDownPayment());
        dashboardStatusBean.setProcessingFee(loanDetail.getProcessingFee());
        dashboardStatusBean.setTenure(loanDetail.getDesiredItemPaybackTenure());
        dashboardStatusBean.setSourceAcquisition(loanDetail.getSourceAcquisition());
        dashboardStatusBean.setDocumentUpdatedList(loanDetail.getDocOutputList());
        
        // 筛选符合条件的数据加入列表
        String status = loanDetail.getStatus().getDisplayName();
        if (status.equals(LoanApplicationStatus.STILL_FILLING.getDisplayName()) || 
            status.equals(LoanApplicationStatus.ONBOARDIN_IN_PROG.getDisplayName()) ||
            status.equals(LoanApplicationStatus.INITIATED.getDisplayName()) ||
            status.equals(LoanApplicationStatus.ADDITIONAL_DATA_REQ.getDisplayName()) ||
            status.equals(LoanApplicationStatus.UPLOAD_IN_PROG.getDisplayName()) ||
            status.equals(LoanApplicationStatus.VERIFIED.getDisplayName()) ||
            status.equals(LoanApplicationStatus.LEAD.getDisplayName())) {
            newList.add(dashboardStatusBean);
        }
    }
    Util.SALES_IN_PROGRESS_LIST = newList; // 建议尽量避免使用静态变量,直接用inProgressList即可
    return newList;
}

设置RecyclerView滚动监听,触发加载事件

当用户滚动到接近底部时,发送加载下一页的事件:

private void setupRecyclerViewScrollListener() {
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            // 只处理向下滚动的情况
            if (dy <= 0) return;
            
            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            if (layoutManager == null) return;
            
            // 获取当前可见的最后一个item位置
            int lastVisibleItemPos = layoutManager.findLastVisibleItemPosition();
            // 获取总item数
            int totalItemCount = layoutManager.getItemCount();
            
            // 当滚动到倒数第2个item时,触发加载下一页(留出提前加载的缓冲空间)
            if (lastVisibleItemPos >= totalItemCount - 2 && !isLoading && hasMoreData) {
                loadNextPageSubject.onNext(currentPage);
            }
        }
    });
}

页面销毁时取消订阅,避免内存泄漏

@Override
protected void onDestroy() {
    super.onDestroy();
    if (loadDisposable != null && !loadDisposable.isDisposed()) {
        loadDisposable.dispose();
    }
    mSalesDashboardPresenter.detachView();
}

第三步:改造Presenter,返回Observable(可选但推荐)

如果你的Presenter原来用的是回调模式,建议改成返回Observable,这样更符合RxJava的链式调用风格:

public class SalesDashboardPresenter {
    private SalesApiService apiService;

    public Observable<SearchLoansOutput> getSalesInquiryObservable(SearchLoansInput input) {
        return apiService.searchLoans(input)
                .subscribeOn(Schedulers.io()) // 在IO线程执行网络请求
                .observeOn(AndroidSchedulers.mainThread()); // 在主线程处理结果
    }
}

为什么原来的take算子没用?

take(n)是用来截取流中的前n个事件,而我们的场景是每次滚动到底部就触发一个请求,是一个持续的事件流,直到没有更多数据为止。所以需要用filter控制请求触发条件,用flatMap把页码事件转换成API请求流,而不是用take。

另外还要注意几个核心点:

  • 防止重复请求:用isLoading标记,正在加载时不处理新的滚动事件
  • 判断是否还有更多数据:如果返回的数据量小于每页大小,说明已加载完所有数据
  • 内存泄漏:页面销毁时一定要取消订阅Disposable

内容的提问来源于stack exchange,提问作者Maulik Dodia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:09:14