Android MVP+LoaderManager+CursorLoader實現圖片搜尋

Anlia發表於2018-01-11

版權宣告:本文為博主原創文章,未經博主允許不得轉載

系列教程: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)
複製程式碼

列印資料看看

Android MVP+LoaderManager+CursorLoader實現圖片搜尋

結合Glide圖片載入庫可以實現獲取gif圖片(僅顯示gif格式的圖片)的功能,效果如下

Android MVP+LoaderManager+CursorLoader實現圖片搜尋


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是怎樣封裝這個過程的大家可以去看下原始碼,程式碼不難,沒有太多層的回撥,並且關鍵的地方我都給了詳細的註釋,相信大家都能看懂。有啥疑問或建議歡迎留言評論,感激不盡。如果覺得寫得還不錯麻煩點個贊,你們的支援是我最大的動力~

相關文章