如何优化ListView大数据加载?实现滚动分页加载功能
解决ListView分页加载与性能优化方案
嘿,我来帮你搞定这个ListView加载卡顿、崩溃的问题,同时实现你想要的“先加载10条,滚动时再加载下一批”的分页效果!
一、先修复Adapter的性能问题
你的现有Adapter存在严重的View复用问题——每次getView都重新创建布局和ViewHolder,这是导致ListView卡顿的核心原因之一。先把这个优化了:
public class CustomAdapterWishlist extends BaseAdapter { private List<Model> filteredData = null; private LayoutInflater mInflater; private FirebaseFirestore db = FirebaseFirestore.getInstance(); private Activity i; private String user_id; public CustomAdapterWishlist(Context context, List<Model> data, Activity i) { this.filteredData = data; mInflater = LayoutInflater.from(context); this.i = i; // 提前获取user_id,避免重复初始化 FirebaseAuth mAuth = FirebaseAuth.getInstance(); user_id = mAuth.getUid(); } @Override public int getCount() { return filteredData == null ? 0 : filteredData.size(); } @Override public Object getItem(int position) { return filteredData.get(position); } @Override public long getItemId(int position) { return position; } // 静态ViewHolder避免内存泄漏 static class ViewHolder { TextView name; TextView price; TextView quantity; TextView barcode; TextView description; TextView image_path; ImageView image; Button btn; LinearLayout layout; TextView productId, pricerange; } @SuppressLint("InflateParams") @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; // 复用convertView,只在为空时创建新布局 if (convertView == null) { convertView = mInflater.inflate(R.layout.row_search_iteam_wishlist, null); holder = new ViewHolder(); // 一次性绑定所有控件 holder.name = convertView.findViewById(R.id.name); holder.price = convertView.findViewById(R.id.price); holder.quantity = convertView.findViewById(R.id.quantity); holder.barcode = convertView.findViewById(R.id.barcode); holder.image_path = convertView.findViewById(R.id.image_path); holder.image = convertView.findViewById(R.id.image); holder.description = convertView.findViewById(R.id.description); holder.btn = convertView.findViewById(R.id.btn); holder.layout = convertView.findViewById(R.id.layout); holder.productId = convertView.findViewById(R.id.productId); holder.pricerange = convertView.findViewById(R.id.pricerange); convertView.setTag(holder); } else { // 直接复用已有的ViewHolder holder = (ViewHolder) convertView.getTag(); } // 绑定数据 Model model = filteredData.get(position); holder.name.setText(model.getName()); holder.price.setText(model.getPrice()); holder.description.setText(model.getDescription()); holder.productId.setText(model.getProductId()); holder.quantity.setText(model.getQuantity()); holder.barcode.setText(model.getBarcode()); holder.image_path.setText(model.getImage_path()); holder.pricerange.setText(model.getminmax()); final String flag = "2"; // 删除按钮逻辑优化:直接刷新列表而非重启Activity holder.btn.setOnClickListener(view -> { Animation myAnim = AnimationUtils.loadAnimation(i, R.anim.bounce); holder.btn.startAnimation(myAnim); db.collection("Wishlist").document(user_id) .collection("ProductList").document(model.getProductId()) .delete().addOnCompleteListener(task -> { if (task.isSuccessful()) { Toast.makeText(i, "item deleted", Toast.LENGTH_SHORT).show(); filteredData.remove(position); notifyDataSetChanged(); } }); }); // 布局跳转逻辑 holder.layout.setOnClickListener(view -> { Intent intent = new Intent(i, Details.class); intent.putExtra("productId", model.getProductId()); intent.putExtra("Flag", flag); ((Global) i.getApplication()).setProductId(model.getProductId()); i.startActivity(intent); }); // 图片加载优化:添加占位图/错误图 try { Picasso.with(i) .load(model.getImage_path()) .fit() .placeholder(R.drawable.default_placeholder) // 替换成你的占位图资源 .error(R.drawable.error_image) // 替换成你的错误图资源 .into(holder.image); } catch (Exception ignored) {} return convertView; } // 新增方法:用于添加分页加载的新数据 public void addNewData(List<Model> newData) { filteredData.addAll(newData); notifyDataSetChanged(); } }
优化点说明:
- 复用convertView和ViewHolder:大幅减少View创建次数,提升滚动流畅度
- 静态化ViewHolder:避免内部类持有外部引用导致的内存泄漏
- 简化删除逻辑:直接从列表移除数据并刷新,不用重启Activity
- 图片加载容错:添加占位图和错误图,避免加载异常崩溃
二、实现Firestore分页加载逻辑
接下来在你的whishlist Activity中实现分页核心逻辑:初始化加载10条,滚动到底部自动加载下一批。
步骤1:定义分页相关变量
public class whishlist extends AppCompatActivity { private ListView listView; private CustomAdapterWishlist adapter; private List<Model> productList = new ArrayList<>(); private FirebaseFirestore db; private String userId; private Query lastQuery; // 记录上一次查询的位置,用于分页 private boolean isLoading = false; // 标记是否正在加载,防止重复请求 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_whishlist); listView = findViewById(R.id.your_listview_id); db = FirebaseFirestore.getInstance(); userId = FirebaseAuth.getInstance().getUid(); // 初始化Adapter adapter = new CustomAdapterWishlist(this, productList, this); listView.setAdapter(adapter); // 加载第一页数据 loadFirstPage(); // 设置滚动监听,触发分页加载 listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 判断是否滚动到了底部,且不在加载状态 if (!isLoading && (firstVisibleItem + visibleItemCount) >= totalItemCount && totalItemCount > 0) { isLoading = true; loadNextPage(); } } }); } }
步骤2:加载第一页数据
private void loadFirstPage() { isLoading = true; db.collection("Wishlist").document(userId) .collection("ProductList") .limit(10) // 每页加载10条 .get() .addOnSuccessListener(documentSnapshots -> { if (!documentSnapshots.isEmpty()) { for (DocumentSnapshot doc : documentSnapshots) { Model model = doc.toObject(Model.class); model.setProductId(doc.getId()); // 把Firestore文档ID设为productId productList.add(model); } adapter.notifyDataSetChanged(); // 如果返回了10条,说明还有更多数据,记录查询位置 if (documentSnapshots.size() == 10) { lastQuery = documentSnapshots.getQuery(); } } isLoading = false; }) .addOnFailureListener(e -> { Toast.makeText(whishlist.this, "加载失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); isLoading = false; }); }
步骤3:加载下一页数据
private void loadNextPage() { if (lastQuery == null) { // 没有更多数据了 isLoading = false; Toast.makeText(this, "没有更多数据啦", Toast.LENGTH_SHORT).show(); return; } // 以上一页最后一条数据为起点,加载下10条 lastQuery.startAfter(lastQuery.getDocuments().get(lastQuery.getDocuments().size() - 1)) .limit(10) .get() .addOnSuccessListener(documentSnapshots -> { if (!documentSnapshots.isEmpty()) { List<Model> newData = new ArrayList<>(); for (DocumentSnapshot doc : documentSnapshots) { Model model = doc.toObject(Model.class); model.setProductId(doc.getId()); newData.add(model); } adapter.addNewData(newData); // 把新数据加到列表里 // 更新查询位置,如果不足10条说明没有更多数据 if (documentSnapshots.size() == 10) { lastQuery = documentSnapshots.getQuery(); } else { lastQuery = null; } } else { lastQuery = null; Toast.makeText(whishlist.this, "没有更多数据啦", Toast.LENGTH_SHORT).show(); } isLoading = false; }) .addOnFailureListener(e -> { Toast.makeText(whishlist.this, "加载失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); isLoading = false; }); }
三、额外注意事项
- Model类序列化:确保你的
Model类有默认构造函数,所有字段都有正确的getter/setter,否则Firestore无法将文档转换成对象 - 图片内存优化:如果图片尺寸较大,建议用Glide/Picasso指定加载尺寸,避免占用过多内存
- 下拉刷新(可选):如果需要支持下拉刷新,可以添加
SwipeRefreshLayout,刷新时清空现有数据重新加载第一页
这样改造后,你的ListView就能实现流畅的分页加载,再也不会出现卡顿和崩溃的问题啦!
内容的提问来源于stack exchange,提问作者lasya valiveti
相关产品推荐
相关产品推荐

