博客一、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和Dagger22.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更新。
在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[]等这样的集合变量。对于<需要用转义字符<代替,对于>可以使用>转义字符代替,也可以不替换。
<?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<String>" /> <variable name="map" type="Map<String,String>" /> <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<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 架构设计所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)