用Kotlin+XML复现微信界面:RecyclerView与Fragment实战指南

发布时间:2026/10/17 8:05:05
用Kotlin+XML复现微信界面:RecyclerView与Fragment实战指南 简介一份基于Android Studio打造的仿微信界面完整工程面向Android入门与中级开发者通过实战项目演示如何搭建类似微信的社交应用UI帮助解决界面设计不知从何下手的问题。资源共1041个文件、压缩包约24.25MB涵盖XML布局、Java源码、PNG图片、Material Design组件资源以及编译生成的flat/dex/class文件和可直接运行的APK安装包便于对照源码与运行效果学习。目前已有3103人学习下载。项目内容覆盖ConstraintLayout等布局设计、BottomNavigationView与RecyclerView组件应用、自定义聊天气泡、适配器与数据绑定、Glide图片加载、主题配色及动画过渡等关键知识点完整工程结构、可运行APK及版本维护记录让读者既能动手实践也能快速验证界面效果是模仿高完成度移动端UI、巩固Android开发技能的实用参考。1. 把微信界面搬进 Android Studio一份能跑的 UI 复现资源很多人以为复刻微信界面是照着截图堆控件真正动手才发现难的是状态切换、列表复用和 Fragment 管理。这份资源是一个完整的 Android Studio 工程用原生 XML 和 Kotlin 实现了微信四个主 Tab 的界面骨架聊天列表可以滚动发现页能点进二级页面底部导航切换不会丢状态。它解决的不是“画得像不像”的问题而是把界面从静态图变成可交互 Demo 的完整流程。适合刚学完 Android 基础、想拿 UI 项目练手的人也适合需要快速搭一个聊天类 App 原型的开发者。工程不依赖第三方 UI 库全部用官方组件导入就能跑。2. 跑通工程与认清资源结构三个文件夹看出界面骨架2.1 环境准备与工程导入先说环境。这个工程用的最低 SDK 版本是 21也就是 Android 5.0覆盖了绝大多数还在用的设备。我在 Android Studio 4.2 和最新的 Hedgehog 版本上都跑过Gradle 版本用的是 7.0 以上都能兼容的配置。如果你本地的 Gradle 版本和工程里不一致导入时 Android Studio 会提示直接让它自动下载对应版本就行不用手动改。打开工程之后先等 Gradle 同步完成。这里有一个常见做法是同步前先把本地的gradle-wrapper.properties打开看一眼确认 distributionUrl 指向的版本号。有些时候同步失败不是因为代码有问题而是 Gradle 下载超时尤其是在网络不稳定的环境里。你可以在文件里把 distributionUrl 改成你本地已存在的 Gradle 版本这样能省很多等待时间。// gradle-wrapper.properties 关键行 distributionUrlhttps\://services.gradle.org/distributions/gradle-7.4-bin.zip这个配置决定了 Gradle 的版本。7.4 搭配 Android Gradle Plugin 7.0 以上是稳定的组合。如果你的环境里已经有其他版本可以把这行改成对应的本地路径比如file:///D:/gradle-7.4-bin.zip。工程的 build.gradle 里也建议把依赖仓库加上 google() 和 mavenCentral()缺了这两个仓库依赖拉不下来整个界面工程就跑不起来。2.2 资源结构layout、drawable、values 各管什么资源包结构就三个文件夹是核心layout放界面骨架drawable放形状和背景values放颜色和字符串。新手最容易搞混的是 drawable 里不只是图片还有 XML 定义的形状比如圆角矩形、边框、渐变微信界面里很多灰色分割线和绿色按钮的背景都是这么画的不需要一张图。!-- drawable/bg_wechat_green.xml -- shape xmlns:androidhttp://schemas.android.com/apk/res/android solid android:color#07C160 / corners android:radius6dp / /shape这个 shape 就是微信绿色按钮的背景。solid指定填充色corners指定圆角半径单位是 dp。这个文件在 layout 里通过android:backgrounddrawable/bg_wechat_green引用。为什么要用 XML 而不是直接放一张图片因为 XML 体积只有几十字节图片动不动几百 KB而且 XML 可以配合状态选择器做按压缩放后面第 5 章会讲。values 文件夹里的 colors.xml 定义了界面主色调。微信的主色不是纯绿而是#07C160这个颜色在按钮、Tab 选中态、未读角标上都用得到。strings.xml 里则是界面文案比如“微信”“通讯录”“发现”“我”四个 Tab 的名字。2.3 依赖配置为什么只用官方库也能复现这个工程的 build.gradle 里依赖很少核心只有三个androidx.appcompat、androidx.recyclerview、com.google.android.material。没有引入图片加载库没有引入数据库也没有引入网络框架。dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.recyclerview:recyclerview:1.3.0 implementation com.google.android.material:material:1.9.0 }为什么只用这些就够了因为微信界面本身就是一个静态 UI 工程数据都是写死的本地 Mock 数据不需要网络请求。图片加载用的是ImageView.setImageResource()直接引用 drawable 里的资源这对练手项目来说是合理的取舍。很多人一上来就引入 Glide、OkHttp反而把界面的核心逻辑淹没了。这个工程的好处就是干净每一行代码都服务于界面本身你不需要去理解与界面无关的第三方库。Material 库在这里主要提供BottomNavigationView用来实现底部四个 Tab。如果不引入 Material 库也可以自己用 LinearLayout 加四个 TextView 实现但那样要处理选中态的切换逻辑代码量会多出不少。用官方组件的好处是稳定而且处理了无障碍和触控反馈。3. 聊天列表与发现页RecyclerView 和 Fragment 的核心写法3.1 聊天列表Adapter 的写死数据与 item 复用聊天列表是微信界面里最核心的部分它用 RecyclerView 实现。RecyclerView 是 Android 官方的高性能列表组件它只创建屏幕内能看到的 item滚动出去的就交给回收池复用。这个机制用好了几千条数据也不会卡顿。先看布局文件。聊天列表的 item 是一个水平方向的 LinearLayout左边是头像中间是昵称和最后一条消息右边是时间。!-- item_chat.xml -- LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding12dp ImageView android:idid/iv_avatar android:layout_width48dp android:layout_height48dp android:srcdrawable/ic_avatar_default / LinearLayout android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:layout_marginStart12dp android:orientationvertical TextView android:idid/tv_name android:layout_widthwrap_content android:layout_heightwrap_content android:textSize16sp android:textColor#000000 / TextView android:idid/tv_message android:layout_widthwrap_content android:layout_heightwrap_content android:textSize13sp android:textColor#999999 android:ellipsizeend android:maxLines1 / /LinearLayout TextView android:idid/tv_time android:layout_widthwrap_content android:layout_heightwrap_content android:textSize11sp android:textColor#CCCCCC / /LinearLayout这里有个关键技巧中间的 LinearLayout 用layout_width0dp配合layout_weight1意思是让它占据除头像和时间外的所有剩余宽度。这样做的好处是不管屏幕多宽时间始终贴在最右边不会被挤掉。头像是固定 48dp 正方形微信的头像实际显示是圆形但这里为了减少代码量先用矩形占位等第 5 章再改成圆形。Adapter 的代码也不复杂核心就三件事绑定数据、复用 ViewHolder、处理点击。class ChatAdapter( private val data: ListChatItem, private val onItemClick: (ChatItem) - Unit ) : RecyclerView.AdapterChatAdapter.ChatViewHolder() { class ChatViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val ivAvatar: ImageView itemView.findViewById(R.id.iv_avatar) val tvName: TextView itemView.findViewById(R.id.tv_name) val tvMessage: TextView itemView.findViewById(R.id.tv_message) val tvTime: TextView itemView.findViewById(R.id.tv_time) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { val view LayoutInflater.from(parent.context) .inflate(R.layout.item_chat, parent, false) return ChatViewHolder(view) } override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { val item data[position] holder.tvName.text item.name holder.tvMessage.text item.lastMessage holder.tvTime.text item.time holder.itemView.setOnClickListener { onItemClick(item) } } override fun getItemCount(): Int data.size }这段代码里有一个新手容易忽略的点onCreateViewHolder里的inflate第三个参数必须是false意思是不要立即把 item 添加到父容器。如果写成trueRecyclerView 在复用时会重复添加 View导致界面显示错乱甚至崩溃。这个参数的含义是“只创建但不挂载”真正的挂载时机由 RecyclerView 自己控制。getItemCount()直接返回数据列表大小。数据来源是写死的比如定义一个listOf(ChatItem(张三, 晚上一起吃饭, 21:12), ...)这样说清楚了数据从哪来后面接真实数据源时只需要把ListChatItem换成数据库查出来的结果Adapter 不用改任何逻辑。3.2 发现页Grid 网格与页面跳转的布局写法发现页和聊天列表不一样它更接近一个“功能入口集合”。微信的发现页是指纹支付、朋友圈、扫一扫这些功能的入口列表。工程里用的是 GridView 的变体——RecyclerView 配合GridLayoutManager每行显示 4 个图标。val layoutManager GridLayoutManager(this, 4) rvDiscover.layoutManager layoutManagerGridLayoutManager的第二个参数是列数这里传 4就是每行 4 个图标。这个数字不是固定死的如果你想要每行 5 个改成 5 就行但要注意图标宽度。表格在手机上单行显示 4 个是最舒服的图标至少能保持 60dp 以上的宽度点击不容易误触。发现页的另一种布局是一整行带箭头的条目比如“朋友圈”“扫一扫”“摇一摇”这些。每一行是一个横向 LinearLayout左边一个图标加文字右边一个灰色小箭头。这个箭头可以直接用 TextView 写一个字符也可以用矢量图。工程里用的是矢量图好处是放大缩小不模糊。整个发现页不能用一个 RecyclerView 混着写因为两种条目的高度不一样。常见做法是外层用 ScrollView 套一个垂直 LinearLayout里面一部分是 GridLayoutManager 的 RecyclerView一部分是条目列表。但 ScrollView 里套 RecyclerView 会有滑动冲突需要把 RecyclerView 的nestedScrollingEnabled设置为false这样它的滚动事件就会传递给外部 ScrollView。rvDiscover.isNestedScrollingEnabled false这行代码是很多人的“玄学”修复点。不写的话发现页会出现“只能滑 RecyclerView滑不动整个页面”的情况。原因是 RecyclerView 内部有独立的滑动机制嵌套在 ScrollView 里时会抢夺手势。关闭它的嵌套滚动后手势就统一交给 ScrollView 处理了。3.3 底部导航Fragment 的切换与状态保持微信的底部导航是四个 Tab微信、通讯录、发现、我。工程里用BottomNavigationView加 Fragment 实现。这里有三种写法但工程用的是最稳的一种show/hide方式。先看布局文件里的 BottomNavigationViewcom.google.android.material.bottomnavigation.BottomNavigationView android:idid/bottom_nav android:layout_widthmatch_parent android:layout_heightwrap_content app:menumenu/menu_bottom_nav /menu 文件里定义四个 Tab每个 Tab 有一个 id、一个标题和一个图标。图标的选中态和未选中态需要用 selector 来控制否则你会发现 Tab 点击后颜色没变化。selector 文件放在 drawable 文件夹里!-- drawable/selector_tab_wechat.xml -- selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_checkedtrue android:drawabledrawable/ic_wechat_selected / item android:state_checkedfalse android:drawabledrawable/ic_wechat_normal / /selectorselector 的原理很简单系统根据 View 的状态自动选择对应的 drawable。state_checked是 BottomNavigationView 的选中状态字段选中时显示绿色图标未选中时显示灰色图标。如果你发现图标不切换基本可以断定是 selector 文件没有正确引用到 menu 里的 icon 属性或者两个图标的尺寸不一致导致看起来没变化。Fragment 的切换用的是showFragment方法private fun switchFragment(position: Int) { supportFragmentManager.beginTransaction().apply { hide(currentFragment) if (fragments[position].isAdded) { show(fragments[position]) } else { add(R.id.fragment_container, fragments[position]) } }.commit() currentFragment fragments[position] }这段代码的关键逻辑是如果目标 Fragment 已经被添加过就 show 它没添加过才 add。这里不能直接用replace因为 replace 会销毁旧 Fragment 的视图再切回来时 Fragment 要重新走onCreateView界面上的滚动位置、输入框内容全部丢失。用 show/hideFragment 的实例还在内存里只是视图隐藏了切回来是瞬间完成的。Fragment 列表的定义是提前在onCreate里用数组存好的fragments arrayOf(WeChatFragment(), ContactsFragment(), DiscoverFragment(), MineFragment())这种写法要求每个 Fragment 必须是同一个 FragmentManager 管理的而且不能是匿名内部类。如果你用匿名类isAdded的判断可能会失效因为每次 new 出来的对象 hash 值不一样。4. 避坑XML 布局与资源引用的九个常见翻车点4.1 编译与资源引用类问题坑 1资源文件命名不规范导致 R 文件生成失败现象编译时报Task :app:mergeDebugResources FAILED错误信息里指向一个图片资源或 layout 文件。原因Android 资源文件名只允许小写字母、数字和下划线。比如把头像图片命名成ic_Avatar.pngR 文件直接编译不过。解决全部改成小写命名ic_avatar.png。检查方法Android Studio 里打开 Project 视图扫描一遍 drawable 和 layout 下的文件只要看到大写字母全部重命名。这个问题在多人协作时特别容易出现因为 Windows 和 Mac 对文件大小写的敏感度不同Windows 上叫ICON.png能跑到 Mac 上可能就崩了。坑 2布局里引用了不存在的 drawable 资源现象编译通过但运行时界面空白Logcat 里报Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7f0700xx。原因布局 XML 里写了一个drawable/ic_test但 drawable 文件夹下根本没有这个文件编译时 Lint 没有拦截。解决逐个检查 XML 里所有drawable/引用在 drawable 文件夹下确认文件存在。这里有一个血泪经验不要相信 Android Studio 的代码补全手写引用最容易出这种问题。坑 3Selector 中状态顺序搞反点击无反馈现象按钮点击时没有按压效果或者明明没选中却显示选中色。原因selector 文件里state_checked为 true 的 item 写在了 false 的 item 下面。Android 的 selector 是自上而下匹配的一旦上面的 item 没有指定状态它就无条件匹配了下面的 item 永远不执行。解决把有状态条件的 item 写在最上面无条件兜底的写在最下面。4.2 布局与交互类问题坑 4ConstraintLayout 约束缺失控件位置乱飞现象在预览视图里看着正常一跑真机按钮跑到屏幕左上角重叠。原因只用拖拽方式添加约束忘了检查每个控件是否有完整的app:layout_constraintLeft_toLeftOf和app:layout_constraintTop_toTopOf。解决不要在预览视图里拖控件直接写 XML。每添加一个控件立刻写下它相对父布局的约束条件。写完一个页面后用Inspect Code跑一遍能自动找出没有约束的控件。坑 5RecyclerView 高度写死为 match_parent列表显示不全现象RecyclerView 下面还有内容但列表只显示了一屏下面的被截断了。原因RecyclerView 在布局里设置了android:layout_heightmatch_parent它自己就把整个页面占满了。解决改成wrap_content如果需要嵌套在 ScrollView 里还要加上android:nestedScrollingEnabledfalse。坑 6Fragment 切换后崩溃报 FragmentManager 状态错误现象快速点击底部导航切换 Tab偶尔崩溃报Can not perform this action after onSaveInstanceState。原因在 Activity 的onSaveInstanceState之后动了 FragmentManager比如在异步回调里执行了beginTransaction().commit()。解决切换操作尽量放在onResume或用户触摸事件的回调里不要在异步线程里直接 commit。如果实在要 commit可以改用commitNow()但这个方法不能连续调用。5. 进阶压感反馈和圆角背景让界面不再像 Demo5.1 用 StateListDrawable 给列表项加按压态很多 Demo 界面最大的破绽是列表点击了没有任何反应看起来就不像真的 App。微信的列表项点击时背景会变浅灰色松手后恢复。这个效果用 selector 就能实现。在 drawable 文件夹下建一个selector_item_click.xmlselector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_pressedtrue color android:color#EBEBEB / /item item android:color#FFFFFF / /selector然后在 item 布局的根布局上设置android:backgrounddrawable/selector_item_click注意这个文件的根节点是 selector不是 shape因为它的 child 是 color。如果你想让按压背景有圆角可以把 color 换成 shape每个 item 里写一个 shape 子节点。这个方案比在代码里用setOnTouchListener写按压缩放要稳定得多因为它是系统级的 drawable 切换不占用主线程也不会和 RecyclerView 的复用机制冲突。有人试过在 Adapter 的onBindViewHolder里动态设置背景滚动时会出现背景闪烁就是因为复用的 item 背景被反复覆盖。5.2 圆形头像与带数字的角标头像改成圆形去掉 Glide 也能做。Android 里有两个方案ShapeableImageView和RoundedBitmapDrawable。工程里用的前者因为它是 Material 库自带的一个 ImageView 子类。com.google.android.material.imageview.ShapeableImageView android:idid/iv_avatar android:layout_width48dp android:layout_height48dp android:srcdrawable/ic_avatar_default app:shapeAppearanceOverlaystyle/ShapeAppearanceOverlay.App.Circle /对应的 style 定义为style nameShapeAppearanceOverlay.App.Circle parent item namecornerFamilyrounded/item item namecornerSize50%/item /stylecornerSize设为 50% 就是圆的直径等于控件宽度控件是正方形时就是圆形。如果控件是长方形这个值会变成椭圆。未读角标是一个红色的数字微信右上角那种。自定义实现也不算复杂写一个BadgeTextView继承 TextView设置背景是个红色圆形 shape数字用setText传入。位置用 FrameLayout 相对父容器右上角对齐设置一个负边距让它往外突出一点。这里有一个细节我折腾了很久角标数字大于 99 时微信显示的是“99”。这个逻辑要在 Activity 里写不能在布局里写死if (count 99) { badge.text 99 } else { badge.text count.toString() } badge.visibility if (count 0) View.VISIBLE else View.GONE5.3 复用这套布局到自己的模拟项目做完这份界面复现你会发现它其实是一套可以被抽出来的 UI 组件库圆形头像、角标、Selector 背景、圆角按钮。我在自己的模拟项目 X 里就直接把 drawable 目录下的背景文件复制过去了Adapter 的 ViewHolder 写法也是从这套代码里改的。从那以后我每次做界面都会强制走一遍全套状态测试——按压、选中、禁用、异常数字把这几个状态测完再交付几乎不会在评审时被挑出 UI 问题。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询