UI - 登錄注冊 - Compose
- 一、聲明式UI
- 1. **顛覆傳統開發模式**
- 2. **技術優勢**
- 3. **開發效率提升**
- 4. **未來生態方向**
- 5. **實際影響**
- 二、創建項目
- 1. Compose UI結構
- 2. Scaffold
- 3. 可組合函數
- 三、創建組件頁面
- 1. LoginPage
- 2. RegisterPage
- 3. MainPage
- 四、導航
- 1. 添加依賴
- 2. 使用導航
- 3. 初始化
- 4. AndroidManifest.xml配置
- 五、代碼分離
- 六、源碼
??下面我們將進行Android UI的學習,我們將開始聲明式UI,Compose的學習,熟練之后你將不會再想要去使用常規的XML繪制UI了,運行效果如下圖所示:
一、聲明式UI
Android Jetpack Compose 是 Google 推出的現代 聲明式 UI 框架,用于簡化 Android 應用界面開發。其核心意義體現在以下方面:
1. 顛覆傳統開發模式
- 聲明式編程:通過描述 UI 應該是什么狀態(而非一步步指令式操作),代碼更直觀、易維護。
- 告別 XML 布局:純 Kotlin 代碼構建 UI,減少模板代碼,提升開發效率。
2. 技術優勢
- 響應式設計:UI 自動響應狀態變化(如數據更新),無需手動調用
setText()
等操作。 - 高性能:基于智能重組(Recomposition)機制,僅更新變化的部分,避免全局刷新。
- 組合優于繼承:通過可復用的 Composable 函數靈活拼裝界面,避免深層 View 嵌套問題。
- 原生支持 Material Design:內置 Material 組件和動畫 API,快速實現現代化設計。
3. 開發效率提升
- 實時預覽:Android Studio 的 交互式預覽 和 Deploy Preview 功能,加速 UI 調試。
- 簡化狀態管理:與
ViewModel
、Flow
等架構組件深度集成,邏輯與 UI 解耦更清晰。
4. 未來生態方向
- 跨平臺擴展:通過 Compose Multiplatform 支持 iOS、桌面(Windows/macOS/Linux)和 Web,共享業務邏輯。
- 社區趨勢:Google 主推的下一代 UI 方案,逐步替代傳統 View 系統,成為 Android 開發生態的核心。
5. 實際影響
- 降低入門門檻:對新手更友好,減少對 XML 和傳統 View 系統的學習成本。
- 提升應用性能:更高效的渲染機制和更少的代碼量,間接優化應用體積和流暢度。
二、創建項目
了解什么是Compose之后,下面創建Compose
項目,記得要選擇Empty Activity
。
命名為Android-UI-Compose
,點擊Finish完成創建。
創建完成之后,我們先分析里面的代碼。
1. Compose UI結構
我們打開MainActivity.kt
,代碼如下所示:
package com.example.android_ui_composeimport android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.android_ui_compose.ui.theme.AndroidUIComposeThemeclass MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {AndroidUIComposeTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->Greeting(name = "Android",modifier = Modifier.padding(innerPadding))}}}}
}@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {Text(text = "Hello $name!",modifier = modifier)
}@Preview(showBackground = true)
@Composable
fun GreetingPreview() {AndroidUIComposeTheme {Greeting("Android")}
}
上述代碼很簡單,因為我們是第一次寫,所以我們講的詳細一點:
-
MainActivity類:
- 繼承自
ComponentActivity
- 重寫了
onCreate()
方法 - 使用
enableEdgeToEdge()
讓內容延伸到系統欄(狀態欄和導航欄)下面 - 使用
setContent
設置Compose UI
- 繼承自
-
UI結構:
- 最外層是
AndroidUIComposeTheme
(自定義主題) - 使用
Scaffold
作為布局骨架(Material Design 3的腳手架組件) Scaffold
內部顯示Greeting
組件
- 最外層是
-
Greeting組件:
- 是一個可組合函數,接收name和modifier參數
- 顯示一個簡單的文本"Hello $name!"
- 使用傳入的modifier控制布局
-
預覽功能:
GreetingPreview
函數提供了在Android Studio中的預覽功能- 使用
@Preview
注解標記 - 同樣應用了主題并顯示Greeting組件
代碼特點:
- 完全使用聲明式UI(Compose)而非傳統XML布局
- 遵循Material Design 3設計規范
- 支持邊緣到邊緣(edge-to-edge)顯示
- 有預覽功能方便開發時查看效果
2. Scaffold
Scaffold是一個腳手架,說到到腳手架你能想到什么?框架,你可以把它理解成一個房間的框架,假如登錄頁面是一個房子的話,那么這個Scaffold就是房子的基本框架,在里面加什么內容取決于我們自己,MainActivity代碼中
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->Greeting(name = "Android",modifier = Modifier.padding(innerPadding))}
??這段代碼,就是Scaffold里面設置modifier = Modifier.fillMaxSize()
占滿屏幕寬度。
3. 可組合函數
??然后在里面放了一個Greeting組件,它是一個文本,由于我們并未設置什么屬性,所以這個組件會出現在左上角,注意到我們的組件上方有一個@Composable
注解,表示這是一個可組合函數,有這個注解的才能被我們的。它里面還調用了Text()
,它也是一個可組合函數,只不過功能更多。
??我們可以類比為常規項目里面的TextView
,在Compose中這些組件會更簡單,說了這么多,我們來實際操作一下,比如我們先運行一下,看看是不是出現在左上角。
三、創建組件頁面
??在Compose中使用Toast有點不一樣,我們在MainActivity中,增加一個rememberToast()
函數,代碼如下所示:
@Composable
fun rememberToast(): (String) -> Unit {val context = LocalContext.currentreturn { message ->Toast.makeText(context, message, Toast.LENGTH_SHORT).show()}
}
??我們來寫一個登錄頁面,在Compose中,這個是比較簡單的。
1. LoginPage
下面我們在MainActivity中增加一個LoginPage
函數,這是一個可組合函數,代碼如下所示:
@Composable
fun LoginPage(navController: NavController) {val username = remember { mutableStateOf("") }val pwd = remember { mutableStateOf("") }val showToast = rememberToast()Scaffold(modifier = Modifier.fillMaxSize() ) { innerPadding ->Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = Modifier.padding(innerPadding).padding(16.dp).fillMaxSize() ) {Box(modifier = Modifier.size(120.dp)) {Image(painter = painterResource(id = R.drawable.ic_launcher_background),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())Image(painter = painterResource(id = R.drawable.ic_launcher_foreground),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())}OutlinedTextField(value = username.value,onValueChange = { username.value = it },label = { Text("賬號") },modifier = Modifier.padding(top = 32.dp).fillMaxWidth())OutlinedTextField(value = pwd.value,onValueChange = { pwd.value = it },label = { Text("密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())Button(onClick = {},shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("登錄")}TextButton(onClick = {},shape = RoundedCornerShape(16.dp),modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}}}
}
我們先來說一下上述的代碼,最外側我們寫了一個可觀察變量。
val username = remember { mutableStateOf("") }val pwd = remember { mutableStateOf("") }
使用remember
和mutableStateOf
創建可觀察的狀態變量,就類似于我們在輸入框里面輸入內容,這個值會變化,然后我們通過這個狀態變量獲取里面的值即可。
然后我們再往下,Column
是一個常用的布局組件,用于垂直排列子元素,Row用于橫向排列子元素,里面設置了
horizontalAlignment = Alignment.CenterHorizontally, // 子元素水平居中verticalArrangement = Arrangement.Center, // 子元素垂直居中
??然后放了一個Box,是一個容器,里面的組件默認會堆疊在 Box 的左上角,現在我們設置了Box的大小,而里面的圖標又設置為填充父容器,所以就是占滿的,就形成了圖標的背景和前景。
??下面就是OutlinedTextField了,輸入框,這個輸入框也是輸入時提示文字會向上浮動,這里要注意onValueChange
函數,里面的值就是it,然后我們賦值給username.value,默認的值value也是username.value,那么當輸入框的內容改變時username.value的值也就變了,同時它還是一個觀察的狀態變量。
2. RegisterPage
??最后就是兩個按鈕了,這個就很好理解了,Button里面添加Text作為文本顯示,到這里未知,頁面組件就寫好了,下面我們寫注冊頁面和主頁面。
@Composable
fun RegisterPage() {val username = remember { mutableStateOf("") }val pwd = remember { mutableStateOf("") }val pwdAgain = remember { mutableStateOf("") }Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = modifier.padding(16.dp).fillMaxSize() ) {Box(modifier = Modifier.size(120.dp)) {Image(painter = painterResource(id = R.drawable.ic_launcher_background),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())Image(painter = painterResource(id = R.drawable.ic_launcher_foreground),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())}OutlinedTextField(value = username.value,onValueChange = { username.value = it },label = { Text("賬號") },modifier = Modifier.padding(top = 32.dp).fillMaxWidth())OutlinedTextField(value = pwd.value,onValueChange = { pwd.value = it },label = { Text("密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())OutlinedTextField(value = pwdAgain.value,onValueChange = { pwdAgain.value = it },label = { Text("再次輸入密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())Button(onClick = { },shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}TextButton(onClick = { },shape = RoundedCornerShape(16.dp),modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(48.dp)) {Text("已有賬號,去登錄")}}
}
3. MainPage
@Composable
fun MainPage(modifier: Modifier = Modifier) {Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = modifier.fillMaxSize() ) {Text("用戶名:${SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME)} \n " +"密碼:${SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)}")}
}
上面的代碼其實我都寫在MainActivity.kt中,稍后我們會分開來。
四、導航
??下面我們就要考慮頁面跳轉的事情了,這里就需要用到導航了。
1. 添加依賴
我們需要先在app
模塊的build.gradle.kt
文件中的dependencies{}
中增加如下代碼:
implementation("androidx.navigation:navigation-compose:2.7.7")
添加后記得要點擊Sync Now
。
然后你會發現我們添加的依賴和上面其他的依賴不一樣,你可以把鼠標放上去,會看到一個提示彈窗,可以點擊這行藍色的文字。
就會變成這樣了。
然后我們在libs.version.toml文件中就能找到它。
2. 使用導航
下面我們回到MainActivity.kt中,找到onCreate()函數,修改里面的代碼,如下所示:
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {AndroidUIComposeTheme {val navController = rememberNavController()NavHost(navController = navController, startDestination = "login") {composable("login") { LoginPage(navController) } // 登錄頁面composable("register") { RegisterPage(navController) } // 注冊頁面composable("main") { MainPage() } // 主頁面}}}}
??可以看到我們使用了rememberNavController()
,這是一個導航的控制器,記得要導包,如果不清楚什么是導包可以評論區留言或者私信我,我再說明一下。然后我們使用了一個NavHost()
用來裝載我們需要導航的頁面,startDestination = "login"
,表示第一個顯示的頁面是登錄頁面,這里我們用了一個字符串,這個其實我們可以寫一個常量的類去保存這些不變的字符串。然后我們在LoginPage和RegisterPage的函數中分別傳遞了navController,因為這兩個頁面是需要導航了,因此就傳進去,然他們導航,當然這只是推薦的方式,后續我們會使用別的更好的方式,這里就先這么用著。
??上述代碼不出意外會報錯,我們可以在對應的函數中增加對應參數,這里RegisterPage也要記得增加,然后就不報錯了,下面,我們先寫一個常量類,在com.example.android_ui_compose
包下新建一個utils
包,包下創建一個EasyConstants類,里面的代碼如下所示:
package com.example.android_ui_compose.utilsobject EasyConstants {const val PAGE_LOGIN = "page_login"const val PAGE_REGISTER = "page_register"const val PAGE_MAIN = "page_main"const val KEY_USERNAME = "username"const val KEY_PASSWORD = "password"
}
這里我們已經知道接下來要做什么了,所以我們就提前準備好需要的常量,這里是三個頁面和賬號密碼的常量Key,下面在utils包再創建一個SPUtils類,代碼如下所示:
package com.example.android_ui_compose.utils
import android.content.Context
import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KPropertyclass SPUtils private constructor(context: Context, name: String = DEFAULT_SP_NAME) {companion object {const val DEFAULT_SP_NAME = "sp_config"private var instance: SPUtils? = null@Synchronizedfun initialize(context: Context, name: String = DEFAULT_SP_NAME): SPUtils {return instance ?: SPUtils(context.applicationContext, name).also { instance = it }}fun getInstance(): SPUtils {return instance ?: throw IllegalStateException("SPUtils not initialized. Call initialize() first.")}}private val sharedPref: SharedPreferences by lazy {context.getSharedPreferences(name, Context.MODE_PRIVATE)}// 基本操作fun putString(key: String, value: String) = sharedPref.edit().putString(key, value).apply()fun getString(key: String, default: String = "") = sharedPref.getString(key, default) ?: defaultfun putInt(key: String, value: Int) = sharedPref.edit().putInt(key, value).apply()fun getInt(key: String, default: Int = 0) = sharedPref.getInt(key, default)fun putLong(key: String, value: Long) = sharedPref.edit().putLong(key, value).apply()fun getLong(key: String, default: Long = 0L) = sharedPref.getLong(key, default)fun putFloat(key: String, value: Float) = sharedPref.edit().putFloat(key, value).apply()fun getFloat(key: String, default: Float = 0f) = sharedPref.getFloat(key, default)fun putBoolean(key: String, value: Boolean) = sharedPref.edit().putBoolean(key, value).apply()fun getBoolean(key: String, default: Boolean = false) = sharedPref.getBoolean(key, default)fun putStringSet(key: String, value: Set<String>) = sharedPref.edit().putStringSet(key, value).apply()fun getStringSet(key: String, default: Set<String> = emptySet()) = sharedPref.getStringSet(key, default) ?: default// 其他操作fun contains(key: String) = sharedPref.contains(key)fun remove(key: String) = sharedPref.edit().remove(key).apply()fun clear() = sharedPref.edit().clear().apply()fun getAll() = sharedPref.all// 使用委托屬性簡化訪問fun stringPref(key: String, default: String = "") =object : ReadWriteProperty<Any, String> {override fun getValue(thisRef: Any, property: KProperty<*>) = getString(key, default)override fun setValue(thisRef: Any, property: KProperty<*>, value: String) = putString(key, value)}fun intPref(key: String, default: Int = 0) =object : ReadWriteProperty<Any, Int> {override fun getValue(thisRef: Any, property: KProperty<*>) = getInt(key, default)override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) = putInt(key, value)}fun booleanPref(key: String, default: Boolean = false) =object : ReadWriteProperty<Any, Boolean> {override fun getValue(thisRef: Any, property: KProperty<*>) = getBoolean(key, default)override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) = putBoolean(key, value)}
}// 擴展函數簡化使用
fun Context.sp(name: String = SPUtils.DEFAULT_SP_NAME) = SPUtils.initialize(this, name)
現在我們再回到MainActivity
,先將onCreate中的字符串改成常量值
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {AndroidUIComposeTheme {val navController = rememberNavController()NavHost(navController = navController, startDestination = PAGE_LOGIN) {composable(PAGE_LOGIN) { LoginPage(navController) } // 登錄頁面composable(PAGE_REGISTER) { RegisterPage(navController) } // 注冊頁面composable(PAGE_MAIN) { MainPage() } // 主頁面}}}}
報錯的地方記得要導包喲~
然后修改LoginPage函數中的代碼,登錄按鈕的點擊事件:
Button(onClick = {if (username.value.isEmpty()) {showToast("請輸入用戶名")return@Button}if (pwd.value.isEmpty()) {showToast("請輸入密碼")return@Button}// 校驗賬號密碼if (username.value == SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME) &&pwd.value == SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)) {showToast("登錄成功")// 進入主界面navController.navigate(PAGE_MAIN) {// 指定要彈出到哪個頁面(這里用當前頁面)popUpTo(navController.currentBackStackEntry?.destination?.route ?: return@navigate) {inclusive = true // 表示彈出的范圍包含當前頁面(即關閉當前頁)}}}},shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("登錄")}
這里的邏輯其實和我之前寫那個常規項目是一樣的,就是跳轉頁面的方式不一樣,通過navController去進行導航到目標頁面,這里登錄成功我們就導航到主頁面,注冊按鈕的點擊代碼:
TextButton(onClick = {navController.navigate(PAGE_REGISTER)},shape = RoundedCornerShape(16.dp),modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}
注冊我們就直接導航到注冊頁面。
下面再進入RegisterPage
函數中的代碼,注冊按鈕的點擊事件:
Button(onClick = {if (username.value.isEmpty()) {showToast("請輸入用戶名")return@Button}if (pwd.value.isEmpty()) {showToast("請輸入密碼")return@Button}if (pwdAgain.value.isEmpty()) {showToast("請再次輸入密碼")return@Button}// 檢查兩次密碼是否一致if (pwd.value != pwdAgain.value) {showToast("兩次密碼不一致")return@Button}// 注冊賬號,保存賬號到SP,進行本地數據持久化,如果清除緩存則會消失SPUtils.getInstance().putString(EasyConstants.KEY_USERNAME, username.value)SPUtils.getInstance().putString(EasyConstants.KEY_PASSWORD, pwd.value)showToast("注冊成功,稍后進入登錄頁面進行登錄")// 一秒后返回登錄頁面CoroutineScope(Dispatchers.Main).launch {delay(1000)navController.popBackStack()}},shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}
這里登錄注冊的代碼就都寫好了,其實主頁面之前就寫好了。
下面我們還需要配置一下SPUtils的初始化。
3. 初始化
??工具類方法中有一個initialize()
方法,我們在使用之前要先初始化,否則會報錯,初始化我們可以在程序執行的時候進行初始化,我們在com.example.android_ui_compose
下創建一個MyApplication
類,里面的代碼如下:
package com.example.android_ui_composeimport android.app.Application
import com.example.android_ui_compose.utils.SPUtilsclass MyApplication : Application() {override fun onCreate() {super.onCreate()SPUtils.initialize(this)}
}
??這里需要說一下它的作用,這里繼承 Android 的 Application 類,用于全局應用級別的初始化,可以在此類中初始化全局工具(如數據庫、網絡庫、SharedPreferences 工具等),可以維護應用級別的狀態或變量,比在 Activity 中初始化更高效,因為 Application 只會創建一次。
4. AndroidManifest.xml配置
??在上面我們寫好了這個類,為了使這個類生效,我們需要在AndroidManifest.xml
中配置配置它,很簡單,配置如下圖所示:
雖然很簡單,但是很容易會被忘記,所以你要記得呀,然后就可以運行了。
五、代碼分離
好了,下面我們在ui包下創建一個page包,page包創建LoginPage,里面代碼如下:
package com.example.android_ui_compose.ui.pageimport androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.example.android_ui_compose.R
import com.example.android_ui_compose.rememberToast
import com.example.android_ui_compose.utils.EasyConstants
import com.example.android_ui_compose.utils.EasyConstants.PAGE_MAIN
import com.example.android_ui_compose.utils.EasyConstants.PAGE_REGISTER
import com.example.android_ui_compose.utils.SPUtils@Composable
fun LoginPage(navController: NavController) {val username = remember { mutableStateOf("") }val pwd = remember { mutableStateOf("") }val showToast = rememberToast()Scaffold(modifier = Modifier.fillMaxSize() ) { innerPadding ->Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = Modifier.padding(innerPadding).padding(16.dp).fillMaxSize() ) {Box(modifier = Modifier.size(120.dp)) {Image(painter = painterResource(id = R.drawable.ic_launcher_background),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())Image(painter = painterResource(id = R.drawable.ic_launcher_foreground),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())}OutlinedTextField(value = username.value,onValueChange = { username.value = it },label = { Text("賬號") },modifier = Modifier.padding(top = 32.dp).fillMaxWidth())OutlinedTextField(value = pwd.value,onValueChange = { pwd.value = it },label = { Text("密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())Button(onClick = {if (username.value.isEmpty()) {showToast("請輸入用戶名")return@Button}if (pwd.value.isEmpty()) {showToast("請輸入密碼")return@Button}// 校驗賬號密碼if (username.value == SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME) &&pwd.value == SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)) {showToast("登錄成功")// 進入主界面navController.navigate(PAGE_MAIN) {// 指定要彈出到哪個頁面(這里用當前頁面)popUpTo(navController.currentBackStackEntry?.destination?.route ?: return@navigate) {inclusive = true // 表示彈出的范圍包含當前頁面(即關閉當前頁)}}}},shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("登錄")}TextButton(onClick = {navController.navigate(PAGE_REGISTER)},shape = RoundedCornerShape(16.dp),modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}}}
}
page包下創建RegisterPage類,里面代碼如下所示:
package com.example.android_ui_compose.ui.pageimport androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.example.android_ui_compose.R
import com.example.android_ui_compose.rememberToast
import com.example.android_ui_compose.utils.EasyConstants
import com.example.android_ui_compose.utils.SPUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch@Composable
fun RegisterPage(navController: NavController) {val username = remember { mutableStateOf("") }val pwd = remember { mutableStateOf("") }val pwdAgain = remember { mutableStateOf("") }val showToast = rememberToast()Scaffold(modifier = Modifier.fillMaxSize() ) { innerPadding ->Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = Modifier.padding(innerPadding).padding(16.dp).fillMaxSize() ) {Box(modifier = Modifier.size(120.dp)) {Image(painter = painterResource(id = R.drawable.ic_launcher_background),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())Image(painter = painterResource(id = R.drawable.ic_launcher_foreground),contentDescription = "App Icon",modifier = Modifier.fillMaxSize())}OutlinedTextField(value = username.value,onValueChange = { username.value = it },label = { Text("賬號") },modifier = Modifier.padding(top = 32.dp).fillMaxWidth())OutlinedTextField(value = pwd.value,onValueChange = { pwd.value = it },label = { Text("密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())OutlinedTextField(value = pwdAgain.value,onValueChange = { pwdAgain.value = it },label = { Text("再次輸入密碼") },visualTransformation = PasswordVisualTransformation(),modifier = Modifier.padding(top = 16.dp).fillMaxWidth())Button(onClick = {if (username.value.isEmpty()) {showToast("請輸入用戶名")return@Button}if (pwd.value.isEmpty()) {showToast("請輸入密碼")return@Button}if (pwdAgain.value.isEmpty()) {showToast("請再次輸入密碼")return@Button}// 檢查兩次密碼是否一致if (pwd.value != pwdAgain.value) {showToast("兩次密碼不一致")return@Button}// 注冊賬號,保存賬號到SP,進行本地數據持久化,如果清除緩存則會消失SPUtils.getInstance().putString(EasyConstants.KEY_USERNAME, username.value)SPUtils.getInstance().putString(EasyConstants.KEY_PASSWORD, pwd.value)showToast("注冊成功,稍后進入登錄頁面進行登錄")// 一秒后返回登錄頁面CoroutineScope(Dispatchers.Main).launch {delay(1000)navController.popBackStack()}},shape = RoundedCornerShape(16.dp), // 設置圓角半徑modifier = Modifier.padding(top = 32.dp).fillMaxWidth().height(48.dp)) {Text("注冊")}TextButton(onClick = { navController.popBackStack() },shape = RoundedCornerShape(16.dp),modifier = Modifier.padding(top = 16.dp).fillMaxWidth().height(48.dp)) {Text("已有賬號,去登錄")}}}
}
再創建一個MainPage類,代碼如下所示:
package com.example.android_ui_compose.ui.pageimport androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.example.android_ui_compose.utils.EasyConstants
import com.example.android_ui_compose.utils.SPUtils@Composable
fun MainPage() {Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = Modifier.fillMaxSize() ) {Text("用戶名:${SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME)} \n " +"密碼:${SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)}")}
}
最后我們再整理一下MainActivity中的代碼,如下所示:
package com.example.android_ui_composeimport android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.android_ui_compose.ui.page.LoginPage
import com.example.android_ui_compose.ui.page.MainPage
import com.example.android_ui_compose.ui.page.RegisterPage
import com.example.android_ui_compose.ui.theme.AndroidUIComposeTheme
import com.example.android_ui_compose.utils.EasyConstants.PAGE_LOGIN
import com.example.android_ui_compose.utils.EasyConstants.PAGE_MAIN
import com.example.android_ui_compose.utils.EasyConstants.PAGE_REGISTERclass MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {AndroidUIComposeTheme {val navController = rememberNavController()NavHost(navController = navController, startDestination = PAGE_LOGIN) {composable(PAGE_LOGIN) { LoginPage(navController) } // 登錄頁面composable(PAGE_REGISTER) { RegisterPage(navController) } // 注冊頁面composable(PAGE_MAIN) { MainPage() } // 主頁面}}}}
}@Composable
fun rememberToast(): (String) -> Unit {val context = LocalContext.currentreturn { message ->Toast.makeText(context, message, Toast.LENGTH_SHORT).show()}
}
好了,現在我們來看看項目的結構。
你是否跟我一樣呢?上面代碼分離之后可以再運行一下試試看,看能夠運行起來嗎,可以就沒有問題,不可以就要再看看是不是哪里弄錯了。
六、源碼
GitHub源碼地址: Android-UI-Compose
好的,后會有期~