版权声明:本文为博主原创文章,未经博主容许不得转载java
系列教程:Android开发之从零开始系列android
源码:AnliaLee/PhotoFactory,欢迎stargit
你们要是看到有错误的地方或者有啥好的建议,欢迎留言评论github
以前写了篇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" />
复制代码
//加载数据的映射(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中拿出数据bash
list.get(position).get(MediaStore.Images.Media.DATA)
复制代码
打印数据看看 app
结合Glide图片加载库能够实现获取gif图片(仅显示gif格式的图片)的功能,效果以下异步
查询本地图片数据是一个异步获取数据的过程,所以咱们不妨使用MVP设计模式将数据获取和数据展现分离开来(有关MVP设计模式的知识你们能够查阅相关资料进行了解,就不在这展开了)。为了让Model,View和Presenter三者之间能够相互引用并回调数据,同时保留后续扩展的可能性,咱们定义相关接口供他们继承maven
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做为Model和View的中间件,负责在二者之间传递数据,实现数据获取和展现的分离。咱们建立Presenter接口的执行类SearchPhotoPresenterImpl,经过初始化传入Model和View的引用,在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层负责异步查询数据,咱们不须要本身写异步的逻辑,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层调用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是怎样封装这个过程的你们能够去看下源码,代码不难,没有太多层的回调,而且关键的地方我都给了详细的注释,相信你们都能看懂。有啥疑问或建议欢迎留言评论,感激涕零。若是以为写得还不错麻烦点个赞,大家的支持是我最大的动力~