如何为RecyclerView实现无限滚动加载更多功能?
实现RecyclerView无限滚动的完整方案
我来帮你基于现有代码实现RecyclerView的无限滚动效果,同时保证加载效率和应用流畅度,咱们一步步来修改:
1. 核心修改:添加滚动监听与分页逻辑
首先要给RecyclerView添加滚动监听,判断用户滚动到列表底部时触发加载更多,同时要避免重复请求(比如用户快速滚动时多次触发)。
需要在MainActivity中新增几个变量:
private int currentPage = 1; // 当前页码,从1开始 private int pageSize = 10; // 每页加载的数据量,根据你的接口调整 private boolean isLoading = false; // 标记是否正在加载,防止重复请求
然后在onCreate方法里添加滚动监听:
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 visibleItemCount = mLayoutManager.getChildCount(); int totalItemCount = mLayoutManager.getItemCount(); int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition(); // 判断是否滚动到了列表底部,且没有在加载中 if (!isLoading && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount) { currentPage++; getRetrofit(currentPage); // 加载下一页数据 isLoading = true; // 标记为正在加载 // 可以在这里给Adapter添加一个加载中的占位项 list.add(new Model(Model.LOADING_TYPE, "", "", "", "", "")); adapter.notifyItemInserted(list.size() - 1); } } });
2. 修改Retrofit请求支持分页
你的接口需要支持分页参数(大部分后端接口会用page或者offset参数),所以要修改RetrofitArrayApi的方法,添加页码参数:
public interface RetrofitArrayApi { @GET("your-post-endpoint") // 替换成你的实际接口路径 Call<List<WPPost>> getPostInfo(@Query("page") int page, @Query("per_page") int perPage); }
然后修改MainActivity里的getRetrofit方法,接收页码参数:
private void getRetrofit(int page) { Retrofit retrofit=new Retrofit.Builder() .baseUrl(baseURL) .addConverterFactory(GsonConverterFactory.create()) .build(); RetrofitArrayApi service = retrofit.create(RetrofitArrayApi.class); Call<List<WPPost>> call= service.getPostInfo(page, pageSize); call.enqueue(new Callback<List<WPPost>>() { @Override public void onResponse(Call<List<WPPost>> call, Response<List<WPPost>> response) { Log.e("mainactivty","response"+ response.body()); progressBar.setVisibility(View.GONE); // 先移除加载中的占位项(如果有的话) if (list.size() > 0 && list.get(list.size() - 1).type == Model.LOADING_TYPE) { list.remove(list.size() - 1); adapter.notifyItemRemoved(list.size()); } if (response.body() != null && !response.body().isEmpty()) { for(int i=0; i<response.body().size();i++){ Log.e ("main","Title"+ response.body().get(i).getTitle().getRendered()+" "+ response.body().get(i).getId()); String tempdetails=response.body().get(i).getExcerpt().getRendered().toString(); tempdetails=tempdetails.replace("<p>",""); tempdetails=tempdetails.replace("</p>",""); String linkdetail=response.body().get(i).getLink().toString(); String date=response.body().get(i).getDate().toString(); list.add(new Model(Model.IMAGE_TYPE, response.body().get(i).getTitle().getRendered(), tempdetails,date, response.body().get(i).getLinks().getWpFeaturedmedia().get(0).getHref(),linkdetail) ); } // 局部刷新,提升性能 int startPosition = list.size() - response.body().size(); adapter.notifyItemRangeInserted(startPosition, response.body().size()); } else { // 没有更多数据了,可以给用户添加一个提示占位项 list.add(new Model(Model.NO_MORE_TYPE, "没有更多数据了", "", "", "", "")); adapter.notifyItemInserted(list.size() - 1); } isLoading = false; // 加载完成,重置标记 } @Override public void onFailure(Call<List<WPPost>> call, Throwable t) { Log.e("mainactivity", "加载失败: " + t.getMessage()); // 移除加载占位项,重置标记 if (list.size() > 0 && list.get(list.size() - 1).type == Model.LOADING_TYPE) { list.remove(list.size() - 1); adapter.notifyItemRemoved(list.size()); } isLoading = false; } }); }
注意:初始化的时候调用getRetrofit(1)来加载第一页数据。
3. 优化Adapter:支持多种布局类型
为了显示加载状态和无数据提示,需要给Adapter添加多布局支持,首先修改Model类,添加对应的类型:
public class Model { public static final int IMAGE_TYPE = 0; public static final int LOADING_TYPE = 1; public static final int NO_MORE_TYPE = 2; public int type; public String title; public String subtitle; public String date; public String imageHref; public String link; // 构造方法也要对应修改 public Model(int type, String title, String subtitle, String date, String imageHref, String link) { this.type = type; this.title = title; this.subtitle = subtitle; this.date = date; this.imageHref = imageHref; this.link = link; } }
然后修改RecyclerViewAdapter,支持多种ViewType:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private ArrayList<Model> dataset; private Context mContext; public RecyclerViewAdapter(ArrayList<Model> mlist, Context context) { this.dataset=mlist; this.mContext=context; } // 加载布局的ViewHolder public static class LoadingViewHolder extends RecyclerView.ViewHolder { ProgressBar progressBar; public LoadingViewHolder(View itemView) { super(itemView); progressBar = itemView.findViewById(R.id.loading_progress); } } // 无更多数据的ViewHolder public static class NoMoreViewHolder extends RecyclerView.ViewHolder { TextView tipText; public NoMoreViewHolder(View itemView) { super(itemView); tipText = itemView.findViewById(R.id.no_more_tip); } } public static class ImageTypeViewHolder extends RecyclerView.ViewHolder{ TextView title,subtitle,date; ImageView imageView; public ImageTypeViewHolder(View itemView){ super(itemView); this.title=(TextView)itemView.findViewById(R.id.title); this.subtitle=(TextView) itemView.findViewById(R.id.subtitle); this.imageView=(ImageView) itemView.findViewById(R.id.icon); this.date=(TextView)itemView.findViewById(R.id.date); } } @Override public int getItemViewType(int position) { return dataset.get(position).type; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == Model.LOADING_TYPE) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false); return new LoadingViewHolder(view); } else if (viewType == Model.NO_MORE_TYPE) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_no_more, parent, false); return new NoMoreViewHolder(view); } else { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.postdetails,parent,false); return new ImageTypeViewHolder(view); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { Model object=dataset.get(position); if (holder instanceof ImageTypeViewHolder) { ((ImageTypeViewHolder) holder).title.setText(object.title); ((ImageTypeViewHolder) holder).subtitle.setText(object.subtitle); ((ImageTypeViewHolder) holder).date.setText(object.date); // 建议用Glide/Picasso加载图片,避免OOM // Glide.with(mContext) // .load(object.imageHref) // .placeholder(R.drawable.placeholder) // .into(((ImageTypeViewHolder) holder).imageView); ((ImageTypeViewHolder) holder).title.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(mContext, WPPostDetails.class); intent.putExtra("itemPosition",position); intent.putExtra("link",object.link.toString()); mContext.startActivity(intent); } }); } else if (holder instanceof NoMoreViewHolder) { ((NoMoreViewHolder) holder).tipText.setText(object.title); } // 加载布局不需要额外绑定数据 } @Override public int getItemCount() { return dataset.size(); } }
你需要新建两个布局文件:
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"> <ProgressBar android:id="@+id/loading_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
item_no_more.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"> <TextView android:id="@+id/no_more_tip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#999999" android:textSize="14sp" /> </LinearLayout>
4. 额外优化建议
- 图片加载优化:务必用Glide或Picasso加载图片,避免内存溢出,同时支持占位图和错误图。
- 下拉刷新:可以给RecyclerView套一层SwipeRefreshLayout,支持用户手动刷新第一页数据。
- 错误重试:加载失败时,可以给用户显示一个重试按钮,重新触发当前页的加载请求。
- 防抖处理:滚动监听里可以添加延迟判断,避免快速滚动时频繁触发请求。
修改后的MainActivity完整代码
package com.ejobbox.ejobbox; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import java.util.ArrayList; import java.util.List; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private ProgressBar progressBar; private LinearLayoutManager mLayoutManager; private RecyclerViewAdapter adapter; private ArrayList<Model> list; private String baseURL="http://mywebsite.com/"; public static List<WPPost> mListPost; // 分页相关变量 private int currentPage = 1; private int pageSize = 10; private boolean isLoading = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView= findViewById(R.id.recycler_view); progressBar=findViewById(R.id.progressbar); mLayoutManager=new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL,false); recyclerView.setLayoutManager(mLayoutManager); list=new ArrayList<Model>(); // 初始化Adapter adapter=new RecyclerViewAdapter(list,MainActivity.this); recyclerView.setAdapter(adapter); // 添加滚动监听 addScrollListener(); // 加载第一页数据 getRetrofit(currentPage); } private void addScrollListener() { 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 visibleItemCount = mLayoutManager.getChildCount(); int totalItemCount = mLayoutManager.getItemCount(); int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition(); if (!isLoading && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount) { // 检查最后一项是否是无数据提示,避免重复请求 if (list.size() > 0 && list.get(list.size() - 1).type != Model.NO_MORE_TYPE) { currentPage++; getRetrofit(currentPage); isLoading = true; // 添加加载占位项 list.add(new Model(Model.LOADING_TYPE, "", "", "", "", "")); adapter.notifyItemInserted(list.size() - 1); } } } }); } private void getRetrofit(int page){ Retrofit retrofit=new Retrofit.Builder() .baseUrl(baseURL) .addConverterFactory(GsonConverterFactory.create()) .build(); RetrofitArrayApi service = retrofit.create(RetrofitArrayApi.class); Call<List<WPPost>> call= service.getPostInfo(page, pageSize); call.enqueue(new Callback<List<WPPost>>() { @Override public void onResponse(Call<List<WPPost>> call, Response<List<WPPost>> response) { Log.e("mainactivty","response"+ response.body()); progressBar.setVisibility(View.GONE); // 移除加载占位项 if (list.size() > 0 && list.get(list.size() - 1).type == Model.LOADING_TYPE) { list.remove(list.size() - 1); adapter.notifyItemRemoved(list.size()); } if (response.body() != null && !response.body().isEmpty()) { int startSize = list.size(); for(int i=0; i<response.body().size();i++){ Log.e ("main","Title"+ response.body().get(i).getTitle().getRendered()+" "+ response.body().get(i).getId()); String tempdetails=response.body().get(i).getExcerpt().getRendered().toString(); tempdetails=tempdetails.replace("<p>",""); tempdetails=tempdetails.replace("</p>",""); String linkdetail=response.body().get(i).getLink().toString(); String date=response.body().get(i).getDate().toString(); list.add(new Model(Model.IMAGE_TYPE, response.body().get(i).getTitle().getRendered(), tempdetails,date, response.body().get(i).getLinks().getWpFeaturedmedia().get(0).getHref(),linkdetail) ); } // 局部刷新,提升性能 adapter.notifyItemRangeInserted(startSize, response.body().size()); } else { // 添加无更多数据提示 list.add(new Model(Model.NO_MORE_TYPE, "没有更多数据了", "", "", "", "")); adapter.notifyItemInserted(list.size() - 1); } isLoading = false; } @Override public void onFailure(Call<List<WPPost>> call, Throwable t) { Log.e("mainactivity", "加载失败: " + t.getMessage()); // 移除加载占位项,重置标记 if (list.size() > 0 && list.get(list.size() - 1).type == Model.LOADING_TYPE) { list.remove(list.size() - 1); adapter.notifyItemRemoved(list.size()); } isLoading = false; } }); } }
内容的提问来源于stack exchange,提问作者ejobbox
相关产品推荐
相关产品推荐

