Android模拟手机QQ登录界面和主界面含源码这类项目在Android学习圈里其实一直很火。我见过不少初学者把自己的第一个完整UI项目就定在这个方向上也有不少人在面试前临时抱佛脚拿它练手。原因很简单登录界面加主界面几乎涵盖了Android UI开发最核心的几块内容——布局嵌套、输入控件定制、页面跳转、数据传递、状态切换。把一个仿QQ项目从头到尾做一遍你对Android的界面开发体系基本就有感觉了。这篇文章不打算给你贴一大坨源码了事而是把整个项目从设计思路到坑位排查完整拆开讲你可以直接照着思路重构一遍也可以拿它作为自己项目的底子。1. 项目整体设计与思路拆解1.1 为什么选“QQ登录主界面”作为练手项目先说结论这个项目几乎是为Android UI入门到进阶量身定做的。登录界面看起来简单实际上隐含的知识点一点都不少。最外层是一个竖向排列的容器布局内部包含了Logo区域、账号输入框、密码输入框、登录按钮、注册入口还有“同意用户协议”这种辅助性文案。这里面的每个元素都对应一个Android基础知识点输入框要处理EditText的焦点切换、密码的明文密文切换登录按钮要处理按压态、不可用态的UI变化页面底部还涉及软键盘弹出时的避让策略。等你把这些细节全部处理好你的布局基本功基本就过关了。主界面就更典型了。QQ的主界面是经典的底部导航栏加多个页面切换结构对应到Android里就是用Fragment加上ViewPager2、BottomNavigationView或者TabLayout来实现。里面还会涉及列表页、消息页、联系人页之间数据的切换与状态保持。把这个部分做完你对于“多页面应用如何组织”会有非常具体的理解。更关键的是这两个界面整合在一起后你能自然接触到Android开发中绕不开的一整套流程Activity跳转、Intent传参、生命周期切换、数据持久化比如记住账号密码、屏幕适配、状态栏颜色处理等等。这些知识点如果分开学总觉得是散的但落在这样一个项目里全都有了一个具体的应用场景。1.2 整体技术选型与架构演进思路先说清楚一个前提这个项目的定位是“界面模拟”不是真正去对接QQ的后台协议。所以它的技术选型完全可以用轻量方案不需要引入重型网络框架也不需要做消息推送。我自己的建议分三个阶段走对应不同基础的人入门版只用一个Activity承载登录界面再用一个Activity承载主界面登录成功后通过Intent直接跳转。数据传递用最基础的putExtra账号密码记忆用SharedPreferences。这套方案的好处是代码量小逻辑链路清晰非常适合第一次接触Android的开发者。进阶版把主界面里面的多个页面抽成Fragment用BottomNavigationView加Fragment切换来模拟QQ的底部导航登录状态用对象形式的Session保存而不是每次都读SharedPreferences。工程化版本引入ViewModel加LiveData管理登录状态用Navigation组件管理页面跳转甚至可以把登录模块和主界面模块拆成不同的包模拟真实项目里的模块划分。我见过不少新手一上来就上Jetpack Compose实际上对于这个项目传统的View体系仍然是更好的学习选择。原因在于Compose虽然新但很多公司现有的存量代码还是传统View体系而且传统View体系里layout_weight、Selector、自定义Drawable这些概念能让你更直观地理解Android的UI渲染流程。1.3 项目目录结构与职责划分不管你是自己从头写还是参考现成源码我强烈建议先想清楚目录结构再来写代码。仿QQ界面这个项目虽然体量不大但目录清晰与否直接影响你后面扩展功能的体验。我习惯的结构是这样的app/src/main/java/com/example/demoqq/ ├── activity/ // 存放LoginActivity、MainActivity ├── fragment/ // 存放主界面的各个Fragment消息、联系人、动态等 ├── adapter/ // Fragment适配器、列表适配器 ├── model/ // 用户信息、消息数据模型 ├── utils/ // 状态栏工具、屏幕适配工具 └── view/ // 自定义View比如仿QQ的删除按钮、气泡等资源文件方面layout目录里放登录页和主界面的XML布局drawable目录里放各种圆角背景、按钮Selectormipmap里放应用图标。把界面元素和逻辑代码分开后期改样式的时候不用动Java/Kotlin代码这是Android开发里最基本的一条工程原则。我看过太多把几十个XML布局全堆在一个文件夹里、靠命名区分用途的项目。在小项目里还能忍一旦页面多了找一个activity_main.xml都要翻半天。这个仿QQ项目正好是养成好习惯的最佳机会毕竟登录页和主界面恰好是两个逻辑上完全独立的模块天然适合分离。2. 核心细节解析与实操要点2.1 登录界面布局的拆解与实现登录界面的布局从视觉上可以拆成四个区域顶部Logo区、中间表单区、底部操作区、隐藏的协议区。初看简单但实操中要踩的坑主要在“输入框样式”和“键盘避让”上。先看表单区的根布局。我建议用LinearLayout配合layout_weight来分配屏幕空间而不是用绝对定位或者FrameLayout硬怼。具体来说Logo区占一份权重表单区占一份权重底部操作区固定在底部这样在不同屏幕尺寸下都能自动适配。账号输入框和密码输入框的处理是重中之重。QQ登录框的视觉特点是下划线分隔、圆角浅色背景、聚焦时边框变色。实现方案不复杂核心是用EditText配合Drawable的Selector。你需要定义三种状态默认状态、聚焦状态、不可用状态。比如默认状态下的背景是一个灰色圆角矩形聚焦状态变成蓝色圆角矩形加细边框这样用户一聚焦输入框就能获得视觉反馈。XML里伪代码大概是这个意思EditText android:idid/et_account android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入账号 android:backgrounddrawable/edittext_bg_selector android:padding12dp android:textSize14sp android:inputTypetext/然后edittext_bg_selector.xml里用selector指定两种状态的背景selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_focusedtrue shape android:shaperectangle solid android:colorandroid:color/white/ stroke android:width1dp android:color#4C8DFF/ corners android:radius6dp/ /shape /item item shape android:shaperectangle solid android:color#F5F6F7/ stroke android:width1dp android:color#DDDDDD/ corners android:radius6dp/ /shape /item /selector密码框要做明文密文切换这需要你在布局里放一个切换按钮通常是一个眼睛图标。纯用XML做不到必须写一点逻辑代码。我比较推荐的做法是用TextInputLayout包裹EditText然后开启app:passwordToggleEnabledtrue。这个类封装的逻辑已经很完善了包括切换动画和内容清除。如果你希望完全仿照QQ的交互体验可以自定义一个EditText在右侧绘制眼睛图标并监听onTouchEvent切换TransformationMethod。QQ输入框还有一个容易被忽略的细节清除按钮。当输入框有内容时右侧会显示一个“×”号点击后清空内容。这个功能用EditText的addTextChangedListener加CompoundDrawable就能实现。代码逻辑如下editText.addTextChangedListener(new TextWatcher() { Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (s.length() 0) { editText.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_clear, 0); } else { editText.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } } // 省略其他回调 });然后拦截右侧图标的点击事件调用setText()。注意Drawable的边界要设置得比图标实际尺寸大一些否则点击区域过小用户经常点了没反应。2.2 登录按钮的状态管理与交互反馈登录按钮在仿QQ项目里的状态管理是很多人忽略的部分。QQ的登录按钮有非常明确的交互逻辑账号和密码都不为空时按钮是亮蓝色可点击状态有一个为空或者都为空时按钮是灰色的禁用状态。实现方式有两种。一种是监听两个EditText的TextWatcher每次都判断当前输入状态然后调用setEnabled(true/false)。这种方式代码直接但需要在两个输入框上分别注册监听逻辑稍显冗余。第二种是引入一个简单的LoginViewModel把输入框绑定到LiveData上然后用MediatorLiveData合并两个输入流根据合并结果自动更新按钮状态。这种方式代码量更大但逻辑更清晰而且为后续的登录请求做了状态管理铺垫。我自己更推荐第一种理由是这个项目的定位是UI模拟核心是让新手看得懂。第二种的抽象程度对初学者来说偏高容易把注意力从UI细节转移到架构细节上。按钮按下后的反馈也很关键。QQ的登录按钮点击后会有一个“正在登录”的态这时候按钮文字变成“登录中...”并且按钮本身不可再次点击。这个不要用Dialog去实现直接在按钮上做状态切换就行。你可以在LoginActivity里定义一个loginState变量用Boolean标识当前是否正在登录在登录方法入口处置为true并更新UI在网络请求回调之后置回false。虽然这个项目没有真正的网络请求但用Handler.postDelayed模拟一下网络延迟非常必要能让你把用户可感知的交互状态调教到位。提示务必处理快速重复点击的问题。即使你设置了setEnabled(false)在某些极端情况下点击事件仍然可能穿透。更稳妥的做法是在onClick回调里再判断一次loginState双重保险。2.3 主界面的结构组织与底部导航实现主界面是整个项目里包含页面最多、最容易写乱的部分。QQ主界面底部有四个Tab消息、联系人、动态、我的。我建议你用ViewPager2加TabLayout来实现这四个页面的切换而不是用老掉牙的FragmentTransaction手动切换。为什么推荐ViewPager2因为它天然支持页面滑动切换配合TabLayout之后指示器和页面联动是自动处理的代码量少很多。传统的手动切换Fragment你需要自己管理show、hide还要防止重建后的状态丢失复杂度明显更高。实现的关键步骤是这样的第一新建四个Fragment类分别对应四个Tab。每个Fragment的布局可以先放一个简单的TextView占位后续再填充真实的列表。第二创建一个FragmentStateAdapter子类。这个适配器需要返回四个Fragment实例并正确实现getItemCount。要注意ViewPager2要求适配器必须是无参数构造的Fragment工厂方式如果你用了带参数的构造方法会直接崩。public class MainPagerAdapter extends FragmentStateAdapter { public MainPagerAdapter(FragmentActivity fa) { super(fa); } Override public Fragment createFragment(int position) { switch (position) { case 0: return new MessageFragment(); case 1: return new ContactFragment(); case 2: return new MomentFragment(); case 3: return new ProfileFragment(); default: return new MessageFragment(); } } Override public int getItemCount() { return 4; } }第三在MainActivity的布局里放TabLayout和ViewPager2然后在onCreate里把它们关联起来TabLayout tabLayout findViewById(R.id.tab_layout); ViewPager2 viewPager findViewById(R.id.view_pager); viewPager.setAdapter(new MainPagerAdapter(this)); // 使用TabLayoutMediator建立关联 TabLayoutMediator mediator new TabLayoutMediator(tabLayout, viewPager, (tab, position) - tab.setText(tabTitles[position])); mediator.attach();注意TabLayoutMediator必须调用attach()否则Tab和数据源不会关联这是最容易漏的一步。如果你想把图标也加进去就在TabLayoutMediator的回调里通过tab.setIcon()设置图标资源。这里有个坑图标需要准备选中和未选中两套状态用Selector来选择对应状态下的图片否则图标颜色不会随选中状态变化。2.4 消息列表页的细节实现如果你想让主界面看起来更像一回事消息页不能只放一个空白Fragment。用RecyclerView展示一个简单的消息列表是这类项目最标配的下一步。列表项布局仿照QQ的简单形态左边一个圆形头像中间是名称和最后一条消息预览右边是时间。这个布局用RelativeLayout或者ConstraintLayout都行我习惯用ConstraintLayout因为约束关系更直观。头像的圆形效果是这里一个容易卡住的点。最简单的方式是用CircleImageView这个第三方库一行代码搞定圆形裁剪。但既然是练手项目我更推荐自己用BitmapShader实现一次圆形裁剪核心代码二三十行能让你理解Canvas绘制的基本逻辑。简单说明一下思路拿到Bitmap之后创建一个与View宽高一致的正方形画布把BitmapShader设置给Paint然后画一个圆形。关键是BitmapShader的TileMode要设置为CLAMP否则头像的边缘会出现拉伸。这一步做完你对自定义绘制的理解会提升一个台阶。消息列表的数据可以写死在本地。用一个ListMessage对象里面包含name、avatarResId、lastMsg、time字段然后交给Adapter渲染。为了让列表有一点真实感可以提前造十几条不同好友的模拟数据比如“文件传输助手”“产品群”“技术交流群”等。但要避免真实人物名。列表的Adapter建议直接继承RecyclerView.Adapter不要偷懒用ArrayAdapter。RecyclerView是现在Android列表开发的主流方案ViewHolder的模式也是标配早一天熟悉早一天受益。3. 实操过程与核心环节实现3.1 环境准备与工程创建动手之前先把环境理清楚。我用的是Android Studio Hedgehog版本Gradle用8.2以上compileSdk34minSdk24targetSdk34。minSdk选24是因为Android 7.0的覆盖率高而且很多新API不用做兼容判断。创建工程时直接选Empty Views Activity语言建议先选Java。不是Kotlin不好而是仿QQ这个项目的很多开源样例和参考资料都是Java写的你遇到问题搜答案的时候Java版本的资料命中率明显更高。等把项目整体跑通了再迁移到Kotlin会顺畅很多。工程创建完了先别急着写代码。把项目跑起来在模拟器上确认空工程能正常启动再开始动手。这个步骤能帮你把“环境问题”和“代码问题”隔离开后面排查问题的时候少受干扰。3.2 登录页UI的落地实践登录页布局我建议用ConstraintLayout做根布局比LinearLayout的嵌套层级更少渲染性能更好。不过为了让布局结构清晰内部嵌套一层LinearLayout完全没问题Android的布局层级并没有那么恐怖别过度优化。从视觉上自上而下排列顶部空出约80dp的间距然后是Logo图片用ImageView加载紧接着是账号输入框下方是密码输入框再往下是登录按钮宽度占满屏幕但左右留16dp边距最后是底部的“注册账号”入口。有一个细节值得提输入框之间的间距不要用margin硬写而是用Space控件或者include分割。这样后续调整间距时只需要改一个地方不用来回改多个控件的margin值。Logo图片建议用矢量图也就是VectorDrawable格式。矢量图在不同屏幕密度下不会失真而且文件体积比PNG小很多。如果你手头没有合适的QQ Logo素材可以先放一个占位的圆角正方形中间写“QQ”重点是练习布局逻辑。3.3 登录校验与页面跳转的代码实现布局完成之后接下来需要把登录动作真正串起来。在LoginActivity里给登录按钮设置OnClickListener点击时读取两个输入框的文本做基础的非空校验。这里要注意一个习惯在读取EditText文本时先通过TextUtils.isEmpty()判断再使用trim()去掉首尾空格。QQ的登录框其实不允许账号带空格你可以在TextWatcher里直接拦截空格输入把空格自动去掉体验更真实。校验通过后进入“模拟登录”流程。因为没有真实的后端接口我用一个延迟任务模拟private void doLogin(String account, String password) { loginState true; btnLogin.setText(登录中...); btnLogin.setEnabled(false); new Handler(getMainLooper()).postDelayed(() - { loginState false; btnLogin.setText(登录); btnLogin.setEnabled(true); // 模拟登录成功跳转主界面 Intent intent new Intent(LoginActivity.this, MainActivity.class); intent.putExtra(account, account); startActivity(intent); finish(); }, 1500); }这个延迟设置成1.5秒是为了让你能看到按钮状态的变化过程。延迟太短按钮的“登录中”状态一闪而过看不出效果延迟太长用户体验又很假。跳转之后记得调用finish()。这样用户从主界面按返回键时不会回到登录页而是直接退出应用。这是真实App的常规做法也是模拟登录过程必须养成的习惯。3.4 登录状态保持与自动登录真实QQ有“记住密码”和“自动登录”选项。虽然这是一个模拟项目这个功能值得加上因为它能让你练习SharedPreferences的使用。在登录成功时把账号和密码写入SharedPreferences。如果用户勾选了“自动登录”再额外保存一个autoLogin标记位SharedPreferences.Editor editor getSharedPreferences(login_pref, MODE_PRIVATE).edit(); if (cbAutoLogin.isChecked()) { editor.putString(account, account); editor.putString(password, password); editor.putBoolean(auto_login, true); } else { editor.clear(); } editor.apply();apply()和commit()的区别要讲一下。apply()是异步写入不会阻塞主线程commit()是同步写入返回值能告诉你写入是否成功。在UI线程里建议用apply()除非你需要关注写入结果。然后在登录页的onCreate里做自动登录检查SharedPreferences prefs getSharedPreferences(login_pref, MODE_PRIVATE); if (prefs.getBoolean(auto_login, false)) { String account prefs.getString(account, ); String password prefs.getString(password, ); doLogin(account, password); }这个功能看似简单但它会引出一个问题自动登录的时候界面可能还没完全渲染出来按钮点击会有视觉跳跃。解决方案是在doLogin里只负责逻辑跳转不依赖按钮引用这样不管是手动点击还是自动登录走的是同一条路径。3.5 主界面的搭建与Fragment页面组织主界面的代码组织直接决定这个项目后续是否好扩展。我在MainActivity里只做两件事初始化ViewPager2和TabLayout管理Fragment容器的生命周期。具体的业务代码全部下沉到各个Fragment里。以“消息”Fragment为例它的布局结构是一个RecyclerView占满全屏顶部隐藏一个搜索栏可以暂不实现。加载数据时先创建ListMessage然后设置RecyclerView的LayoutManager和Adapter。RecyclerView的LayoutManager用LinearLayoutManager方向为VERTICAL。不要用GridLayoutManager消息列表是典型的信息流列表不是九宫格。列表分割线用DividerItemDecoration实现DividerItemDecoration divider new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); divider.setDrawable(ContextCompat.getDrawable(this, R.drawable.divider_bg)); recyclerView.addItemDecoration(divider);divider_bg是一个高度为0.5dp的灰色Drawable。注意系统自带的DividerItemDecoration在Android 9以下需要自己设置颜色否则分割线可能是默认黑色。其他三个Fragment可以先不塞列表放一个居中的TextView显示对应页面的名称。这样主界面的整体结构已经完整了后面你有空再一个一个补充真实内容。有人可能会觉得这样太简陋但我的观点是骨架先立起来皮肉慢慢长。如果你一上来就想着把每个页面都做成完整的样子很容易陷入细节反而把主界面的框架结构抛在脑后。3.6 界面细节增强状态栏与沉浸式效果仿QQ的登录界面状态栏是浅色背景配深色图标的主界面则是白色状态栏。这个细节看起来很“小”但直接影响观感。实现方式如下// 登录页 Window window getWindow(); window.setStatusBarColor(Color.parseColor(#FFFFFF)); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);第一行把状态栏背景设置为白色第二行把状态栏图标设置为深色。如果不设置LIGHT_STATUS_BAR状态栏会保持白色背景但图标也是白色直接看不见。如果追求“沉浸式”可以配合WindowInsetsController把状态栏做成透明让登录页的Logo区域直接延伸到状态栏后面。但沉浸式会带来一个布局适配问题输入框的paddingTop需要额外加上状态栏高度否则输入框会被状态栏遮住。这部分可以通过ViewCompat.setOnApplyWindowInsetsListener动态获取状态栏高度再设置给根布局。我建议初学者先只做颜色适配沉浸式留到后面熟练了再搞。4. 常见问题与排查技巧实录4.1 输入框无法弹出软键盘的问题这个问题的表现是点击EditText光标出现了但软键盘死活不弹。根源大多是AndroidManifest.xml里MainActivity设置了android:windowSoftInputModestateHidden而且没有adjustPan或者adjustResize配合。正确做法是在登录页的activity标签里这样配置activity android:name.activity.LoginActivity android:windowSoftInputModeadjustResize|stateHidden /adjustResize的作用是当软键盘弹出时自动调整布局尺寸让输入框不被遮挡。stateHidden则是页面初次展示时隐藏键盘。两者并不冲突。如果你发现键盘弹出来之后登录按钮被键盘遮住了那说明adjustResize没有生效。检查一下根布局是不是ScrollView如果是键盘弹出时布局尺寸调整可能没法正确传递。更好的方案是根布局用LinearLayout或ConstraintLayout配合adjustResize就能自动压缩高度。4.2 ViewPager2与TabLayout关联后白屏TabLayoutMediator关联之后页面完全不显示或者只显示第一个页面。大部分时候问题出在FragmentStateAdapter的构造方法上。ViewPager2的设计要求FragmentStateAdapter在构造时传入FragmentActivity或Fragment如果你错误地传入了一个普通的ContextViewPager2无法创建对应的Fragment容器自然就白屏了。另一个常见原因是Fragment的空构造方法缺失。如果你在Fragment里写了带参数的构造方法一定要补一个无参构造否则系统重建Fragment时会直接抛异常。这种崩溃通常只在屏幕旋转或进程被杀后恢复时出现平时正常启动反而看不出问题排查起来特别隐蔽。4.3 RecyclerView数据刷新后不显示列表页的RecyclerView加载完数据还是不显示先检查两个地方。第一Adapter的getItemCount()是否正确返回了数据长度第二LayoutManager有没有设置。这两个是基础但不是唯一原因。还有一个容易踩的坑你在Fragment里创建的RecyclerView在onCreateView里设置完Adapter后又在onViewCreated里重新setAdapter覆盖了。这种情况在代码复制粘贴时特别容易出现。建议把RecyclerView的初始化统一放到onViewCreated里保证只执行一次。如果数据更新了但界面没变化notifyDataSetChanged()仍然无效那就要怀疑是否在子线程更新了数据。RecyclerView必须在主线程更新子线程操作会直接抛CalledFromWrongThreadException但有时候异常被吞掉了表现为界面不刷新。这种情况可以在Adapter里加一个assert(Looper.myLooper() Looper.getMainLooper())来提前发现。4.4 图片加载崩溃与OOM问题仿QQ项目里如果你用头像资源直接加载大图很容易触发OutOfMemoryError。在模拟器上可能不明显在真机上特别是低端机型上一个几百KB的图片解码成Bitmap后内存占用可能是原始大小的好几倍。如果你不希望引入图片加载库最直接的方案是用BitmapFactory.Options做降采样。设置inSampleSize2图片宽高会缩小到原来的二分之一内存占用直接降到四分之一。具体写法是先用inJustDecodeBoundstrue读取图片尺寸根据目标宽高计算inSampleSize再重新解码。BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeResource(getResources(), resId, options); int targetWidth 100; // 目标宽度 int sampleSize 1; while (options.outWidth / (sampleSize * 2) targetWidth) { sampleSize * 2; } options.inJustDecodeBounds false; options.inSampleSize sampleSize; Bitmap bitmap BitmapFactory.decodeResource(getResources(), resId, options);但说句实在话如果项目里要加载的图片超过三张直接用Glide是更实际的选择。Glide不仅帮你处理了内存缓存和磁盘缓存还解决了生命周期绑定问题页面销毁后会自动取消加载。为了练手写一遍降采样是好事但不要为了“不用第三方库”而不用第三方库工具是拿来用的。4.5 默认返回键行为不符合预期登录成功跳转到MainActivity后如果在主界面按返回键一般期望是退出应用而不是返回到登录页。前面我提过跳转时调用finish()但有些场景下你可能会忘记在LoginActivity里调用。还有一种常见情况从主界面退出后再点应用图标期望直接进入登录页而不是回到之前的登录状态。如果你没有做登录状态的持久化处理应用冷启动后默认就是登录页反而没问题。真正麻烦的是应用进程没有被杀死时按Home键退到桌面再点图标回来会直接恢复之前的主界面状态。这是Android的默认行为不算bug但如果你的模拟项目里登录状态是假的没做持久化这种恢复会显得不真实。解决方案是判断SharedPreferences里是否有登录态有就跳到主界面没有就留在登录页。5. 从实战中沉淀的经验与改造方向我实际做完这个仿QQ项目后最大的感受是很多问题不是看教程能看出来的非得自己亲手敲一遍才能体会到。比如TabLayoutMediator的attach()那行代码我第一遍写的时候漏掉了页面怎么都不出效果查了两小时才找到原因。这种记忆比任何教程都牢固。如果你学有余力建议在基础版之上做三个方向的扩展第一个方向是数据层落地。把静态的ListMessage换成Room数据库存储消息列表的数据从库里读取。这样你顺便把Android的数据库方案也学了。第二个方向是接入真实网络。用一个开源的IM SDK或者自己搭一个简单的WebSocket服务把登录和消息收发改成真实网络请求。这一步会极大扩展项目的实用性也能让你了解UI和网络是怎么配合的。第三个方向是迁移到Kotlin和Compose。当你把传统View体系的这个项目摸透之后用Jetpack Compose重写一遍登录页和主界面。这种“对比式学习”能让你同时理解新旧两套UI体系的差异面试时被问到相关问题也能答得很从容。我在实际操练中还有一个体会这个项目完成后把它作为面试作品放在简历上效果比写一堆“精通XX框架”要有说服力得多。面试官问“你做过什么项目”你能把登录页面的状态管理、主界面的Fragment组织、自动登录的持久化方案讲清楚就已经赢了大部分只会背概念的人。最后再分享一个小技巧做完一个版本之后把代码用Git提交一个tag比如v1.0-basic。后面你每做一次大重构都新建一个分支。这样你随时可以回看自己从简陋到完善的全过程不仅对技术成长有帮助还能在复盘时看到自己思维的演变轨迹。