Android UI(一)登录注册

UI - 登录注册

下面我们将进行Android UI的学习,这个可以讲的很简单,也可以讲的很深,我将不会从简单的一个一个控件开始讲述,而是从功能去讲述UI,本篇文章以常规项目的模式为主,也就是有XML,当然我也会写一篇Compose来编写功能的文章,好了,下面我们进入正题。

最终效果图如下所示:

这里你看到中间有黑屏的地方不要慌,那不是报错,那是因为录屏的时候当输入密码时是不可以录制的,所以就黑屏了,等输入完成之后就可以了,你在实际运行的时候不会这样的。

一、创建项目

下面创建项目,记得要选择Empty Views Activity

就命名为Android-UI,后续对于UI的讲述都在这个项目下,我也会提供源码给你,点击Finish完成项目创建。

二、创建页面

一般情况下,我们做这种页面开发,一个单独的页面都是创建一个Activity的,它是Android中的四大组件之一,很重要,下面我们在项目中创建一个登录页面和一个注册页面。

鼠标右键点击这个包名目录 → New → Activity → Empty Views Activity

通过这个图我们就知道创建Activity的入口,这个我只说一次,你创建几次就知道怎么找到了,无须多言。

这里我们创建一个LoginActivity作为登录页面,这里注意一点就是,我们通过上述方式创建Activity时,进入这个窗口时,Generate Layout File选项会默认勾选上,它的意思是生成对应的布局XML文件,文件名称就是activity_login,格式就是.xml。

而下面这个Launcher Activity是默认不勾选的,我勾选上的,因为我们创建项目的时候有一个默认的MainActivity,它是启动页,而现在我们需要将登录作为启动页面,所以我这里勾选上,那么下一次我们启动App的时候就会直接打开登录页面,而不是主页面,当然这个配置我们也可以在AndroidManifest.xml中进行改动。其他的就没有什么好解释的了,点击Finish完成登录页面的创建。

我们就可以在这里看到一个LoginActivity,然后我们再用同样的方式创建一个注册页面,名称是RegisterActivity,这里就不用勾选那个Launcher Activity选项了。

因为启动页面只有一个,不需要那么多启动页面,注册页面我们可以从登录页面跳转过去。你可能会好奇,如果注册页面也设置为启动页面会怎么样。我们先完成页面的创建,然后再说这个问题。

三、启动顺序

这里就涉及到Activity相关的知识点,那么我们就说一下,这里我们目前有两个去启动的Activity,分别是MainActivityLoginActivity,我们打开AndroidManifest.xml看一下。

在运行之前,你可以先猜一下,启动的是那个Activity,然后我们再运行。运行的结果是,启动了LoginActivity,而不是MainActivity。

那我们再把MainActivity的配置放到前面去,在运行一下就是启动的MainActivity了,由此我们知道了一个点,那就是当我们有多个Activity配置了启动页面后,谁在前面就启动谁,这个就和xml文件的读取机制有关系,从上往下进行读取的,如果说我们的AndroidManifest.xml中只有一个是启动页面,那么不管它在什么位置都会启动它。

还有一个点,那就是activity标签中的exported属性,在Android 12及以上的版本中增加了这个属性,如果你在对应的手机上运行而不配置的话就会报错,一般启动页面的配置是true,其他的是false。这个属性是一个安全属性,控制其他应用能否直接启动该Activity。

  • false:仅允许同一应用(或相同用户ID的应用)启动此Activity。
  • true:允许其他应用(如浏览器、其他APP)通过Intent调用它。

最后我们再改动一下,这里我就把LoginActivity作为启动页,并且去掉了MainActivity的相关配置。

四、布局

下面我们打开activity_login.xml,先看看里面的代码:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayout是一个约束布局,允许我们通过拖动锚点的方式对页面的控件进行布局,是现在主推的方式,当然还有其他布局方式、线性布局、相对布局、帧布局、网格布局、协调布局,你没有必要去学习所有布局,而是要用最合适的方式去布局。

那么我们如何选择布局?

  • 简单列表/表单 → LinearLayout
  • 复杂动态界面 → ConstraintLayout(首选)
  • 重叠或单控件 → FrameLayout
  • Material Design动画 → CoordinatorLayout

那对于我们的登录页面来说我们用什么布局好一些呢?这里就要分析这个页面需要哪些控件了,账号、密码输入框、登录按钮、注册入口。

我们先设计的简单一些,上述的控件我们就可以用线性布局去实现,activity_login.xml代码如下所示:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".LoginActivity">

    <ImageView
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:contentDescription="LOGO"
        android:src="@mipmap/ic_launcher" />

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="32dp">

        <EditText
            android:id="@+id/et_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints="username"
            android:hint="账号" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp">

        <EditText
            android:id="@+id/et_pwd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints="pwd"
            android:hint="密码"
            android:inputType="textPassword" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="32dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="登录"
        app:cornerRadius="16dp" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_register"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="注册"
        app:cornerRadius="16dp" />
</LinearLayout>

运行后的效果图如下所示:

上述代码, 似乎没有什么能讲的,如果你觉得有需要的话,可以评论或者留言,下面我们再编写注册页面的布局代码activity_register.xml,如下所示:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".LoginActivity">

    <ImageView
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:contentDescription="LOGO"
        android:src="@mipmap/ic_launcher" />

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="32dp">

        <EditText
            android:id="@+id/et_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints="username"
            android:hint="账号" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp">

        <EditText
            android:id="@+id/et_pwd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints="pwd"
            android:hint="密码"
            android:inputType="textPassword" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp">

        <EditText
            android:id="@+id/et_pwd_again"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints="username"
            android:hint="再次输入密码"
            android:inputType="textPassword" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="32dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="注册"
        app:cornerRadius="16dp" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_login"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="已有账号,去登录"
        app:cornerRadius="16dp" />
</LinearLayout>

注册页面和登录页面也没差太多,上面有很多的共同点,不过我们还是简单说一下,这里输入框我们通过TextInputLayout(Material Design)增强效果,用它包裹EditText,输入时提示文字会浮动到输入框上方。上面我们需要用到的控件都给了一个id,我们在代码中需要通过id去找到对应的控件。

最终我们需要进入到主页面MainActivity,我们将账号和密码显示在这个主页面上,修改布局代码activity_main.xml中的TextView,添加一个id,如下图所示:

五、业务逻辑

这里的业务逻辑比较简单,因为我们设计的就比较简单,当我们点击登录或者注册按钮时需要检查当前输入框是否有内容,有的话就可以执行登录或者注册的方法,这里我们就不对输入框的内容进行规则检查了,先做的简单一些,注册成功之后就通过本地持久化技术保存起来,在登录页面登录时拿出数据进行对比,一致则进入主页面,是不是很简单呢?

下面我们进入逻辑编写的环节。

1. 本地数据持久化

这里我们使用到SharedPreferences来做数据持久化,它是轻量级的,并不是数据库,数据库后面再说。

我们在com.example.android_ui包下创建一个utils包,参考下图方式,只说明一次

然后在utils包下创建一个SPUtils类,注意我们需要创建的是Kotlin文件,参考下图方式:

然后编写SPUtils类的代码,如下所示:

kotlin 复制代码
package com.example.android_ui.utils
import android.content.Context
import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class SPUtils private constructor(context: Context, name: String = DEFAULT_SP_NAME) {

    companion object {
        const val DEFAULT_SP_NAME = "sp_config"
        private var instance: SPUtils? = null

        @Synchronized
        fun 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) ?: default

    fun 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)

这里就是一个简单的工具类,SP就是键值对的方式对数据进行存取,我们需要对这个工具类进行一个初始化。

2. 初始化

工具类方法中有一个initialize()方法,我们在使用之前要先初始化,否则会报错,初始化我们可以在程序执行的时候进行初始化,我们在com.example.android_ui下创建一个MyApplication类,里面的代码如下:

kotlin 复制代码
package com.example.android_ui

import android.app.Application
import com.example.android_ui.utils.SPUtils

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        SPUtils.initialize(this)
    }
}

这里需要说一下它的作用,这里继承 Android 的 Application 类,用于全局应用级别的初始化,可以在此类中初始化全局工具(如数据库、网络库、SharedPreferences 工具等),可以维护应用级别的状态或变量,比在 Activity 中初始化更高效,因为 Application 只会创建一次。

3. AndroidManifest.xml配置

在上面我们写好了这个类,为了使这个类生效,我们需要在AndroidManifest.xml中配置配置它,很简单,配置如下图所示:

虽然很简单,但是很容易会被忘记,所以你要记得呀。

4. 常量类

前面我们说过SP是针对于键值对的形式,也就是Key-Value的形式,你可以理解钥匙和锁的关系,一对一的,那么我们的Key一般是不变的,所以我们可以用一个常量类去存放这些Key,在utils包下新建一个EasyConstants类,代码如下所示:

kotlin 复制代码
object EasyConstants {
    const val KEY_USERNAME = "username"
    const val KEY_PASSWORD = "password"
}

这里我目前只写了两个Key,账号和密码,后面再持续增加,这里就告一段落,下面我们就要进入LoginActivity中去编写代码了。

5. findViewById

因为是常规项目,所以我们有XML,xml中的代码我们之前都已经写好了,那么下面我们要做的就是在代码文件中去操作xml中的控件去实现我们的业务逻辑。

在此之前我们还是需要了解一下findViewById的作用:

  • findViewById(int id) 是一个用于通过布局文件中的ID查找视图(View)对象的方法,定义在 Activity 和 View 类中。
  • 连接布局与代码的桥梁,将XML布局文件中定义的UI组件()与Java/Kotlin代码关联,使开发者能够在代码中操作界面元素
  • 视图控制的基础,获取到View对象后,可以:
    • 修改属性(文字、颜色、可见性等)
    • 设置事件监听(点击、长按等)
    • 动态改变布局

针对这个我们可以使用传统的方式和现代改进方案ViewBinding,我们先使用传统的方式去写登录页面,LoginActivity代码如下所示:

kotlin 复制代码
package com.example.android_ui

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.android_ui.utils.EasyConstants
import com.example.android_ui.utils.SPUtils

class LoginActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_login)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        initView()
    }

    private fun initView() {
        // 获取控件
        val etUserName =  findViewById<EditText>(R.id.et_username)
        val etPassword = findViewById<EditText>(R.id.et_pwd)
        // 登录
        findViewById<Button>(R.id.btn_login).setOnClickListener {
            val username = etUserName.text.toString().trim()
            if (username.isEmpty()) {
                showMsg("请输入用户名")
                return@setOnClickListener
            }
            val password = etPassword.text.toString().trim()
            if (password.isEmpty()) {
                showMsg("请输入密码")
                return@setOnClickListener
            }
            // 校验账号密码
            if (username == SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME) &&
                password == SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)) {
                showMsg("登录成功")
                // 进入主界面
                startActivity(Intent(this@LoginActivity, MainActivity::class.java))
                // 关闭当前界面
                finish()
            }

        }
        // 注册
        findViewById<Button>(R.id.btn_register).setOnClickListener {
            // 清空输入框
            etUserName.text.clear()
            etPassword.text.clear()
            // 跳转到注册界面
            startActivity(Intent(this, RegisterActivity::class.java))
        }
    }

    private fun showMsg(msg: String) {
        // 弹出吐司
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }

}

在initView方法中,我们简单说明一下:

  1. 基本结构

    • 继承自 AppCompatActivity,是一个标准的 Android Activity
    • 使用 enableEdgeToEdge() 实现边缘到边缘的全面屏显示
    • 通过 setContentView 设置布局文件为 activity_login
  2. 视图初始化

    • initView() 方法中获取了两个 EditText(用户名和密码输入框)和两个 Button(登录和注册按钮)
    • 使用 findViewById 获取视图控件
  3. 登录逻辑

    • 点击登录按钮时,获取输入的用户名和密码
    • 检查输入是否为空,为空则显示提示
    • 验证用户名密码是否与 SPUtils 中存储的匹配(从 SharedPreferences 读取)
    • 验证成功则跳转到 MainActivity 并关闭当前界面
  4. 注册逻辑

    • 点击注册按钮时清空输入框
    • 跳转到 RegisterActivity
  5. 辅助方法

    • showMsg() 显示 Toast 提示信息
  6. 其他特性

    • 使用 ViewCompat 处理系统窗口插入(如状态栏、导航栏)
    • 使用 trim() 处理输入字符串前后的空格
    • 使用 return@setOnClickListener 从 lambda 表达式返回

下面我们使用ViewBinding去写RegisterActivity的代码。

6. ViewBinding

ViewBinding的使用,我们需要现在当前模块的build.gradle文件中去配置打开,如下图所示:

修改build.gradle文件之后要记得点击Sync Now,否则不会生效,同步的时候AS底部栏会有一个加载状态条,当这个条消失时表示同步完成,我们配置就完成了,就可以正式去使用ViewBinding了,这个后面我们就默认都是打开的了,也不会再重复说明了。

下面进入到注册页面,RegisterActivity代码如下所示:

kotlin 复制代码
package com.example.android_ui

import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.android_ui.databinding.ActivityRegisterBinding
import com.example.android_ui.utils.EasyConstants
import com.example.android_ui.utils.SPUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class RegisterActivity : AppCompatActivity() {

    private lateinit var binding: ActivityRegisterBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityRegisterBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        initView()
    }

    private fun initView() {
        // 注册
        binding.btnRegister.setOnClickListener {
            val username = binding.etUsername.text.toString().trim()
            if (username.isEmpty()) {
                showMsg("请输入用户名")
                return@setOnClickListener
            }
            val password = binding.etPwd.text.toString().trim()
            if (password.isEmpty()) {
                showMsg("请输入密码")
                return@setOnClickListener
            }
            val passwordAgain = binding.etPwdAgain.text.toString().trim()
            if (passwordAgain.isEmpty()) {
                showMsg("请再次输入密码")
                return@setOnClickListener
            }
            // 检查两次密码是否一致
            if (password != passwordAgain) {
                showMsg("两次密码不一致")
                return@setOnClickListener
            }
            // 注册账号,保存账号到SP,进行本地数据持久化,如果清除缓存则会消失
            SPUtils.getInstance().putString(EasyConstants.KEY_USERNAME, username)
            SPUtils.getInstance().putString(EasyConstants.KEY_PASSWORD, password)
            showMsg("注册成功,稍后进入登录页面进行登录")
            // 一秒后返回登录页面
            CoroutineScope(Dispatchers.Main).launch {
                delay(1000)
                finish()
            }
        }
        // 返回登录页面
        binding.btnLogin.setOnClickListener {
            finish()
        }
    }

    private fun showMsg(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }
}

整体逻辑和登录页面差不都,我们就简单说一下ViewBinding:

ViewBinding 是 Android 官方提供的一种视图绑定技术,它能够:

  1. 为每个 XML 布局文件自动生成绑定类
  2. 提供类型安全的视图引用
  3. 替代传统的 findViewById() 方法
  4. 避免空指针异常和类型转换错误

针对与xml文件和生成的绑定类名:

XML 文件名 生成的绑定类名
activity_main.xml ActivityMainBinding
fragment_home.xml FragmentHomeBinding
item_user.xml ItemUserBinding

最后我们再写MainActivity中的代码:

kotlin 复制代码
package com.example.android_ui

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.android_ui.databinding.ActivityMainBinding
import com.example.android_ui.utils.EasyConstants
import com.example.android_ui.utils.SPUtils

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        binding.tvTest.text = "用户名:${SPUtils.getInstance().getString(EasyConstants.KEY_USERNAME)} \n " +
                "密码:${SPUtils.getInstance().getString(EasyConstants.KEY_PASSWORD)}"
    }
}

这里就是进入主页面之后就根据缓存显示账号和密码,下面运行一下:

六、源码

GitHub源码地址: Android-UI

好的,后会有期~

相关推荐
阳光明媚sunny42 分钟前
OOM内存溢出产生原因和避免方法
android
whysqwhw2 小时前
安卓MediaCodec录像推流
android
whysqwhw2 小时前
安卓MediaCodec图片声音合成视频
android
Chesnut.2 小时前
【2025.08.06最新版】Android Studio下载、安装及配置记录(自动下载sdk)
android·java
fatiaozhang95273 小时前
咪咕MGV3200-KLH_GK6323V100C_板号E503744_安卓9_短接强刷包-可救砖
android·网络·电视盒子·刷机固件·机顶盒刷机
东风西巷3 小时前
My APK 安卓版:高效管理手机应用的工具软件
android·智能手机·软件需求
教程分享大师11 小时前
中兴B860AV5.2-U_S905L3SB安卓9.0系统带root权限当贝纯净版线刷包
android
_小马快跑_12 小时前
Android | LiveData 与 Flow 的异同点对比
android
whysqwhw14 小时前
安卓MediaCodec录像功能
android
whysqwhw15 小时前
安卓MediaCodec实现视频文件的转码压缩
android