版權宣告:本文為博主原創文章,未經博主允許不得轉載
系列教程: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設計模式的知識大家可以查閱相關資料進行了解,就不在這展開了)。為了讓Model,View和Presenter三者之間可以相互引用並回撥資料,同時保留後續擴充套件的可能性,我們定義相關介面供他們繼承
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作為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層的實現
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是怎樣封裝這個過程的大家可以去看下原始碼,程式碼不難,沒有太多層的回撥,並且關鍵的地方我都給了詳細的註釋,相信大家都能看懂。有啥疑問或建議歡迎留言評論,感激不盡。如果覺得寫得還不錯麻煩點個贊,你們的支援是我最大的動力~