Android MVVM架构学习——ViewModel DataBinding

news2024/11/16 0:28:49

关于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.ConstraintLayout
        android: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。
     */
    @Override
    protected 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。
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 启用边缘到边缘的UI
        EdgeToEdge.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中展示的效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1595524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人机GB42590接收端 +接收端,同时支持2.4G与5.8G双频WIFI模组

严格按照GB42590的协议开发的发射端&#xff0c;通过串口和模块通讯&#xff0c;默认波特率 921600。 http://www.doit.am/首页-深圳四博智联科技有限公司-淘宝网https://shop144145132.taobao.com/?spma230r.7195193.1997079397.2.71f6771dJHT2r0 二、接口文档 单片机和模…

【web3技术】什么是 WEB3?

Web3 简介 中心化网络已经帮助数十亿人融入了互联网,并在其上创建了稳定、可靠的基础设施。 与此同时,少数中心化巨头几乎垄断了互联网,甚至可以为所欲为。 Web3 是摆脱这一困境的方案。 不同于科技巨头垄断的传统互联网,Web3 采用去中心化,由所有用户构建、运营和拥有。…

探索数据结构:顺序串与链式串的深入理解

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 串的定义 串是一种特殊的顺序表&#xff0c;即每一个元素都是单独一…

Matlab 2024安装教程(附免费安装包资源)

鼠标右击软件压缩包&#xff0c;选择“解压到MatlabR2024a“。 2.打开解压后的文件夹&#xff0c;鼠标右击“MATHWORKS_R2024A“选择装载。 鼠标右击“setup“选择”以管理员身份运行“。点击“是“&#xff0c;然后点击”下一步“。复制一下密钥粘贴至输入栏&#xff0c;然后…

移动开发避坑指南——内存泄漏

在日常编写代码时难免会遇到各种各样的问题和坑&#xff0c;这些问题可能会影响我们的开发效率和代码质量&#xff0c;因此我们需要不断总结和学习&#xff0c;以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结&#xff0c;以提高大家的开发质量。本系列文章…

爬虫的目的是做什么

通过网站域名获取HTML数据解析数据&#xff0c;获取想要的信息存储爬取的信息如果有必要&#xff0c;移动到另一个网页重复过程 这本书上的代码的网址是 &#xff1a; GitHub - REMitchell/python-scraping: Code samples from the book Web Scraping with Python http://shop.…

低代码集成Java系列:高效构建自定义插件

前言 随着软件开发的快速发展和需求的不断增长&#xff0c;开发人员面临着更多的压力和挑战。传统的开发方法需要花费大量的时间和精力&#xff0c;而低代码开发平台的出现为开发人员提供了一种更加高效、快速的开发方式。今天小编就以构建命令插件为例&#xff0c;展示如何使…

不要顺从胃的指示进食

没有人喜欢一直胖着&#xff0c;但想瘦&#xff0c;运动力、运动量、毅力、耐力、坚持、饮食管控方面等都不可缺&#xff0c;道理懂得都懂&#xff0c;但大多数超重胖子却都是有心而无力的。原因&#xff0c;除了生理体积影响了行动外&#xff0c;更重要的一点是&#xff1a;由…

汽车抗疲劳驾驶测试铸铁试验底座技术要求有哪些

铸铁平台试验台底座的主要技术参数要求 1、 试验台底座设计制造符合JB/T794-1999《铸铁平板》标准。 2、 试验铁底板及所有附件的计量单位全部采用 单位&#xff08;SI&#xff09;标准。 3、铸铁平台平板材质&#xff1a;用细密的灰口铸铁HT250或HT200&#xff0c;强度符…

Mysql的事务隔离级别以及事务的四大特性。

MySQL 的事务隔离级别是数据库管理系统中的一个重要概念&#xff0c;它决定了事务如何隔离和影响其他并发事务。MySQL 支持四种事务隔离级别&#xff0c;分别是&#xff1a;读未提交&#xff08;READ UNCOMMITTED&#xff09;、读已提交&#xff08;READ COMMITTED&#xff09;…

Collection与数据结构 二叉树(二):二叉树精选OJ例题(上)

1. 判断是否为相同的二叉树 OJ链接 public boolean isSameTree(Node p, Node q) {if (p null && q ! null || p ! null && q null){//结构不同return false;}if (p null && q null){//结构相同,都是空树return true;}if (p.value ! q.value){//…

STC89C52学习笔记(十二)

STC89C52学习笔记&#xff08;十二&#xff09; 一、AD/DA 1.定义 AD能够将模拟信号转化为数字信号&#xff0c;DA能够将数字信号转化为模拟信号。 2.两种类型的DA转换器 &#xff08;1&#xff09;PWM型DA滤波器 由于PWM是通过脉冲调制的方法来调整的&#xff0c;低通滤…

【数字IC/FPGA】什么是无符号数?什么是有符号数?

进制 虽然在日常生活中&#xff0c;我们已经习惯了使用10进制数字&#xff0c;但在由数字电路构成的数字世界中&#xff0c;2进制才是效率更高的选择。 10进制与2进制 10进制&#xff08;decimal&#xff09;计数法&#xff08;一般也叫阿拉伯计数法&#xff09;是在日常生活…

C++ | Leetcode C++题解之第19题删除链表的倒数第N个结点

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummy new ListNode(0, head);ListNode* first head;ListNode* second dummy;for (int i 0; i < n; i) {first first->next;}while (fi…

UE4_导入内容_骨架网格体

FBX 导入支持 骨架网格体&#xff08;Skeletal Mesh&#xff09; 。这提供了一种简化的处理流程来将有动画的网格体从 3D应用程序中导入到虚幻引擎内&#xff0c;以便在游戏中使用。除了导入网格体外&#xff0c;如果需要&#xff0c;动画和变形目标都可以使用FBX格式 在同一文…

IDA导入jni.h头文件步骤

源地址&#xff1a;https://www.ctvol.com/asreverse/2273.html 导入步骤1&#xff1a; 点击IDA Pro 菜单项“File->Load file->Parse C header file ” 选择jni.h头文件。 导入步骤2&#xff1a; 1、点击IDA Pro 主界面上的“Structures”选项卡。 2、按下Insert键…

为什么会有c++内存模型

1. 引言 c的内存模型主要解决的问题是多线程的问题。怎么理解多线程呢&#xff1f;单核时候&#xff0c;只有1个CPU内核处理多线程&#xff0c;各线程之间随着时间的推进&#xff0c;会不断的切换&#xff0c;如下图形便于理解。 实际上线程间的切换是非常快的&#xff0c;所以…

OpenHarmony实战开发-异步并发概述 (Promise和async/await)。

Promise和async/await提供异步并发能力&#xff0c;是标准的JS异步语法。异步代码会被挂起并在之后继续执行&#xff0c;同一时间只有一段代码执行&#xff0c;适用于单次I/O任务的场景开发&#xff0c;例如一次网络请求、一次文件读写等操作。 异步语法是一种编程语言的特性&…

信息系统项目管理师——管理类计算

风险管理——风险曝光度 风险曝光度概率*影响&#xff0c;概率指风险发生的概率&#xff0c;影响指风险一旦发生&#xff0c;受到影响的项。 题号【GX20061101](61) 知识点[风险曝光度] 风险的成本估算完成后&#xff0c;可以针对风险表中每个风险计算其风险曝光度。某软件小…

h5 笔记4 表格与表单

<table></table>设置表格&#xff1b; <tr></tr>设置行数&#xff1b; <td></td>设置列数&#xff1b; <caption></caption>设置表格标题&#xff1b; <th></th>设置列标题。 直列&#xff1a;column&#xf…