Android MVVM架构学习——ViewModel DataBinding
关于MVVM架构,我并不想花篇幅去做重复性的描述,网上一搜都是一堆讲解,大家可以自行了解,我所做的只是以最简单的例子,最有效的步骤,从零开始,去实现一个相对有点学习参考价值的项目。
先来看本文预计的实现效果

可以看到,就是一个非常简单的例子,当点击登录按钮之后,对用户的输入进行一个简单的判断,满足要求之后跳转到首页,并显示用户输入的账户信息。那么接下来,将分步骤讲解如何以符合MVVM设计规范的代码来实现这个功能,重在展示如何从零开始,构建一个MVVM框架。
本文使用的开发环境:
Android Studio Iguana | 2023.2.1 Patch 1
Gradle版本:
gradle-8.4-bin.zip
1.build.gradle文件(模块级)
1.1使用DataBinding
defaultConfig {...buildFeatures {dataBinding = true}...}
1.2 引用依赖
dependencies {implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'implementation 'androidx.lifecycle:lifecycle-livedata:2.7.0'}
2.绘制布局
当我们新建项目或者是新建activity时,系统会默认为我们生成一个布局文件,如下

我们需要把默认布局改成DataBinding布局。选中根部局标签,按下Alt+Enter,在弹出的选项中,选择第一个Convert to data binding layout,系统会自动为我们修改布局

修改后的布局:
<?xml version="1.0" encoding="utf-8"?>
<!--使用databinding功能,根布局需要使用<layout>标签 -->
<layout 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"><!--这是Data Binding的<data>标签,用于定义布局中使用的数据对象和表达式--><data></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.main.MainActivity"></androidx.constraintlayout.widget.ConstraintLayout></layout>
3.Activity文件
/*** 登录活动类,负责展示登录界面并处理登录逻辑。*/
public class LoginActivity extends AppCompatActivity {private ActivityLoginBinding binding; // 视图绑定对象private LoginViewModel viewModel; // 登录视图模型/*** 在活动创建时调用,用于初始化界面和设置监听器。* * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。*/@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 启用边缘到边缘的界面显示EdgeToEdge.enable(this);// 使用数据绑定初始化视图binding = DataBindingUtil.setContentView(this, R.layout.activity_login);// 设置视图嵌入系统边界的监听,用于动态设置视图的内边距ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});// 创建或获取登录视图模型viewModel = new ViewModelProvider(this).get(LoginViewModel.class);// 将视图模型绑定到视图binding.setViewModel(viewModel);// 初始化点击监听器和观察者initListener();initObserver();}/*** 初始化按钮监听器,用于处理登录按钮的点击事件。*/private void initListener() {// 当登录按钮被点击时,设置账号和密码,并触发登录动作binding.btnLogin.setOnClickListener(v -> {viewModel.setAccount(binding.etAccount.getText().toString());viewModel.setPassword(binding.etPassword.getText().toString());viewModel.login();});}/*** 初始化观察者,用于处理登录结果。*/private void initObserver() {// 观察登录结果,根据结果进行跳转或显示错误信息viewModel.getLoginResult().observe(this, loginResult -> {if (loginResult.isSuccess()) {// 登录成功,跳转到主界面,并传递账号信息Intent intent = new Intent(this, MainActivity.class);intent.putExtra("account", viewModel.getAccount().getValue());startActivity(intent);finish();} else {// 登录失败,显示错误信息Toast.makeText(this, loginResult.getErrorMessage(), Toast.LENGTH_SHORT).show();}});}
}
4.定义ViewModel
比较好的编程规范是,每创建一个Activity/Fragment,都创建与其对应的ViewModel
/*** 登录视图模型类,用于管理登录相关的数据和逻辑。*/
public class LoginViewModel extends ViewModel {// 账户名和密码的LiveData对象,用于在UI变化时通知订阅者private MutableLiveData<String> account = new MutableLiveData<>();private MutableLiveData<String> password = new MutableLiveData<>();private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();/*** 获取账户名的LiveData对象。* @return 账户名的LiveData对象。*/public MutableLiveData<String> getAccount() {return account;}/*** 获取密码的LiveData对象。* @return 密码的LiveData对象。*/public MutableLiveData<String> getPassword() {return password;}/*** 获取登录结果的LiveData对象。* @return 登录结果的LiveData对象。*/public LiveData<LoginResult> getLoginResult() {return loginResult;}/*** 设置账户名。* @param account 用户输入的账户名。*/public void setAccount(String account) {this.account.postValue(account);}/*** 设置密码。* @param password 用户输入的密码。*/public void setPassword(String password) {this.password.postValue(password);}/*** 执行登录操作。* 根据输入的账户名和密码进行校验,成功则更新登录结果为成功,失败则更新为错误信息。*/public void login() {if (checkAccount(getAccount().getValue(), getPassword().getValue())) {LoginResult successResult = new LoginResult(true, null);loginResult.postValue(successResult);} else {LoginResult errorResult = new LoginResult(false, "账号或密码错误");loginResult.postValue(errorResult);}}/*** 校验账户名和密码是否有效。* @param account 用户输入的账户名。* @param password 用户输入的密码。* @return 如果账户名和密码有效返回true,否则返回false。*/private boolean checkAccount(String account, String password) {if (account == null || password == null || account.isEmpty() || password.isEmpty()) {return false;}return true;}/*** 登录结果类,封装登录是否成功和错误信息。*/public static class LoginResult {private boolean success;private String errorMessage;/*** 构造登录结果对象。* @param success 登录是否成功。* @param errorMessage 错误信息,登录失败时提供。*/public LoginResult(boolean success, String errorMessage) {this.success = success;this.errorMessage = errorMessage;}/*** 判断登录是否成功。* @return 登录成功返回true,失败返回false。*/public boolean isSuccess() {return success;}/*** 设置登录是否成功。* @param success 设置登录成功状态。*/public void setSuccess(boolean success) {this.success = success;}/*** 获取错误信息。* @return 错误信息字符串,登录成功时为null。*/public String getErrorMessage() {return errorMessage;}/*** 设置错误信息。* @param errorMessage 设置登录失败的错误信息。*/public void setErrorMessage(String errorMessage) {this.errorMessage = errorMessage;}}}
5.MainActivity
/*** 主活动类,负责管理应用程序的主要界面。*/
public class MainActivity extends AppCompatActivity {private MainViewModel viewModel; // 视图模型,用于管理活动背后的业务逻辑private ActivityMainBinding binding; // 数据绑定实例,用于简化UI更新/*** 在活动创建时调用。* @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。*/@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 启用边缘到边缘的UIEdgeToEdge.enable(this);// 设置数据绑定binding = DataBindingUtil.setContentView(this, R.layout.activity_main);// 设置视图的内边距,以适应系统栏位的高度ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});// 初始化视图模型viewModel = new ViewModelProvider(this).get(MainViewModel.class);// 从意图中获取账户信息Intent intent = getIntent();String account = intent.getStringExtra("account");// 将账户信息显示在文本视图上binding.text.setText("登录账户为:"+account);}
}
至此,就完成了demo中展示的效果
相关文章:
Android MVVM架构学习——ViewModel DataBinding
关于MVVM架构,我并不想花篇幅去做重复性的描述,网上一搜都是一堆讲解,大家可以自行了解,我所做的只是以最简单的例子,最有效的步骤,从零开始,去实现一个相对有点学习参考价值的项目。 先来看本…...
防抖与节流
...
理解 Nginx 的多站点配置:为每个网站单独配置
Nginx 是一个高性能的 Web 服务器,广泛用于托管和管理网站。它之所以受欢迎,部分原因在于它的灵活性和强大的配置能力。特别是对于管理多个网站,Nginx 提供了一种高效且组织良好的方法。让我们逐步了解如何使用 Nginx 配置多个网站࿰…...
支持向量机模型pytorch
通过5个条件判定一件事情是否会发生,5个条件对这件事情是否发生的影响力不同,计算每个条件对这件事情发生的影响力多大,写一个支持向量机模型pytorch程序,最后打印5个条件分别的影响力。 示例一 支持向量机(SVM)是一种…...
轮转数组(力扣)
189. 轮转数组 - 力扣(LeetCode) 189. 轮转数组 题解 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 样例输入 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮…...
批量插入10w数据方法对比
环境准备(mysql5.7) CREATE TABLE user (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 唯一id,user_id bigint(10) DEFAULT NULL COMMENT 用户id-uuid,user_name varchar(100) NOT NULL COMMENT 用户名,user_age bigint(10) DEFAULT NULL COMMENT 用户年龄,create_time time…...
HAL STM32 I2C方式读取MT6701磁编码器获取角度例程
HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 📍相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》🎈《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》📌MT6701当前最新文档资料:https://www.magntek.com.cn/u…...
如何排查nginx服务启动情况,杀死端口,以及防火墙开放指定端口【linux与nginx排查手册】
利用NGINX搭建了视频服务,突然发现启动不了了,于是命令开始 使用以下命令查看更详细的错误信息: systemctl status nginx.service Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. Run…...
用Rust实现免费调用ChatGPT的命令行工具 (一)
代码已经开源:🚀 fgpt 欢迎大家star⭐和fork 👏 ChatGPT现在免费提供了GPT3.5的Web访问,不需要注册就可以直接使用,但是,它的使用方式是通过Web页面,不够方便。 更多技术分享关注 入职啦&…...
mysql 查询实战1-题目
学习了mysql 查询实战-变量方式-解答-CSDN博客,接着练习sql,从实战中多练习。 1,题目: 1,查询部门工资最高的员工 1,建表: DROP TABLE IF EXISTS department; create table department(dept_i…...
Word学习笔记之奇偶页的页眉与页码设置
1. 常用格式 在毕业论文中,往往有一下要求: 奇数页右下角显示、偶数页左下角显示奇数页眉为每章标题、偶数页眉为论文标题 2. 问题解决 2.1 前期准备 首先,不论时要求 1、还是要求 2,这里我们都要做一下设置: 鼠…...
数据赋能(58)——要求:数据赋能实施部门能力
“要求:数据赋能实施部门能力”是作为标准的参考内容编写的。 在实施数据赋能中,数据赋能实施部门的能力体现在多个方面,关键能力如下图所示。 在实施数据赋能的过程中,数据赋能实施部门应具备的关键能力如下。 理性思维与逻辑分…...
Unity URP PBR_Cook-Torrance模型
Cook-Torrance模型是一个微表面光照模型,认为物体的表面可以看作是由许多个理想的镜面反射体微小平面组成的。 单点反射镜面反射漫反射占比*漫反射 漫反射 基础色/Π 镜面反射DFG/4(NV)(NL) D代表微平面分布函数,描述的是法线与半角向量normalize(L…...
Unity之XR Interaction Toolkit如何在VR中实现渐变黑屏效果
前言 做VR的时候,有时会有跳转场景,切换位置,切换环境,切换进度等等需求,此时相机的画面如果不切换个黑屏,总会感觉很突兀。刚好Unity的XR Interaction Toolkit插件在2.5.x版本,出了一个TunnelingVignette的效果,我们今天就来分析一下他是如何使用的,然后我们自己再来…...
html+vue编写分页功能
效果: html关键代码: <div class"ui-jqgrid-resize-mark" id"rs_mlist_table_C87E35BE"> </div><div class"list_component_pager ui-jqgrid-pager undefined" dir"ltr"><div id"pg…...
计算机网络 实验指导 实验17
实验17 配置无线网络实验 1.实验拓扑图 Table PC0 和 Table PC1 最开始可能还会连Access Point0,无影响后面会改 名称接口IP地址网关地址Router0fa0/0210.10.10.1fa0/1220.10.10.2Tablet PC0210.10.10.11Tablet PC1210.10.10.12Wireless互联网220.10.10.2LAN192.16…...
在 Vue中,v-for 指令的使用
在 Vue中,v-for 指令用于渲染一个列表,基于源数据多次渲染元素或模板块。它对于展示数组或对象中的数据特别有用。 数组渲染 假设你有一个数组,并且你想为每个数组元素渲染一个 <li> 标签: <template> <ul>…...
达梦数据库执行sql报错:数据溢出
数据库执行sql报错数据溢出 单独查询对应的数字进行计算是不是超过了某个字段类型的上限或下限 如果已经超过了,进行对字段进行cast类型转换处理,转换为dec num都可以尝试 这里就是从 max(T.BLOCK_ID as dec*8192t.bytes)/1024/1024 max_MB,换成了这个…...
从「宏大叙事」到「生活叙事」,小红书品牌种草的的“正确姿势”
不同于抖音和微博,在小红书上,品牌营销的基调应该是怎样的?品牌怎样与小红书用户对话?什么样的内容,才能走进小红书用户的心中?本期,小编将带大家洞察品牌在小红书营销的“正确姿势”。从「小美…...
Python Selenium 的基本使用方法
文章目录 1. 概述2. 安装Chrome及ChromeDriver2.1 安装Chrome2.2 安装ChromeDriver 3. 安装Selenium4. 常见用法4.1 启动4.2 查找元素4.3 等待页面加载元素 1. 概述 Selenium 是一个用于自动化 web 浏览器的工具,它提供了一套用于测试 web 应用程序的工具和库。Sel…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
