当前位置: 首页 > news >正文

Android UI(一)登录注册 - Compose

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 调试。
  • 简化状态管理:与 ViewModelFlow 等架构组件深度集成,逻辑与 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")}
}

上述代码很简单,因为我们是第一次写,所以我们讲的详细一点:

  1. MainActivity类

    • 继承自ComponentActivity
    • 重写了onCreate()方法
    • 使用enableEdgeToEdge()让内容延伸到系统栏(状态栏和导航栏)下面
    • 使用setContent设置Compose UI
  2. UI结构

    • 最外层是AndroidUIComposeTheme(自定义主题)
    • 使用Scaffold作为布局骨架(Material Design 3的脚手架组件)
    • Scaffold内部显示Greeting组件
  3. Greeting组件

    • 是一个可组合函数,接收name和modifier参数
    • 显示一个简单的文本"Hello $name!"
    • 使用传入的modifier控制布局
  4. 预览功能

    • GreetingPreview函数提供了在Android Studio中的预览功能
    • 使用@Preview注解标记
    • 同样应用了主题并显示Greeting组件

代码特点:

  1. 完全使用声明式UI(Compose)而非传统XML布局
  2. 遵循Material Design 3设计规范
  3. 支持边缘到边缘(edge-to-edge)显示
  4. 有预览功能方便开发时查看效果

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("") }

使用remembermutableStateOf创建可观察的状态变量,就类似于我们在输入框里面输入内容,这个值会变化,然后我们通过这个状态变量获取里面的值即可。

然后我们再往下,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

好的,后会有期~

http://www.dtcms.com/a/331817.html

相关文章:

  • 基于Python和Dify的成本对账系统开发
  • OpenCV Canny 边缘检测
  • 软考中级【网络工程师】第6版教材 第3章 局域网 (上)
  • Linux中tty与8250-uart的虐恋(包括双中断发送接收机制)
  • Linux中Samba服务配置与使用指南
  • YouBallin正式上线:用Web3重塑创作者经济
  • 会议通信系统核心流程详解(底稿1)
  • JVM的逃逸分析深入学习
  • 17.2 修改购物车商品
  • RLVR(可验证奖励的强化学习):大模型后训练的客观评估策略
  • 负载因子(Load Factor) :哈希表(Hash Table)中的一个关键性能指标
  • AI大模型+Meta分析:助力发表高水平SCI论文
  • 多任务并发:进程管理的核心奥秘
  • 【记录】Apache SeaTunnel 系统监控信息
  • 使用ETL工具同步Oracle的表到Doris
  • 使用load data或insert导入10w条数据
  • 51单片机-GPIO介绍
  • 网络组播技术详解
  • 深入理解 `std::any`:C++ 中的万能容器
  • 俄罗斯加强互联网管控,限制 WhatsApp 和 Telegram 通话
  • P5663 [CSP-J2019] 加工零件
  • 腾讯K8S环境【TKE】中,如何驱逐指定pod重新部署?
  • Kafka下载和安装
  • Python:如何处理WRF投影(LCC, 兰伯特投影)?
  • 深度学习 --- ResNet神经网络
  • 【递归完全搜索】CCC 2008 - 24点游戏Twenty-four
  • 【完整源码+数据集+部署教程】膝关节屈伸运动检测系统源码和数据集:改进yolo11-RFAConv
  • pip和dnf只下载不安装离线包
  • 沈帅波出席茅台红缨子高粱节探讨产业赋能新模式
  • Ansys FreeFlow入门:对搅拌罐进行建模