Android MVP架构(Volley+CursorLoader+ContentProvider)

若是,不熟悉MVP架构的,可以先阅读,Android MVP架构

本篇,介绍Android MVP架构(Volley+CursorLoader+ContentProvider)来实现需求。

项目结构,分析图如下

这里写图片描述

除开MVP架构外,还具备以下几种主要知识点:

  • 数据库:ContentProvider+CursorLoader+SQLite实现数据实时刷新
  • 网络通讯:Volley的几种请求
  • 网络图片:Volley中的ImageLoader
  • 数据解析:Gson库
  • MaterialDesign设计库

采用以上anroid程序员必备技术,上手容易,不需要花费太多精力,去学习其他的第三方类库。

当然,也可以采用RxJava+SQLBrite+Glide+OkHttp+Retrofit等第三方热门框架来实现Android MVP架构。具体如何实现,将由下篇博客介绍。

项目的效果图和需求:

一个电影列表界面

这里写图片描述

一个切换界面的抽屉菜单

这里写图片描述

一个收藏列表的界面

这里写图片描述

根据上面的页面,归纳出以下功能点

  • 电影列表
  • 选择多部电影进行收藏。
  • 查看被收藏的电影列表。
    按模块划分,可以分为电影列表模块,电影收藏模块。

接着,按上面分析,进行编写代码:

前期准备,项目的gradle中类库引用如下:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.android.volley:volley:1.0.0'
    compile 'com.google.code.gson:gson:2.8.0'
    compile 'com.android.support:recyclerview-v7:25.3.1'
}

1. 项目通用的BasePrester和BaseView接口:

BasePresenter接口用于一个开始加载资源的方法和解除对View对象引用的方法:

public interface BasePresenter {
    /**
     * 开启任务
     */
    void start();

    /**
     * 解除对View的引用
     */
    void unbindView();
}

BaseView接口,拥有一个设置Presenter对象的方法:

public interface BaseView<T> {
    /**
     * 设置Presenter
     */
    void setPresenter(T t);
}

2. Modle模块编写

Model模块分为本地数据和网络远程数据。

1. 本地数据源:

根据上面展示的收藏电影列表界面,来建立以下数据库中表及其字段。

将表中字段和表的Uri存放在一个BaseColumns实现类中:

public class MovieConstract implements BaseColumns {
    /**
     * 数据库的信息
     */
    public static final String SQLITE_NAME="movie.db";
    public static final int SQLITE_VERSON=1;
    /**
     * 表和字段信息
     */
    public static final  String TABLE_NAME_MOVI="movieData";
    public static final  String COLUMN_ID ="id";
    public static final String COLUMN_YEAR="year";
    public static final String COLUMN_TITLE="title";
    public static final String COLUMN_IMAGES="image";

    /**
     * 内容提供者的authority
     */
    public  static final String AUTHORITY="com.xingen.mvppractice.data.source.local.MovieDataProvider";
    public static final String SCHEME="content";
    private static final Uri CONTENT_URI=Uri.parse(SCHEME+"://"+AUTHORITY);
    public static final Uri MOVIEDATA_URI=Uri.withAppendedPath(CONTENT_URI,TABLE_NAME_MOVI);
}

数据库建立如下:

public class MovieDataHelper extends SQLiteOpenHelper {
    public static final String CREATE_TABLE_MOVIE = "create table " +
            MovieConstract.TABLE_NAME_MOVI + "(" +
            MovieConstract._ID + " integer primary key autoincrement," +
            MovieConstract.COLUMN_ID + " text," +
            MovieConstract.COLUMN_TITLE + " text," +
            MovieConstract.COLUMN_YEAR + " text," +
            MovieConstract.COLUMN_IMAGES + " text"
            + ")";

    public MovieDataHelper(Context context) {
        super(context, MovieConstract.SQLITE_NAME, null, MovieConstract.SQLITE_VERSON);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_MOVIE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

如何自定义的ContentProvider,请阅读ContetProvider+SQLite+CursorLoader实现数据库观察者模式。也可以阅读本项目中代码。

接下来,编写增,删,查,改的操作。创建数据库的表中数对应的操作类的超级接口:

public interface  LocalDataSource<T> {
    /**
     * 获取全部
     * @return
     */
    List<T> queryAll();

    /**
     *  指定条件下的查询
     * @param select
     * @param selectArg
     * @return
     */
    List<T> queryAction(String select,String[] selectArg);

    /**
     * 新增
     * @param t
     * @return
     */
    long insert(T t);

    /**
     *  批量插入
     * @param list
     * @return
     */
    int bulkInsert( List<T> list);

    /**
     * 更新
     * @param t
     * @param select
     * @param selectArg
     * @return
     */
    int update(T t,String select,String[] selectArg);

    /**
     * 指定条件的删除
     * @param t
     * @param select
     * @param selectArg
     * @return
     */
    int delite(T t,String select,String[] selectArg);

    /**
     * 删除全部
     */
    void deliteAll();
}

最后,编写接口的实现类,即每个表的各种对应的操作类,采用ContentResolver对象来完成:

public class MovieLocalSource implements LocalDataSource<MovieData> {
    private ContentResolver contentResolver;
    public MovieLocalSource(ContentResolver contentResolver){
        this.contentResolver=contentResolver;
    }
    @Override
    public List<MovieData> queryAll() {
        //查询工作,由CursorLoader已经完成
        return null;
    }

    @Override
    public List<MovieData> queryAction(String select, String[] selectArg) {
        //查询工作,由CursorLoader已经完成
        return null;
    }

    @Override
    public long insert(MovieData movieData) {
       ContentValues contentValues= TransformUtils.transformMovieData(movieData);
         Uri uri=this.contentResolver.insert(MovieConstract.MOVIEDATA_URI,contentValues);
         if(uri!=null){
             String s=   uri.toString();
             long rowId=Long.valueOf(s.substring(s.lastIndexOf("/",s.length())));
             return rowId;
         }
        return -1;
    }

    @Override
    public int bulkInsert(List<MovieData> list) {
        ContentValues[] contentValuesArray=new ContentValues[list.size()];
        for (int i=0;i<list.size();++i){
           contentValuesArray[i]=  TransformUtils.transformMovieData(list.get(i));
        }
      return   this.contentResolver.bulkInsert(MovieConstract.MOVIEDATA_URI,contentValuesArray);
    }

    @Override
    public int update(MovieData movieData, String select, String[] selectArg) {
        return 0;
    }

    @Override
    public int delite(MovieData movieData, String select, String[] selectArg) {
        return 0;
    }
    @Override
    public void deliteAll() {
    }
}

更多以上SQLite,自定义ContentProvider如何配置,ContetResolver使用。
请阅读前面教程之[ContetProvider+SQLite+CursorLoader实现数据库观察者模式] (http://blog.csdn.net/hexingen/article/details/71597884)。

本地数据源包结构图如下:

这里写图片描述

2. 网络数据源

后台服务器返回的数据结构有多种(String,xml,json),这里统一返回String类型的数据,然后各种对应类型再解析。考虑到现在主流的Post传递的数据类型为json。因此,重写StringRequest,使其支持Json数据类型的Body:

 public class StringBodyRequest extends StringRequest  {
    /** Charset for request. */
    private static final String PROTOCOL_CHARSET = "utf-8";

    /** Content type for request. */
    private static final String PROTOCOL_CONTENT_TYPE =
            String.format("application/json; charset=%s", PROTOCOL_CHARSET);
    /**
     * 自定义header:
     */
    private Map<String, String> headers;
    /**
     * post传递的参数
     */
    private final String mRequestBody;
    /**
     *  请求结果的监听器
     */
    private RequestResultListener resultListener;
    /**
     * 请求的id
     */
    private int requestId;

    public StringBodyRequest( String url, int requestId,StringBodyRequest.RequestResultListener resultListener) {
      this(Method.GET,url,null,requestId,resultListener);
    }
    public StringBodyRequest(int method, String url, JSONObject jsonObject,int requestId,StringBodyRequest.RequestResultListener resultListener) {
        super(method, url,null,null);
        this.headers = new HashMap<>();
        this.mRequestBody=(jsonObject==null?null:jsonObject.toString());
        this.resultListener=resultListener;
        this.requestId=requestId;
    }
    /**
     * 重写getHeaders(),添加自定义的header
     *
     * @return
     * @throws AuthFailureError
     */
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }
    /**
     * 设置请求的header
     * "Charset", "UTF-8"://编码格式:utf-8
     * "Cookie", coockie:设置coockie
     * @param
     * @return
     */
    public Map<String, String> setHeader(String key, String content) {
        if(!TextUtils.isEmpty(key)&&!TextUtils.isEmpty(content)){
            headers.put(key, content);
        }
        return headers;
    }
    /**
     * 重写Content-Type:设置为json
     */
    @Override
    public String getBodyContentType() {
        return PROTOCOL_CONTENT_TYPE;
    }
    /**
     * post参数类型
     */
    @Override
    public String getPostBodyContentType() {
        return getBodyContentType();
    }
    /**
     * post参数
     */
    @Override
    public byte[] getPostBody() throws AuthFailureError {
        return getBody();
    }

    /**
     * 将string编码成byte
     * @return
     * @throws AuthFailureError
     */
    @Override
    public byte[] getBody() throws AuthFailureError {
        try {
            return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 重写传递异常的回调
     * @param error
     */
    @Override
    public void deliverError(VolleyError error) {
         this.resultListener.failure(requestId,error);
    }

    /**
     * 重写传递结果的回调
     * @param response
     */
    @Override
    protected void deliverResponse(String response) {
          this.resultListener.success(requestId,response);
    }

    /**
     * 自定义请求结果和异常的回调接口
     */
    public  interface  RequestResultListener{
          void success(int requestId,String response);
          void failure(int reqestId,VolleyError error);
    }
}

ImageLoader中Lrucache配置和Volley操作类的配置省略不贴出来。在项目中有详细介绍,请自行阅读。如何自定义请求,阅读 Volley源码分析之自定义GsonRequest教程

考虑到请求有几种情况,有请求方式,header,body差异。因此,远程操作类接口封装以下几种方法:

public interface RemoteDataSource {

    void excuteRequest(String url, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener);

    void excuteRequest(String url, Map<String, String> headrMap, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener);

    void excuteRequest(int method, String url, JSONObject jsonObject, Map<String, String> headrMap, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener);

    void excuteRequest(int method, String url, JSONObject jsonObject, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener);
}

远程网络的操作类的实现具体如下:

public class RemoteDataSourceImp implements RemoteDataSource {
    /**
     * 静态方式构建
     * @return
     */
    public static RemoteDataSource newInstance(){
        return  new RemoteDataSourceImp();
    }
    @Override
    public void excuteRequest(String url, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener) {
        excuteRequest(url,null,requestId,tag,resultListener);
    }

    @Override
    public void excuteRequest(String url, Map<String, String> headrMap, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener) {
       excuteRequest(Request.Method.GET,url,null,headrMap,requestId,tag,resultListener);
    }

    @Override
    public void excuteRequest(int method, String url, JSONObject jsonObject, Map<String, String> headrMap, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener) {
           excuteRequest(createRequest(method,url,jsonObject,headrMap,requestId,tag,resultListener));
    }

    @Override
    public void excuteRequest(int method, String url, JSONObject jsonObject, int requestId, String tag, StringBodyRequest.RequestResultListener resultListener) {
        excuteRequest(Request.Method.GET,url,jsonObject,null,requestId,tag,resultListener);
    }

    /**
     *  创建不同body,Header的请求
     * @param method
     * @param url
     * @param jsonObject
     * @param headrMap
     * @param requestId
     * @param tag
     * @param resultListener
     * @return
     */
    private StringBodyRequest createRequest(int method, String url,JSONObject jsonObject, Map<String,String> headrMap, int requestId,String tag,StringBodyRequest.RequestResultListener resultListener){
        StringBodyRequest stringBodyRequest=new StringBodyRequest(method,url,jsonObject,requestId,resultListener);
        stringBodyRequest.setTag(tag);
        if(headrMap!=null){
            Set<Map.Entry<String,String>> headerSet = headrMap.entrySet();
            for (Map.Entry<String,String> entry:headerSet){
                stringBodyRequest.setHeader(entry.getKey(),entry.getValue());
            }
        }
        return  stringBodyRequest;
    }
    /**
     * 执行 Request
     * @param stringBodyRequest
     */
    private void excuteRequest(StringBodyRequest stringBodyRequest){
        VolleySingle.getInstance().addRequest(stringBodyRequest);
    }
}

远程数据源的包结构图如下:

这里写图片描述

3. 实际业务模块:

这里,列举:电影列表界面的模块

  1. View告诉Presenter要加载数据,Presenter要获取远程数据源,然后回调的响应数据更新到UI上.

  2. View告诉Presenter要收藏的电影,Presenter将收藏数据传递给本地数据源,进行存储,最后Presenter将存储结果更新到UI上。

这里写图片描述

根据上面的View,Presenter间的交互关系,抽出其行为:

public interface MovieListConstract {

    interface  Presenter extends BasePresenter{
        /**
         *  收藏的数据
         */
       void collectionMovie(List<Movie> list);
    }
    interface  View extends BaseView<MovieListConstract.Presenter>{
        /**
         *  加载从数据源中获取的数据
         */
          void loadMovieList(List<Movie> list);
          /**
           *  显示最新信息
           */
          void showToast(String s);
    }
}

接着编写View接口的具体实现类Fragment:

public class MovieListFragment extends Fragment implements MovieListConstract.View, View.OnClickListener, SwipeRefreshLayout.OnRefreshListener {
    private View rootView;
    private RecyclerView recyclerView;
    private MovieListAdapter adapter;
    private ScrollChildSwipeRefreshLayout swipeRefreshLayout;
    public static final String TAG = MovieListFragment.class.getSimpleName();
    public static MovieListFragment newInstance() {
        return new MovieListFragment();
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        this.rootView = inflater.inflate(R.layout.fragment_movielist, container, false);
        return this.rootView;
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initView();
        
        //开始加载远程任务
        this.presenter.start();
    }
    /**
     * 初始化控件
     */
    private void initView() {
        this.recyclerView = (RecyclerView) this.rootView.findViewById(R.id.movielist_recyclerView);
        this.adapter = new MovieListAdapter();
        this.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        this.recyclerView.setAdapter(this.adapter);
        this.recyclerView.addItemDecoration(new BaseItemDecoration(getActivity()));

        this.rootView.findViewById(R.id.movielist_collection_btn).setOnClickListener(this);

        swipeRefreshLayout = (ScrollChildSwipeRefreshLayout) rootView.findViewById(R.id.movielist_refreshLayout);
        swipeRefreshLayout.setColorSchemeColors(Color.parseColor("#263238"), Color.parseColor("#ffffff"), Color.parseColor("#455A64"));
        swipeRefreshLayout.setScrollUpChild(recyclerView);
        swipeRefreshLayout.setOnRefreshListener(this);

        //自动加载下拉提示框
        setLoadingIndicator(true);
        //以上代码不响应onRefresh(),需要手动响应onReFresh()。
        this.onRefresh();
    }

    /**
     * 控制SwipeRefreshLayout的显示与隐藏
     *
     * @param active
     */
    public void setLoadingIndicator(final boolean active) {

        if (swipeRefreshLayout == null) {
            return;
        }
        /**
         *     通过swipeRefreshLayout.post来调用swipeRefreshLayout.setRefreshing()来实现,一进入页面就自动下拉提示窗。
         */
        swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                //确保布局加载完成后,调用
                swipeRefreshLayout.setRefreshing(active);
            }
        });

    }

    @Override
    public void onDestroyView() {
    //解除对View的引用
        this.presenter.unbindView();
        super.onDestroyView();
    }

    private MovieListConstract.Presenter presenter;

    @Override
    public void setPresenter(MovieListConstract.Presenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public void showToast(String s) {
        Toast.makeText(BaseApplication.getAppContext(), s, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loadMovieList(List<Movie> list) {
        this.adapter.upData(list);
        this.setLoadingIndicator(false);
    }

    @Override
    public void onClick(View v) {
        if (this.adapter.getMoviesCollecion().size() == 0) {
            showToast("请勾选中电影");
        } else {
            this.presenter.collectionMovie(this.adapter.getMoviesCollecion());
        }
    }
    @Override
    public void onRefresh() {
            swipeRefreshLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    setLoadingIndicator(false);
                }
            }, 1000 * 2);

    }
}

在接下来编写Presenter的实现类:

public class MovieListPresenter implements MovieListConstract.Presenter, StringBodyRequest.RequestResultListener {
    private MovieListConstract.View view;
    private LocalDataSource<MovieData> localDataSource;
    private RemoteDataSource remoteDataSource;

    public MovieListPresenter(RemoteDataSource remoteDataSource, LocalDataSource<MovieData> localDataSource, MovieListConstract.View view) {
        this.remoteDataSource = remoteDataSource;
        this.localDataSource = localDataSource;
        this.view = view;
        this.view.setPresenter(this);
    }
    @Override
    public void start() {
        loadRemoteTask();
    }

    /**
     *  豆瓣中电影的Api:
     */
    private final String URL = "https://api.douban.com/v2/movie/search?q=张艺谋";
    
    private final int REQUEST_MOVIELIST = 1;
    private final String TAG = MovieListPresenter.class.getSimpleName();

    /**
     * 开始加载远程的数据
     */
    private void loadRemoteTask() {
        remoteDataSource.excuteRequest(URL, REQUEST_MOVIELIST, TAG, this);
    }
    /**
      *Presenter将收藏的数据传递给本地数据源,进行存储。
      */
    @Override
    public void collectionMovie(List<Movie> list) {
        List<MovieData> movieDataList = new ArrayList<>();
        for (Movie movie : list) {
            movieDataList.add(TransformUtils.transformMovies(movie));
        }
        //本地数据源将收藏的电影存储,将结果反馈给Presenter。
       int size= this.localDataSource.bulkInsert(movieDataList);
        if(size>0){//批量插入成功
            if(isViewBind()){//Presenter传递数据到View上,进行UI更新
                this.view.showToast("收藏成功,可在收藏页面查看");
            }
        }
    }

    @Override
    public void unbindView() {
        this.view = null;
    }
    @Override
    public void success(int requestId, String response) {
        Log.i(TAG," 响应的数据 "+response);
        switch (requestId) {
            case REQUEST_MOVIELIST://响应成功,解析数据
                List<Movie> list = GsonUtils.paserJson(response, MovieList.class).getSubjects();
                
                // Presenter将数据传递到View上, 进行UI更新
                this.view.loadMovieList(list);
                this.view.showToast("获取列表成功");
                break;
            default:
                break;
        }
    }

    @Override
    public void failure(int requestId, VolleyError error) {
        switch (requestId) {
            case REQUEST_MOVIELIST:
                if (isViewBind()) {
                    this.view.showToast("加载失败");
                }
                break;
            default:
                break;
        }
    }

    /**
     * 检查View是否被绑定
     *
     * @return
     */
    private boolean isViewBind() {
        return this.view == null ? false : true;
    }
}

最后,在Activity中创建View 和 Presenter对象:

public class MovieListActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener , View.OnClickListener{
     private  MovieListConstract.Presenter presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_movielist);
        initView();
        MovieListFragment fragment=null;
        if(savedInstanceState!=null){
             fragment=(MovieListFragment) getSupportFragmentManager().findFragmentByTag(MovieListFragment.TAG);
        }else{
            fragment=MovieListFragment.newInstance();
            getSupportFragmentManager().beginTransaction().add(R.id.movielist_content_layout,fragment,MovieListFragment.TAG).commit();
        }
        this.presenter=new MovieListPresenter(RemoteDataSourceImp.newInstance(),new MovieLocalSource(ContentResolverUtils.createResolver(BaseApplication.getAppContext())),fragment);
    }

    /**
     * 初始化控件
     */
    private void initView() {
        NavigationView navigationView=(NavigationView) this.findViewById(R.id.movielist_navigationview);
        FloatingActionButton floationActionButton=(FloatingActionButton) this.findViewById(R.id.movielist_floationActionBtn);

        floationActionButton.setOnClickListener(this);
        navigationView.setNavigationItemSelectedListener(this);
    }
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.activity_movielist_drawer_collect://转调收藏电影的界面.
                Intent intent=new Intent(this, CollectionMovieActivity.class);
                startActivity(intent);
                break;
            case R.id.activity_movielist_drawer_movielist:
                break;
        }
        //关闭抽屉菜单
        DrawerLayout drawerLayout=(DrawerLayout) this.findViewById(R.id.movielist_drawer);
        drawerLayout.closeDrawer(GravityCompat.START);
        return true;
    }

    @Override
    public void onClick(View v) {
        Snackbar.make(v,"MVP案例",Snackbar.LENGTH_SHORT).setAction("Action",null).show();
    }
    @Override
    public void onBackPressed() {
        DrawerLayout drawerLayout=(DrawerLayout) this.findViewById(R.id.movielist_drawer);
        if(drawerLayout.isDrawerOpen(GravityCompat.START)){//按Back键,关闭抽屉菜单。
            drawerLayout.closeDrawer(GravityCompat.START);
        }else{
            super.onBackPressed();
        }
    }
}

电影收藏的业务也是类似,只要要抽出View与Presenter的交互行为,剩下的便是调用数据源。
最好,可以结合Android MVP架构来加深理解。

4. 其他配置:

一个具备添加数据的Adapter抽象类:

public abstract class BaseRecyclerViewAdapter<T ,VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
    public abstract  void upData(T t);
}

一个支持非直接子类滚动视图的SwipeRefreshLayout:

public class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout{
    private View scrollUpChild;
    public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    /**
     * 设置在哪个view中触发刷新。
     * @param view
     */
    public void setScrollUpChild(View view){
        this.scrollUpChild=view;
    }

    /**
     *ViewCompat..canScrollVertically():用于检查view是否可以在某个方向上垂直滑动
     * @return
     */
    @Override
    public boolean canChildScrollUp() {
        if(scrollUpChild!=null){
            return ViewCompat.canScrollVertically(scrollUpChild,-1);
        }
        return super.canChildScrollUp();
    }

}

一些其他的工具类:

public class TransformUtils {
    /**
     *  将Cursor 生成MovieData对象
     * @param cursor
     * @return
     */
    public static MovieData transformMovieData(Cursor cursor) {
        MovieData movieData = new MovieData();
        movieData.setId(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_ID)));
        movieData.setTitle(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_TITLE)));
        movieData.setYear(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_YEAR)));
        movieData.setImages(cursor.getString(cursor.getColumnIndex(MovieConstract.COLUMN_IMAGES)));
        return movieData;
    }
    public static MovieData transformMovies(Movie movie){
        MovieData movieData=new MovieData();
        movieData.setId(movie.getId());
        movieData.setYear(movie.getYear());
        movieData.setTitle(movie.getTitle());
        movieData.setImages(movie.getImages().getLarge());
        return  movieData;
    }
    /**
     * 将Movie生成Cursor.
     * @param movie
     * @return
     */
    public static ContentValues transformMovieData(MovieData movie){
        ContentValues contentValues=new ContentValues();
        contentValues.put(MovieConstract.COLUMN_ID,movie.getId());
        contentValues.put(MovieConstract.COLUMN_TITLE,movie.getTitle());
        contentValues.put(MovieConstract.COLUMN_YEAR,movie.getYear());
        contentValues.put(MovieConstract.COLUMN_IMAGES,movie.getImages());
        return contentValues;
    }
    /**
     * 将Movie生成Cursor.
     * @param movie
     * @return
     */
    public static ContentValues transformMovie(Movie movie){
        ContentValues contentValues=new ContentValues();
        contentValues.put(MovieConstract.COLUMN_ID,movie.getId());
        contentValues.put(MovieConstract.COLUMN_TITLE,movie.getTitle());
        contentValues.put(MovieConstract.COLUMN_YEAR,movie.getYear());
        contentValues.put(MovieConstract.COLUMN_IMAGES,movie.getImages().getLarge());
        return contentValues;
    }
}

工具包,UI包结构图如下:

这里写图片描述

5. 项目运行效果如下:

这里写图片描述

本项目代码https://github.com/13767004362/MVPDemo


todo-mvp-contentproviders官方案例


1. 项目结构图

2. 项目链接https://github.com/googlesamples/android-architecture/tree/todo-mvp-contentproviders/

s/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值