恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Android商城界面开发:ListView+BaseAdapter从零搭建购物列表

  • 首页
  • 资讯中心
  • /
  • Android商城界面开发:ListView+BaseAdapter从零搭建购物列表

相关资讯

C++构建SNL语言编译器:从词法分析到LL1全流程解析 2026/10/12 11:59:34
基于Python的电影推荐系统毕业设计:ItemCF算法、数据库设计与Flask实现 2026/10/12 11:59:34
Python 调用 libvirt 管理 KVM 虚拟机:从连接到监控的完整实践 2026/10/12 11:59:34

最新资讯

VSCode插件开发:根据文件路径一键跳转到对应文件的实现方案与TaoToken配置
软考高项第23章组织通用管理备考:核心考点与速记口诀
收敛交叉映射CCM:非线性时间序列因果检测实战
锂电池RUL预测实战:CatBoost回归从特征工程到SHAP可解释性
Selective Search原理与工程实现:传统目标检测区域提议方法详解
VBScript+任务计划实现QQ定时自动发消息:零依赖Windows自动化实战

今日推荐

Debian新手入门:从部署到日常操作的完整指南
MongoDB复制集扩缩容实战:从rs.add到选主事故复盘
条形码目标检测数据集实战:从YOLOv8训练到部署

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Android商城界面开发:ListView+BaseAdapter从零搭建购物列表

发布时间:2026/10/12 12:04:35
Android商城界面开发:ListView+BaseAdapter从零搭建购物列表 简介面向Android初学者的购物商城界面源码基于Android Studio与ListView组件搭建解决了从零构建商品列表展示页面的常见需求适合课程设计、期末项目或入门练手整体代码结构简单直观便于初学者快速上手与二次修改。资源包共五百一十八个文件约九点零五兆字节其中布局文件、程序源码、构建脚本、配置信息与图片素材等类型齐全压缩包结构清晰便于直接导入开发环境或按需查阅。目前已有一万三千五百四十二人学习下载。通过研读项目可掌握ListView的基本工作原理、自定义BaseAdapter时getCount、getItem、getItemId、getView四个核心方法的重写以及利用OnScrollListener实现上滑下滑加载更多商品的交互逻辑这些知识点可进一步迁移到下拉刷新、异步加载图片等进阶功能中帮助初学者快速积累安卓界面开发的实战经验。1. 用 Android Studio 搭购物商城界面为什么 ListView 仍是初学者的最佳选择在 Android Studio 里做购物商城界面很多人的第一反应是直接用 RecyclerView。但我拆过几份教学项目后发现ListView 反而是最适合起步的那一个它对 Adapter 的要求更直白每个回调方法都能对应到一个实际 UI 行为。本文要拆的这份购物商城界面源码就是基于 ListView BaseAdapter 自定义 item 布局的经典组合适合刚学完四大组件、想拿一个完整项目练手的初学者。整份资源能解决一个问题让你看明白商品列表数据是怎么一步步变成屏幕上的商品卡片。需要提醒的是这套源码侧重界面与交互逻辑不含真实的支付、登录和后台接口。2. 把架子搭起来工程结构、布局文件和 ListView 的三个关键属性2.1 新建工程时该选哪个模板以及工程里需要留意的四个文件用 Android Studio 新建项目时常见做法是选 Empty Activity 模板因为自带一个MainActivity 和一份可运行的 manifest省掉手工建目录的麻烦。这里有个容易被新手忽略的点模板生成后会附带一个content_main.xml或activity_main.xml不同 Studio 版本生成的布局结构不一样但最终我们只需要在一个主布局文件里操作。这份源码里你需要关心的文件我一般会圈定成四个文件作用activity_main.xml主界面布局放 ListViewitem_product.xml商品条目的自定义布局一个条目对应一件商品ProductAdapter.java继承 BaseAdapter 的自定义适配器负责数据到视图的转换MainActivity.java初始化数据、实例化 ListView 和 Adapter 的入口新建项目后AS 默认会帮你生成一个activity_main.xml里面可能是一个ConstraintLayout包着一个TextView。我们要做的事情就是把这个默认布局里的TextView删掉换成ListView。2.2 写 activity_main.xmlListView 的尺寸、滚动条和分割线配置主布局是整个商城界面的地基。一个购物商城的主界面通常顶部是一个搜索栏或 Banner下面才是商品列表。但为了聚焦核心知识点这份源码把主布局简化成了纯 ListView这样能快速跑通列表逻辑不被其他 UI 干扰。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical ListView android:idid/list_view android:layout_widthmatch_parent android:layout_heightmatch_parent android:divider#E0E0E0 android:dividerHeight1dp android:scrollbarsvertical android:fadeScrollbarstrue / /LinearLayout这一段布局里divider是列表项之间的分隔线颜色dividerHeight是分隔线高度。把分隔线设成 1dp 的浅灰色模拟出电商 App 里常见的细分割线效果。fadeScrollbars设为true表示滚动条在不滚动时自动隐藏这个细节对视觉观感影响很大很多新手不知道有这一项导致右侧一直杵着一根滚动条。android:scrollbarsvertical的含义是只显示垂直滚动条。ListView 本身支持垂直滚动这是它作为列表组件的核心能力之一。这里不需要设置android:stackFromBottom之类的属性那是聊天界面用的商品列表应该从头开始排列。2.3 设计 item_product.xml商品卡片需要哪些视图元素每个商品条目能承载的信息量取决于 item 布局怎么设计。购物商城里一个标准的商品卡片需要三样东西商品图片、商品名称、商品价格。这是最简配置也是这个项目的边界——它不做多图轮播不做 SKU 选择专注把列表项渲染明白。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding8dp ImageView android:idid/img_product android:layout_width80dp android:layout_height80dp android:scaleTypecenterCrop android:contentDescriptionstring/product_image / LinearLayout android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:orientationvertical android:paddingStart12dp android:paddingEnd8dp TextView android:idid/tv_name android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize16sp android:textColor#333333 android:maxLines2 android:ellipsizeend / TextView android:idid/tv_price android:layout_widthwrap_content android:layout_heightwrap_content android:textSize15sp android:textColor#FF5722 android:textStylebold android:layout_marginTop4dp / /LinearLayout /LinearLayout这里有个布局技巧值得注意右边名称和价格所在的LinearLayout用了layout_width0dp配合layout_weight1这样它就会填满 ImageView 右侧剩余空间。如果不用weight而是直接写match_parent会直接把 ImageView 挤出去这是新手最容易翻车的地方之一。maxLines2和ellipsizeend一起使用保证商品名称超过两行时以省略号结尾不会撑高条目。实际商城里的商品名长短不一这两行属性是必须的。2.4 ListView 的三个关键配置复用缓存、滚动监听和空视图在 Java 代码里拿到 ListView 之后有几个配置项值得养成习惯ListView listView findViewById(R.id.list_view); listView.setEmptyView(findViewById(R.id.tv_empty)); // 数据为空时显示 listView.setDivider(new ColorDrawable(Color.parseColor(#E0E0E0))); listView.setDividerHeight(1); listView.setSelector(R.drawable.selector_item_bg); // 点击时的背景反馈setEmptyView是个少有人用的方法它允许你指定一个 TextView 作为空数据提示比如“暂无商品”。setSelector控制的是条目被按下时的背景色变化商城列表如果没有点击反馈用户会怀疑自己有没有点到。这个 selector 可以是一个StateListDrawable也可以用系统自带的?android:attr/selectableItemBackground。3. 让数据流动起来BaseAdapter 的四个重写方法与 getView 的绑定逻辑3.1 Product 数据模型图片、名称、价格三个字段怎么定义写 Adapter 之前必须先有数据模型。商城里的商品对象在真实项目中对应后端返回的 JSON 字段但这里只做界面展示不接网络所以一个简单的 Java Bean 就够用。public class Product { private String imageUrl; // 商品图片地址本地资源时是 resId 对应的字符串 private String name; // 商品名称 private String price; // 商品价格保留字符串类型避免精度问题 public Product(String imageUrl, String name, String price) { this.imageUrl imageUrl; this.name name; this.price price; } public String getImageUrl() { return imageUrl; } public String getName() { return name; } public String getPrice() { return price; } }价格字段我用String而不是double这是刻意为之。电商场景里价格需要精确展示“¥99.00”这种格式如果用浮点数参与运算容易出现精度误差。在这个项目里价格只做展示不参与计算所以字符串最稳妥。3.2 BaseAdapter 的 getCount、getItem、getItemId 三个方法不能偷懒BaseAdapter 是一个抽象类四个方法必须全部实现。其中getItemId是最容易被糊弄的方法新手经常会随手返回position这在多数场景下没毛病但如果你之后要用CursorAdapter或者涉及到 ListView 的选中态管理返回真正的 ID 会更安全。常见做法是把 Product 对象的 hash 或自增 ID 放进去。public class ProductAdapter extends BaseAdapter { private ListProduct productList; private LayoutInflater inflater; public ProductAdapter(Context context, ListProduct productList) { this.productList productList; this.inflater LayoutInflater.from(context); } Override public int getCount() { return productList null ? 0 : productList.size(); } Override public Object getItem(int position) { return productList.get(position); } Override public long getItemId(int position) { return position; } }getCount返回的是列表条目的总数ListView 在测量自身高度和计算滚动范围时依赖它。getItem返回当前位置的数据对象在滚动监听里要用它拿数据。getItemId当前返回 position 够用但要意识到它的语义是“这条数据的唯一标识”不是“位置”。3.3 getView 方法详解convertView 判空和 ViewHolder 的必要性getView是整个 Adapter 里最核心的方法它决定了一个位置的商品数据如何变成一棵视图树。网上关于 ListView 优化的文章几乎都在围绕这个方法做文章原因在于它会被频繁调用——滑动列表时每个新出现的条目都要执行一次getView。Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; // 关键如果 convertView 不为 null说明这个视图曾经被创建过直接复用 if (convertView null) { convertView inflater.inflate(R.layout.item_product, parent, false); holder new ViewHolder(); holder.imgProduct convertView.findViewById(R.id.img_product); holder.tvName convertView.findViewById(R.id.tv_name); holder.tvPrice convertView.findViewById(R.id.tv_price); convertView.setTag(holder); } else { // 从缓存里取回 ViewHolder避免重复 findViewById holder (ViewHolder) convertView.getTag(); } // 拿到当前位置的商品数据 Product product productList.get(position); // 绑定视图数据 holder.tvName.setText(product.getName()); holder.tvPrice.setText(product.getPrice()); // 图片加载本地资源直接 setImageResource网络图片走异步加载 if (product.getImageUrl().startsWith(http)) { // 实际项目用 Glide/Picasso这里留接口 // Glide.with(context).load(product.getImageUrl()).into(holder.imgProduct); } else { int resId Integer.parseInt(product.getImageUrl()); holder.imgProduct.setImageResource(resId); } return convertView; } static class ViewHolder { ImageView imgProduct; TextView tvName; TextView tvPrice; }这段代码里最容易被忽视的是inflate方法的第三个参数parent。传parent的目的是让 LayoutInflater 知道这个视图将来会被放进哪个父容器从而正确解析layout_width和layout_height。如果传null条目会丢失布局参数显示尺寸异常。ViewHolder 的意义在于避免每次滑动都执行findViewById。这个方法的开销虽然不大但列表滑动时每秒可能调用几十次累积起来就是肉眼可见的卡顿。从写 Adapter 的第一天起就养成用 ViewHolder 的习惯后面写 RecyclerView 时也是同一套思路。3.4 在 MainActivity 里把 Adapter 挂到 ListView 上写完了 Adapter最后一步是在 Activity 里把它们组装起来。这一步的代码量不大但顺序不能乱先有数据再创建 Adapter最后setAdapter。public class MainActivity extends AppCompatActivity { private ListProduct productList new ArrayList(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1. 准备模拟数据 initProductData(); // 2. 创建适配器并设置给 ListView ListView listView findViewById(R.id.list_view); ProductAdapter adapter new ProductAdapter(this, productList); listView.setAdapter(adapter); } private void initProductData() { // 模拟 20 条商品数据图片用本地 drawable 资源 for (int i 1; i 20; i) { productList.add(new Product( String.valueOf(R.drawable.product_default), 商品名称示例第 i 件商品, ¥ (99 i) .00 )); } } }setAdapter一旦被调用ListView 就会立刻执行getCount和若干次getView来绘制首屏可见的条目。所以 Adapter 里的productList不能是空的否则界面上什么都不会显示。这也是新手最容易踩的坑Activity 里先setAdapter后给列表添加数据界面空白一片。4. 从界面到交互模拟数据、滚动加载和异步图片的接入方式4.1 模拟数据为什么要用 List 而不是数组在很多课程里商品数据被写成一个Product[]数组。数组在编译期长度就固定了但商城列表的数据量是动态变化的——用户下拉加载更多或筛选后列表变短数组都处理不了。所以这里统一用ListProduct。在真实项目里这个 List 通常来自网络请求的 JSON 解析比如用 Retrofit Gson 把后端返回的商品数组转换成 List。本项目的边界就是不接网络用本地写死的模拟数据跑通完整流程。这也意味着你拿到源码后替换数据源的位置十分清晰只需改initProductData()里的逻辑即可。4.2 配合 OnScrollListener 实现滚动到底部加载更多滚动加载是商城列表最基础的交互方式。ListView 的OnScrollListener里有onScrollStateChanged和onScroll两个回调。实现加载更多的常见做法是在onScroll里判断当前滚动位置是否接近底部再在onScrollStateChanged里触发加载动作。listView.setOnScrollListener(new AbsListView.OnScrollListener() { Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 只有在停止滚动时才加载更多避免滚动过程中频繁触发 if (scrollState SCROLL_STATE_IDLE view.getLastVisiblePosition() adapter.getCount() - 2) { loadMoreData(); } } Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 这里可以记录滚动位置或者触发预加载 } });getLastVisiblePosition()返回的是当前屏幕最底部那个条目的下标。当它大于等于getCount() - 2时说明用户已经滚到了倒数第 2 条附近此时触发loadMoreData()比较合适。SCROLL_STATE_IDLE的判断是为了防止用户快速滑动时触发多次加载。loadMoreData()里的实现也很固定往productList里追加新数据然后调用adapter.notifyDataSetChanged()。这个方法是 Adapter 和 ListView 之间的通信桥梁不调用它ListView 不会知道数据变了界面就停留在旧状态。4.3 异步加载图片的三条路Glide、Picasso、手写线程ListView 里的图片加载是基础也是最容易翻车的点。项目里如果直接用setImageResource加载 drawable 资源在数据量小的时候没问题但一旦换成网络图片 URL就必须引入异步加载方案。常见的路线有三条第一是用 Glide。这是目前最主流的方案用法一句话内部管理了缓存、线程池和生命周期。Glide.with(context) .load(https://example.com/product/001.jpg) .placeholder(R.drawable.placeholder) .error(R.drawable.error) .into(holder.imgProduct);第二是用 Picasso。它和 Glide 类似但缓存策略和 GIF 支持有差异。如果项目里只需要静态图Picasso 更轻量。注意 Glide 和 Picasso 对图片 URL 中的重定向处理不同同一个 URL 在这两个库里的表现可能不一样选定一个就不要中途混用。第三是自己写一个简单的异步加载工具类。核心思路是子线程下载图片 → 通过 Handler 切回主线程 → 设置到 ImageView。但这里面有两个绕不开的坑一是 ImageView 复用时图片错位二是缓存管理要自己实现。在没有足够经验之前不建议手写图片加载直接用 Glide 是这个项目里最稳的选择。5. 常见问题排查ListView 开发中五个绕不开的坑5.1 图片错位列表快速滑动后条目显示成别的商品图现象快速上下滑动 ListView屏幕上出现明显的图片闪烁甚至某一行的图片变成了另一行的商品图。原因ListView 的条目视图是复用的。当某个 ImageView 被复用去显示新数据时新图片还在异步加载中此时 ImageView 上残留的是上一个商品的旧图片。用户看到的就是错位。解决用 Glide 时调用.override()指定加载尺寸同时让加载完成前显示占位图。更彻底的方案是在图片加载前做一次 URL 比对但 Glide 内部已经处理了大部分情况手写异步加载时必须在回调里检查 ImageView 的 Tag 是否还指向当前商品。5.2 item 点击没反应Selector 被 TextView 挡住现象点击条目时没有任何视觉反馈setOnItemClickListener里的回调不执行。原因item 布局里的子控件尤其是 TextView默认是“可点击”的会拦截点击事件。在一些 ROM 上onItemClick会被吞掉。解决布局根LinearLayout上设置android:descendantFocusabilityblocksDescendant或者给 ListView 设置android:listSelectordrawable/selector_item_bg。另外检查 item 布局里的控件是否设置了clickable或focusable属性只要有这些属性就可能在表象上“吃掉”点击事件。5.3 ScrollView 嵌套 ListView 高度异常现象把 ListView 放在 ScrollView 里结果 ListView 只显示了一行其余条目都看不见。原因ScrollView 没有限制子 View 的高度ListView 测量高度时只算了首屏的高度。这是一个经典的老问题本质上是两个可滚动组件嵌套时的高度测量冲突。解决常见做法是给 ListView 设置android:nestedScrollingEnabledtrue或者重写onMeasure让它一次性把所有条目都测量出来。但更推荐的做法是从架构上避免这种嵌套——商城首页用 NestedScrollView 或直接改用 RecyclerView 配合 NestedScrolling 机制。5.4 数据更新后界面没刷新忘了调用 notifyDataSetChanged现象执行了productList.add(...)后界面上没有任何变化。原因Adapter 和 ListView 之间没有自动同步机制。修改了数据源后必须手动通知 AdapterListView 才会重新执行getCount和getView。解决每次修改productList后立刻补上adapter.notifyDataSetChanged()。注意productList必须和 Adapter 构造函数里传的是同一个对象引用如果你在 Activity 里重新 new 了一个ArrayList再塞给 Adapter那 Adapter 里持有的还是旧引用。5.5 布局加载太慢导致滑动卡顿现象列表能正常显示但快速滑动时明显掉帧FPS 掉到 40 以下。原因大概率是在getView里做了耗时操作比如直接加载本地大图、执行复杂的字符串拼接或者没使用 ViewHolder 导致频繁findViewById。解决把findViewById移到 ViewHolder 里用convertView.setTag()缓存图片统一走 Glide 并指定缩略图尺寸不要在getView里做任何 IO 操作或复杂计算。还有一个细节避免在getView里创建匿名内部类每次new OnClickListener()都会增加内存分配频率。6. 让列表跑得更顺ViewHolder 复用机制与局部刷新技巧ListView 的性能优化本质上就一句话尽量少创建新对象尽量少执行耗时代码。ViewHolder 解决的是“少创建 View”的问题但还有一处细节容易被忽略——notifyDataSetChanged()是全量刷新会导致当前可见的所有 item 全部重新执行getView。如果列表有 20 个可见条目那一次数据更新就会触发 20 次视图重建。更好的做法是局部刷新。ListView 提供了getChildAt(index)方法拿到当前可见的某个 item 视图再通过getTag()取出 ViewHolder 直接改数据。private void updateItemAt(int position, Product newProduct) { // 计算目标条目在屏幕上的相对位置 int firstVisible listView.getFirstVisiblePosition(); int lastVisible listView.getLastVisiblePosition(); if (position firstVisible position lastVisible) { // 条目在可见范围内直接复用现有视图 View itemView listView.getChildAt(position - firstVisible); ViewHolder holder (ViewHolder) itemView.getTag(); holder.tvName.setText(newProduct.getName()); holder.tvPrice.setText(newProduct.getPrice()); } else { // 条目在屏幕外更新数据源即可下次滚到时自然生效 productList.set(position, newProduct); } }这个技巧在购物车修改数量、商品收藏状态切换等场景下非常有用。它可以避免因为一个条目的变化导致整个列表重新绘制。需要注意的一点是getChildAt的索引是相对屏幕的不是数据下标所以必须用position - firstVisible做换算。如果目标位置在屏幕之外直接改数据源就行因为 ListView 滚动到那里时会自动调用getView拉取最新数据。从那以后我每次写完一个 ListView 项目都会强制自己走一遍这个检查清单getView里有没有做耗时操作convertView是否真的复用了数据更新后有没有调用刷新方法图片加载有没有指定占位图。这四个问题如果都处理干净这个列表的性能和稳定性基本就有了保障。这份购物商城界面源码的价值在于把 Adapter 模式完整地走了一遍你把它跑通之后再上手 RecyclerView 时会发现套路几乎一模一样——替换掉 ListView 的对应 API就是一份全新的知识点。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号