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

如何优化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:08:44