news 2026/10/7 18:51:43

Android Studio购物商城界面开发:Fragment+RecyclerView实现多Tab导航

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android Studio购物商城界面开发:Fragment+RecyclerView实现多Tab导航

简介:面向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 能切换,再做商品的展示,最后才回头处理这些视觉细节——顺序反了容易什么都想弄,结果什么都毛糙。希望这篇对你起步有帮助。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 18:51:13

20种运放典型电路汇总:从虚短虚断到实战避坑指南

运放这东西&#xff0c;说难不难&#xff0c;说简单也不简单。难的是光看数据手册&#xff0c;你不知道那些参数跟自己手头的电路到底有什么关系&#xff1b;简单的是只要抓住它几条核心规律&#xff0c;绝大多数典型电路都能在几分钟内直接“推”出来。这篇就把20种最常见的运…

作者头像 李华
网站建设 2026/10/7 18:51:04

impeccable深度拆解:从词根到写作实战的高级词汇学习指南

很多人第一次看到 impeccable 这个词&#xff0c;是在美剧里的台词里&#xff0c;或者外媒的文章标题里。我当时是在一份英文产品文档里碰到的&#xff0c;写的是 “The design remains impeccable under extreme load”&#xff0c;第一反应是查字典&#xff0c;记住“无可挑剔…

作者头像 李华
网站建设 2026/10/7 18:51:03

家庭记账本系统毕设:SSM+微信小程序从拆包到答辩避坑指南

简介&#xff1a;基于Java SSM框架与微信小程序构建的家庭记账本系统&#xff0c;是一份适合毕业设计、课程设计及期末大作业的完整项目包。系统覆盖用户登录注册、账目录入、查询、数据统计与报表生成等核心功能&#xff0c;界面简洁、操作便捷&#xff0c;项目已在导师指导下…

作者头像 李华
网站建设 2026/10/7 18:50:09

手机PDF转换成Word免费版!零套路无水印,新手直接用

日常办公、学生整理资料&#xff0c;经常会遇到需要把PDF转换成Word的情况。但大部分转换工具要么需要付费开会员、要么转换后自带水印、要么排版错乱&#xff0c;十分影响使用。很多人找了半天&#xff0c;都没找到真正免费、操作简单、适配手机的PDF转Word方法。今天给大家整…

作者头像 李华
网站建设 2026/10/7 18:49:28

Java炸弹人课设源码拆解:Swing游戏开发与碰撞检测

简介&#xff1a;一款基于Java开发的炸弹人小游戏源码包&#xff0c;是面向计算机相关专业学生的课程设计和毕业设计参考作品。项目采用图形界面与事件驱动架构&#xff0c;完整实现了经典炸弹人的玩法流程&#xff0c;包括地图生成、玩家移动、怪物追踪、爆炸范围判定以及胜负…

作者头像 李华
网站建设 2026/10/7 18:48:01

国产AI突围:MoE架构如何省算力降能耗,落地全工业场景

1. 国产AI突围的底层逻辑&#xff1a;为什么能源和架构成了胜负手过去两年&#xff0c;我一直在跟踪国内大模型从实验室走向产线的全过程。说实话&#xff0c;最初大家比拼的是参数规模&#xff0c;谁的模型大谁就有话语权。但到了2024年下半年&#xff0c;风向明显变了——能源…

作者头像 李华