您的位置:首页 > 移动开发 > Android开发

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

2018-01-11 09:20 351 查看
版权声明:本文为博主原创文章,未经博主允许不得转载

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

源码:AnliaLee/PhotoFactory,欢迎star

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

前言

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

新功能使用示例

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

首先在Gradle中导入PhotoFactory

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 Mo
4000
delDataCallBack {
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是怎样封装这个过程的大家可以去看下源码,代码不难,没有太多层的回调,并且关键的地方我都给了详细的注释,相信大家都能看懂。有啥疑问或建议欢迎留言评论,感激不尽。如果觉得写得还不错麻烦点个赞,你们的支持是我最大的动力~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 开源库 教程
相关文章推荐