基于Android Architecture Blueprints的MVP案例分析与实现
株洲新程IT教育 李赞红
1 概述
Android Architecture Blueprints是Google官方版本的MVP实现,我们通过https://github.com/googlesamples/android-architecture/tree/todo-mvp可以阅读具体源码。AAB用于TODO计划任务的管理,主要有TODO列表、添加新的TODO、查看TODO详情与TODO统计等功能,总体来说,该案例具有较强的参考价值,特别是在数据存储方面提供了三种方式:远程数据存储、本地数据存储和缓存存储,并使用了标准的MVP设计模式,如图1所示。
图1:AndroidArchitecture Blueprints 的MVP实现
本文将结合AndroidArchitecture Blueprints实现一个业务更简单的案例,一方面帮助大家理解MVP的设计思路与实现过程,另一方面把常见的查询、添加和删除操作作为参考实现,方便大家理解。
2 MVP
MVP从MVC架构模式演化而来, MVC分别代表模型、视图和控制器,在Android中,定义Class类作为模型,Layout XML表示视图,而Activity用作控制器,这样一来,在Activity中充斥了大量代码,无论是从扩展性和重用性都无法达到理想的效果。个人以为,MVC分层在Android App开发中显得极为牵强。而MVP解决了这个问题。
MVP即为Model、View和Presenter。Model表示模型,实现数据存储与业务逻辑;View表示视图,提供用户交互的接口;Presenter表示主导器,相当于MVC中的Controller但比Controller更灵活。MVP的关系如图2所示。
图2:MVP关系
从上图可以看出:
A) View将功能委托给Presenter完成,Presenter调用Model完成业务功能与数据存储,并再次通过View更新UI;
B) View和Model没有直接关联,无法相互调用;
C) Presenter和View可以相互调用;
D) Presenter调用Model完成业务功能。
根据Google官方说明,Fragment作为View层的实现组件,包含Presenter的引用;Model就是一个普通的类,使用了单例模式获取对象;Presenter虽然也是一个普通的类,但拥有View和Model的引用,是一个八面玲珑的家伙。
使用Fragment而不是Activity作为View,主要有两个原因:
A)通过Activity和Fragment分离更适合对于MVP架构的实现,Activity将作为全局的控制者将Presenter于View联系在一起;
B)采用Fragment更有利于平板电脑的布局或者是多视图屏幕。
我们为View和Presenter定义了最基本的实现,View的基本实现为BaseView接口,Presenter的基本实现为BasePresenter接口。
public interface BaseView<T> {
void setPresenter(T presenter);
}
public interface BasePresenter {
void start();
}
BasePresenter接口中定义了start()方法,主要用于完成一些初始化的工作,BaseView接口定义的setPresenter()方法则用于建立与Presenter的关系。
MVP的优点归纳如下:
A) 各个层次之间的职责更加单一清晰;
B) 很大程度上降低了代码的耦合度;
C) 复用性大大提高;
D) 面向接口编程,定义与实现分离,方便测试与维护;
E) 代码更简洁。
有优点自然有缺点:
A) 类变得更多了;
B) 组件与组件之间的关系很复杂。
3 面向接口编程
案例AndroidArchitecture Blueprints严格遵循面向接口编程,同时,数据存储模块提供了面向接口编程的最佳实践,当我们怀疑面向接口编程的必要性时,Google通过现实案例马上打消了我们的这种想法。面向接口编程是一门优雅而美好的代码艺术,功能之间通过轻量级的接口联系在一起,摆脱了低级趣味的依赖关系。通过这种极度抽象的上层建筑来维护组件之间的关联,大大增强了代码的艺术性、维护性与重用性。
所以,View要定义接口,Presenter要定义接口,Model也要定义接口。
每一个界面都涉及到五个组件:View、Presenter、Contract、Model和Activity。其中,View定义了与UI相关的操作,比如提示信息的显示与隐藏、状态的改变、数据的显示、新Activity的打开等等,Fragment将实现View接口;Presenter是功能实现的粘合剂,View的功能委托给Presenter,Presenter调用Model完成业务功能,最后Presenter再次调用View来更新UI;Contract将View与Presenter两个接口进行集中管理;Model实现业务功能与数据存储;Activity是界面显示的起点,负责创建其他各个组件,并依次维护各组件之间的关系。
在Android ArchitectureBlueprints案例中,是以界面功能为单位对package进行定义的:
图3:package划分
如图3所示,添加和修改TODO定义在addedittask包中,TODO详情定义在taskdetail包中,TODO列表显示定义在tasks包中,TODO数据统计定义在statistics包中。而data包定义了Model有关的类,util包主要是一些工具类。
现有一个接受客户意见和反馈的建议收集模块,主要有我的建议列表、提建议和删除我的建议等功能,我们以提建议为例通过面向接口编程的思想来定义主要的功能接口。首先,我们定义一个Model层的接口AdviceDataSource,该接口定义如下:
public interfaceAdviceDataSource {
public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull AddedAdviceCallBack callBack);
public interface AddedAdviceCallBack{
void onAdviceAdded();
}
}
方法addAdvice()用于添加一条新建议,参数content表示建议的内容,uid表示用户id。考虑到所有请求都是基于网络的异步请求,方法最后的参数指定了一个回调接口,用于响应请求结束后的后续动作(比如添加成功后要关闭当前Activity)。
接下来,定义AddAdviceContract接口,该接口集中管理View和Presenter两个接口:
public interfaceAddAdviceContract {
public interface Presenter extendsBasePresenter{
public void addAdvice(String content);
}
public interface View extendsBaseView<Presenter>{
public void showAdvicesList();
}
}
代码中AddAdviceContract .Presenter接口继承自BasePresenter接口,定义了addAdvice()方法,该方法将在Fragment中调用,接收来自Fragment中的建议内容,并传递给AdviceDataSource.addAdvice()方法进行处理(保存到远程服务器)。AddAdviceContract .View接口是BaseView的派生接口,定义了showAdvicesList()方法,该方法用于添加完建议后,关闭当前Activity以便重新加载并显示建议列表。
现在我们来理顺提建议这个功能的调用流程:在实现了View接口的Fragment中,调用AddAdviceContract .Presenter的addAdvice()方法提交新的建议,该方法又调用AdviceDataSource. addAdvice()方法将建议内容保存到远程服务器,确定保存成功后,在AddAdviceContract .Presenter的addAdvice()方法中调用AddAdviceContract.View的showAdvicesList()方法关闭当前窗口以显示新的建议列表。
总体来说,在View中所有的功能都要委托给Presenter,Presenter调用Model完成任务后再次通过View更新结果,如图4所示。
图4:各组件之间的调用顺序
4 功能实现
4.1 数据接口
本案例中使用了株洲新程IT教育提供的数据接口,在这里,我们统一使用id为5的用户作为测试,数据接口的url如下(不保证以后能用):
A)获取用户的建议列表
http://tr.api.gson.cn/system/feedback/{用户id}
请求类型:get
B)提交建议
http://tr.api.gson.cn/system/feedback/{用户id}
请求类型:post
参数:content 建议内容
C)删除指定建议
http://tr.api.gson.cn/system/feedback/delete/{用户id}/{建议id}
请求类型:get
4.2 第三方jar库
为了简化访问HTTP请求,使用了xUtils3.x第三方库,该工具主要集成了view、bitmap、db、http和task五大模块,简单易用,值得推荐。数据接口返回的是json格式的数据,阿里的fastjson具有强大的JSON数据解析功能,牛逼得不要不要的。
4.3 初始化xUtils
这个功能与MVP无关,但为了保持案例的完整性,顺便提一下。定义MvpApplication类如下:
public classMvpApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
x.Ext.init(this);
x.Ext.setDebug(true);
}
}
在AndroidManifest.xml配置文件中,配置自定义的Application。
<?xmlversion="1.0" encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mvp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MvpApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activityandroid:name=".advices.AdviceActivity" android:label="建议">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".addadvice.AddAdviceActivity" android:label="提建议">
<intent-filter>
<actionandroid:name="com.example.mvp.addadvice.AddAdviceActivity"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
注意,访问网络数据必须授予android.permission.INTERNET权限。
4.4 定义Model
我们定义了一个名为Advice的JavaBean,用于保存建议的基本属性,并生成了一系列的getter方法和setter方法。
@HttpResponse(parser= AdviceDataSource.AdviceJSONResponseParser.class)
public classAdvice {
private int id; //建议id
private int uid; //用户id
private String content; //建议内容
private String reply; //回复内容
private Date createTime;//创建时间
private Date replydate;//回复时间
private int replyUid;//回复用户id
public Advice() {
}
public Advice(int uid, String content, DatecreateTime) {
this.uid = uid;
this.content = content;
this.createTime = createTime;
}
public Advice(int id, int uid, Stringcontent, String reply, Date createTime, Date replydate, int replyUid) {
this.id = id;
this.uid = uid;
this.content = content;
this.reply = reply;
this.createTime = createTime;
this.replydate = replydate;
this.replyUid = replyUid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getReply() {
return reply;
}
public void setReply(String reply) {
this.reply = reply;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime){
this.createTime = createTime;
}
public Date getReplydate() {
return replydate;
}
public void setReplydate(Date replydate) {
this.replydate = replydate;
}
public int getReplyUid() {
return replyUid;
}
public void setReplyUid(int replyUid) {
this.replyUid = replyUid;
}
}
在Class中定义了@HttpResponse(parser= AdviceDataSource.AdviceJSONResponse
Parser.class)元注释,这是xUtils提供的功能,指定了AdviceJSONResponseParser解析器,用于将响应结果中的json数据解析为Advice对象。AdviceJSONResponseParser定义在AdviceDataSource接口中,AdviceDataSource接口还定义了建议列表、提建议和删除建议的功能方法,定义如下:
public interfaceAdviceDataSource {
public class AdviceJSONResponseParserimplements ResponseParser{
@Override
public void checkResponse(UriRequesturiRequest) throws Throwable {
}
@Override
public Object parse(Type type,Class<?> aClass, String s) throws Throwable {
JSONObject json =JSON.parseObject(s);
if(aClass == List.class) {
if(json.getBoolean("success")) {
JSONArray data =json.getJSONArray("data");
List<Advice> list =new ArrayList<>();
for (int i = 0; i <data.size(); i++) {
JSONObject current =data.getJSONObject(i);
Advice advice = newAdvice(current.getIntValue("id"),
current.getIntValue("uid"),
current.getString("content"),
current.getString("reply"),
current.getDate("create_time"),
current.getDate("reply_date"),
current.getIntValue("reply_uid"));
list.add(advice);
}
return list;
}
}else{
return s;
}
return null;
}
}
public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull AddedAdviceCallBack callBack);
public void getMyAdvices(@NonNull int uid,@NonNull LoadAdviceCallBack callBack);
public void deleteAdvice(@NonNull int id,@NonNull int uid, @NonNullDeletedAdviceCallBack callBack);
public interface LoadAdviceCallBack{
void onAdviceLoaded(List<Advice>advices);
}
public interface DeletedAdviceCallBack{
void onAdviceDeleted();
}
public interface AddedAdviceCallBack{
void onAdviceAdded();
}
}
AdviceRepository类实现了AdviceDataSource接口,使用单例模式获取AdviceRepository对象。通过xUtils提供的http模块非常简单方便地实现了数据读写的功能。
public classAdviceRepository implements AdviceDataSource{
private static AdviceRepositorysAdviceRepository;
private AdviceRepository() {
}
public static AdviceRepository getInstance(){
if(sAdviceRepository == null){
sAdviceRepository = newAdviceRepository();
}
return sAdviceRepository;
}
@Override
public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull final AddedAdviceCallBack callBack) {
String url ="http://tr.api.gson.cn/system/feedback/" + uid;
RequestParams params = newRequestParams(url);
params.addBodyParameter("content", content);
x.http().post(params, newCommonCallBackAdapter<String>() {
@Override
public void onSuccess(String s) {
callBack.onAdviceAdded();
}
});
}
@Override
public void getMyAdvices(@NonNull int uid,@NonNull final LoadAdviceCallBack callBack) {
String url ="http://tr.api.gson.cn/system/feedback/" + uid;
RequestParams params = newRequestParams(url);
x.http().get(params, newCommonCallBackAdapter<List<Advice>>() {
@Override
public voidonSuccess(List<Advice> advices) {
callBack.onAdviceLoaded(advices);
}
});
}
@Override
public void deleteAdvice(@NonNull int id,@NonNull int uid, @NonNull finalDeletedAdviceCallBack callBack) {
String url ="http://tr.api.gson.cn/system/feedback/delete/" + uid + "/"+ id;
LogUtil.d(url);
RequestParams params = newRequestParams(url);
x.http().get(params, newCommonCallBackAdapter<String>() {
@Override
public void onSuccess(String s) {
callBack.onAdviceDeleted();
}
});
}
}
因为所有的http请求都是异步请求,所以每个方法必须通过接口回调才能在请求结束后处理后续事宜。
CommonCallBackAdapter抽象类是CommonCallBack接口的适配器,后者定义了多达4个方法,但常用的只有1个,CommonCallBackAdapter类提供了另外3个不常用方法的默认实现,这样代码就简单多了。
public abstractclass CommonCallBackAdapter<ReturnType>
implementsCallback.CommonCallback<ReturnType> {
@Override
public void onError(Throwable throwable,boolean b) {
}
@Override
public void onCancelled(CancelledExceptione) {
}
@Override
public void onFinished() {
}
}
4.5 我的建议列表功能实现
“我的建议列表”UI中包含了下面几个功能:
A) 显示我的建议;
B) 菜单:提建议,将打开新的Activity;
C) 菜单:刷新建议列表。后台如果回复了用户的建议,通过刷新可以马上看到回复内容。
运行效果如图5所示。
图5:我的建议列表
本UI涉及到三个布局文件:item_advices.xml、fragment_advices.xml和activity_advices.xml。其中item_advices.xml是ListView列表项的布局,fragment_advices.xml是Fragment的布局文件,包含了一个ListView组件,activity_advices.xml是Activity的布局文件,包含了一个FrameLayout(帧布局),Fragment对象将添加到FrameLayout布局中。
item_advices.xml:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:padding="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/tv_date"
android:text="02/06"
android:gravity="center"
android:textSize="30sp"
/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="建议"
android:id="@+id/tv_content"
android:textSize="16dp"
/>
<TextView
android:layout_marginTop="10dp"
android:textColor="@android:color/holo_blue_dark"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/tv_reply"
android:text="回复"
/>
</LinearLayout>
</LinearLayout>
fragment_advices.xml:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv_advices"></ListView>
</LinearLayout>
activity_advices.xml:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frame_content"
></FrameLayout>
</LinearLayout>
在advices包中,定义了“我的建议列表”所需要的View、Presenter和Activity,因为Model已经统一在AdviceRepository类中实现,所以不需要重复定义Model。一般来说,一个小模块定义一个对应的Model类,比如,用户模块可以将注册、登陆、修改密码、修改基本资料、注销等功能都定义在一个Model类中。
AdvicesContract接口用于集中管理View和Presenter两个接口,两个接口放在一块儿也更利用我们阅读源码。
public interfaceAdvicesContract {
public interface Presenter extendsBasePresenter{
/**
* 加载我的建议
*/
void loadMyAdvices();
/**
* 删除建议
* @param id
*/
void deleteAdvice(@NonNull int id);
/**
* 添加新建议
*/
void addNewAdvice();
}
public interface View extendsBaseView<Presenter>{
/**
* 显示我的建议
* @param advices
*/
void showAdvices(List<Advice>advices);
/**
* 打开提建议的Activity
*/
void showAddAdvice();
/**
* 删除我的一条建议
* @param id
*/
void deleteAdvice(@NonNull int id);
}
}
AdvicesPresenter类是对AdvicesContract.Presenter接口的实现,AdvicesPresenter持有View和Model的引用,本类中分别为AdvicesContract.View和AdviceRepositorymRepository。在构造方法中不仅要建立AdvicesPresenter与AdvicesContract.View和AdviceRepository的关系,同时也要建立AdvicesContract.View与AdvicesPresenter的关系(Presenter和View是相互持有对方引用的)。
public classAdvicesPresenter implements AdvicesContract.Presenter {
private AdvicesContract.View mView;
private AdviceRepository mRepository;
publicAdvicesPresenter(AdvicesContract.View view, AdviceRepository repository) {
mView = view;//建立Presenter与View的关系
mRepository = repository;
//建立View与Presenter的关系
mView.setPresenter(this);
}
@Override
public void loadMyAdvices() {
//读取id为5的用户的建议
mRepository.getMyAdvices(5, newAdviceDataSource.LoadAdviceCallBack() {
@Override
public voidonAdviceLoaded(List<Advice> advices) {
mView.showAdvices(advices);
}
});
}
@Override
public void deleteAdvice(@NonNull final intid) {
//删除5号用户对应的建议
mRepository.deleteAdvice(id, 5, newAdviceDataSource.DeletedAdviceCallBack() {
@Override
public void onAdviceDeleted() {
mView.deleteAdvice(id);
}
});
}
@Override
public void addNewAdvice() {
mView.showAddAdvice();
}
@Override
public void start() {
loadMyAdvices();
}
}
Presenter的start()方法通常用于初始化,本例中需要显示所有的建议列表,所以调用了loadMyAdvices()方法。
在loadMyAdvices()方法中,通过调用Model类AdviceRepository的getMyAdvices()方法获取我的建议,再调用AdvicesContract.View中的showAdvices()方法刷新列表。其他道理都差不多。再次强调一下,View将功能委托给Presenter完成,Presenter又调用View刷新结果。
AdvicesFragment类是一个继承自Fragment的View,在AdvicesFragment类中,我们早早为ListView定义了一个适配器AdviceAdapter对象,一开始什么都不显示,读取到数据后再刷新结果。在Fragment的onResume()回调方法中,通常要调用Presenter的start()方法完成初始化。以下是AdviceFragment类的源码:
public classAdvicesFragment extends Fragment implements AdvicesContract.View {
private AdvicesContract.PresentermPresenter;
private AdviceAdapter mAdviceAdapter;
public static AdvicesFragment getInstance(){
return new AdvicesFragment();
}
@Override
public void onCreate(@Nullable BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
mAdviceAdapter = new AdviceAdapter(newArrayList<Advice>(0));
}
@Nullable
@Override
public View onCreateView(LayoutInflaterinflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root =inflater.inflate(R.layout.fragment_advices, container, false);
setHasOptionsMenu(true);//显示选项菜单
return root;
}
@Override
public void onViewCreated(View view,@Nullable Bundle savedInstanceState) {
ListView listView = (ListView)view.findViewById(R.id.lv_advices);
listView.setOnItemLongClickListener(newAdapterView.OnItemLongClickListener() {
@Override
public booleanonItemLongClick(AdapterView<?> adapterView, View view,
inti, final long l) {
AlertDialog.Builder builder =new AlertDialog.Builder(getContext());
AlertDialog alertDialog =builder.setTitle("确定删除")
.setMessage("确定要删除该建议吗?")
.setPositiveButton("删除", new DialogInterface.OnClickListener() {
@Override
public voidonClick(DialogInterface dialogInterface, int i) {
LogUtil.d("==建议id:" + l);
mPresenter.deleteAdvice((int) l);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public voidonClick(DialogInterface dialogInterface, int i) {
}
}).create();
alertDialog.show();
return true;
}
});
listView.setAdapter(mAdviceAdapter);
}
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void showAdvices(List<Advice>advices) {
mAdviceAdapter.refreshAdvices(advices);
}
@Override
public void showAddAdvice() {
Intent intent = newIntent(AddAdviceActivity.ACTION);
startActivityForResult(intent, 0x002);
}
@Override
public void onActivityResult(intrequestCode, int resultCode, Intent data) {
if(requestCode == 0x002 &&resultCode == Activity.RESULT_OK) {
mPresenter.loadMyAdvices(); //添加完建议后重新加载,实时将新的建议显示出来
}
}
@Override
public void deleteAdvice(@NonNull int id) {
mAdviceAdapter.deleteAdvice(id);//删除建议
}
@Override
public voidsetPresenter(AdvicesContract.Presenter presenter) {
this.mPresenter = presenter;
}
@Override
public void onCreateOptionsMenu(Menu menu,MenuInflater inflater) {
inflater.inflate(R.menu.menu_advices,menu);
}
@Override
public booleanonOptionsItemSelected(MenuItem item) {
if(item.getItemId() ==R.id.menu_add_advice){
mPresenter.addNewAdvice();
}else if(item.getItemId() ==R.id.menu_refresh_advice){
mPresenter.loadMyAdvices();//刷新
}
return true;
}
/**
* 适配器
*/
class AdviceAdapter extends BaseAdapter {
private List<Advice> mAdvices;
public AdviceAdapter(List<Advice>advices) {
mAdvices = advices;
}
public voidsetAdvices(List<Advice> advices) {
mAdvices = advices;
}
@Override
public int getCount() {
return mAdvices.size();
}
@Override
public Object getItem(int i) {
return mAdvices.get(i);
}
@Override
public long getItemId(int i) {
return mAdvices.get(i).getId();
}
@Override
public View getView(int i, View view,ViewGroup viewGroup) {
View root =LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_advices, viewGroup, false);
TextView tvDate = (TextView)root.findViewById(R.id.tv_date);
TextView tvContent = (TextView)root.findViewById(R.id.tv_content);
TextView tvReply = (TextView)root.findViewById(R.id.tv_reply);
Advice advice = mAdvices.get(i);
Calendar calendar = Calendar.getInstance();
calendar.setTime(advice.getCreateTime());
tvDate.setText(calendar.get(Calendar.MONTH) + 1
+ "/" +calendar.get(Calendar.DAY_OF_MONTH));
tvContent.setText(advice.getContent());
tvReply.setText(advice.getReply());
return root;
}
public voidrefreshAdvices(List<Advice> advices) {
setAdvices(advices);
notifyDataSetChanged();
}
public void deleteAdvice(int id) {
mPresenter.loadMyAdvices();
}
}
}
在Fragment中可以定义ActionBar的菜单,但默认是关闭的,需要通过setHasOptionsMenu(true)方法启用。另外,当需要改变ListView中的数据时,建议将功能封装在Adapter中。
本界面还实现了长按列表项删除建议的功能,您可以阅读源码了解方法的调用规律,基本顺序为View -> Presenter -> View。
最后是AdviceActivity类,该类继承自AppCompatActivity,在onCreate()方法中,先判断AdviceFragment对象是否存在,如果存在,就不需要创建新的AdviceFragment对象了,不存在才创建,节约内存。然后将AdviceFragment添加到Activity的FrameLayout中。最后,通过new AdvicesPresenter(fragment, AdviceRepository.getInstance())语句创建Presenter,这个语句同时创建了Model层的AdviceRepository对象。至此,Fragment(View)、Model和Presenter创建完毕,而在Presenter类中则构建了View与Presenter、Presenter与View、Presenter与Model的关系。
@ContentView(R.layout.activity_advices)
public classAdviceActivity extends AppCompatActivity {
private AdvicesPresenter mAdvicesPresenter;
@Override
protected void onCreate(@Nullable BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
FragmentManager fragmentManager =getSupportFragmentManager();
//判断Fragment是否存在
AdvicesFragment fragment =(AdvicesFragment) fragmentManager
.findFragmentById(R.id.frame_content);
//不存在则创建
if(fragment == null){
fragment =AdvicesFragment.getInstance();
ActivityUtils.addFragmentToActivity(fragmentManager,
fragment,R.id.frame_content);
}
//创建Presenter
mAdvicesPresenter = new AdvicesPresenter(fragment,AdviceRepository.getInstance());
}
}
上面代码中的ActivityUtils是一个工具类,定义了一个addFragmentToActivity()方法,用于将Fragment添加到Activity中。
public classActivityUtils {
public static voidaddFragmentToActivity(FragmentManager fragmentManager,
Fragmentfragment, int resId){
FragmentTransaction transaction =fragmentManager.beginTransaction();
transaction.add(resId, fragment);
transaction.commit();
}
}
4.6 提建议功能实现
提建议就是一个添加功能,运行效果图6所示:
图6:提建议
本功能定义在addadvice包中,同样包含了View、Presenter、Contract和Activity,也就是说,每一个界面都至少需要这几个组件。本功能包含了fragment_add_advice.xml和activity_add_advice.xml两个布局文件,前者为Fragment定义了界面,后者为Activity定义界面。
fragment_add_advice.xml:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="300px"
android:hint="请输入您宝贵的建议"
android:padding="10dp"
android:gravity="left|top"
android:id="@+id/et_content"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_send"
android:text="提交建议"
/>
</LinearLayout>
activity_add_advice.xml:
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frame_content"
></FrameLayout>
</LinearLayout>
首先,定义AddAdviceContract接口,该接口集中定义View接口和Presenter接口。因为功能较少,所以方法也很少。
public interfaceAddAdviceContract {
public interface Presenter extendsBasePresenter{
public void addAdvice(String content);
}
public interface View extendsBaseView<Presenter>{
public void showAdvicesList();
}
}
其次,分别定义View(AddAdviceFragment)和Presenter(AddAdvicePresenter),其中,AddAdvicePresenter类代码如下:
public classAddAdvicePresenter implements AddAdviceContract.Presenter {
private AddAdviceContract.View mView;
private AdviceRepository mAdviceRepository;
publicAddAdvicePresenter(AddAdviceContract.View view, AdviceRepositoryadviceRepository) {
mView = view;
mAdviceRepository = adviceRepository;
mView.setPresenter(this);
}
@Override
public void addAdvice(String content) {
mAdviceRepository.addAdvice(content, 5,new AdviceDataSource.AddedAdviceCallBack() {
@Override
public void onAdviceAdded() {
mView.showAdvicesList();
}
});
}
@Override
public void start() {
//啥也不干
}
}
在方法addAdvice()中,保存建议后,调用AddAdviceFragment(这是一个View)的showAdvicesList(),该方法用于关闭当前Activity,并重新刷新AdviceFragment实时显示新增的内容。AddAdviceFragment类的源码如下:
public classAddAdviceFragment extends Fragment implements AddAdviceContract.View {
private AddAdviceContract.PresentermPresenter;
@ViewInject(R.id.et_content)
private EditText etContent;
@ViewInject(R.id.btn_send)
private Button btnSend;
public static AddAdviceFragmentgetInstance(){
return new AddAdviceFragment();
}
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Nullable
@Override
public View onCreateView(LayoutInflaterinflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root =inflater.inflate(R.layout.fragment_add_advice, container, false);
x.view().inject(this, root);
this.setHasOptionsMenu(true);
return root;
}
@Event(R.id.btn_send)
private void send(View v){
mPresenter.addAdvice(etContent.getText().toString());
}
@Override
public void showAdvicesList() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
@Override
public voidsetPresenter(AddAdviceContract.Presenter presenter) {
this.mPresenter = presenter;
}
}
嗯嗯,全是一个套路……
5.7 文件结构图
为了让各位了解文件之间的关系,可以参考如图7所示的文件结构图。
图7:文件结构图
5.8 后记
本案例是通过理解AndroidArchitecture Blueprints之后写的类似的案例,如有错误,请指教。如需转载,请注明“株洲新程IT教程李赞红”。
欢迎下载本人的另两本原创教程:
A)《Android自定义组件开发详解》(http://download.csdn.net/detail/lifenote/9445898)
B)《轻松搞定ExtJS》(http://download.csdn.net/detail/lifenote/1405716)
您可以发邮件至lifenote@21cn.com索取源码!
株洲新程IT教育 李赞红
2016年6月5日