尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android Studio仿微信主界面实战:布局选型、自定义控件与深色模式适配

Android Studio仿微信主界面实战:布局选型、自定义控件与深色模式适配 简介这是一份面向Android初学者与界面开发爱好者的实战项目源码围绕「用Android Studio制作微信界面」展开帮助读者在真实工程中理解移动端UI搭建流程。资源包共1041个文件约24.25MB涵盖426个flat资源、237个json配置、88个xml布局、73个png图片以及20个java源码、28个dex与26个class等编译产物另有gradle构建脚本、jar依赖与apk安装包完整保留了可运行工程结构。内容涉及Android Studio基础操作、ConstraintLayout布局、BottomNavigationView与RecyclerView等Material Design组件、自定义聊天气泡视图、Adapter数据绑定、Glide图片加载、VectorDrawable图标、主题配色与过渡动画并包含Git版本控制痕迹与更新记录。已有3102人学习下载适合想通过模仿微信界面掌握布局设计、组件复用与用户体验优化的开发者参考实践。1. 拆解微信主界面从一张截图到 Android Studio 工程骨架很多人第一次打开 Android Studio 想做的第一件事就是照着微信首页撸一个界面出来。原因很直接微信主界面结构清晰、控件类型全、复用度高底部导航加顶部标题栏加中间列表几乎覆盖了日常开发八成以上的布局场景。但真正动手就会发现光靠拖控件根本还原不出那种质感标题栏高度、图标间距、列表分割线、点击态反馈每一项都有讲究。这篇笔记就围绕「用 Android Studio 制作微信的界面」这件事把工程结构、布局选型、自定义控件、资源适配和常见翻车点一次讲透。适合刚装完 Android Studio 的新手照着复现也适合做过几个页面但总觉得「差口气」的熟手对照参数。下面所有步骤都基于原生 View 体系不依赖 Compose保证你在任何一台装了 Android Studio 的机器上都能跑起来。2. 工程初始化与微信主界面布局选型为什么不用 ConstraintLayout 一把梭2.1 新建工程时的四个关键选择打开 Android Studio 之后New Project里模板很多做微信界面不要选Empty Views Activity之外的任何花哨模板。语言选 Java 还是 Kotlin 看你自己顺手程度本文示例用 Kotlin但布局 XML 完全通用。Minimum SDK建议选 API 24这个版本覆盖了绝大多数真机同时避免低版本一些属性不生效的玄学问题。Build configuration language选 Kotlin DSL 还是 Groovy 都行不影响界面制作。建完工程后先别急着写布局把res目录结构理一遍。微信界面涉及大量图标和背景建议按下面这样组织res/ ├── layout/ # 布局文件 ├── drawable/ # 矢量图标、shape 背景 ├── mipmap-anydpi-v26/ # 自适应图标 ├── values/ │ ├── colors.xml # 颜色统一管理 │ ├── dimens.xml # 尺寸统一管理 │ └── strings.xml # 文案 └── menu/ # 底部导航菜单把颜色和尺寸抽到colors.xml和dimens.xml里是后面能快速调整界面的前提。微信主界面的主色调是#EDEDED背景、#07C160绿色强调色、#191919主文字、#888888次要文字这几个值先写进去。2.2 主界面整体结构垂直线性布局套帧布局微信主界面从上到下分三层顶部标题栏、中间内容区、底部导航栏。最外层用LinearLayout垂直排列中间内容区用FrameLayout承载多个 Fragment 切换。为什么不整个用ConstraintLayout因为这种「上下固定、中间撑满」的结构LinearLayout加layout_weight反而更直观层级也更浅渲染性能更好。ConstraintLayout适合复杂错位布局这里属于杀鸡用牛刀。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:backgroundcolor/wechat_bg !-- 顶部标题栏高度 48dp 是微信实际使用的值 -- include layoutlayout/layout_top_bar / !-- 中间内容区weight1 让它撑满剩余空间 -- FrameLayout android:idid/content_container android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / !-- 底部导航栏高度 56dp -- include layoutlayout/layout_bottom_nav / /LinearLayout这段布局的逻辑很直白include把标题栏和底部导航抽成独立文件方便复用和单独调试。FrameLayout的layout_height0dp配合layout_weight1是标准写法意思是「高度由剩余空间决定」。参数上标题栏 48dp、底部导航 56dp 是微信在多数机型上的实际值你可以根据设计稿微调但不要低于 44dp否则触摸热区太小用户点起来会骂人。2.3 顶部标题栏TextView 加 ImageView 的极简组合微信顶部标题栏看起来简单其实左右两边的图标和中间标题的对齐方式有讲究。常见做法是左边放一个「微信」文字标题右边放搜索和加号两个图标。用RelativeLayout或者ConstraintLayout都行这里用ConstraintLayout保证图标垂直居中。?xml version1.0 encodingutf-8? androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_height48dp android:backgroundcolor/wechat_top_bar TextView android:idid/tv_title android:layout_widthwrap_content android:layout_heightwrap_content android:text微信 android:textSize17sp android:textColorcolor/wechat_text_primary android:textStylebold app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent android:layout_marginStart16dp / ImageView android:idid/iv_search android:layout_width24dp android:layout_height24dp android:srcdrawable/ic_search android:contentDescription搜索 app:layout_constraintEnd_toStartOfid/iv_add app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent android:layout_marginEnd16dp / ImageView android:idid/iv_add android:layout_width24dp android:layout_height24dp android:srcdrawable/ic_add android:contentDescription添加 app:layout_constraintEnd_toEndOfparent app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent android:layout_marginEnd16dp / /androidx.constraintlayout.widget.ConstraintLayout这里的关键参数是textSize17sp和textStylebold微信标题就是加粗的 17sp。图标统一 24dp间距 16dp这是 Material Design 的推荐值也是微信实际采用的。contentDescription别省无障碍功能需要上架审核也会看。2.4 底部导航栏用 BottomNavigationView 还是自己写底部导航栏是微信界面的灵魂。Android 官方提供了BottomNavigationView但默认样式和微信差距很大改起来反而麻烦。我一般会自己用LinearLayout加四个LinearLayout子项来写每个子项里放ImageView和TextView这样图标大小、文字间距、选中变色都能精确控制。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_height56dp android:orientationhorizontal android:backgroundcolor/wechat_bottom_nav android:elevation8dp LinearLayout android:idid/tab_chat android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:orientationvertical android:gravitycenter ImageView android:idid/iv_tab_chat android:layout_width24dp android:layout_height24dp android:srcdrawable/ic_tab_chat_selected / TextView android:idid/tv_tab_chat android:layout_widthwrap_content android:layout_heightwrap_content android:text微信 android:textSize10sp android:textColorcolor/wechat_green android:layout_marginTop2dp / /LinearLayout !-- 通讯录、发现、我 三个 tab 结构相同省略重复代码 -- /LinearLayout每个 tab 的layout_weight1保证四等分。图标 24dp、文字 10sp、间距 2dp这套参数还原度最高。选中态通过切换ImageView的src和TextView的textColor实现不要用selector写在 XML 里然后忘记在代码里调setSelected这是新手最常见的翻车点之一。3. 中间内容区与自定义控件把聊天列表做出微信味3.1 聊天列表项布局一张头像加两行文字的经典结构微信聊天列表每一项的结构是左边圆形头像中间上方昵称下方消息预览右边上方时间下方未读红点。这个布局用ConstraintLayout最合适因为元素之间有明确的对齐关系。?xml version1.0 encodingutf-8? androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_height72dp android:background?attr/selectableItemBackground android:paddingStart16dp android:paddingEnd16dp ImageView android:idid/iv_avatar android:layout_width48dp android:layout_height48dp android:scaleTypecenterCrop app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent app:layout_constraintBottom_toBottomOfparent / TextView android:idid/tv_name android:layout_width0dp android:layout_heightwrap_content android:textSize16sp android:textColorcolor/wechat_text_primary android:maxLines1 android:ellipsizeend app:layout_constraintStart_toEndOfid/iv_avatar app:layout_constraintEnd_toStartOfid/tv_time app:layout_constraintTop_toTopOfid/iv_avatar android:layout_marginStart12dp / TextView android:idid/tv_message android:layout_width0dp android:layout_heightwrap_content android:textSize14sp android:textColorcolor/wechat_text_secondary android:maxLines1 android:ellipsizeend app:layout_constraintStart_toStartOfid/tv_name app:layout_constraintEnd_toEndOfparent app:layout_constraintBottom_toBottomOfid/iv_avatar / TextView android:idid/tv_time android:layout_widthwrap_content android:layout_heightwrap_content android:textSize12sp android:textColorcolor/wechat_text_secondary app:layout_constraintEnd_toEndOfparent app:layout_constraintTop_toTopOfid/iv_avatar / /androidx.constraintlayout.widget.ConstraintLayout头像 48dp 是微信实际尺寸列表项总高 72dp左右内边距 16dp。android:background?attr/selectableItemBackground这行很重要它给列表项加了点击水波纹效果不用自己写 selector。ellipsizeend保证长文本截断显示省略号微信就是这么处理的。3.2 圆形头像用 CircleImageView 还是自己写微信头像是圆形的原生ImageView不支持。常见做法有两种引入CircleImageView第三方库或者自己写一个BitmapShader的CircleImageView。第三方库省事但增加依赖自己写更可控。这里给一个自己写的精简版class CircleImageView JvmOverloads constructor( context: Context, attrs: AttributeSet? null, defStyleAttr: Int 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { private val paint Paint(Paint.ANTI_ALIAS_FLAG) private val bitmapShaderMatrix Matrix() override fun onDraw(canvas: Canvas) { val drawable drawable ?: return val bitmap (drawable as BitmapDrawable).bitmap val radius min(width, height) / 2f // 用 BitmapShader 把图片裁剪成圆形 val shader BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) val scale max(width / bitmap.width.toFloat(), height / bitmap.height.toFloat()) bitmapShaderMatrix.setScale(scale, scale) shader.setLocalMatrix(bitmapShaderMatrix) paint.shader shader canvas.drawCircle(width / 2f, height / 2f, radius, paint) } }这段代码的核心是BitmapShader它把图片当作画笔的纹理然后drawCircle画圆时只显示圆形区域内的纹理。scale计算保证图片短边填满圆形不会出现留白。注意onDraw里没有调用super.onDraw因为我们要完全接管绘制逻辑。如果图片不是BitmapDrawable会崩实际使用时要加类型判断。3.3 未读红点自定义 BadgeView 的两种实现微信未读消息的红点有两种纯红点和带数字的红点。纯红点用一个View加圆形背景就行带数字的需要动态测量文字宽度。下面是一个简单的BadgeViewclass BadgeView JvmOverloads constructor( context: Context, attrs: AttributeSet? null ) : TextView(context, attrs) { init { setBackgroundResource(R.drawable.bg_badge) setTextColor(Color.WHITE) textSize 10f gravity Gravity.CENTER minWidth dp2px(16) minHeight dp2px(16) setPadding(dp2px(4), 0, dp2px(4), 0) } fun setBadgeCount(count: Int) { if (count 0) { visibility GONE } else { visibility VISIBLE text if (count 99) 99 else count.toString() } } private fun dp2px(dp: Int): Int (dp * resources.displayMetrics.density 0.5f).toInt() }bg_badge是一个圆角矩形 shapecorners radius设成高度的一半就是胶囊形。minWidth和minHeight保证单个数字时也是圆形。count 99显示99是微信的规则直接抄。dp2px转换别用TypedValue.applyDimension忘了加 0.5f否则会有 1px 误差导致圆角看起来不圆。4. 资源适配与主题配置让界面在不同机型上不跑偏4.1 颜色与尺寸统一管理所有颜色和尺寸都抽到values目录下这是保证界面一致性的基础。微信主界面的核心色值如下!-- colors.xml -- resources color namewechat_bg#EDEDED/color color namewechat_top_bar#EDEDED/color color namewechat_bottom_nav#F7F7F7/color color namewechat_green#07C160/color color namewechat_text_primary#191919/color color namewechat_text_secondary#888888/color color namewechat_divider#E5E5E5/color /resources尺寸同理标题栏 48dp、底部导航 56dp、列表项 72dp、头像 48dp、图标 24dp这些值写进dimens.xml。后面如果设计稿调整改一处全局生效不用满工程搜数字。4.2 深色模式适配values-night 目录现在 Android 系统都支持深色模式微信也跟进了。适配方法是在res下建values-night目录放一份深色版的colors.xml。系统切换深色模式时会自动读取values-night里的值。!-- values-night/colors.xml -- resources color namewechat_bg#111111/color color namewechat_top_bar#1E1E1E/color color namewechat_bottom_nav#1E1E1E/color color namewechat_text_primary#D9D9D9/color color namewechat_text_secondary#666666/color color namewechat_divider#2C2C2C/color /resources注意wechat_green不用改深色模式下绿色保持不变。wechat_bg从#EDEDED变成#111111文字颜色反转。如果忘了建values-night深色模式下界面会白得刺眼这是用户差评的高发区。4.3 状态栏沉浸式让标题栏顶到屏幕顶部微信的标题栏和状态栏是融为一体的没有那条系统状态栏的色带。实现方式是设置WindowCompat.setDecorFitsSystemWindows(window, false)然后给根布局加fitsSystemWindows处理。// MainActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContentView(R.layout.activity_main) // 给顶部标题栏加状态栏高度的 padding val topBar findViewByIdView(R.id.top_bar) ViewCompat.setOnApplyWindowInsetsListener(topBar) { view, insets - val statusBarHeight insets.getInsets(WindowInsetsCompat.Type.statusBars()).top view.setPadding(0, statusBarHeight, 0, 0) insets } }这段代码的逻辑是先让内容延伸到状态栏区域然后给标题栏加一个等于状态栏高度的顶部内边距这样标题栏背景色就填满了状态栏区域文字和图标不会被状态栏遮挡。WindowInsetsCompat是 AndroidX 提供的兼容方案低版本也能用。5. 避坑与排查微信界面制作中五个血泪教训5.1 列表滑动卡顿图片加载是元凶现象聊天列表滑动时明显掉帧快速滑动甚至白屏。原因在onBindViewHolder里同步加载图片或者用了setImageResource加载大图。解决引入 Glide 或 Coil 异步加载并设置圆形裁剪和缓存策略。Glide.with(holder.ivAvatar) .load(avatarUrl) .circleCrop() .placeholder(R.drawable.ic_avatar_placeholder) .into(holder.ivAvatar)circleCrop()直接出圆形不用自己写CircleImageView。placeholder是占位图避免加载过程中出现空白。5.2 底部导航点击无反应触摸事件被拦截现象底部导航栏的 tab 点击没反应或者只有文字区域能点。原因子LinearLayout没有设置clickable和focusable或者父布局拦截了触摸事件。解决给每个 tab 的根LinearLayout加android:clickabletrue和android:focusabletrue并在代码里设置setOnClickListener。5.3 标题栏在不同机型上高度不一致现象有的手机标题栏正常有的手机标题栏被状态栏遮住一半。原因没有处理WindowInsets或者硬编码了状态栏高度。解决用ViewCompat.setOnApplyWindowInsetsListener动态获取状态栏高度不要写死 24dp 或 25dp。5.4 深色模式下文字看不见现象切换深色模式后某些文字颜色没变白底白字。原因颜色值直接写在布局里没有引用colors.xml。解决所有颜色必须走color/xxx引用并在values-night里提供深色版本。检查方法是在 Android Studio 的Resource Manager里切换Night模式预览。5.5 列表分割线错位或重复现象聊天列表的分割线要么没有要么每项都有导致双线。原因RecyclerView的addItemDecoration重复调用或者分割线绘制逻辑没判断位置。解决用DividerItemDecoration并只调用一次或者自定义ItemDecoration时在getItemOffsets里判断position最后一项不画分割线。6. 进阶技巧用自定义 View 还原微信的「滑动返回」手感微信的滑动返回手感很特别边缘滑动时当前页面跟随手指移动松手后根据滑动距离决定回弹还是退出。这个效果用ViewDragHelper实现最稳。下面是一个精简版的SwipeBackLayoutclass SwipeBackLayout JvmOverloads constructor( context: Context, attrs: AttributeSet? null ) : FrameLayout(context, attrs) { private val dragHelper ViewDragHelper.create(this, 1f, object : ViewDragHelper.Callback() { override fun tryCaptureView(child: View, pointerId: Int): Boolean { // 只有从边缘 20dp 内开始滑动才拦截 return dragHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT, pointerId) } override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int { return left.coerceIn(0, width) } override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { val threshold width * 0.3f if (releasedChild.left threshold || xvel 500) { // 滑动超过 30% 或速度够快退出页面 dragHelper.settleCapturedViewAt(width, 0) } else { // 否则回弹 dragHelper.settleCapturedViewAt(0, 0) } invalidate() } }) override fun onTouchEvent(event: MotionEvent): Boolean { dragHelper.processTouchEvent(event) return true } override fun computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this) } } }ViewDragHelper.create的第二个参数1f是灵敏度值越大越灵敏微信的手感大概在 0.8 到 1.2 之间我一般用 1f。isEdgeTouched判断是否从边缘开始滑动EDGE_LEFT表示只响应左边缘。clampViewPositionHorizontal限制滑动范围在 0 到宽度之间防止滑出屏幕。onViewReleased里判断滑动距离超过 30% 或者速度超过 500 就退出否则回弹。computeScroll里调continueSettling驱动动画这是ViewDragHelper的标准用法。这套代码可以直接放进你的工程把SwipeBackLayout作为 Activity 的根布局就能获得接近微信的滑动返回体验。注意onTouchEvent返回true表示消费事件如果返回false滑动会失效。另外ViewDragHelper的Callback里tryCaptureView返回true才会开始拖拽返回false则事件继续往下传。我自己的习惯是每做完一个界面都会在至少三台不同分辨率、不同 Android 版本的设备上跑一遍重点看状态栏、导航栏、深色模式和字体缩放这四个变量。字体缩放到最大时微信的列表项文字会换行布局高度要能自适应别写死高度。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表