基于Android Architecture Blueprints的MVP案例分析与实现

基于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日

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值