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.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中展示的效果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发快速入门

基本概念 ArkTS 因为ArkTS是基于Type Script扩展而来&#xff0c;是Type Script的超集&#xff0c;所以也可以关注一下Type Script的语法来理解ArkTS的语法 ArkUI HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。方舟开发框架…

Cohere Command R+:企业级可扩展大模型

Command R 是一种最先进的 RAG 优化模型&#xff0c;旨在处理企业级工作负载&#xff0c;并首先在 Microsoft Azure 上提供 今天&#xff0c;我们推出了 Command R&#xff0c;这是我们最强大、可扩展的大型语言模型 &#xff08;LLM&#xff09;&#xff0c;专为在实际企业用…

Go语言图像处理深入指南:探索 `image/draw` 标准库

Go语言图像处理深入指南&#xff1a;探索 image/draw 标准库 引言基础知识图像类型颜色模型draw 包概览 image/draw 的基本用法创建和操作图像复制和粘贴图像遮罩和图像合成 高级技巧自定义图像处理性能优化多线程图像处理 实战案例图像滤镜实现动态图像生成图像处理工具开发 总…

2024年MathorCup妈妈杯C题终极资料分享(微调后可直接提交的数据结果+多种可视化呈现)

【腾讯文档】2024年妈杯认证杯资料汇总说明 https://docs.qq.com/doc/DSExyRGhNUm1pTHh4 我们直接将我们认为最为合适数据结果以及最好用的运行代码进行了整理以便大家可以在最后一天无法运行代码急需结果或者不会写代码时&#xff0c;提供帮助。、 % 数据加载 data readtab…

Vivado编译常见错误合集(一)

引言&#xff1a;本文对Vivado编译时常见的错误或者关键警告做一些梳理汇总&#xff0c;便于日后归纳总结。 1. 普通IO引脚约束为时钟时报错。 原因&#xff1a;Xilinx Vivado开发环境编译HDL时&#xff0c;对时钟信号设置了编译规则&#xff0c;如果时钟由于硬件设计原因分配…

Ceph [OSDI‘06]论文阅读笔记

原论文&#xff1a;Ceph: A Scalable, High-Performance Distributed File System (OSDI’06) Ceph简介及关键技术要点 Ceph是一个高性能、可扩展的分布式文件系统&#xff0c;旨在提供出色的性能、可靠性和可扩展性。为了最大化数据和元数据管理的分离&#xff0c;它使用了一…

MYSQL执行过程和顺序详解

一、前言 1.1、说明 就MySQL在执行过程、sql执行顺序&#xff0c;以及一些相关关键字的注意点方面的学习分享内容。 在参考文章的基础上&#xff0c;会增加自己的理解、看法&#xff0c;希望本文章能够在您的学习中提供帮助。 如有错误的地方&#xff0c;欢迎指出纠错&…

vue中使用axios获取不到响应头Content-Disposition的解决办法

项目中&#xff0c;后端返回的文件流; 前端需要拿到响应头里的Content-Disposition字段的值&#xff0c;从中获取文件名 在控制台Headers中可以看到相关的字段和文件名&#xff0c;但是在axios里面却获取不到 如果想要让客户端访问到相关信息&#xff0c;服务器不仅要在head…

嵌入式单片机 TTL电平、232电平、485电平的区别和联系

一、简介 TTL、232和485是常见的串口通信标准&#xff0c;它们在电平和通信方式上有所不同&#xff0c; ①一般情况下TTL电平应用于单片机外设&#xff0c;属于MCU/CPU等片外外设&#xff1b; ②232/485电平应用于产品整体对外的接口&#xff0c;一般是片外TTL串口转232/485…

<计算机网络自顶向下> CDN

视频服务挑战 规模性异构性&#xff1a;不同用户有不同的能力&#xff08;比如有线接入和移动用户&#xff1b;贷款丰富和受限用户&#xff09;解决方法是&#xff1a;分布式的应用层面的基础设施CDN 多媒体&#xff1a;视频 视频是固定速度显示的一系列图像的序列&#xff…

【ubuntu20.04】安装GeographicLib

下载地址 GeographicLib: Installing GeographicLib 我们是ubuntu20.04 &#xff0c;所以下载第一个 GeographicLib-2.3.tar.gz 接着跟着官方步骤安装&#xff0c;会出错&#xff01;&#xff01;&#xff01;&#xff01;马的 官方错误示例&#xff1a;tar xfpz Geographi…

YOLOV5 + 双目相机实现三维测距(新版本)

文章目录 YOLOV5 双目相机实现三维测距&#xff08;新版本&#xff09;1. 项目流程2. 测距原理3. 操作步骤和代码解析4. 实时检测5. 训练6. 源码下载 YOLOV5 双目相机实现三维测距&#xff08;新版本&#xff09; 本文主要是对此篇文章做一些改进&#xff0c;以及解释读者在…

微软搭建零售新媒体创意工作室大举抢占数字营销广告市场

“微软新零售创意工作室新平台利用生成式人工智能&#xff0c;在几秒钟内轻松定制横幅广告。零售媒体预计到2026年将成为一个价值1000亿美元的行业。” 零售媒体在过去几年中发展迅速。根据eMarketerOpens在新窗口的数据&#xff0c;预计到2024年&#xff0c;仅美国的零售媒体…

基于粒子群优化的配电网重构

一、配电网重构原理 配电网重构是指在满足配电网运行基本约束的前提下&#xff0c;通过改变配电网中一个或多个开关的状态对配电网中一个或多个指标进行优化。通过配电网重构&#xff0c;可以在不增加设备投资的情况下&#xff0c;充分发挥配电系统的潜力&#xff0c;提高系统…

【C语言】每日一题,快速提升(1)!

调整数组使奇数全部都位于偶数前面 题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分 所有偶数位于数组的后半部分 解题思路&#xff1a; 给定两个下标left和right&#xff0c;left放在数组的起始…

【Web】VS Code 插件及快捷键

专栏文章索引&#xff1a;Web 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、安装步骤 二、插件 1.Chinese (Simplified) (简体中文) 2.open in browser 3.vscode-icons 4.Live Server 5.Live Server Preview 6.翻译(英汉词典) 三、快捷键 1.缩放代码…

Vivado ILA Capture Control 模式与 Advanced Trigger的功能使用以及TSM(触发状态机)的编写

文章目录 一、前言二、ILA的基本功能使用以及局限性2.1 ILA的调用2.2 ILA波形窗口观察2.3 ILA基本功能的局限性&#xff1a;如何观测低频的数据&#xff1f; 二、Capture Control 功能介绍三、Advanced Trigger功能以及TSM编写3.1 触发状态机的写法3.2 设置Advanced Trigger3.3…

MYSQL中的ER图

1.首先学习如何设计数据&#xff0c;在此之前我们先学会画出我们的规划图 利用Axure RP 9 然后进行以后的操作。其实非常简单看看就会

NODE MCU (ESP8285-ESP8266)用Arduino lDE 2.3.2烧录系统后串口监控不打印问题

问题: Arduino lDE 2.3.2,集合DOIT ESP-Mx DevKit板子,烧录代码后,串口监视器 打印不出来调试数据 分析: Arduino lDE 2.3.2工具提示,不支持调试 板载flash按钮无需按下,即可烧录系统,由于烧录和调试共用串口,所以怀疑是Arduino lDE 2.3.2在烧录时设置了串口的配置…

Hello 算法10:搜索

https://www.hello-algo.com/chapter_searching/binary_search/ 二分查找法 给定一个长度为 n的数组 nums &#xff0c;元素按从小到大的顺序排列&#xff0c;数组不包含重复元素。请查找并返回元素 target 在该数组中的索引。若数组不包含该元素&#xff0c;则返回 -1 。 # 首…