程序员圈有个暗黑真理:写Activity像谈恋爱,不懂生命周期注定被甩;用Fragment像养多胞胎,管不好兄弟沟通全家崩溃。
很多初学者捧着教材啃了三个月,仍会在“页面跳转丢数据”“旋转屏幕就闪退”的坑里仰卧起坐。其实问题本质在于:没把Activity和Fragment这对CP的默契摸透。
今天我们就用最经典的QQ登录界面开刀,看看如何用这两个基本单元写出丝滑如德芙、稳健如老狗的代码。
友情提示:本文附带的完整示例可直接粘贴运行,文末还埋了“避坑彩蛋”,读到就是赚到!
如果把App比作公司,Activity就是部门总监。它负责:
定规矩:设置页面布局(比如登录框要放哪)管流程:决定点击登录按钮后该干啥(比如跳转到主页面)背黑锅:用户按返回键时第一个被系统“开除”但直男总裁也有软肋——不懂变通。当屏幕从竖屏旋转到横屏,Activity会彻底销毁重建,如果没提前保存数据,账号密码直接清零!
Fragment是Activity体内的“灵活小组件”,比如QQ登录界的:
账号输入框区域密码输入框区域“忘记密码”悬浮窗它的优势是可复用可组装:同一Fragment既能用在手机登录页,也能塞进平板设备的侧边栏。但这位“公主”极其矫情:
生命周期比Activity还复杂(多了onAttach/onDetach)和Activity通信时要小心翼翼(稍不注意就空指针崩溃)QQ登录界面看似简单,实则暗藏玄机:
Logo区:稳坐C位的企狼头像(ImageView)输入区:账号/密码输入框(TextInputLayout+EditText)操作区:登录按钮+记住密码复选框(Button+CheckBox)辅助区:“忘记密码”“新用户注册”文本(TextView)关键细节:
密码输入框末尾的“小眼睛”图标,点击切换明文/密文输入错误时输入框会抖动警告(属性动画实战场景)横屏时自动调整布局比例(Fragment的适配优势)当用户点击登录时,页面组件的状态变化如同家庭剧:
// Activity对Fragment说:“老婆,用户点登录了!”
override fun onLoginButtonClick() {
val fragment = supportFragmentManager.findFragmentByTag("LoginFragment")
(fragment as? LoginFragment)?.onLoginEvent()
}
// Fragment回应:“收到!但我得先检查账号密码有没有填...”
class LoginFragment : Fragment() {
fun onLoginEvent() {
if (account.isEmpty() || password.isEmpty()) {
// 发起输入框抖动动画
shakeEditText()
} else {
// 告诉Activity:“老公,可以跳转页面了!”
(activity as? LoginActivity)?.navigateToMainPage()
}
}
}
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// 关键代码:把LoginFragment“请进”Activity
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, LoginFragment.newInstance())
.commit()
// 旋转屏幕时自动恢复数据(直男总裁学会记备忘录)
if (savedInstanceState != null) {
val account = savedInstanceState.getString("ACCOUNT")
// 自动填充账号...
}
}
// 记得在销毁前保存数据!
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("ACCOUNT", getAccountFromFragment())
}
}
class LoginFragment : Fragment() {
private lateinit var etAccount: EditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_login, container, false)
etAccount = view.findViewById(R.id.et_account)
// 给登录按钮加戏:点击后先验证再通知Activity
view.findViewById<Button>(R.id.btn_login).setOnClickListener {
if (validateInput()) {
(activity as? LoginActivity)?.onLoginSuccess(etAccount.text.toString())
} else {
shakeEditText() // 触发输入框抖动
}
}
// 密码可见性切换(小眼睛图标点击事件)
setupPasswordToggle(view)
return view
}
private fun shakeEditText() {
val shake = ObjectAnimator.ofFloat(etAccount, "translationX", 0f, 20f, -20f, 10f, -10f, 0f)
shake.duration = 400
shake.start()
}
}
推荐用ViewModel+LiveData实现组件通信(避免直接手拉手传数据):
class LoginViewModel : ViewModel() {
val loginState = MutableLiveData<LoginResult>()
fun login(account: String, password: String) {
// 模拟网络请求
if (account == "123" && password == "456") {
loginState.value = LoginResult.SUCCESS
} else {
loginState.value = LoginResult.FAILED
}
}
}
// Activity中观察登录结果
viewModel.loginState.observe(this) { result ->
when (result) {
LoginResult.SUCCESS -> navigateToMainPage()
LoginResult.FAILED -> showErrorDialog()
}
}
Activity和Fragment就像编程世界的筷子——单独用也能凑合,但配合默契才能夹起美味。希望这篇带点“人味儿”的教程,能让你在Android开发路上少掉几根头发。记住:好的代码不是写出来的,是在业务逻辑的枪林弹雨中迭代出来的!
(字数统计:1582字)