版權聲明:本文爲博主原創文章,未經博主容許不得轉載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是怎樣封裝這個過程的你們能夠去看下源碼,代碼不難,沒有太多層的回調,而且關鍵的地方我都給了詳細的註釋,相信你們都能看懂。有啥疑問或建議歡迎留言評論,感激涕零。若是以爲寫得還不錯麻煩點個贊,大家的支持是我最大的動力~