
简介这份资源面向具备一定 Java 基础、希望借助 Android Studio 实践界面开发的初学者与进阶学习者围绕仿微信客户端界面这一典型场景帮助读者打通从布局搭建到交互细节的完整链路。压缩包共 1041 个文件约 24.25MB以 flat、json、xml 等构建与布局配置为主辅以 png、jpg 图片素材、java 源码、dex 与 class 编译产物及 gradle 构建脚本完整保留了可运行的工程结构。内容覆盖 ConstraintLayout 布局、BottomNavigationView 底部导航、RecyclerView 聊天列表与适配器数据绑定、聊天气泡自定义 View、Glide 头像加载、VectorDrawable 图标、主题配色与页面切换动画等知识点并附有版本控制与更新记录痕迹。已有 3103 人学习适合对照工程源码理解仿微信界面的组织方式与实现思路。1. 从一张聊天列表说起为什么用 Android Studio 复刻微信界面值得认真做很多同学接到「用 Android Studio 制作微信的界面」这个题目时第一反应是打开布局文件拖控件把头像、昵称、消息气泡摆得像模像样就交差。但真正做过完整项目的人都知道界面复刻的难点从来不在「像不像」而在「撑不撑得住」——消息列表滚动到第 200 条会不会卡、底部导航切换时 Fragment 会不会重叠、软键盘弹出后输入框会不会被顶飞。这些才是答辩时老师最爱追问、面试官最爱深挖的地方。这套方案面向两类人一是正在做 Android 课程设计或毕设、需要交一份结构完整可运行作品的学生二是想通过一个高辨识度案例把 Activity、Fragment、RecyclerView、自定义 View 串起来的入门开发者。微信界面恰好覆盖了移动端最典型的几类 UI 模式——列表流、底部导航、聊天详情、朋友圈九宫格——把它拆透等于把 Android UI 开发的主干走了一遍。下面按需求、架构、模块实现、避坑、验收的顺序展开代码和结构都可以直接照着改。2. 需求拆解与技术选型四个页面、三层结构怎么定2.1 功能边界先划清「做什么」和「不做什么」复刻微信界面最容易翻车的地方是范围失控。微信本身功能庞杂如果一开始就想着把支付、小程序、视频号都塞进去最后大概率是每个页面都半成品。我一般会先把范围压到「四个主 Tab 一个聊天详情页」这个最小可交付集合模块核心功能交付标准微信聊天列表会话列表、未读红点、置顶列表可滚动、点击进入详情通讯录字母索引、分组侧边字母条可快速定位发现朋友圈入口、功能宫格宫格点击有反馈我个人信息卡、设置项列表头像昵称可展示聊天详情消息气泡、输入框、发送发送后消息上屏并滚动到底明确「不做什么」同样重要不做真实网络请求、不做账号体系、不做消息推送。数据全部用本地模拟这样既保证可运行又不会因为后端缺失导致答辩时页面空白。这个边界划清楚后面所有设计都有了锚点。2.2 技术选型为什么是 Fragment RecyclerView 这套组合界面框架的选择直接决定了代码的可维护性。底部四个 Tab 的切换常见有三种做法多个 Activity 跳转、ViewPager 滑动、Fragment 容器替换。第一种会导致返回栈混乱第二种在微信这种「Tab 之间不该滑动切换」的场景里体验不对所以标准做法是单 Activity 四个 Fragment 底部自定义导航栏。列表部分必须用 RecyclerView 而不是 ListView原因很实在ViewHolder 复用机制成熟、支持局部刷新、能配合 ItemDecoration 做分割线。聊天列表、通讯录、消息气泡三处列表全部复用同一套 RecyclerView 思路只是 Adapter 的 item 布局不同。数据层用一个简单的本地 Repository 单例持有模拟数据避免把数据散落在各个 Fragment 里。依赖方面保持克制除了 AndroidX 基础库和 Material 组件不引入第三方 UI 框架。原因很简单复刻微信界面的价值就在于自己写布局、自己调间距套一个现成框架反而失去了练习意义答辩时也讲不出东西。2.3 工程目录按功能分包而不是按类型分包新手常犯的错是把所有 Activity 放一个包、所有 Adapter 放一个包项目一大就找不到东西。我一般按功能模块分包每个模块内部再分页面和数据com.example.wechatui ├── main/ # 主容器 │ ├── MainActivity.java │ └── BottomNavView.java # 自定义底部导航 ├── chat/ # 微信 Tab │ ├── ChatListFragment.java │ ├── ChatListAdapter.java │ └── ChatDetailActivity.java ├── contact/ # 通讯录 Tab │ ├── ContactFragment.java │ └── ContactAdapter.java ├── discover/ # 发现 Tab ├── profile/ # 我 Tab ├── data/ # 模拟数据与 Repository │ ├── ChatRepository.java │ └── model/ # 实体类 └── widget/ # 通用自定义 View ├── AvatarView.java └── BadgeView.java # 未读红点这样分包的好处是改聊天列表只动 chat 包通用控件沉到 widget 包数据统一走 data 包。答辩时老师问「你的项目结构怎么组织的」这套目录本身就是答案。3. 主框架与导航实现单 Activity 容器怎么搭稳3.1 底部导航栏自定义 View 比 BottomNavigationView 更可控系统自带的 BottomNavigationView 能用但微信底部导航有几个细节它不好处理选中态图标变色、未读红点叠加、点击时图标有轻微缩放。所以我一般自己写一个BottomNavView内部用 LinearLayout 横排四个 item每个 item 是一个图标加文字的垂直组合。public class BottomNavView extends LinearLayout { private int currentIndex 0; private OnTabSelectedListener listener; public BottomNavView(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(HORIZONTAL); // 四个 Tab 的图标与文字资源 int[] icons {R.drawable.ic_chat, R.drawable.ic_contact, R.drawable.ic_discover, R.drawable.ic_me}; String[] titles {微信, 通讯录, 发现, 我}; for (int i 0; i icons.length; i) { View item inflate(context, R.layout.item_bottom_nav, this); ImageView icon item.findViewById(R.id.nav_icon); TextView title item.findViewById(R.id.nav_title); icon.setImageResource(icons[i]); title.setText(titles[i]); final int index i; item.setOnClickListener(v - selectTab(index)); } } public void selectTab(int index) { if (index currentIndex) return; currentIndex index; // 遍历刷新选中态选中变绿未选中变灰 for (int i 0; i getChildCount(); i) { View child getChildAt(i); boolean selected (i index); ((ImageView) child.findViewById(R.id.nav_icon)) .setColorFilter(selected ? 0xFF07C160 : 0xFF999999); ((TextView) child.findViewById(R.id.nav_title)) .setTextColor(selected ? 0xFF07C160 : 0xFF999999); } if (listener ! null) listener.onTabSelected(index); } }这段代码的关键点有三个一是selectTab里先判断index currentIndex直接返回避免重复点击触发无谓的 Fragment 重建二是选中色用微信标志性的绿色0xFF07C160未选中用灰色颜色值集中在这里方便统一调整三是通过OnTabSelectedListener把切换事件抛给 MainActivity导航栏本身不持有 Fragment 逻辑职责单一。参数上图标资源建议用矢量图 VectorDrawable这样一套图能适配所有屏幕密度不用准备多套 png。3.2 Fragment 容器用 show/hide 而不是 replaceMainActivity 里承载四个 Fragment切换方式有两种replace和show/hide。replace每次都会销毁重建 Fragment聊天列表滚动位置会丢失体验很差。正确做法是初始化时一次性 add 四个 Fragment之后只做 show/hideprivate void switchFragment(int index) { FragmentTransaction tx getSupportFragmentManager().beginTransaction(); for (int i 0; i fragments.size(); i) { Fragment f fragments.get(i); if (i index) { tx.show(f); } else { tx.hide(f); } } tx.commit(); }这里有个血泪经验四个 Fragment 在onCreate里一次性 add 时一定要给每个 Fragment 设置 tag 或者用集合持有引用否则配置变更比如旋转屏幕后getSupportFragmentManager恢复的 Fragment 和你手里的引用对不上就会出现「切到通讯录却显示聊天列表」的玄学问题。稳妥的做法是在onCreate里先判断savedInstanceState null为空才执行初始化 add不为空则从 FragmentManager 里按 tag 找回。3.3 状态保存旋转屏幕不丢数据的兜底界面类项目最容易被忽略的是配置变更。用户在聊天详情页输入了一半文字转个屏内容没了这种 bug 答辩时被当场演示就很尴尬。处理方式分两层输入框内容用onSaveInstanceState存到 Bundle列表滚动位置 RecyclerView 自带LinearLayoutManager的状态保存只要 Adapter 数据源没被清空就能自动恢复。对于「我」页面这种静态展示不需要额外处理。这套兜底做完才算一个能经得起折腾的界面。4. 核心页面实现聊天列表与消息气泡怎么落地4.1 聊天列表多类型 Item 与未读红点微信聊天列表的 item 结构是固定的左侧头像、中间昵称加最后一条消息、右侧时间加未读红点。用 RecyclerView 实现时Adapter 的onBindViewHolder负责把数据填进去红点用自定义的BadgeView控制显隐Override public void onBindViewHolder(ViewHolder holder, int position) { ChatItem item dataList.get(position); holder.name.setText(item.getName()); holder.lastMsg.setText(item.getLastMessage()); holder.time.setText(item.getTime()); // 未读数大于 0 才显示红点超过 99 显示 99 if (item.getUnread() 0) { holder.badge.setVisibility(View.VISIBLE); holder.badge.setText(item.getUnread() 99 ? 99 : String.valueOf(item.getUnread())); } else { holder.badge.setVisibility(View.GONE); } // 头像用本地资源实际项目可换 Glide 加载 holder.avatar.setImageResource(item.getAvatarRes()); }设计说明BadgeView继承 TextView在onDraw里画一个红色圆角背景文字居中。未读数超过 99 显示「99」是微信的真实规则这个细节写进去答辩时很加分。头像这里用本地资源是为了保证离线可运行如果后续要接网络图片把setImageResource换成图片加载库即可接口不变。列表分割线用ItemDecoration实现左边留出头像宽度这样分割线不会顶到屏幕边缘和微信的视觉效果一致。4.2 消息气泡用 9-patch 还是自定义 Drawable聊天详情页的消息气泡是整套界面里最考验细节的部分。气泡要有小三角指向头像方向左右两侧消息三角方向相反。实现方式有两种9-patch 图片和自定义 Drawable。9-patch 简单但需要准备两套图自定义 Drawable 灵活但代码稍多。我一般选后者用LayerDrawable组合一个圆角矩形加一个旋转 45 度的正方形来模拟三角!-- res/drawable/bubble_left.xml 左侧接收气泡 -- layer-list item shape android:shaperectangle solid android:color#FFFFFF/ corners android:radius8dp/ /shape /item item android:left-6dp android:top12dp android:bottom12dp rotate android:fromDegrees45 android:pivotX50% android:pivotY50% shape android:shaperectangle solid android:color#FFFFFF/ size android:width12dp android:height12dp/ /shape /rotate /item /layer-list逻辑说明底层是圆角矩形做气泡主体上层是一个旋转 45 度的正方形通过负的 left 偏移让它露出一半形成三角。右侧气泡把android:left改成android:right-6dp即可镜像。参数上圆角 8dp、三角边长 12dp 是视觉上比较接近微信的比例实际调的时候以真机预览为准。消息列表同样用 RecyclerView发送消息后调用notifyItemInserted并scrollToPosition到底部这两步缺一不可只刷新不滚动用户会以为没发出去。4.3 输入框与软键盘adjustResize 的正确用法聊天输入框被软键盘遮挡是高频问题。解决方案是在 AndroidManifest 里给 ChatDetailActivity 设置android:windowSoftInputModeadjustResize同时根布局不要用固定高度让内容区随键盘压缩。如果发现设置后无效八成是 Activity 用了全屏主题或者根布局是 ScrollView 包裹导致测量异常。稳妥做法是根布局用 ConstraintLayout消息列表layout_height0dp加layout_weight撑满剩余空间输入框贴底这样键盘弹出时列表自动缩短输入框始终可见。5. 避坑与排查五个最容易翻车的点5.1 现象切换 Tab 后 Fragment 重叠原因通常是replace和add混用或者初始化时重复 add 了同一个 Fragment。解决方式是统一用 show/hide并在onCreate里判断savedInstanceState是否为空为空才初始化不为空从 FragmentManager 按 tag 找回已有实例。5.2 现象列表滑动卡顿、图片错位原因多半是在onBindViewHolder里做了耗时操作或者图片加载没有复用机制导致错位。解决方式是把数据绑定逻辑控制在轻量级图片加载统一走缓存库并在绑定前先重置占位图避免复用时显示上一条的数据。5.3 现象未读红点位置偏移原因是 BadgeView 的父布局约束不对红点用了绝对定位。解决方式是把红点放在头像的 FrameLayout 里用layout_gravitytop|end加负 margin 微调这样无论头像尺寸怎么变红点始终贴着右上角。5.4 现象软键盘弹出后输入框消失原因是windowSoftInputMode没设或设成了adjustPan。解决方式是改成adjustResize并检查根布局是否有固定高度或全屏属性。如果还不行在代码里监听布局变化手动滚动列表到底部。5.5 现象真机上字体和间距与设计稿对不上原因是设计稿用的 px 而布局用的 dp或者没考虑状态栏高度。解决方式是统一用 dp 和 sp状态栏高度通过WindowInsets动态获取不要写死 24dp。这个坑几乎每个新手都会踩一次提前统一单位能省很多返工。6. 验收与答辩怎么证明这套界面「真的能用」验收这套界面我一般分三步走每一步都有可量化的检查点。第一步是功能走查四个 Tab 逐个点击确认切换无重叠、选中态正确聊天列表滚动到第 100 条以上观察是否掉帧进入聊天详情发送三条消息确认上屏顺序和滚动位置正确旋转屏幕确认输入框内容和列表位置不丢。第二步是边界测试未读数设为 0、99、100 三个值确认红点显隐和「99」逻辑通讯录字母索引滑到首尾确认不越界软键盘弹出收起反复五次确认输入框不漂移。第三步是代码审查检查是否有硬编码颜色和尺寸、Fragment 是否有内存泄漏风险、Adapter 是否用了 ViewHolder。答辩时老师最爱问的三个问题提前准备好答案一是「为什么用 Fragment 不用 Activity」答返回栈管理和状态保持二是「RecyclerView 比 ListView 好在哪」答 ViewHolder 强制复用和局部刷新三是「你的项目怎么保证可扩展」答按功能分包加 Repository 统一数据源。演示环节建议准备一份「异常演示脚本」主动展示旋转屏幕、快速切换 Tab、长列表滚动这几个场景比被动等老师挑刺从容得多。从那以后我每次做界面类项目都会在提交前强制走一遍「旋转屏幕 快速切换 长列表」这三连测因为翻车往往就藏在这些最不起眼的操作里。希望这套结构和实现思路能帮到你把这份界面作业做成一份拿得出手的作品。本文还有配套的精品资源点击获取