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

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架构&#xff0c;我并不想花篇幅去做重复性的描述&#xff0c;网上一搜都是一堆讲解&#xff0c;大家可以自行了解&#xff0c;我所做的只是以最简单的例子&#xff0c;最有效的步骤&#xff0c;从零开始&#xff0c;去实现一个相对有点学习参考价值的项目。 先来看本…...

防抖与节流

...

理解 Nginx 的多站点配置:为每个网站单独配置

Nginx 是一个高性能的 Web 服务器&#xff0c;广泛用于托管和管理网站。它之所以受欢迎&#xff0c;部分原因在于它的灵活性和强大的配置能力。特别是对于管理多个网站&#xff0c;Nginx 提供了一种高效且组织良好的方法。让我们逐步了解如何使用 Nginx 配置多个网站&#xff0…...

支持向量机模型pytorch

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个支持向量机模型pytorch程序,最后打印5个条件分别的影响力。 示例一 支持向量机&#xff08;SVM&#xff09;是一种…...

轮转数组(力扣)

189. 轮转数组 - 力扣&#xff08;LeetCode&#xff09; 189. 轮转数组 题解 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 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磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…...

如何排查nginx服务启动情况,杀死端口,以及防火墙开放指定端口【linux与nginx排查手册】

利用NGINX搭建了视频服务&#xff0c;突然发现启动不了了&#xff0c;于是命令开始 使用以下命令查看更详细的错误信息&#xff1a; systemctl status nginx.service Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. Run…...

用Rust实现免费调用ChatGPT的命令行工具 (一)

代码已经开源&#xff1a;&#x1f680; fgpt 欢迎大家star⭐和fork &#x1f44f; ChatGPT现在免费提供了GPT3.5的Web访问&#xff0c;不需要注册就可以直接使用&#xff0c;但是&#xff0c;它的使用方式是通过Web页面&#xff0c;不够方便。 更多技术分享关注 入职啦&…...

mysql 查询实战1-题目

学习了mysql 查询实战-变量方式-解答-CSDN博客&#xff0c;接着练习sql&#xff0c;从实战中多练习。 1&#xff0c;题目&#xff1a; 1&#xff0c;查询部门工资最高的员工 1&#xff0c;建表&#xff1a; DROP TABLE IF EXISTS department; create table department(dept_i…...

Word学习笔记之奇偶页的页眉与页码设置

1. 常用格式 在毕业论文中&#xff0c;往往有一下要求&#xff1a; 奇数页右下角显示、偶数页左下角显示奇数页眉为每章标题、偶数页眉为论文标题 2. 问题解决 2.1 前期准备 首先&#xff0c;不论时要求 1、还是要求 2&#xff0c;这里我们都要做一下设置&#xff1a; 鼠…...

数据赋能(58)——要求:数据赋能实施部门能力

“要求&#xff1a;数据赋能实施部门能力”是作为标准的参考内容编写的。 在实施数据赋能中&#xff0c;数据赋能实施部门的能力体现在多个方面&#xff0c;关键能力如下图所示。 在实施数据赋能的过程中&#xff0c;数据赋能实施部门应具备的关键能力如下。 理性思维与逻辑分…...

Unity URP PBR_Cook-Torrance模型

Cook-Torrance模型是一个微表面光照模型&#xff0c;认为物体的表面可以看作是由许多个理想的镜面反射体微小平面组成的。 单点反射镜面反射漫反射占比*漫反射 漫反射 基础色/Π 镜面反射DFG/4(NV)(NL) D代表微平面分布函数&#xff0c;描述的是法线与半角向量normalize(L…...

Unity之XR Interaction Toolkit如何在VR中实现渐变黑屏效果

前言 做VR的时候,有时会有跳转场景,切换位置,切换环境,切换进度等等需求,此时相机的画面如果不切换个黑屏,总会感觉很突兀。刚好Unity的XR Interaction Toolkit插件在2.5.x版本,出了一个TunnelingVignette的效果,我们今天就来分析一下他是如何使用的,然后我们自己再来…...

html+vue编写分页功能

效果&#xff1a; html关键代码&#xff1a; <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&#xff0c;无影响后面会改 名称接口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中&#xff0c;v-for 指令用于渲染一个列表&#xff0c;基于源数据多次渲染元素或模板块。它对于展示数组或对象中的数据特别有用。 数组渲染 假设你有一个数组&#xff0c;并且你想为每个数组元素渲染一个 <li> 标签&#xff1a; <template> <ul>…...

达梦数据库执行sql报错:数据溢出

数据库执行sql报错数据溢出 单独查询对应的数字进行计算是不是超过了某个字段类型的上限或下限 如果已经超过了&#xff0c;进行对字段进行cast类型转换处理&#xff0c;转换为dec num都可以尝试 这里就是从 max(T.BLOCK_ID as dec*8192t.bytes)/1024/1024 max_MB,换成了这个…...

从「宏大叙事」到「生活叙事」,小红书品牌种草的的“正确姿势”

不同于抖音和微博&#xff0c;在小红书上&#xff0c;品牌营销的基调应该是怎样的&#xff1f;品牌怎样与小红书用户对话&#xff1f;什么样的内容&#xff0c;才能走进小红书用户的心中&#xff1f;本期&#xff0c;小编将带大家洞察品牌在小红书营销的“正确姿势”。从「小美…...

Python Selenium 的基本使用方法

文章目录 1. 概述2. 安装Chrome及ChromeDriver2.1 安装Chrome2.2 安装ChromeDriver 3. 安装Selenium4. 常见用法4.1 启动4.2 查找元素4.3 等待页面加载元素 1. 概述 Selenium 是一个用于自动化 web 浏览器的工具&#xff0c;它提供了一套用于测试 web 应用程序的工具和库。Sel…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...