简介:面向Android初学者的购物商城界面源码项目,基于ListView与BaseAdapter实现商品列表展示、自定义条目布局及滚动加载等核心逻辑,适合正在学习Android UI设计、数据绑定与适配器模式的开发者将其作为对照练习或课程设计参考。压缩包共518个文件,体积约9.05MB,内含完整的Android工程:java文件负责业务逻辑与Adapter实现,xml文件定义界面布局及ListView、item_product等组件结构,gradle及manifest文件用于构建配置,png、jpg图片作为商品配图,另附可直接安装的apk产物,目录划分清晰,导入Android Studio后即可运行并查看效果。目前已有13542人浏览学习。通过阅读和运行该项目,可以具体掌握自定义Adapter中getCount、getItem、getItemId、getView四个方法的作用与编写方式,学会在item产品条目中通过ImageView与TextView组合展示商品图片、名称和价格,同时还能结合OnScrollListener实现滚动到底部加载更多商品的扩展功能,源码结构简洁、注释明确,十分适合初学者仿写并在此基础上增加下拉刷新、网络图片加载等更丰富的商城交互功能。
1. 一个用 Android Studio 做购物商城界面的最小闭环:它到底在解决谁的什么问题
拿到「Android Studio 实现简单的购物商城界面源码」这个需求,大多数人是刚学完四大组件和布局,想用一套完整界面把知识串起来,或者课程设计、毕业设计需要交一个能跑的 App。这个标题的关键词不在「购物」而在「界面」——它不涉及真实的支付、订单、库存,而是把首页商品流、分类、购物车、我的这四个 Tab 的骨架搭出来,让 App 装上能看、能点、能滑动。解决的是「从空工程到可演示 UI」这一步,适合还没进公司、没接触过大型项目的 Android 初学者,以及需要一个干净模板来魔改成自己毕设的人。我下面这套方案不引第三方 UI 库,全部用 Android 自带的布局和控件,在 Android Studio 里新建一个空工程就能复现。
2. 拆页面结构:首页、分类、购物车、我的四项 Tab 怎么落地到 Fragment + 底部导航
2.1 为什么这个标题绕不开 Fragment 和 BottomNavigationView
做一个带底部导航的商城界面,常见做法有两种:一种是用多个 Activity 各自维护页面,另一种是用单 Activity + 多 Fragment。我强烈建议初学者直接走第二种,原因不复杂——购物商城界面的四个 Tab 有大量共享状态,比如购物车角标、登录态、搜索关键词,如果用多个 Activity,这些数据得靠 Intent 传来传去或者自己写全局单例,代码很快就臭了。而 Fragment 本身就活在 Activity 的容器里,切换只是 replace 或 hide/show,状态保留也更自然。
Android Studio 里新建工程时,模板自带一个MainActivity,但默认模板是单页面。我们需要把它改成承载四个 Fragment 的容器。底层导航我选BottomNavigationView而不是TabLayout,因为后者更偏向页面内切换,前者才是标准的 App 级导航。两者的区别在 Material 规范里讲得很清楚:BottomNavigationView 适合 3 到 5 个顶级目的地,购物商城的首页、分类、购物车、我的正好落在 3 到 5 这个区间。
2.2 build.gradle 里先放好依赖:别等到 sync 报错才回头
打开工程后,第一件事不是写布局,而是确认build.gradle(Module 级别)里的依赖。很多新手在这里踩的第一个坑就是依赖缺失,BottomNavigationView用的时候报「无法解析符号」,原因往往是只引了appcompat没引material。
// 文件路径:app/build.gradle(Module 级别的 build.gradle) dependencies { // AndroidX 基础库,工程默认自带 implementation 'androidx.appcompat:appcompat:1.6.1' // Material 组件库:BottomNavigationView、CardView 都从这里来 implementation 'com.google.android.material:material:1.11.0' // 约束布局:用来做复杂页面的自适应 implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // Fragment 官方库 implementation 'androidx.fragment:fragment-ktx:1.6.2' // RecyclerView 官方库 implementation 'androidx.recyclerview:recyclerview:1.3.2' }我一般会先把依赖注释写清楚,再让 Gradle 去 sync,这样报错时能很快定位是哪一行没拉下来。上面用的是我本地能拉到的一组常见稳定版本,你完全可以按 Android Studio 提示的最新版本替换——但注意,material和appcompat这两个库如果版本相差太远,偶尔会出现Theme.MaterialComponents找不到的问题,这时优先统一升到当前稳定版。参数上真正需要记住的就一条:BottomNavigationView必须搭配 Material 主题,否则运行时会直接崩,报You need to use a Theme.AppCompat theme之类的错。
2.3 搭建容器布局:activity_main.xml 里的上下结构
Activity 的布局结构很简单,上面一个FragmentContainerView占满剩余空间,下面一个BottomNavigationView固定高度。这里有个细节:FragmentContainerView是官方推荐的 Fragment 容器,比直接写<fragment>标签更灵活,因为它允许你在代码里动态替换,而不是把某个 Fragment 写死在布局里。
<!-- 文件路径:res/layout/activity_main.xml --> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 容器:四个 Fragment 都替换到这里 --> <androidx.fragment.app.FragmentContainerView android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <!-- 底部导航:固定四 Tab --> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" app:labelVisibilityMode="labeled" app:menu="@menu/menu_bottom_nav" /> </LinearLayout>这里layout_weight="1"是关键参数,它让容器吃掉底部导航以外的所有垂直空间。app:labelVisibilityMode="labeled"表示始终显示文字标签,购物商城这种场景下我一般建议保持开启,因为「首页」「分类」这类两字词不用猜。如果你更在意视觉清爽,改成unlabeled只显示图标也行,但不是这个标题下该优先做的事。
底部导航的菜单需要在res/menu/menu_bottom_nav.xml里定义,注意item的title和icon都要配齐,否则导航栏显示会变得很怪。图标我直接用系统自带的android.R.drawable里的图标,省去自己找图标的麻烦。
<!-- 文件路径:res/menu/menu_bottom_nav.xml --> <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/nav_home" android:icon="@android:drawable/ic_menu_compass" android:title="首页" /> <item android:id="@+id/nav_category" android:icon="@android:drawable/ic_menu_sort_by_size" android:title="分类" /> <item android:id="@+id/nav_cart" android:icon="@android:drawable/ic_menu_agenda" android:title="购物车" /> <item android:id="@+id/nav_mine" android:icon="@android:drawable/ic_menu_myplaces" android:title="我的" /> </menu>说实话,android.R.drawable里的图标比较素,但作为源码演示足够了,并且不引入任何资源版权问题。等你的界面能跑了,再换成自己设计的 selector 图标也不迟。
3. 首页布局实战:Toolbar 搜索、商品卡片、双列网格怎么搭出来
3.1 先画骨架:fragment_home.xml 的层级安排
首页是购物商城界面的脸面,也是这个标题下工作量最重的一块。它的标准结构从上到下是:搜索栏(Toolbar 或搜索框)、Banner 轮播位、分类快捷入口、商品瀑布/双列网格。其中 Banner 轮播如果做复杂了很耗时,我会先放一个ViewPager2的原生轮播,不引第三方 Banner 库;商品展示用RecyclerView配GridLayoutManager,双列网格。这个组合也是商城类 App 最常见的实现路径。
先写层级骨架:
<!-- 文件路径:res/layout/fragment_home.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="match_parent" android:orientation="vertical"> <!-- 顶部搜索栏 --> <LinearLayout android:layout_width="match_parent" android:layout_height="48dp" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="12dp"> <EditText android:id="@+id/search_input" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@drawable/bg_search_box" android:hint="搜索商品" android:paddingHorizontal="12dp" android:maxLines="1" /> <TextView android:id="@+id/search_btn" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="搜索" android:textColor="#FF5722" android:paddingHorizontal="12dp" /> </LinearLayout> <!-- 内容区 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/home_recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:padding="8dp" /> </LinearLayout>为什么内容区直接上 RecyclerView 而不是 ScrollView 里套多个布局?因为商城首页本质上是一个长列表,RecyclerView 天然回收视图、支持多种 item type,后续往里加 Banner、加快捷入口、加商品条目都只是给 Adapter 增加 item 类型的事。如果一开始就用 ScrollView 把所有内容写死,界面确实很快能出来,但后续每一步改动都会变成在泥潭里挣扎。我见过太多课程设计就是毁在 ScrollView 套三四个 ListView 上,滑动手势冲突、布局重绘卡顿,全是自己给自己埋的雷。已有 1 个赞。这个细节你可以直接抄走——商城首页一律 RecyclerView 多 item 方案,别用 ScrollView + ListView 嵌套。
3.2 商品卡片:CardView 与圆角、阴影参数的设置
商品卡片是首页的核心视觉单元。我用 Material 库里的CardView作为容器,里面放商品图、标题、价格。CardView 自带圆角和阴影,比手动写 shape drawable 省事得多。
<!-- 文件路径:res/layout/item_product_card.xml --> <?xml version="1.0" encoding="utf-8"?> <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" app:cardBackgroundColor="#FFFFFF" app:cardCornerRadius="12dp" app:cardElevation="2dp" app:strokeWidth="0dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 商品占位图:先用纯色背景,后面换成图片加载 --> <View android:layout_width="match_parent" android:layout_height="120dp" android:background="#F5F5F5" /> <!-- 商品标题 --> <TextView android:id="@+id/tv_product_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="2" android:paddingHorizontal="8dp" android:paddingTop="8dp" android:textColor="#333333" android:textSize="14sp" /> <!-- 价格 --> <TextView android:id="@+id/tv_product_price" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="8dp" android:paddingTop="4dp" android:paddingBottom="8dp" android:textColor="#FF5722" android:textSize="16sp" android:textStyle="bold" /> </LinearLayout> </com.google.android.material.card.MaterialCardView>这里三个参数值得单独拎出来讲。cardCornerRadius="12dp"决定了卡片四个角的圆润程度,商城 App 一般在 8dp 到 16dp 之间,太小显得生硬,太大显得玩具感;cardElevation="2dp"控制阴影高度,关系到立体感的强弱,双列网格里我建议 2dp 到 3dp 就好,阴影太重会让列表显得脏;strokeWidth="0dp"是关键——默认 MaterialCardView 会带一圈描边,你在预览里看到的淡灰色边框就是它,设成 0 才得到真正的纯白卡片。这三个参数适合照抄,因为它们对应的是绝大多数商城界面的视觉基线。已有 3 个赞。
3.3商品瀑布流?先别上 StaggeredGridLayoutManager
商品列表该用GridLayoutManager还是StaggeredGridLayoutManager(瀑布流),这个选择题其实很影响实现复杂度。瀑布流的视觉效果好,商品卡片参差不齐,很像淘宝、京东的商品流,但代价是:item 高度不一致会导致复用时的闪烁,新手很难控制;而且瀑布流对图片高度依赖,如果拿到的商品图全是正方形,瀑布流和网格没有任何区别。
我给的方案是先用GridLayoutManager配spanCount=2,让所有商品卡片统一高度。等你把整个商城的骨架调通,再改成瀑布流也只是换个 manager 的事。这样做的理由很现实:界面的第一要务是「每个 Tab 都能跑」,而不是「首页漂亮得能上架」。视觉效果可以后面迭代,跑不起来的界面连迭代的资格都没有。
4. 让界面"活"起来:商品数据、Adapter、点击事件的最少代码
4.1 先写死后台数据:一个 Data 类和一个模拟数据源
商城界面的数据暂时不接后端,常见做法是先写一个Product数据类,再用一个静态方法返回写死的商品列表。别小看这一步——把数据源独立出来,以后换成从 JSON 接口读取,只需要改数据源这一层,界面代码一行都不用动。这也是我在这个标题下反复强调的「边界」思想:界面和数据的边界要清晰。
// 文件路径:model/Product.kt // 商品数据类:只保存商品展示所需的最小字段 data class Product( val id: Int, // 商品唯一标识 val name: String, // 商品名称 val price: String, // 价格,用 String 而不是 Double,避免精度问题 val imageResId: Int // 图片资源 ID )字段说明:price用 String 存,是因为购物商城界面的价格往往有「¥9.90」这种带格式的展示需求,用 Double 存再格式化反而容易出问题;imageResId先指向 drawable 里的占位图,后面接入 Glide 加载网络图时,把imageResId换成imageUrl: String即可。
模拟数据我直接放在一个DataSource对象里:
// 文件路径:data/DataSource.kt object DataSource { // 模拟商品列表:先用 20 条数据让滚动效果跑起来 fun getProducts(): List<Product> = List(20) { index -> Product( id = index, name = "商品标题 ${index + 1},这里展示两行以内的描述", price = "¥${String.format("%.2f", (index + 1) * 9.9)}", imageResId = R.drawable.ic_placeholder_image ) } }这段代码里最关键的是List(20) { index -> ... },Kotlin 里这个语法会一次性生成 20 条商品数据,够 RecyclerView 滑出翻页效果。新手容易犯的错是只造 3 条数据,结果列表都撑不满一屏,还以为自己写错了。模拟数据至少要 20 条,这是让「滚动」这件事有体验可言的最低线。
4.2 商品 Adapter:ViewHolder 和点击回调的写法
RecyclerView的 Adapter 是这个工程里最需要理解透的一块代码。它负责三件事:创建 ViewHolder、把数据绑定到 ViewHolder、告诉列表有多少条数据。点击事件则通过回调接口暴露给外部,不要让点击逻辑写在 Adapter 里面,否则以后维护起来很头疼。
// 文件路径:adapter/ProductAdapter.kt class ProductAdapter( private val products: List<Product>, private val onItemClick: (Product) -> Unit // 点击回调,由外部传入 ) : RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() { // 1. 创建 ViewHolder:把 item_product_card.xml 解析成 View override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_product_card, parent, false) return ProductViewHolder(view) } // 2. 绑定数据:把 Product 的字段塞进对应控件 override fun onBindViewHolder(holder: ProductViewHolder, position: Int) { val product = products[position] holder.nameView.text = product.name holder.priceView.text = product.price } // 3. 条目数量:RecyclerView 靠这个决定渲染多少条 override fun getItemCount(): Int = products.size inner class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val nameView: TextView = itemView.findViewById(R.id.tv_product_name) val priceView: TextView = itemView.findViewById(R.id.tv_product_price) init { // 条目点击事件:把当前的 Product 通过回调传出去 itemView.setOnClickListener { val position = bindingAdapterPosition if (position != RecyclerView.NO_POSITION) { onItemClick(products[position]) } } } } }这段代码里有两个细节是实战中很重要但新手容易忽略的。第一个是bindingAdapterPosition而不是getAdapterPosition(),前者考虑了列表增删后的位置变化,不容易崩;第二个是NO_POSITION的判断,因为 RecyclerView 的 item 可能被复用到屏幕外,此时位置无效,不做判断就会抛IndexOutOfBoundsException。
还有个小问题:用findViewById每次绑定都得找层控件,性能有点浪费,但在这个标题下的数据量级完全够用,ViewBinding 属于进阶优化项。如果你已经把 ViewBinding 用熟了,可以直接替换成 ViewBinding 版本。
4.3 MainActivity 里组装:四个 Fragment 的切换与出现次数问题
最后是把所有零件装进MainActivity。这里有个高频大坑:很多人在onNavItemSelected里直接replace(),结果每次切换都会重新创建 Fragment,购物车滚动的浏览位置,切走再回来就没了。正确做法是show()/hide()配合首次添加。我一般写成下面这样。
// 文件路径:MainActivity.java(用 Java 写是为了照顾还没转 Kotlin 的读者) public class MainActivity extends AppCompatActivity { private final Fragment[] fragments = new Fragment[4]; private int currentIndex = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BottomNavigationView bottomNav = findViewById(R.id.bottom_nav); bottomNav.setOnItemSelectedListener(item -> { int index = 0; if (item.getItemId() == R.id.nav_home) index = 0; else if (item.getItemId() == R.id.nav_category) index = 1; else if (item.getItemId() == R.id.nav_cart) index = 2; else if (item.getItemId() == R.id.nav_mine) index = 3; switchTab(index); return true; }); // 先把四个 Fragment 实例化好 fragments[0] = new HomeFragment(); fragments[1] = new CategoryFragment(); fragments[2] = new CartFragment(); fragments[3] = new MineFragment(); switchTab(0); } private void switchTab(int index) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); // 隐藏当前 Fragment if (fragments[currentIndex] != null) { ft.hide(fragments[currentIndex]); } // 如果目标 Fragment 还没添加过,就添加进来;否则直接显示 if (!fragments[index].isAdded()) { ft.add(R.id.fragment_container, fragments[index]); } else { ft.show(fragments[index]); } currentIndex = index; ft.commit(); } }hide()和show()的本质是把 Fragment 的 View 从「可见」切到「不可见」,并不是销毁重建,所以 Fragment 里的状态——比如 RecyclerView 的滚动位置、EditText 的输入内容——都还在。代价是这四个 Fragment 的 View 会一直持有内存,但商城界面这种轻量页面完全担得住。如果用replace(),内存省了,体验碎了,我不推荐在底部导航这种场景下用replace做切换。这个方案是 Android 官方 BottomNavigationView 样例一直沿用的思路。
5. 避坑排查:在 Android Studio 里复现这个界面最容易翻车的 6 处
5.1 Gradle 依赖拉不下来:现象是初次构建卡死或报Could not resolve
现象:新建工程后第一次 Sync,经过漫长的等待,最后 Build Output 里报Could not resolve com.google.android.material:material:...。原因多数不是依赖写错,而是 Gradle 默认从 google() 仓库拉取时网络不通畅,或者仓库顺序错了。解决:检查settings.gradle(新版工程)或build.gradle(旧版工程)里的仓库配置,把google()放在mavenCentral()前面;另外把 Gradle 的离线模式关掉——有些人为了提速开了离线,依赖缺失时它不会重新联网拉取。这个位置很容易劝退初学者,因为报错信息里满屏的链接和版本号,看起来非常吓人,但它本质上就是仓库访问的事。
5.2 BottomNavigationView 运行时崩溃:You need to use a Theme.AppCompat theme
现象:编译通过,一装上运行,闪退。Logcat 里明确写着You need to use a Theme.AppCompat theme (or descendant) with this activity。原因:BottmNavigationView 依赖 Material 主题的属性,而新建工程默认主题是Theme.Material3.DayNight.NoActionBar这组直接从 Material Components 库来的主题确实能跑,但实际上如果你把主题不小心改成了@android:style/Theme.Material或干脆没设置主题,就会触发这个崩。解决:打开res/values/themes.xml,确认应用主题的 parent 是Theme.Material3.DayNight或Theme.MaterialComponents.DayNight.DarkActionBar,且MainActivity的 manifest 里没有单独指定别的主题。
5.3 商品图片显示成空白或巨大色块:ImageView的scaleType和占位图尺寸问题
现象:用了ImageView放商品图,有的图片被拉伸得变形,有的直接不显示。原因:网络图或本地图的宽高比和ImageView的布局尺寸不一致,scaleType默认值fitCenter的行为和你预期不同;另外如果ImageView的layout_width和layout_height都写了wrap_content,大图会把布局撑爆。解决:给商品图和 Banner 图的 ImageView 固定高度,比如layout_height="120dp"+layout_width="match_parent",scaleType用centerCrop,它会按中心裁剪并填满控件,不拉伸变形,多余部分切掉。占位图建议在 drawable 里放一张 400x400 的纯色图,不要用 1024x1024 的大图,否则列表滑动时会掉帧——这也是这个标题下最容易忽略的性能问题。
5.4 RecyclerView 嵌套滚动时的卡顿与崩溃:不要在onBindViewHolder里做耗时操作
现象:列表能显示,但快速滑动时明显掉帧,甚至偶发IllegalStateException。原因:很多时候不是 RecyclerView 本身的问题,而是有人在onBindViewHolder里做了文件读取、网络请求或复杂的字符串格式化,这些操作在主线程执行,导致每一帧的绑定时间超过 16ms。解决:onBindViewHolder里只做控件赋值和简单判断;图片加载交给 Glide 或 Coil 这类异步库;如果列表里有大量findViewById,考虑 ViewBinding。排查时可以在 Logcat 里开Systrace或Layout Inspector,看每一帧的耗时,但不用把它当默认操作,先把绑定逻辑变干净就够解决九成问题。
5.5 四个 Tab 切换后数据丢失:replace()的错误使用
现象:首页滚到下面,切到购物车再切回来,首页回到了顶部;购物车加了几件商品,切走再回来,购物车空了。原因:百分百是用了replace()做 Fragment 切换,replace 会销毁旧的 Fragment 重新创建,状态自然全没了。解决:按第 4 章的hide()/show()方案改。注意commit()要放在switchTab方法的最后,不要在show()和hide()之间做其他 FragmentTransaction,否则会报Fragment already added的异常。
5.6 中文字体显示粗劣或出现豆腐块:sp单位与厂商字体
现象:同一行文字在不同手机上大小不一样,或者在某个 ROM 上中文显示成方框。解决:文字大小一律用sp,不要用dp或 px;方框问题多数是布局里写了android:fontFamily="sans"这类不完整的字体声明,删掉即可。这里属于「玄学」问题,不好复现,但大概率是主题里默认字体被改过。遇到字体显示异常,优先检查有没有在themes.xml或 Adapter 里给TextView强行指定过字体。这个坑我在帮别人调代码时见到过两三次,每次都花了不少时间排查。
6. 交作业之前:换肤、空态、间距这三件小事能大幅提升观感
界面能跑通之后,别急着提交,我一般会再做三件事:统一主题色、处理间距和空态、验证极端状态。这三件事不问代码量大小,只问界面观感。
主题色在res/values/themes.xml里的colorPrimary、colorSecondary和colorSurface三个属性里改。商城类 App 的主题色通常是一个高饱和的橙色或红色,比如淘系的橙色#FF5722;你要做的只是把这三个色值换掉,然后重启 App 看整体效果——但注意,colorPrimary会同时影响状态栏颜色、按钮颜色、选中态图标颜色,所以换一种主题色,全 App 的效果都会跟着变。这是「换肤」的最小实现路径,不需要引任何换肤框架。
间距问题我给一个可抄的方案:在RecyclerView上添加一个ItemDecoration,用它统一控制条目间距,而不是靠每条 item 自己的layout_margin。前者是全局的、可以在代码里动态调整;后者会导致早一条晚一条间距不一致。不要小看这个细节,双列网格的间距如果不一致,眼睛的观感会非常明显。
最后是空态。购物车和搜索结果是必然出现空数据的,空列表直接白屏会显得很「残」。在每个 Fragment 里加一个简单的空态 View,放在列表下层,数据为空时显示、有数据时隐藏,代码量不到十行,但观感完全两样。我现在的习惯是:拿到这类界面需求,先搭骨架让四个 Tab 能切换,再做商品的展示,最后才回头处理这些视觉细节——顺序反了容易什么都想弄,结果什么都毛糙。希望这篇对你起步有帮助。
本文还有配套的精品资源,点击获取