Android开发中的MVP模式详解

Android面试必问!啃完999页Android面试高频宝典,完整PDF 前言 首先介绍一下自己,计算机水本,考研与我无缘。之前在帝都某公司算法部实习,公司算大公司吧,然而个人爱好偏开发,大二的时候写个一个app,主要是用各种框架。 1. Android架构设计模式 MVC架构设计模式:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。 MVP架构设计模式:MVC全名是Model View Persenter,MVP由MVC演变而来,是现在主流的开发模式。 MVVM架构设计模式:MVVM全名是M 阅读详情

Android开发中的MVP模式详解

MVC、MVP、MVVM介绍

image

在Android开发中,如果不注重架构的话,Activity类就会变得愈发庞大。这是因为在Android开发中View和其他的线程可以共存于Activity内。那最大的问题是什么呢? 其实就是Activity中同时存在业务逻辑和UI逻辑。这导致增加了单元测试和维护的成本。

image

这就是为什么要清晰架构的原因之一。不仅是因为Activity类变得臃肿,也是其他的一些问题,例如Activity和Fragment相结合时的生命周期、数据绑定等等。

MVP简介

MVP(Model,View,Presenter)

  • View:负责处理用户时间和视图展现。在Android中就可能是Activity或者Fragment。
  • Model: 负责数据访问。数据可以是从接口或者本地数据库中获取。
  • Presenter: 负责连接View和Model。

用一句话来说:MVP其实就是面向接口编程,V实现接口,P使用接口。

清晰的架构:

image

举个栗子:
在Android Studio中新建一个Activity,系统提供了LoginActivity,直接用它是极好的。

不得不说,Material Design的效果真是美美哒!

好,那我们就用用户登录页来按照MVP的模式实现一下:

  • M: 很显然Model应该是User类。
  • V: View就是LoginActivity。
  • P: P那我们一会就创建一个LoginPresenter类。

齐了,那接下来就详细分析下他们这三部分:

  • User: 应该有email, password, boolean login(email, password)。
  • LoginActivity:点击登录应该要出loading页。登录成功后要进入下一个页面。如果登录失败应该弹toast提示。那就需要void showLoading(),void hideLoading(),void showErrorTip(),void doLoginSuccess()这四个方法。
  • LoginPresenter:这是Model和View的桥梁。他需要做的处理业务逻辑,直接与Model打交道,然后将UI的逻辑交给LoginActivity处理。
    那怎么做呢? 按照我上面总结的那一句话,、MVP其实就是面向接口编程,V实现接口,P使用接口。很显然我们需要提供一个接口。那就新建一个ILoginView的接口。这里面有哪些方法呢? 当然是上面我们在分析LoginActiity时提出的那四个方法。这样LoginActivity直接实现ILoginView接口就好。

开始做:

  • 先把Model做好吧,创建User类。

    public class User {
        private String email;
        private String password;
        public User(String email, String password) {
            this.email = email;
            this.password = password;
        }
    
        public boolean login() {
            // do login request..
            return true;
        }
    }
  • 创建ILoginView接口,定义登录所需要的ui逻辑。

    public interface ILoginView {
        void showLoading();
        void hideLoading();
        void showErrorTip();
        void doLoginSuccess();
    }
  • 创建LoginPresenter类,使用ILoginView接口,那该类主要有什么功能呢? 它主要是处理业务逻辑的,
    对于登录的话,当然是用户在UI页面输入邮箱和密码,然后Presenter去开线程、请求接口。然后得到登录结果再去让UI显示对应的视图。那自然就是有一个void login(String email, String passowrd)的方法了

    public class LoginPresenter {
        private ILoginView mLoginView;
    
        public LoginPresenter(ILoginView loginView) {
            mLoginView = loginView;
        }
    
        public void login(String email, String password) {
            if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) {
                //
                mLoginView.showErrorTip();
                return;
            }
            mLoginView.showLoading();
            User user = new User(email, password);
    
            // do network request....
            // ....
            onSuccess() {
                boolean login = user.login();
                if (login) {
                    mLoginView.doLoginSuccess();
                } else {
                    mLoginView.showErrorTip();
                }
                mLoginView.hideLoading();
            }
    
            onFailde() {
                mLoginView.showErrorTip();
                mLoginView.hideLoading();
            }
        }
    }       
  • 创建LoginActivity,实现ILoginView的接口,然后内部调用LoginPresenter来处理业务逻辑。

    public class LoginActivity extends AppCompatActivity implements ILoginView {
        private LoginPresenter mLoginPresenter;
    
        private AutoCompleteTextView mEmailView;
        private EditText mPasswordView;
        private View mProgressView;
        private View mLoginButton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
            mPasswordView = (EditText) findViewById(R.id.password);
            mLoginButton = findViewById(R.id.email_sign_in_button);
            mProgressView = findViewById(R.id.login_progress);
    
            mLoginPresenter = new LoginPresenter(this);
    
            mLoginButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mLoginPresenter.login(mEmailView.getText().toString().trim(), mPasswordView.getText().toString().trim());
                }
            });
        }
    
        @Override
        public void showLoading() {
            mProgressView.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void hideLoading() {
            mProgressView.setVisibility(View.GONE);
        }
    
        @Override
        public void showErrorTip() {
            Toast.makeText(this, "login faled", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void doLoginSuccess() {
            Toast.makeText(this, "login success", Toast.LENGTH_SHORT).show();
        }
    }

上面只是抛砖引玉。MVP的优点十分明显,就是代码解耦、可以让逻辑清晰,但是同样它也会有缺点,它的缺点就是项目的复杂程度会增加,项目中会多出很多类。
之前很多人都在讨论该如何去正确的设计使用MVP来避免它的缺点,众说纷纭,很多人讨论的你死我活。直到Google发布了MVP架构蓝图,大家才意识到这才是规范。

项目地址:android-architecture
Google将该项目命名为Android的架构蓝图,我想从名字上已可以看穿一切。

在它的官方介绍中是这样说的:

The Android framework offers a lot of flexibility when it comes to defining how to organize and architect an Android app. This freedom, whilst very valuable, can also result in apps with large classes, inconsistent naming and architectures (or lack of) that can make testing, maintaining and extending difficult.

Android Architecture Blueprints is meant to demonstrate possible ways to help with these common problems. In this project we offer the same application implemented using different architectural concepts and tools.

You can use these samples as a reference or as a starting point for creating your own apps. The focus here is on code structure, architecture, testing and maintainability. However, bear in mind that there are many ways to build apps with these architectures and tools, depending on your priorities, so these shouldn’t be considered canonical examples. The UI is deliberately kept simple.

已完成的示例:

  • todo-mvp/ - Basic Model-View-Presenter architecture.
  • todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders.
  • todo-mvp-databinding/ - Based on todo-mvp, uses the Data Binding Library.
  • todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture.
  • todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection
  • todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers
  • todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.

我们接下来就用todo-mvp来进行分析,这个应用非常简单,主要有以下几个功能:

  • 列表页:展示所有的todo项
  • 添加页:添加todo项
  • 详情页:查看todo项的详情
  • 统计页:查看当前所有已完成todo及未完成项的统计数据

代码并不多:

功能也比较简单:


我们先从两个Base类开始看,分别是BaseView以及BasePresenter类。

BaseView类:

public interface BaseView<T> {

    void setPresenter(T presenter);

}

BaseView中的setPresenter()是将Presenter的实例传入到View中。
BasePresenter类:

public interface BasePresenter {

    void start();

}

BasePresenter中只有一个start()方法,从名字上我们就能看出他的作用。

接下来继续看一下项目的入口Activity,TaskDetailActivity的实现:

public class TaskDetailActivity extends AppCompatActivity {

    public static final String EXTRA_TASK_ID = "TASK_ID";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.taskdetail_act);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);
        ab.setDisplayShowHomeEnabled(true);

        // Get the requested task id
        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);

        TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                .findFragmentById(R.id.contentFrame);

        if (taskDetailFragment == null) {
            // 创建对应的Fragment
            taskDetailFragment = TaskDetailFragment.newInstance(taskId);

            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
                    taskDetailFragment, R.id.contentFrame);
        }

        // Create the presenter,因为Presenter是M和V的连接桥,所以在第二个参数中会传入TasksRepository
        new TaskDetailPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                taskDetailFragment);
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }
}

可以看到这里Activity相当于一个管理类,里面控制着MVP中的V和P,上面的代码主要做了两部分:

  • 创建对应的Fragment
  • 创建对应的Presenter

这里分别看一下TaskDetailFragment和TaskDetailPresenter的源码:

public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
    private TaskDetailContract.Presenter mPresenter;

    public static TaskDetailFragment newInstance(@Nullable String taskId) {
        Bundle arguments = new Bundle();
        arguments.putString(ARGUMENT_TASK_ID, taskId);
        TaskDetailFragment fragment = new TaskDetailFragment();
        fragment.setArguments(arguments);
        return fragment;
    }

    @Override
    public void onResume() {
        super.onResume();
        // 调用Presenter.start()方法
        mPresenter.start();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ....
    }

    @Override
    public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
        // 将Prsenter传递给V中 
        mPresenter = checkNotNull(presenter);
    }

    ....
}

及

public class TaskDetailPresenter implements TaskDetailContract.Presenter {
    private final TasksRepository mTasksRepository;

    private final TaskDetailContract.View mTaskDetailView;

    @Nullable
    private String mTaskId;

    // 把M和V传递进来
    public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
        // 将该Presenter设置给V
        mTaskDetailView.setPresenter(this);
    }

    @Override
    public void start() {
        // 调用获取数据的方法
        openTask();
    }
    ....
}

可以看到上面分别实现了TaskDetailContract类中的Presenter和View接口,而不是BasePresenter和BaseView中的接口,那这个TaskDetailContract类是什么呢?

/**
 * This specifies the contract between the view and the presenter.
 */
public interface TaskDetailContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showMissingTask();

        void hideTitle();

        void showTitle(String title);

        void hideDescription();

        void showDescription(String description);

        void showCompletionStatus(boolean complete);

        void showEditTask(String taskId);

        void showTaskDeleted();

        void showTaskMarkedComplete();

        void showTaskMarkedActive();

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

        void editTask();

        void deleteTask();

        void completeTask();

        void activateTask();
    }
}

按照上面描述的介绍可以看出通过这个连接类将VP联系到了一起,它统一管理view和presenter中的所有接口,这样比起分开写会更加清晰。

分析完VP之后我们继续看一下TaskDetailPresenter类,因为Presenter是MV的桥梁。

public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");

        mTaskDetailView.setPresenter(this);
    }

    @Override
    public void start() {
        openTask();
    }

    private void openTask() {
        if (Strings.isNullOrEmpty(mTaskId)) {
            mTaskDetailView.showMissingTask();
            return;
        }
        // 控制V显示UI
        mTaskDetailView.setLoadingIndicator(true);
    // 通过M去获取数据
        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // The view may not be able to handle UI updates anymore
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.setLoadingIndicator(false);
                if (null == task) {
                    mTaskDetailView.showMissingTask();
                } else {
                    showTask(task);
                }
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.showMissingTask();
            }
        });
    }

在它的构造函数中传入了一个TasksRepository,这个就是Model层。而在该Presenter中的start()方法回去调用获取数据的方法。

我们继续看一下M层TasksRepository的实现:

/**
 * Concrete implementation to load tasks from the data sources into a cache.
 * <p>
 * For simplicity, this implements a dumb synchronisation between locally persisted data and data
 * obtained from the server, by using the remote data source only if the local database doesn't
 * exist or is empty.
 */
public class TasksRepository implements TasksDataSource {
    ....
}

先看一下TasksDataSource接口:

/**
 * Main entry point for accessing tasks data.
 * <p>
 * For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other
 * methods to inform the user of network/database errors or successful operations.
 * For example, when a new task is created, it's synchronously stored in cache but usually every
 * operation on database or network should be executed in a different thread.
 */
public interface TasksDataSource {

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

    void getTasks(@NonNull LoadTasksCallback callback);

    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

    void saveTask(@NonNull Task task);

    void completeTask(@NonNull Task task);

    void completeTask(@NonNull String taskId);

    void activateTask(@NonNull Task task);

    void activateTask(@NonNull String taskId);

    void clearCompletedTasks();

    void refreshTasks();

    void deleteAllTasks();

    void deleteTask(@NonNull String taskId);
}

可以看出M层被赋予了数据获取的功能,与之前我们写的M层只定义实体对象截然不同,数据的增删改查都是M层实现的。Presenter只需要调用M层的方法即可。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。

这里我们就以上面用到的getTasks()方法为例来介绍一下:

public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
        checkNotNull(taskId);
        checkNotNull(callback);
        // 从缓存中获取数据
        Task cachedTask = getTaskWithId(taskId);

        // Respond immediately with cache if available
        if (cachedTask != null) {
            callback.onTaskLoaded(cachedTask);
            return;
        }

        // Load from server/persisted if needed.

        // Is the task in the local data source? If not, query the network.
        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // Do in memory cache update to keep the app UI up to date
                if (mCachedTasks == null) {
                    mCachedTasks = new LinkedHashMap<>();
                }
                mCachedTasks.put(task.getId(), task);
                callback.onTaskLoaded(task);
            }

            @Override
            public void onDataNotAvailable() {
                // 服务器的数据源
                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                    @Override
                    public void onTaskLoaded(Task task) {
                        // Do in memory cache update to keep the app UI up to date
                        if (mCachedTasks == null) {
                            mCachedTasks = new LinkedHashMap<>();
                        }
                        mCachedTasks.put(task.getId(), task);
                        callback.onTaskLoaded(task);
                    }

                    @Override
                    public void onDataNotAvailable() {
                        callback.onDataNotAvailable();
                    }
                });
            }
        });
    }

上面的注释说的非常明白了,这里就不去仔细看了。

下面用一张图来总结一下:

image

由于架构的引入,虽然代码量有了一定的上升,但是功能分离的非常清晰明确,而且每个部分都可以进行单独的测试,对于后期的扩展维护会更加简单容易。


更多文章请查看AndroidNote

你的star是我的动力!!!

Android 架构MVC MVP MVVM+实例 工作原理1.View接受用户的交互请求。2.View将请求转交给Controller。3.Controller操作Model进行数据更新。4.数据更新之后,Model通知View数据变化。5.View显示更新之后的数据。MVC的缺点随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。MVP是什么======MVP是MVC架构的一个演化版,全称是Model-View-Presenter。将MVC中的V和C结合生成MVP中的V,引入新的伙伴Presenter。 阅读详情

相关推荐

浏览器form表单的行为及其http原理

1. html form语法DTD定义 <!ATTLIST form %attrs; action %URI; #REQUIRED method (get|post) "get" name NMTOKEN #IMPLIED enctype %ContentType; "application/x-www-form-urlencoded" onsubmit %Script; #I

Cloud-Future的博客 1016

Android技术框架篇(二):MVP框架

mvp架构的介绍

看,好自为之的博客!!! 6636

Android中对MVC,MVP,MVVM架构的理解

但是随着软件越来越来复杂,会发现这样的写法不是太好,有些缺陷,Controller和View越来越耦合了,Controller指挥View去显示,具体怎么显示Controller是不关注的,比如setContentView(R.layout.activity_m_v_c),和具体的摆放逻辑全部是在xml中进行摆放布局的,但是Controller层要操控具体的数据应该放在哪个控件上,随着软件越来越大,Controller和View层的关系越来越紧密,所以引入了MVP架构。1、Model层对应的代码。

路宇的博客 2454

Android MVP架构模式

详解Android如何实现MVP架构

保温杯拖把风扇的博客 5129

Vue 实现华视身份证读卡器功能

在www.downcc.com下载“华视cvr100驱动”安装程序; 华视电子官网有相应的文档说明和程序下载 一、服务安装 选择安装目录: 启动读卡服务: 安装成功会有如图所示: 注意事项: 安装开发包必须使用管理员权限,因为windows服务和读卡器设备驱动程序均需要管理员权限,否则会安装失败。 win10需要修改组策略才有管理员权限。 二、服务的使用 打开浏览器,输入地址http://127.0.0.1:19196/mainpage,回车 点击openDevice: 点.

zyqgt960812的博客 6503

用R语言实现“中断时间序列分析” --- R code for ITS

ITS方法可用于评估一项干预措施的效果,从水平改变和斜率改变两个角度来描述干预引起的变化。

franksui的博客 4030

使用ajax()或者 bootstrapTable() 传递form表单——包括表单含文件时

目录1、有个问题写在前面2、基础知识3、 $.ajax()3* $("#formID").ajaxSubmit({})4、 $('#tableID').bootstrapTable({})5、 写在最后 1、有个问题写在前面 用bootstrapTable()方法传的表单里如果包含文件应该怎么写?在线等!! 2、基础知识 ①$.ajax()或者$(’#tableID’).bootstrapTable({})各自的属性; ②以上方法的contentType属性、<form>标签的enctype属

qq_38907313的博客 2340

hcaptcha逆向实战:从API解析到参数构造的完整链路

本文详细拆解了hcaptcha验证码的逆向实战流程,从环境准备、捕获API与版本号,到解析核心加密逻辑hsw.js,再到构造getcaptcha请求参数,最后完成验证提交。文章重点剖析了motionData、n、c等关键参数的获取与构造方法,为开发者模拟浏览器行为、实现自动化验证提供了清晰的完整链路指南。

ik678901的博客 632

Winform中Form.Show()和Showdialog()和Activate()方法的区别

区别说明 Show()出来的子窗口和主窗口之间不受影响,可以同时操作,Showdialog()出来的子窗口只能在关闭子窗口后才能操作主窗口 Activate()是让窗体显示在前面 当通过new创建了一个Form窗体时,该窗体的Visible属性默认是false,即不显示该窗体,可以通过运行试试 所以可以通过Show()方法来将该窗体显示出来,如果是由不显示到显示,即第一次调用该窗体的Show()方法,则该窗体会显示在主窗体前面,但如果将鼠标切回主窗体,此时主窗体在前,再次调用该窗体的Show(),则子窗体

遗落在树梢的风筝 4985

别再手动巡检了!vRealize Operations Manager 8.x 自动化报告配置全攻略(附模板下载)

本文详细介绍了如何利用vRealize Operations Manager 8.x实现自动化巡检报告配置,大幅提升虚拟化运维效率。通过模板定制、智能调度和自动分发等功能,帮助运维团队告别手动生成报告的繁琐工作,实现90%以上的时间节省。文章还提供了实用的配置技巧和最佳实践,助力企业构建高效的自动化报告系统。

weixin_33806859的博客 298

wincc 服务器授权型号,WinCC V7.5 SP1软件安装及授权方法

上篇教程给大家介绍了TIA Portal V16 安装方法,今天分享一下WinCC V7.5 SP1。目前V7.5 SP1是WinCC最新的版本,发布时间2019年11月13日。先放一个官网对WinCC V7.5 的介绍视频,感兴趣的可以先看一下。1、软件下载官网下载链接:https://support.industry.siemens.com/cs/document/109772879五个文件下...

weixin_39956110的博客 1万+

FORM的ENCTYPE="multipart/form-data" 时request.getParameter()值为null问题的解决

解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的方法  url:http://blog.youkuaiyun.com/georgejin/archive/2007/07/25/1706647.aspx  今天在原来上传文件页面的基础上,想添加一段文件的简介因为同时要上传文件,所以ENCTYPE="mult

jackpk的专栏 2万+

华为交换机密码策略配置避坑指南:明文/密文、长度、复杂度,一个命令就出错

本文详细解析华为交换机密码策略配置的关键要点,包括密码长度、复杂度及等保合规要求。通过实战案例揭示常见配置误区,如明文/密文密码格式差异、复杂度检查联动机制等,帮助网络工程师避免安全漏洞。特别针对等保二级和三级要求,提供可直接落地的安全加固方案。

weixin_30879169的博客 372

FORM的ENCTYPE=multipart/form-data 时request.getParameter()值为null问题的解决

  解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的方法   ?   url:http://blog.youkuaiyun.com/georgejin/archive/2007/07/25/1706647.aspx   ?  ...

计算机毕业论文源码,学生个人网页制作html源码。贴近用户做网络推广和互联网优化。 794

【EB_Tresos专题】-06-配置一个UDS诊断DID

<--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> <--返回「Autosar_BSW高阶配置」总目录--> <--返回「Autosar_BSW高阶配置」专栏主页--> <--返回「个人博客」首页--> 结尾 ...

「汽车电子助手」的博客 4089

RT-Thread Studio 使用笔记(七)| 配合STM32CubeMX添加裸机驱动(以ADC为例)

1. 配置STM32CubeMX 2. 复制stm32xxxx_hal_msp.c文件 将 CubeMx 生成的代码 stm32f7xx_hal_msp.c 函数复制到 RT-Thread Studio 生成的工程中,并参与工程编译: 由于我们并没有使用 CubeMx 生成的工程,所以这里需要将 stm32f7xx_hal_msp.c 文件中 #include “main.h” 替换为 #i...

Mculover666的博客(嵌入式) 9629

extjs form java_[Java教程]ExtJS入门教程02,form也可以很优雅

[Java教程]ExtJS入门教程02,form也可以很优雅0 2014-03-28 12:00:40在上一篇《Extjs window 入门》中,我们已经看到了如何将一个form组件放到window中,今天我们来看看form的一些优雅的工作方式。使用fieldDefaults,优雅的设置字段默认值form中的子项,通常都是field控件,而这些field控件通常都会使用一些相同的配...

weixin_32413177的博客 189

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Warning: Instance created by useForm is not connected to any Form element. Forget to pass form prop

如果是Model里用了from报警告,我更倾向于 使用 useRef 判断 当useRef有值时再调用。3.还有一种情况就是确实写了form,并且写了form={form},但还警告,我就是这种情况。意思是:警告:“useForm”创建的实例未连接到任何Form元素。在调用form的实例时,Modal内部的组件并未渲染,才导致了该错误。form必须在标签上写form={form}1.使用 useRef 判断 当useRef有值时再调用。如果你表单是子组件则可能需要这样。

从事前端,不断积累经验,学习新的技术。头像不是本人是莉莉崽(美貌、可爱集于一身的小姐姐,小仙女),一个宝藏博主,值得喜欢! 8514

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的方法

 今天在原来上传文件页面的基础上,想添加一段文件的简介因为同时要上传文件,所以ENCTYPE="multipart/form-data" 必须要加在form里面可是这样的话,我再servlet里面用request.getParameter()方法无论如何都只是获得null值,不是一般的郁闷,百度了一下,有人出现了同样的问题可是它用的是jspsmartupload组件实现文件上传的,

snow8261的专栏 2161

antd 之 Form 表单·记

同一个 Form 下的两个 Form.list,如何在第二个Form.list中使用第一个Form.list的添加的所有数据? Form.provider 提供了两个form之间共享数据的途径。所以我用了它,但是在Form.provider的onFormChange事件中:Form.provider 仅仅提供第一层的name及其对应的value,也就是Form.list的name和value,取不到更深一层的Form.item的name和value。 抛开Form.provider,现在这两个Form已

青蛙king的博客 1855

解决当FORM的ENCTYPE="multipart/form-data" 时request.getParameter()获取不到值的问题

今天在原来上传文件页面的基础上,想添加一段文件的简介 因为同时要上传文件,所以ENCTYPE="multipart/form-data" 必须要加在form里面 可是这样的话,我再servlet里面用request.getParameter()方法无论如何都只是获得null值, 不是一般的郁闷,百度了一下,有人出现了同样的问题可是它用的是jspsmartupload组件实现文件上

dingjingchao的博客 6380

关于layui框架的form表单布局(小白向)

from表单的使用 在使用from表单的时候,必须先声明from模块,否则select、checkbox、radio等将无法显示,并且无法使用form相关功能,这是很多小白一开始经常犯的错误,虽然这很简单但也很基础,所以有必要在这里提一下 from模块声明 &amp;lt;script&amp;gt; layui.use('form', function () { var form = layui.form; f...

DSRC优快云的博客 3万+

html怎么让form弄成一个表,创建一个HTML表,其中每个TR是一个FORM

如果你想要一个“可编辑的网格”,即一个表结构,允许你使任何行的形式,使用CSS模仿TABLE标记的布局:display:table,display:table-row和display:table-cell 。没有必要将整个表格包装在一个表单中,并且不需要为表格的每个表面行创建单独的表单和表格。试试这个:DIV.table{display:table;}FORM.tr, DIV.tr{display...

weixin_42391580的博客 402
上一篇: Android启动模式详解
下一篇: Android WorkManager
CharonChui
博客等级 码龄12年 66粉丝 134原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值