Android 架构设计

Android 架构设计,第1张

概述文章目录一、MVC(Model-View-Controller)1.1、角色定义1.2、缺点二、MVP(Model-View-Presenter)2.1、角色定义2.2、MVP基础实例2.3、MVP结合RxJava和Dagger2三、MVVM(Model-View-ViewModel)2.1、解析DataBinding博客一、MVC(Model-View-Controller)1.1、角色定义模型

文章目录一、MVC(Model-View-Controller)1.1、角色定义1.2、缺点二、MVP(Model-View-Presenter)2.1、角色定义2.2、MVP基础实例2.3、MVP结合RxJava和Dagger2三、MVVM(Model-View-ViewModel)2.1、解析Data Binding
博客

一、MVC(Model-VIEw-Controller)1.1、角色定义模型层(Model):针对业务模型,建立的数据结构和相关的类,就可以理解为 Model。Model 是与 VIEw 无关,而与业务相关的。视图层(VIEw):一般采用 XML 文件或者 Java 代码进行页面的描述,也可以使用 JavaScript+HTML 等方式作为 VIEw 层。控制层(Controller):AndroID 的控制层通常在 Activity、Fragment 或者由它们控制的其他业务类中。

1.2、缺点Activity 并不是一个标准的 MVC 模式中的 Controller,它的首要职责是加载应用的布局和初始化用户界面,接受并处理来自用户的 *** 作请求,进而做出响应。随着界面及逻辑的复杂度不断提升,Activity 类的职责不断增加,以致变得庞大臃肿。VIEw 层和 Model 层相互耦合,不易开发和维护。二、MVP(Model-VIEw-Presenter)2.1、角色定义Model:主要提供数据的存取功能。Presenter 需要通过 Model 层来存储、获取数据。VIEw:负责处理用户事件和视图部分的展示。在 AndroID 中,它可能是 Activity、Fragment 类或者是某个 VIEw 控件。Presenter:作为 VIEw 和 Model 之间沟通的桥梁,它从 Model 层检索数据后返回给 VIEw 层,使得 VIEw 和 Model 之间没有耦合。

在 MVP 里,Presenter 完全将 Model 和 VIEw 进行了分离,主要的程序逻辑在 Presenter 中实现。而且 Presenter 与具体的 VIEw 是没有之间关联的,而是通过定义好的接口进行交互,从而使得在变更 VIEw 时可以保持 Presenter 的不变,这点符合面向接口编程的特点。VIEw 只应该有简单的 Set/Get 方法,以及用户输入和设置界面的内容,除此之外就不应该有更多的内容。绝不允许 VIEw 直接访问 Model,这就是与 MVC 的很大不同之处。

2.2、MVP基础实例

写一个用来访问淘宝IP库的例子,参考地址。访问一个IP地址,并在界面上显示该IP对应的国家、地区和城市。这里需要访问网络,为了方便,采用了 OKhttp 的封装库 OkhttpFinal。

在MVP中,Model、Presenter和VIEw之间都是使用的接口进行交互,都是通过调用接口的方法进行交互的。Contract接口是为了便于管理。

项目结构。

配置build.gradle。

    implementation 'cn.finalteam:okhttpfinal:2.0.7'

在自定义Application中实例化,记得在AndroIDManifest中注册。

public class MVPApplication extends Application {    @OverrIDe    public voID onCreate() {        super.onCreate();        OkhttpFinalConfiguration.Builder builder = new OkhttpFinalConfiguration.Builder();        OkhttpFinal.getInstance().init(builder.build());    }}

1、实现Model

首先要创建Model实体BaseBean。

public class BaseBean<T> {    private int code;    private T data;    public int getCode() {        return code;    }    public voID setCode(int code) {        this.code = code;    }    public T getData() {        return data;    }    public voID setData(T data) {        this.data = data;    }}

IpInfoBean实体类。

public class IpInfoBean {    private String ip;    private String country;    private String area;    private String region;    private String city;    private String county;    private String isp;    private String country_ID;    private String area_ID;    private String region_ID;    private String city_ID;    private String county_ID;    private String isp_ID;    ...}

定义一个获取网络数据的接口类。

public interface NetTask<T> {    voID execute(T data, LoadTaskCallBack loadTaskCallBack);}

这里有一个回调监听接口LoadTaskCallBack,定义了网络回调的各种状态。

public interface LoadTaskCallBack<T> {    voID onStart();    voID onSuccess(T t);    voID onFailed();    voID onFinish();}

实现获取网络数据的接口。这是一个单例类,在execute方法中通过OkhttpFinal来获取数据,同时在回调中调用自己定义的回调函数LoadTaskCallBack。

public class IpInfoTask implements NetTask<String> {    private static IpInfoTask INSTANCE = null;    private static final String HOST = "http://ip.taobao.com/outGetIpInfo";    private IpInfoTask() {    }    public static IpInfoTask getInstance() {        if (INSTANCE == null) {            INSTANCE = new IpInfoTask();        }        return INSTANCE;    }    @OverrIDe    public voID execute(String data, final LoadTaskCallBack loadTaskCallBack) {        RequestParams requestParams = new RequestParams();        requestParams.addFormDataPart("ip", data);        requestParams.addFormDataPart("accessKey", "alibaba-inc");        httpRequest.post(HOST, requestParams, new BasehttpRequestCallback<BaseBean>() {            @OverrIDe            public voID onStart() {                loadTaskCallBack.onStart();            }            @OverrIDe            public voID onFinish() {                loadTaskCallBack.onFinish();            }            @OverrIDe            protected voID onSuccess(BaseBean ipInfo) {                loadTaskCallBack.onSuccess(ipInfo);            }            @OverrIDe            public voID onFailure(int errorCode, String msg) {                loadTaskCallBack.onFailed();            }        });    }}

2、实现Presenter

首先定义一个契约接口IpInfoContract,契约接口主要用来存放相同业务的Presenter和VIEw
的接口,便于查找和维护。可以看出IPresenter接口定义了获取数据的方法,而IVIEw定义了与界面交互的方法。

public interface IpInfoContract {    interface IPresenter {        voID getIpInfo(String ip);//获取数据方法    }    interface IVIEw extends BaseVIEw<IPresenter> {        voID setIpInfo(BaseBean ipInfo); //显示数据        voID showLoading(); //显示加载中        voID hIDeLoading(); //隐藏加载中        voID shoError(); //显示加载错误        boolean isActive(); //Fragment是否加载到Activity中    }}

还定义了一个BaseVIEw接口,并在其中定义一个setPresenter方法用来绑定Presenter,在其中都是定义一些VIEw的最基本的方法。

public interface BaseVIEw<T> {    voID setPresenter(T presenter);}

实现IPresenter接口。可以看出,Presenter就是一个桥梁,其通过NetTask,也就是Model层来获得和保存数据,然后再通过VIEw更新界面,这期间通过定义接口使得VIEw和Model没有任何交互。

public class IpInfoPresenter implements IpInfoContract.IPresenter, LoadTaskCallBack<BaseBean> {    private IpInfoContract.IVIEw vIEw; //注入VIEw接口    private NetTask netTask; //网络加载接口    public IpInfoPresenter(IpInfoContract.IVIEw addTaskVIEw, NetTask netTask) {        this.vIEw = addTaskVIEw;        this.netTask = netTask;    }    @OverrIDe    public voID onStart() {        if (vIEw.isActive()) {            vIEw.showLoading();        }    }    @OverrIDe    public voID onSuccess(BaseBean ipInfo) {        if (vIEw.isActive()) {            vIEw.setIpInfo(ipInfo);        }    }    @OverrIDe    public voID onFailed() {        if (vIEw.isActive()) {            vIEw.shoError();            vIEw.hIDeLoading();        }    }    @OverrIDe    public voID onFinish() {        if (vIEw.isActive()) {            vIEw.hIDeLoading();        }    }    @OverrIDe    public voID getIpInfo(String ip) {        netTask.execute(ip, this);    }}

3、实现VIEw

首先是IpInfoFragment,它实现了IVIEw接口。

public class IpInfoFragment extends Fragment implements IpInfoContract.IVIEw {    private TextVIEw tv_country;    private TextVIEw tv_area;    private TextVIEw tv_city;    private button bt_ipinfo;    private Dialog mDialog;    private IpInfoContract.IPresenter mPresenter; //注入presenter接口    @Nullable    @OverrIDe    public VIEw onCreateVIEw(@NonNull LayoutInflater inflater, @Nullable VIEwGroup container, @Nullable Bundle savedInstanceState) {        VIEw root = inflater.inflate(R.layout.fragment_ipinfo, container, false);        tv_country = root.findVIEwByID(R.ID.tv_country);        tv_area = root.findVIEwByID(R.ID.tv_area);        tv_city = root.findVIEwByID(R.ID.tv_city);        bt_ipinfo = root.findVIEwByID(R.ID.bt_ipinfo);        return root;    }    @OverrIDe    public voID onActivityCreated(@Nullable Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        mDialog = new ProgressDialog(getActivity());        mDialog.setTitle("获取数据中");        bt_ipinfo.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw vIEw) {                mPresenter.getIpInfo("125.84.80.236");            }        });    }    public static IpInfoFragment newInstance() {        return new IpInfoFragment();    }    @OverrIDe    public voID setIpInfo(BaseBean ipInfo) {        if (ipInfo != null && ipInfo.getData() != null) {            Log.d("TAg", ipInfo.getCode() + "/" + ipInfo.getData());            IpInfoBean ipData = JsONObject.parSEObject(ipInfo.getData().toString(), IpInfoBean.class);            tv_country.setText(ipData.getCountry());            tv_area.setText(ipData.getArea());            tv_city.setText(ipData.getCity());        }    }    @OverrIDe    public voID showLoading() {        mDialog.show();    }    @OverrIDe    public voID hIDeLoading() {        if (mDialog.isShowing()) {            mDialog.dismiss();        }    }    @OverrIDe    public voID shoError() {        Toast.makeText(getActivity().getApplicationContext(), "网络出错", Toast.LENGTH_SHORT).show();    }    @OverrIDe    public boolean isActive() {        return isAdded();    }    @OverrIDe    public voID setPresenter(IpInfoContract.IPresenter presenter) {        this.mPresenter = presenter;    }}

再就是IpInfoActivity。在这个例子中IpInfoActivity并不作为VIEw层,而是作为VIEw、Model和Presenter三层的纽带。

public class IpInfoActivity extends AppCompatActivity {    private IpInfoPresenter ipInfoPresenter;    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_ipinfo);        IpInfoFragment ipInfoFragment = (IpInfoFragment) getSupportFragmentManager().findFragmentByID(R.ID.contentFrame);        if (ipInfoFragment == null) {            ipInfoFragment = IpInfoFragment.newInstance();            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), ipInfoFragment, R.ID.contentFrame);        }        IpInfoTask ipInfoTask = IpInfoTask.getInstance();        ipInfoPresenter = new IpInfoPresenter(ipInfoFragment, ipInfoTask);        ipInfoFragment.setPresenter(ipInfoPresenter);    }}

最后是ActivityUtils工具类的代码。

public class ActivityUtils {    public static voID addFragmentToActivity(FragmentManager fragmentManager, Fragment fragment, int frameID) {        FragmentTransaction transaction = fragmentManager.beginTransaction();        transaction.add(frameID, fragment);        transaction.commit();    }}

MVP的实现方式很多,这个例子只是一种最基础的方式。

2.3、MVP结合RxJava和Dagger2

2.3.1、MVP结合RxJava

还是RxJava结合Retrofit中的例子,首先配置Gradle

    compileOptions {        sourceCompatibility JavaVersion.VERSION_1_8        targetCompatibility JavaVersion.VERSION_1_8    }
    implementation 'io.reactivex.rxjava3:rxjava:3.0.0'    implementation 'io.reactivex.rxjava3:rxandroID:3.0.0'    implementation 'com.squareup.retrofit2:retrofit:2.9.0'    implementation 'com.squareup.retrofit2:converter-gson:2.6.0'    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

定义访问网络的接口。

public interface IpService {    @FormUrlEncoded    @POST("outGetIpInfo")    Observable<BaseBean<IpInfoBean>> getIpMsg(@FIEld("ip") String ip,                                              @FIEld("accessKey") String accessKey);}

修改NetTask接口。

public interface NetTask<T> {    disposable execute(T data, LoadTaskCallBack loadTaskCallBack);}

修改NetTask接口的实现类IpInfoTask。主要是用RxJava结合Retrofit替代了OkhttpFinal访问网络。

public class IpInfoTask implements NetTask<String> {    private static IpInfoTask INSTANCE = null;    private static final String HOST = "http://ip.taobao.com/";    private Retrofit retrofit;    private disposable disposable;    private IpInfoTask() {        createRetrofit();    }    private voID createRetrofit() {        retrofit = new Retrofit.Builder()                .baseUrl(HOST)                .addConverterFactory(GsonConverterFactory.create())                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())                .build();    }    public static IpInfoTask getInstance() {        if (INSTANCE == null) {            INSTANCE = new IpInfoTask();        }        return INSTANCE;    }    @OverrIDe    public disposable execute(String data, final LoadTaskCallBack loadTaskCallBack) {        IpService ipService = retrofit.create(IpService.class);        ipService.getIpMsg(data, "alibaba-inc")                .subscribeOn(Schedulers.io())                .observeOn(AndroIDSchedulers.mainThread())                .subscribe(new Observer<BaseBean<IpInfoBean>>() {                    @OverrIDe                    public voID onSubscribe(@NonNull disposable d) {                        loadTaskCallBack.onStart();                        disposable = d;                    }                    @OverrIDe                    public voID onNext(@NonNull BaseBean<IpInfoBean> ipInfoBeanBaseBean) {                        loadTaskCallBack.onSuccess(ipInfoBeanBaseBean);                    }                    @OverrIDe                    public voID one rror(@NonNull Throwable e) {                        loadTaskCallBack.onFailed();                    }                    @OverrIDe                    public voID onComplete() {                        loadTaskCallBack.onFinish();                    }                });        return disposable;    }}

为了能取消网络请求,我们先来定义BasePresenter接口。

public interface BasePresenter {    voID subscribe();    voID unsubscribe();}

修改IPresenter接口,使它继承BasePresenter接口。

public interface IpInfoContract {    interface IPresenter extends BasePresenter {        voID getIpInfo(String ip);//获取数据方法    }    ...}

改写IpInfoPresenter类。通过返回的disposable对象,并将disposable对象加入到Compositedisposable对象中实现取消网络请求。

public class IpInfoPresenter implements IpInfoContract.IPresenter, LoadTaskCallBack<BaseBean> {    private IpInfoContract.IVIEw vIEw; //注入VIEw接口    private NetTask netTask; //网络加载接口    private disposable disposable;    private Compositedisposable compositedisposable;    public IpInfoPresenter(IpInfoContract.IVIEw addTaskVIEw, NetTask netTask) {        this.vIEw = addTaskVIEw;        this.netTask = netTask;        compositedisposable = new Compositedisposable();    }    @OverrIDe    public voID onStart() {        if (vIEw.isActive()) {            vIEw.showLoading();        }    }    @OverrIDe    public voID onSuccess(BaseBean ipInfo) {        if (vIEw.isActive()) {            vIEw.setIpInfo(ipInfo);        }    }    @OverrIDe    public voID onFailed() {        if (vIEw.isActive()) {            vIEw.shoError();            vIEw.hIDeLoading();        }    }    @OverrIDe    public voID onFinish() {        if (vIEw.isActive()) {            vIEw.hIDeLoading();        }    }    @OverrIDe    public voID getIpInfo(String ip) {        disposable = netTask.execute(ip, this);        subscribe();    }    @OverrIDe    public voID subscribe() {        if (disposable != null) {            compositedisposable.add(disposable);        }    }    @OverrIDe    public voID unsubscribe() {        if (compositedisposable != null && compositedisposable.isdisposed()) {            compositedisposable.clear();        }    }}

最后改写IpInfoFragment,加入如下代码来取消网络请求。

    @OverrIDe    public voID onPause() {        super.onPause();        mPresenter.unsubscribe();    }

2.3.2、MVP结合Dagger2

Dagger2参考地址

配置build.gradle

    implementation 'com.Google.dagger:dagger:2.26'    annotationProcessor 'com.Google.dagger:dagger-compiler:2.26'

一、NetTask相关注入

NetTaskModule相当于一个工厂类,用来提供对象,这里去掉了IpInfoTask的单例模式,因为用Dagger2可以实现IpInfoTask的全局单例。

@Modulepublic class NetTaskModule {    @Singleton    @ProvIDes    NetTask provIDeIpInfoTask() {        return new IpInfoTask();    }}
@Singleton@Component(modules = NetTaskModule.class)public interface NetTaskComponent {    NetTask getNetTask();}

改写MvpApplication。可以看到NetTaskComponent 是一个全局单例,因此IpInfoTask也是一个全局单例。

public class MVPApplication extends Application {    private NetTaskComponent netTaskComponent;    @OverrIDe    public voID onCreate() {        super.onCreate();        netTaskComponent = DaggerNetTaskComponent.builder().netTaskModule(new NetTaskModule()).build();    }    public NetTaskComponent getTasksRepositoryComponent() {        return netTaskComponent;    }}

二、IpInfoPresenter相关注入

为了更方便管理Component,创建FragmentScoped。

@documented@Scope@Retention(RetentionPolicy.RUNTIME)public @interface FragmentScoped {}

IpInfoPresenter的构造方法需要一个NetTask和一个IpInfoContract.VIEw。作为参数也需要相关的注入逻辑,NetTask的注入逻辑前面已经处理了,接下来需要IpInfoContract.VIEw的相关注入逻辑。

首先创建IpInfoModule,注意这里的IpInfoContract.IVIEw是通过IpInfoModule 的构造方法传进来的。

@Modulepublic class IpInfoModule {    private IpInfoContract.IVIEw mVIEw;    public IpInfoModule(IpInfoContract.IVIEw vIEw) {        this.mVIEw = vIEw;    }    @ProvIDes    IpInfoContract.IVIEw provIDeIpInfoContract() {        return mVIEw;    }}

再创建IpInfoComponent。

@FragmentScoped@Component(modules = IpInfoModule.class, dependencIEs = NetTaskComponent.class)public interface IpInfoComponent {    voID inject(IpInfoActivity ipInfoActivity);}

改写IpInfoPresenter。它的构造方法用@Inject标记,意味着Dagger2可以使用IpInfoPresenter构造方法来构建IpInfoPresenter。又创建了一个setPresenter方法,并用@Inject标记,这叫方法注入。这个方法会紧接着IpInfoPresenter的构造方法调用后调用。

public class IpInfoPresenter implements IpInfoContract.IPresenter, LoadTaskCallBack<BaseBean> {    private IpInfoContract.IVIEw vIEw; //注入VIEw接口    private NetTask netTask; //网络加载接口    private disposable disposable;    private Compositedisposable compositedisposable;    @Inject    public IpInfoPresenter(IpInfoContract.IVIEw addTaskVIEw, NetTask netTask) {        this.vIEw = addTaskVIEw;        this.netTask = netTask;        compositedisposable = new Compositedisposable();    }    @Inject    voID setPresenter() {        vIEw.setPresenter(this);    }    ...}

最后在IpInfoActivity中完成注入 *** 作。

public class IpInfoActivity extends AppCompatActivity {    @Inject    IpInfoPresenter ipInfoPresenter;    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_ipinfo);        IpInfoFragment ipInfoFragment = (IpInfoFragment) getSupportFragmentManager().findFragmentByID(R.ID.contentFrame);        if (ipInfoFragment == null) {            ipInfoFragment = IpInfoFragment.newInstance();            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), ipInfoFragment, R.ID.contentFrame);        }//        IpInfoTask ipInfoTask = IpInfoTask.getInstance();//        ipInfoPresenter = new IpInfoPresenter(ipInfoFragment, ipInfoTask);//        ipInfoFragment.setPresenter(ipInfoPresenter);        DaggerIpInfoComponent.builder()                .ipInfoModule(new IpInfoModule(ipInfoFragment))                .netTaskComponent(((MVPApplication) getApplication()).getTasksRepositoryComponent())                .build()                .inject(this);    }}
三、MVVM(Model-VIEw-viewmodel)

将Presenter改为viewmodel,其和MVP类似,不同的是viewmodel跟Model和VIEw进行双向绑定:当VIEw发生改变时,viewmodel通知Model进行数据更新;同理Model数据更新后,viewmodel通知VIEw更新。

2.1、解析Data Binding

在Data Binding之前,我们不可避免地要编写大量诸如findVIEwByID()、setText()和setonClickListener()等代码。通过DataBinding,可以省略大量类似的模板代码。

使用Data Binding只需要配置build.gradle如下。

androID {    ...    dataBinding {        enabled = true    }}

2.1.1、基础使用

public class IpInfoBean {    private String country;    private String city;    public String getCountry() {        return country;    }    public voID setCountry(String country) {        this.country = country;    }    public String getCity() {        return city;    }    public voID setCity(String city) {        this.city = city;    }}
<layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="ipInfo"            type="com.liangyao.mvvmdemo.IpInfoBean" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:ID="@+ID/tv_country"            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{ipInfo.country}" />        <TextVIEw            androID:ID="@+ID/tv_city"            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="30dp"            androID:text="@{ipInfo.city}" />    </linearLayout></layout>

在Activity中,添加布局文件就不再用setContentVIEw()了,而是用DataBinding生成的辅助类来代替。

public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBindingImpl binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean infoBean = new IpInfoBean();        infoBean.setCountry("中国");        infoBean.setCity("重庆");        binding.setIpInfo(infoBean);    }}

运行效果如下。

2.2.2、事件处理

方法一

        <button            androID:ID="@+ID/btn_next"            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="下一步" />
        binding.btnNext.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                Toast.makeText(MainActivity.this, "下一步", Toast.LENGTH_SHORT).show();            }        });

方法二

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="OnClickListener"            type="androID.vIEw.VIEw.OnClickListener" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <button            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:onClick="@{OnClickListener}"            androID:text="下一步" />    </linearLayout></layout>
        binding.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                Toast.makeText(MainActivity.this, "下一步", Toast.LENGTH_SHORT).show();            }        });    }

2.2.3、布局属性

1、import用法与别名。data节点支持import用法,相当于导入包名。跟Java的import不同,这里具体到了类名。如果引用了两个相同的类名,就可以用别名来区分。

    <data>        <import type="com.liangyao.mvvmdemo.IpInfoBean" />        <variable            name="ipInfo"            type="IpInfoBean" />    </data>
    <data>        <import type="com.liangyao.mvvmdemo.IpInfoBean" />        <import            alias="IpInfoBean2"            type="com.liangyao.mvvmdemo.model.IpInfoBean" />        <variable            name="ipInfo"            type="IpInfoBean" />        <variable            name="ipInfo2"            type="IpInfoBean2" />    </data>

2、变量定义。利用DataBinding也可以在XML文件中定义一些基本数据类型和String类型。

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="name"            type="String" />        <variable            name="age"            type="int" />        <variable            name="man"            type="boolean" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{name}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{String.valueOf(age)}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{String.valueOf(man)}" />    </linearLayout></layout>

java.lang.*包中的类会被自动导入,因此无需使用import。在Activity中使用,如下所示。

        binding.setname("张三");        binding.setAge(22);        binding.setMan(true);

除了可以定义基本类型的变量外,我们还可以定义List、Map、String[]等这样的集合变量。对于<需要用转义字符&lt;代替,对于>可以使用&gt;转义字符代替,也可以不替换。

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <import type="java.util.ArrayList" />        <import type="java.util.Map" />        <variable            name="List"            type="ArrayList&lt;String>" />        <variable            name="map"            type="Map&lt;String,String&gt;" />        <variable            name="arrays"            type="String[]" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{List.get(1)}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{map.get(`name`)}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{arrays[1]}" />    </linearLayout></layout>
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBindingImpl binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        ArrayList<String> List = new ArrayList<>();        List.add("张三");        List.add("李四");        binding.setList(List);        Map<String, String> map = new HashMap<>();        map.put("name", "张三");        binding.setMap(map);        String[] arrays = {"张三", "李四"};        binding.setArrays(arrays);    }}

3、自定义Binding类名。

默认情况下,Binding辅助类的名称取决于布局文件的命名。我们可以通过修改布局文件的data节点中的class属性,来改变Binding辅助类的命名与生成的位置。

    <data >    ...    </data>

也可以改变生成位置,只需要指定包名,如下。

    <data >    ...    </data>
    <data >    ...    </data>

4、静态方法调用

public class Utils {    public static String getCity(IpInfoBean bean) {        return bean.getCity();    }}
<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <import type="com.liangyao.mvvmdemo.Utils" />        <variable            name="ipInfoBean"            type="com.liangyao.mvvmdemo.IpInfoBean" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{Utils.getCity(ipInfoBean)}" />    </linearLayout></layout>
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean ipInfoBean = new IpInfoBean();        ipInfoBean.setCountry("中国");        ipInfoBean.setCity("重庆");        binding.setIpInfoBean(ipInfoBean);    }}

5、支持表达式

XML中的表达式与Java表达式有很多相似之处,下面是二者的相同之处。

数学表达式:+ - * / %字符串拼接:+ -逻辑表达式:& & ||位 *** 作符:& || ^一元 *** 作符:+ - ! ~位移 *** 作符:>> >>> <<比较 *** 作符:== > < >= <=instanceof分组 *** 作符:()字面量character,String,numeric,null强转、方法调用字段访问数组访问:[]三元 *** 作符:?:

下面举个简单的例子:

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <import type="androID.vIEw.VIEw" />        <variable            name="ipInfoBean"            type="com.liangyao.mvvmdemo.IpInfoBean" />        <variable            name="show"            type="boolean" />    </data>    <linearLayout        androID:ID="@+ID/activity_main"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:layout_margintop="50dp"            androID:text="@{`城市:`+ipInfoBean.city}"            androID:visibility="@{show?VIEw.VISIBLE:VIEw.GONE}" />    </linearLayout></layout>
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean ipInfoBean = new IpInfoBean();        ipInfoBean.setCountry("中国");        ipInfoBean.setCity("重庆");        binding.setIpInfoBean(ipInfoBean);        binding.setShow(true);    }}

6、Converter

Converter 指的是转换器,其把数据格式转为需要的格式。假设TextVIEw需要显示一个String类型的数据,但是你只有一个Date类型的数据,一般解决方法就是定义一个静态方法来做格式转换,在这里我们用Data Binding提供的Converter。

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="time"            type="java.util.Date" />    </data>    <linearLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{time}" />    </linearLayout></layout>

TextVIEw的text需要的是String类型的值,这里给了一个Date类型的值,因此,我们需要写一个Converter来进行类型转换。convertDate方法在哪个类中并不重要,重要的是@BindingConverter注解。convertDate方法会将Date类型转换为String类型的日期并返回。

public class Utils {    @BindingConversion    public static String convertDate(Date date) {        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");        return sdf.format(date);    }}

最后在Activity中使用。

public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        binding.setTime(new Date());    }}

2.2.4、动态更新和双向绑定

在此前的例子中,如果Model实体类的内容发生变化,那么界面UI是不会动态更新的。Data Binding提供了3种动态更新机制,根据Model实体类的内容来更新UI,分别对应于类(Observable)、字段(ObservableFIEld)和集合类型(Observable容器类)。从MVVM模式的角度来讲,通过动态更新机制,当Model发生变化时,就会通知viewmodel对VIEw进行动态更新。结合动态更新机制,我们还可以实现双向绑定:当VIEw发生变化时,viewmodel也会通知Model进行数据更新。

1、使用Observable:通过继承BaSEObservable来实现动态更新。

package com.mryuan.learndemo;import androIDx.databinding.BaSEObservable;import androIDx.databinding.Bindable;public class IpInfoBean extends BaSEObservable {    private String country;    private String city;    @Bindable    public String getCountry() {        return country;    }    public voID setCountry(String country) {        this.country = country;        notifyPropertyChanged(BR.country);    }    @Bindable    public String getCity() {        return city;    }    public voID setCity(String city) {        this.city = city;        notifyPropertyChanged(BR.city);    }}
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean bean = new IpInfoBean();        bean.setCountry("中国");        bean.setCity("重庆");        binding.setIpInfo(bean);        binding.btnUpdate.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                bean.setCity("上海");            }        });    }}

2、使用ObservableFIEld:除了继承BaSEObservable来实现动态更新外,还可以使用系统为我们提供的所有基本数据类型对应的Observable类,比如ObservableInt、Observablefloat、ObservableBoolean等,也可以使用引用数据类型和基本数据类型通用的ObservableFIEld。下面以Observablefiled为例。

public class IpInfoBean {    private ObservableFIEld<String> country = new ObservableFIEld<>();    private ObservableFIEld<String> city = new ObservableFIEld<>();    public ObservableFIEld<String> getCountry() {        return country;    }    public voID setCountry(String country) {        this.country.set(country);    }    public ObservableFIEld<String> getCity() {        return city;    }    public voID setCity(String city) {        this.city.set(city);    }}
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean bean = new IpInfoBean();        bean.setCountry("中国");        bean.setCity("重庆");        binding.setIpInfo(bean);        binding.btnUpdate.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                bean.setCity("上海");            }        });    }}

3、使用Observable容器类:当有多个IpInfoBean类型的数据需要动态更新时,我们可以使用Observable容器类ObservableArrayList。Observable容器类包括ObservableArrayList和ObservableArrayMap。

public class IpInfoBean {    private String country;    private String city;    public String getCountry() {        return country;    }    public voID setCountry(String country) {        this.country = country;    }    public String getCity() {        return city;    }    public voID setCity(String city) {        this.city = city;    }}
<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <import type="androIDx.databinding.ObservableList" />        <import type="com.mryuan.learndemo.IpInfoBean" />        <variable            name="List"            type="ObservableList&lt;IpInfoBean>" />    </data>    <linearLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{List.get(0).city}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{List.get(1).city}" />        <button            androID:ID="@+ID/btn_update"            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="改变城市" />    </linearLayout></layout>
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        ObservableList<IpInfoBean> List = new ObservableArrayList<>();        IpInfoBean bean = new IpInfoBean();        bean.setCity("重庆");        List.add(bean);        IpInfoBean bean2 = new IpInfoBean();        bean2.setCity("重庆2");        List.add(bean2);        binding.setList(List);        binding.btnUpdate.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                bean.setCity("上海");                bean2.setCity("上海2");                List.add(bean);            }        });    }}

4、双向绑定:从MVVM模式的角度来讲,双向绑定就是Model和VIEw通过viewmodel进行双向动态更新。前面讲到了Model实体类发生变化,UI会动态更新;接下来将通过UI变化来让实体类动态更新,这需要结合动态更新机制。

在布局文件中定义EditText来改变IpInfoBean的city字段,关键就是将@{ipInfoBean.city}改为@={ipInfoBean.city}。定义TextVIEw来动态显示IpInfoBean的city字段的变化。

public class IpInfoBean extends BaSEObservable {    private String country;    private String city;    @Bindable    public String getCountry() {        return country;    }    public voID setCountry(String country) {        this.country = country;        notifyPropertyChanged(BR.country);    }    @Bindable    public String getCity() {        return city;    }    public voID setCity(String city) {        this.city = city;        notifyPropertyChanged(BR.city);    }}
<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="ipInfoBean"            type="com.mryuan.learndemo.IpInfoBean" />    </data>    <linearLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{ipInfoBean.city}" />        <EditText            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@={ipInfoBean.city}" />        <button            androID:ID="@+ID/btn_update"            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="改变城市" />    </linearLayout></layout>
public class MainActivity extends AppCompatActivity {    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        IpInfoBean bean = new IpInfoBean();        bean.setCity("重庆");        binding.setIpInfoBean(bean);        binding.btnUpdate.setonClickListener(new VIEw.OnClickListener() {            @OverrIDe            public voID onClick(VIEw v) {                bean.setCity("上海");            }        });    }}

2.2.5、结合RecyclerVIEw

Data Binding除了在Activity中使用外,还可以在RecyclerVIEw中使用。首先是Activity布局文件,RecyclerVIEw控件定义了ID,这样就可以在Activity中用Data Binding来使用RecyclerVIEw控件了。

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <linearLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:orIEntation="vertical">        <androIDx.recyclervIEw.Widget.RecyclerVIEw            androID:ID="@+ID/recyclerVIEw"            androID:layout_wIDth="match_parent"            androID:layout_height="wrap_content" />    </linearLayout></layout>

接着定义item的布局(item_ip_info.xml)。

<?xml version="1.0" enCoding="utf-8"?><layout xmlns:androID="http://schemas.androID.com/apk/res/androID">    <data>        <variable            name="ipInfoBean"            type="com.mryuan.learndemo.IpInfoBean" />    </data>    <linearLayout        androID:layout_wIDth="match_parent"        androID:layout_height="wrap_content"        androID:gravity="center_horizontal"        androID:orIEntation="vertical">        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{ipInfoBean.country}" />        <TextVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            androID:text="@{ipInfoBean.city}" />    </linearLayout></layout>

接下来是Adapter代码。与常规RecyclerVIEw的Adapter不同,在注释1处我们没有将VIEw传入IpInfoVIEwHolder中,而是将ItemIpInfoBinding 传了进去。在IpInfoVIEwHolder 中并没有通过findVIEwByID来找到相应的控件,而是提供了返回ItemIpInfoBinding的getBinding方法,在注释2处将相应position的IpInfoBean赋值给绑定的布局文件。

public class IpInfoAdapter extends RecyclerVIEw.Adapter<IpInfoAdapter.IpInfoVIEwHolder> {    private List<IpInfoBean> mData;    public IpInfoAdapter(List<IpInfoBean> mData) {        this.mData = mData;    }    @NonNull    @OverrIDe    public IpInfoVIEwHolder onCreateVIEwHolder(@NonNull VIEwGroup parent, int vIEwType) {        ItemIpInfoBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_ip_info, parent, false);        return new IpInfoVIEwHolder(binding);//注释1    }    @OverrIDe    public voID onBindVIEwHolder(@NonNull IpInfoVIEwHolder holder, int position) {        holder.getBinding().setIpInfoBean(mData.get(position));    }    @OverrIDe    public int getItemCount() {        return mData.size();    }    public class IpInfoVIEwHolder extends RecyclerVIEw.VIEwHolder {        private ItemIpInfoBinding binding;        public IpInfoVIEwHolder(@NonNull ItemIpInfoBinding binding) {            super(binding.getRoot());            this.binding = binding;        }        public ItemIpInfoBinding getBinding() {            return binding;        }    }}

最后在Activity中使用。

public class MainActivity extends AppCompatActivity {    private RecyclerVIEw mRecyclerVIEw;    private IpInfoAdapter mAdapter;    @OverrIDe    protected voID onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentVIEw(this, R.layout.activity_main);        mRecyclerVIEw = binding.recyclerVIEw;        initRecyclerVIEw();    }    private voID initRecyclerVIEw() {        linearlayoutmanager manager = new linearlayoutmanager(this);        mRecyclerVIEw.setLayoutManager(manager);        mAdapter = new IpInfoAdapter(getData());        mRecyclerVIEw.setAdapter(mAdapter);    }    private List<IpInfoBean> getData() {        List<IpInfoBean> data = new ArrayList<>();        for (int i = 0; i < 5; i++) {            IpInfoBean bean = new IpInfoBean();            bean.setCountry("中国" + i);            bean.setCity("重庆" + i);            data.add(bean);        }        return data;    }}
总结

以上是内存溢出为你收集整理的Android 架构设计全部内容,希望文章能够帮你解决Android 架构设计所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1028380.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存