Android Databinding:再见Presenter,你好ViewModel

原文链接:

ANDROID
DATABINDING: GOODBYE PRESENTER, HELLO VIEWMODEL!

MVP(Model-View-Presenter)近来成为Android应用的UI层架构设计中主要的设计模式。例如Ted MosbyNycleusMortar 等框架都引入Presenters来帮助你的应用构建一个整洁的架构(clean
architecture)。他们也在不同程度上帮助你解决Android平台上的设备旋转和状态保持等问题。这虽然和MVP不直接相关,但是使用该模式可以帮助你隔离这些模板代码。

Data Binding,在2015年谷歌IO大会上引入,是一项重要的改变。在维基百科中MVP中Presenter的作用是这样描述的:

Presenter作用于model和view之间。它从model中获取数据,并且格式化数据供view来显示(The
presenter acts upon the model and the view. It    
retrieves  data from repositories (the model), and formats it for display in the view.

Data Binding框架代替了Presenter(作用于model和view之间)的主要功能,剩余的(获取数据并格式化供显示)则由增强型的Model--ViewModel来实现。ViewModel是标准的JAVA类,它唯一的责任就是为一个视图提供数据。它可以从多个数据源(Models)中合并数据,然后供界面显示。我写了一篇文章描述了ViewModel和Data
Model与Transport Model间的区别。

这个架构就是MVVM--Model-View-ViewModel,这个概念最初由微软在2005年提出来。让我们介绍下从MVP到MVVM的变动,下面的图片是拷贝Hanne Dorfmann‘s介绍Ted Mosby框架中的插图

因此,所有View对应数据的绑定和更新都是通过Data Binding框架来实现。ObservableField类支对model的改动同步到View,同样当用户操作View时,属性的变动也可以推送到ViewModel,前提是在XML中引用ViewModel中的该属性。当然你也可以通过代码监听属性的变动,这样就能实现CheckBox按下TextView就变成成灰色的功能。但是将View的视觉状态的改变用标准的JAVA来实现的一个好处是:易于单元测试

在MVP插图中有一个方法的调用Presenter.loadUsers(),这是一个命令。在MVVM中这些方法调用被定义在ViewModel中。来自维基百科:

ViewModel 是一个抽象View,它定义了公共的属性和命令。

这可能会改变你的编码习惯,在MVP模式中,Model只包含数据不包含业务。但是不要害怕将业务逻辑定义在Model或者ViewModel中,这本来就是面向对象编程的核心思想(译者注:类中包含数据也包含操作数据的方法)。回到Presenter.loadUsers()方法--这个方法会被定义在ViewModel中,该方法可能会在Activity中调用,也可能通过XML资源文件中的数据绑定命令(data
bound command)来执行(该功能谷歌已经给出承诺,但是还没实现)。如果不能支持数据绑定命令那只有借助古老的android:onClick或者添加视图的监听器。

系统调用的处理

有些系统初始化的调用,如打开Dialog或者任何需要Context对象的调用,仍然需要定义在Activity中。不要把这种代码放在ViewModel中。如果ViewModel中包含import android.content.Context;,那么说明你的代码是错误的,千万不要这样子做(译者注:不包含android平台的代码,才方便做单元测试)。

有几种好的方法可以避免以上的问题,One way would be to keep elements of the presenter concept from Mosby by referencing an interface to the View in the ViewModel.
This way you won’t reduce the testability. But instead of having a seperate Presenter class as in Mosby, I’d stick to the View as the concrete implementation of that interface just to keep it simple. Another approach could be to use an event bus like Square’s
Otto
 to initiate commands like new
ShowToastMessage("hello world")
. This will yield a greater separation of the view and the viewmodel – but is that a good thing?

现在还需要框架吗?

说了那么多,Data Binding可以代替Mosby或者Mortar框架了?答案是:一定程度上可以代替。希望可以看到这些框架继续发展或者引入MVVM模式,这样我们就可以最好的利用Data Binding同时减少第三方库的依赖。虽然MVP最近势头减弱,但是视图状态变化的持久性(view state【ViewModel】 persistence和生命周期管理同样重要。  最近就看到了这样的一个框架:AndroidViewModel
framework

概要

当我听说Android M中引入了Data Binding时,我意识到它可以帮助我们构建干净的架构。但是它也不能包治百病--也存在缺点。在XML中定义就是问题,因为XML是不会被编译的,XML也是不能进行单元测试的。因此,大部分应该在编译时发现的错误,却要等到运行时才能发现。如果有工具可以帮助我们那就不同了--所以希望谷歌可以在Android Studio中加入工具对Data Binding提供更好支持。比如语法检查,引用检查,自动补齐,属性重命名自动更新到XML等功能。

例子

下面是一个例子,放在一起是为了演示MVP和MVVM的不同。例子中使用了Mosby作为MVP框架并使用了Butterknife作为注入框架。在MVVM演示中只使用了Data Binding。Presenter被丢弃了,Fragment中的代码更少了,但是ViewModel承担了更多的责任,增加了更多的代码。

在本例中我直接引用了View为了产生Toast Message,这是不提倡的。使用Robolectric和Mockito模拟Fragment后它是可以单元测试的。

这个应用包含一个登陆界面,有些数据是异步加载的

想要在AS中阅读代码,进入此Github repo

MVP – VIEW – XML

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin"
                tools:context=".MainActivityFragment">

    <TextView
        android:text="..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:id="@+id/loggedInUserCount"/>

    <TextView
        android:text="# logged in users:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="false"
        android:layout_toLeftOf="@+id/loggedInUserCount"/>

    <RadioGroup
        android:layout_marginTop="40dp"
        android:id="@+id/existingOrNewUser"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:orientation="horizontal">

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Returning user"
            android:id="@+id/returningUserRb"/>

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New user"
            android:id="@+id/newUserRb"
            />

    </RadioGroup>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/username_block"
        android:layout_below="@+id/existingOrNewUser">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Username:"
            android:id="@+id/textView"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/username"
            android:minWidth="200dp"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="false"
        android:id="@+id/password_block"
        android:layout_below="@+id/username_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Password:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:ems="10"
            android:id="@+id/password"/>

    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/password_block"
        android:id="@+id/email_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Email:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:ems="10"
            android:id="@+id/email"/>
    </LinearLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Log in"
        android:id="@+id/loginOrCreateButton"
        android:layout_below="@+id/email_block"
        android:layout_centerHorizontal="true"/>
</RelativeLayout>

MVP – VIEW – JAVA

package com.nilzor.presenterexample;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.hannesdorfmann.mosby.mvp.MvpFragment;
import com.hannesdorfmann.mosby.mvp.MvpView;
import butterknife.InjectView;
import butterknife.OnClick;

public class MainActivityFragment extends MvpFragment implements MvpView {
    @InjectView(R.id.username)
    TextView mUsername;

    @InjectView(R.id.password)
    TextView mPassword;

    @InjectView(R.id.newUserRb)
    RadioButton mNewUserRb;

    @InjectView(R.id.returningUserRb)
    RadioButton mReturningUserRb;

    @InjectView(R.id.loginOrCreateButton)
    Button mLoginOrCreateButton;

    @InjectView(R.id.email_block)
    ViewGroup mEmailBlock;

    @InjectView(R.id.loggedInUserCount)
    TextView mLoggedInUserCount;

    public MainActivityFragment() {
    }

    @Override
    public MainPresenter createPresenter() {
        return new MainPresenter();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        attachEventListeners();
    }

    private void attachEventListeners() {
        mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
        mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
    }

    /** Prepares the initial state of the view upon startup */
    public void setInitialState() {
        mReturningUserRb.setChecked(true);
        updateDependentViews();
    }

    /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
    public void updateDependentViews() {
        if (mReturningUserRb.isChecked()) {
            mEmailBlock.setVisibility(View.GONE);
            mLoginOrCreateButton.setText(R.string.log_in);
        }
        else {
            mEmailBlock.setVisibility(View.VISIBLE);
            mLoginOrCreateButton.setText(R.string.create_user);
        }
    }

    public void setNumberOfLoggedIn(int numberOfLoggedIn) {
        mLoggedInUserCount.setText(""  + numberOfLoggedIn);
    }

    @OnClick(R.id.loginOrCreateButton)
    public void loginOrCreate() {
        if (mNewUserRb.isChecked()) {
            Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
        }
    }
}

MVP – PRESENTER

package com.nilzor.presenterexample;

import android.os.Handler;
import android.os.Message;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;

public class MainPresenter implements MvpPresenter {
    MainModel mModel;
    private MainActivityFragment mView;

    public MainPresenter() {
        mModel = new MainModel();
    }

    @Override
    public void attachView(MainActivityFragment view) {
        mView = view;
        view.setInitialState();
        updateViewFromModel();
        ensureModelDataIsLoaded();
    }

    @Override
    public void detachView(boolean retainInstance) {
        mView = null;
    }

    private void ensureModelDataIsLoaded() {
        if (!mModel.isLoaded()) {
            mModel.loadAsync(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    updateViewFromModel();
                    return true;
                }
            });
        }
    }

    /** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
    private void updateViewFromModel() {
        if (mView != null && mModel.isLoaded()) {
            mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
        }
    }
}

MVP – MODEL

package com.nilzor.presenterexample;

import android.os.AsyncTask;
import android.os.Handler;
import java.util.Random;

public class MainModel {
    public Integer numberOfUsersLoggedIn;
    private boolean mIsLoaded;
    public boolean isLoaded() {
        return mIsLoaded;
    }

    public void loadAsync(final Handler.Callback onDoneCallback) {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn = new Random().nextInt(1000);
                mIsLoaded = true;
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onDoneCallback.handleMessage(null);
            }
        }.execute((Void) null);
    }
}

MVVM – VIEW – XML

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="data" type="com.nilzor.presenterexample.MainModel"/>
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MainActivityFragment">

        <TextView
            android:text="@{data.numberOfUsersLoggedIn}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:id="@+id/loggedInUserCount"/>

        <TextView
            android:text="# logged in users:"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="false"
            android:layout_toLeftOf="@+id/loggedInUserCount"/>

        <RadioGroup
            android:layout_marginTop="40dp"
            android:id="@+id/existingOrNewUser"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:orientation="horizontal">

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Returning user"
                android:checked="@{data.isExistingUserChecked}"
                android:id="@+id/returningUserRb"/>

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="New user"
                android:id="@+id/newUserRb"
                />

        </RadioGroup>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/username_block"
            android:layout_below="@+id/existingOrNewUser">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Username:"
                android:id="@+id/textView"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/username"
                android:minWidth="200dp"/>
        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="false"
            android:id="@+id/password_block"
            android:layout_below="@+id/username_block">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Password:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:ems="10"
                android:id="@+id/password"/>

        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/password_block"
            android:id="@+id/email_block"
            android:visibility="@{data.emailBlockVisibility}">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Email:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress"
                android:ems="10"
                android:id="@+id/email"/>
        </LinearLayout>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.loginOrCreateButtonText}"
            android:id="@+id/loginOrCreateButton"
            android:layout_below="@+id/email_block"
            android:layout_centerHorizontal="true"/>
    </RelativeLayout>
</layout>

MVVM – VIEW – JAVA

package com.nilzor.presenterexample;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Toast;

import com.nilzor.presenterexample.databinding.FragmentMainBinding;

public class MainActivityFragment extends Fragment {
    private FragmentMainBinding mBinding;
    private MainModel mViewModel;

    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        mBinding = FragmentMainBinding.bind(view);
        mViewModel = new MainModel(this, getResources());
        mBinding.setData(mViewModel);
        attachButtonListener();
        return view;
    }

    private void attachButtonListener() {
        mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.logInClicked();
            }
        });
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        ensureModelDataIsLodaded();
    }

    private void ensureModelDataIsLodaded() {
        if (!mViewModel.isLoaded()) {
            mViewModel.loadAsync();
        }
    }

    public void showShortToast(String text) {
        Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
}

MVVM – VIEWMODEL

package com.nilzor.presenterexample;

import android.content.res.Resources;
import android.databinding.ObservableField;
import android.os.AsyncTask;
import android.view.View;

import java.util.Random;

public class MainModel {
    public ObservableField numberOfUsersLoggedIn = new ObservableField();
    public ObservableField isExistingUserChecked = new ObservableField();
    public ObservableField emailBlockVisibility = new ObservableField();
    public ObservableField loginOrCreateButtonText = new ObservableField();
    private boolean mIsLoaded;
    private MainActivityFragment mView;
    private Resources mResources;

    public MainModel(MainActivityFragment view, Resources resources) {
        mView = view;
        mResources = resources; // You might want to abstract this for testability
        setInitialState();
        updateDependentViews();
        hookUpDependencies();
    }
    public boolean isLoaded() {
        return mIsLoaded;
    }

    private void setInitialState() {
        numberOfUsersLoggedIn.set("...");
        isExistingUserChecked.set(true);
    }

    private void hookUpDependencies() {
        isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
                updateDependentViews();
            }
        });
    }

    public void updateDependentViews() {
        if (isExistingUserChecked.get()) {
            emailBlockVisibility.set(View.GONE);
            loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
        }
        else {
            emailBlockVisibility.set(View.VISIBLE);
            loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
        }
    }

    public void loadAsync() {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
                mIsLoaded = true;
                return null;
            }
        }.execute((Void) null);
    }

    public void logInClicked() {
        // Illustrating the need for calling back to the view though testable interfaces.
        if (isExistingUserChecked.get()) {
            mView.showShortToast("Invalid username or password");
        }
        else {
            mView.showShortToast("Please enter a valid email address");
        }
    }
}

时间: 2024-10-09 10:35:20

Android Databinding:再见Presenter,你好ViewModel的相关文章

设计模式笔记之三:Android DataBinding库(MVVM设计模式)

本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236908&idx=1&sn=9e53f42e18a81795ef0cfe6fe3959ec2&scene=24&srcid=0910cK3vXJpNzY0CO28i1Qhs#wechat_redirect 什么是MVVM 说到DataBinding,就有必要先提起MVVM设计模式.Model–View–ViewModel(MVV

Android DataBinding库(MVVM设计模式)

说到 DataBinding,就有必要先提起 MVVM设计模式. Model–View–ViewModel(MVVM) 是一个软件架构设计模式,相比 MVVM,大家对 MVC 或 MVP 可能会更加熟悉. MVC:(VIew-Model-Controller) 早期将 View.Model.Controller 代码块进行划分,使得程序大部分分离,降低耦合. MVP:(VIew-Model-Presenter) 由于 MVC 中 View和Model之间的依赖太强,导致 Activity 中的代

十月,再见;你好,十一月

十月,再见! 十一月,你好! 2015年就剩最后两个月了. 曾经荒废时光的你,没时间遗憾,唯有追赶和改变. 总有人在你刷朋友圈的时候,苦练口语: 总有人在你打游戏时,坚持阅读: 总有人在你熟睡时,回想得失总结不足. 别让明天的你,再憎恨今天不曾尽力的自己! 两个月,足以做出改变. 行动吧! 不知是什么时候养成的晚睡晚起的习惯,凌晨一两点睡觉,早上十一二点起床.本来说早点起来总结我逝去的十 月,还是晚了,不过没关系.还在想以什么题目开始,看到十一月接踵而至,就拿<十月,再见:你好,十一月>当 做

2017再见~2018你好!

今天是2017-12-27,最近新版本发布,这2天相对有点闲了,忽然想着要不要总结记录下点什么呢~ 依稀还记得16年的最后一个工作日12.30号加班到快12点才回家~时间真的过得好块~再过几天就是2018了~ 说说我的2017吧,还是按月来说说吧~ 1月: 有点骄傲的事是,自己参与做的东西给大老板做了演示~1月7号周六,全天加班保障演示顺利进行,中午项目组长带我们吃了一个便饭,只可惜没有加班费,之后版本经理还说给我们记红事件(关系到绩效). 2月: 除了过年,好像记不起来有什么大事,应该就是每天

oi再见,你好明天。

oi再见,你好明天.录Menci大佬的翻唱<模你抄>如下:屏幕在深夜微微发亮思想在那虚树路径上彷徨平面的向量交错生长织成 忧伤的网 剪枝剪去我们的疯狂SPFA告诉我前途在何方01背包装下了忧伤笑颜 洋溢脸庞 键盘微凉 鼠标微凉指尖流淌 代码千行凸包周长 直径多长一进考场 全都忘光你在OJ上提交了千百遍却依然不能卡进那时限双手敲尽代码也敲尽岁月只有我一人 写的题解凋零在OJ里面 tarjan陪伴强联通分量生成树完成后思路才闪光欧拉跑过的七桥古塘让你 心驰神往 队列进出图上的方向线段树区间修改求出

android dataBinding详解

官方介绍地址:http://developer.android.com/intl/zh-cn/tools/data-binding/guide.html 2015 Google IO 大会带来的 Data Binding 库使得 Android 开发者可以方便的实现 MVVM 架构模式.使用DataBinding可以改善应用程序的开发,使代码更加干净优雅.何为MVVM模式,其实就是在View和Model层之间多了一层ViewModel,避免之前MVC模式中View层直接操作Model层,从而使代

Android dataBinding 之 配合使用BaseAdapter适配器

Overview 绑定适配器负责发出相应的框架调用来设置值.例如,设置属性值就像调用 setText() 方法一样.再比如,设置事件监听器就像调用 setOnClickListener() 方法. 数据绑定库允许您通过使用适配器指定为设置值而调用的方法.提供您自己的绑定逻辑,以及指定返回对象的类型. 看看如何实现的吧 首先我们看一下我们的item.xml <?xml version="1.0" encoding="utf-8"?> <layout

2018再见|2019你好

前言 只有光头才能变强 Hello,首先祝大家元旦快乐!(文末有个人送书福利) 2018年自己成长了很多,这篇文章来回顾一下2018我做了什么事,展望2019年~ 背景:2019应届生,方向:Java 上半年(一月份到五月份) 在2018年年初,我就给自己定下一个目标:"在今年暑假可以找到一份实习,如果实习单位就不错就一直做下去,我不希望爸妈担心我工作的事".对的,是一个非常简单明确的目标. 所以我在年初的时候就"一早早"开始准备面试题了.当时是把自己写过的笔记复习

Android DataBinding不能自动生成ViewDataBinding类的解决方法

如果Build.gradle和Layout文件配置正确,仍无法生成ViewDataBinding类. 经测试,Gradle的sync无效,clean project无效,invalidate and restart无效,但是Build->Rebuild Project生效了. 还不行的话,使用ViewDataBinding抽象类的setVariable方法,也可以生效. AS版本:3.1.3 gradle版本:3.1.3 原文地址:https://www.cnblogs.com/acesui/p