Android MVP+LoaderManager+CursorLoader实现图片搜索

版权声明:本文为博主原创文章,未经博主允许不得转载

系列教程:Android开发之从零开始系列

源码:AnliaLee/PhotoFactory,欢迎star

大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论

前言

之前写了篇Android项目实践——三行代码解决照片选择与压缩,我们利用封装好的PhotoFactory简化了从系统相册获取照片的操作,但要想筛选出指定的图片原有的功能就不够用了,于是我们继续开发和完善PhotoFactory,将简化操作进行到底。本次我们将使用LoaderManager+CursorLoader机制结合MVP设计模式实现图片搜索的功能


新功能使用示例

在讲解功能的实现过程之前,先简单介绍一下如何使用。更新后的PhotoFactory可以根据图片的路径、名称或图片格式等条件搜索图片,执行搜索后返回符合条件图片的list。这里我们以筛选出手机本地所有gif图片为例:

repositories {
    ...
    maven { url 'https://jitpack.io' }
}

dependencies {
    compile 'com.github.AnliaLee:PhotoFactory:1.0.1'
}
  • 配置权限(动态权限的配置这里就不赘述了)
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 在Activity中调用photoFactory.FactorySearch方法,完成相关配置后在回调中获取查询到的数据
//加载数据的映射(MediaStore.Images.Media.DATA等)
String[] projection = new String[]{
    MediaStore.Images.Media.DATA,//图片路径
    MediaStore.Images.Media.DISPLAY_NAME,//图片文件名,包括后缀名
    MediaStore.Images.Media.TITLE//图片文件名,不包含后缀
};

photoFactory = new PhotoFactory(this,this);
photoFactory.FactorySearch(getSupportLoaderManager(),getApplicationContext(),projection)
        .setSelectionByFormat(new String[]{".gif"})//设置查询条件(通过图片格式查找,非必选)
        //.setSelection(new String[]{"图片收藏","WeiXin"}) (或模糊匹配搜索指定图片,非必选)
        .setLoadingEvent(new InterfaceManager.LoadingCallBack() {//设置异步加载时loading操作(非必选)
            @Override
            public void showLoading() {
                myProgressDialog.show();
            }

            @Override
            public void hideLoading() {
                if(myProgressDialog.isShowing()){
                    myProgressDialog.dismiss();
                }
            }
        })
        .setErrorEvent(new InterfaceManager.ErrorCallBack() {//设置搜索出错时的操作(非必选)
            @Override
            public void dealError(String s) {
                Toast.makeText(SearchGifActivity.this, s, Toast.LENGTH_SHORT).show();
            }
        })
        .execute(new InterfaceManager.SearchDataCallBack() {//执行搜索并获取回调数据
            @Override
            public void onFinish(final List<Map<String, Object>> list) {
                searchGifAdapter = new SearchGifAdapter(SearchGifActivity.this,list);
                recyclerView.setAdapter(searchGifAdapter);
            }
        });

onFinish返回给我们的list即为查询到的gif图片集合,我们可以通过之前配置的数据映射参数map中拿出数据

list.get(position).get(MediaStore.Images.Media.DATA)

打印数据看看

结合Glide图片加载库可以实现获取gif图片(仅显示gif格式的图片)的功能,效果如下


MVP+LoaderManager+CursorLoader实现图片搜索

查询本地图片数据是一个异步获取数据的过程,因此我们不妨使用MVP设计模式将数据获取和数据展示分离开来(有关MVP设计模式的知识大家可以查阅相关资料进行了解,就不在这展开了)。为了让ModelViewPresenter三者之间可以相互引用并回调数据,同时保留后续扩展的可能性,我们定义相关接口供他们继承

public interface InterfaceManager {
    /**
     * MVP模式接口
     */
    interface Model {
        void getData(Map<String, Object> map, ModelDataCallBack callBack);
    }
    interface View {
        void onFinish(List<Map<String, Object>> list);
    }
    interface Presenter{
        void getData(Map<String, Object> map);
    }

    /**
     * model数据回调
     */
    interface ModelDataCallBack {
        void getListDataSuccess(List<Map<String, Object>> list);
        void getDataFailed(String message);
    }

    /**
     * 搜索数据回调
     */
    interface SearchDataCallBack extends View{
        @Override
        void onFinish(List<Map<String, Object>> list);
    }

    /**
     * 加载中回调
     */
    interface LoadingCallBack{
        void showLoading();
        void hideLoading();
    }

    /**
     * 错误回调
     */
    interface ErrorCallBack{
        void dealError(String message);
    }
}

Presenter层的实现

我们先来看看Presenter层是怎么写的。Presenter作为ModelView的中间件,负责在两者之间传递数据,实现数据获取和展示的分离。我们创建Presenter接口的执行类SearchPhotoPresenterImpl,通过初始化传入ModelView的引用,在getData方法中先让Model获取数据,等Model将数据回调后再执行View的方法将数据传回给用户,这样用户就可以开始处理数据了

public class SearchPhotoPresenterImpl implements InterfaceManager.Presenter{
    //省略部分代码...
    InterfaceManager.Model model;//定义Model层引用

    //下面三个属于View层
    InterfaceManager.View view;
    InterfaceManager.LoadingCallBack loadingCallBack;
    InterfaceManager.ErrorCallBack errorCallBack;

    private Handler mHandler = new Handler();

    @Override
    public void getData(Map<String, Object> map) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if(loadingCallBack !=null){
                    loadingCallBack.showLoading();
                }
            }
        });
        model.getData(map, new InterfaceManager.ModelDataCallBack() {//让Model去获取数据
            @Override
            public void getListDataSuccess(final List<Map<String, Object>> list) {
                //Model将数据回调后让View执行数据处理的操作
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        view.onFinish(list);//View层的方法
                        if(loadingCallBack !=null){
                            loadingCallBack.hideLoading();
                        }
                    }
                });
            }

            @Override
            public void getDataFailed(final String message) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if(errorCallBack !=null){
                            errorCallBack.dealError(message);
                        }

                        if(loadingCallBack !=null){
                            loadingCallBack.hideLoading();
                        }
                    }
                });
            }
        });
    }
}

Model层的实现

Model层负责异步查询数据,我们不需要自己写异步的逻辑,Android官方提供了LoaderManager+CursorLoader机制用来异步查找本地文件,我们只需要实现LoaderManager.LoaderCallbacks接口,并重写其内部相应方法

// 在初始化Loader时回调,在这个方法中实例化CursorLoader
public Loader<Cursor> onCreateLoader(int id, Bundle args);
// 数据查询完毕后会回调这个方法,我们就在这将数据保存至list中并传给Presenter层
public void onLoadFinished(Loader<Cursor> loader, Cursor data);
// 这个方法在重启Loader时才会调用,一般不需要重写
public void onLoaderReset(Loader<Cursor> loader);

那么怎么定位图像数据呢?查阅资料后我们知道:

Android的多媒体文件主要存储在 /data/data/com.android.providers.media/databases 目录下,该目录下有两个db文件,
* 内部存储数据库文件:internal.db
* 存储卡数据库:external-XXXX.db

媒体文件的操作主要是围绕着这两个数据库来进行。这两个数据库的结构是完全一模一样的。这两个数据库包含的表:

album_art 、audio 、search 、album_info 、audio_genres、 searchhelpertitle、albums、 audio_genres_map、 thumbnails、
android_metadata、 audio_meta、 video、artist_info 、audio_playlists 、videothumbnails、artists 、audio_playlists_map、
artists_albums_map 、images

我们要找的就是images表中的数据,我们设置好查询内容和条件后就可以用CursorLoader去查数据了,创建Model层的执行类SearchPhotoModelImpl

public class SearchPhotoModelImpl implements InterfaceManager.Model {
    /**
     * Loader的唯一ID号
     */
    private final static int IMAGE_LOADER_ID = 1000;

    @Override
    public void getData(Map<String, Object> map, final InterfaceManager.ModelDataCallBack callBack) {
        LoaderManager loaderManager = (LoaderManager) map.get("lm");
        final Context applicationContext = (Context) map.get("ac");
        final boolean isQueryByFormat = (boolean) map.get("isQueryByFormat");//是否只通过图片格式查询
        final String[] selections = (String[]) map.get("selections");//查询条件
        final String [] projection = (String[]) map.get("projection");//内容映射

        //初始化指定id的Loader
        loaderManager.initLoader(IMAGE_LOADER_ID, null, new LoaderManager.LoaderCallbacks<Cursor>() {
            @Override
            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
                //构造筛选语句
                String selection = "";
                for (int i = 0; i < selections.length; i++) {
                    if (i != 0) {
                        selection = selection + " OR ";
                    }

                    if(isQueryByFormat){
                        selection = selection + MediaStore.Files.FileColumns.DATA + " LIKE '%" + selections[i] + "'";
                    }else {
                        selection = selection + MediaStore.Files.FileColumns.DATA + " LIKE '%" + selections[i] + "%'";
                    }
                }
                //按图片修改时间递增顺序对结果进行排序;待会从后往前移动游标就可实现时间递减
                String sortOrder = MediaStore.Files.FileColumns.DATE_ADDED;//根据添加时间递增

                CursorLoader imageCursorLoader = new CursorLoader(applicationContext, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        projection, selection, null, sortOrder);
                return imageCursorLoader;
            }

            @Override
            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
                if (data == null){
                    callBack.getDataFailed("查询失败!");
                    return;
                }

                List<Map<String,Object>> list = new ArrayList<>();
                Map<String,Object> dataMap;
                //游标从最后开始往前递减,以此实现时间递减顺序(最近访问的文件,优先显示)
                if (data.moveToLast()) {
                    do {
                        dataMap = new HashMap<>();
                        for(int i=0;i<projection.length;i++){
                            dataMap.put(projection[i],data.getString(i));
                        }
//                        dataMap.put("path",data.getString(0));
                        list.add(dataMap);
                    } while (data.moveToPrevious());
                }
                callBack.getListDataSuccess(list);//回调 Presenter层方法
            }

            @Override
            public void onLoaderReset(Loader<Cursor> loader) {

            }
        });
    }
}

View层的实现

最后,用户只需要在View层调用presenter.getData方法并在相应的接口方法中编写处理数据的逻辑即可

new InterfaceManager.SearchDataCallBack() {
    @Override
    public void onFinish(List<Map<String, Object>> list) {
        Log.e("Tag","size:"+list.size());
        for(int i=0;i<list.size();i++){
            Log.e("DATA"+i,list.get(i).get(MediaStore.Images.Media.DATA).toString());
        }
    }
})

整个图片搜索的实现过程就是这样了,至于PhotoFactory是怎样封装这个过程的大家可以去看下源码,代码不难,没有太多层的回调,并且关键的地方我都给了详细的注释,相信大家都能看懂。有啥疑问或建议欢迎留言评论,感激不尽。如果觉得写得还不错麻烦点个赞,你们的支持是我最大的动力~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值