對Github上一個開源專案圖片選擇器的分析
由於老闆對我的要求,而自身基礎不紮實(在此面壁思過),只能借(cao)鑑(xi)網上優秀且成熟的開源框架,並作修改,以方便使用。符合專案預期功能。
MutilmageSecletor:
https://github.com/lovetuzitong/MultiImageSelector/blob/master/README_zh.md
PhotoPicker:
https://github.com/donglua/PhotoPicker
以上是我學習的開源框架。
首先是對這兩個框架的大致瀏覽,發現主要是:
面對著相似度極高的結構和模型,深刻感到這個世界的惡意,所為開源只不過是服務一群懶人(包括我這位初學者)。
兩個框架的主要介面:
甲:
乙:
看上去功能也差不多。
開源真是初學者的巨大福音啊。
以MultiImageSelector(甲)為例分析一下。
首先我感覺這個DEMO做得好的原因是,沒有花裡胡哨的功能,而且,排版美觀,也就是UI好看。
然後在框架的編寫上有一點非常值得我去借鑑,就是他使用了比較清晰的結構,而公司的專案卻將所有的Interface class等檔案全部堆到一個包內,絕對不好管理。
分別是adapter、bean、utils、view這四個包,和一個主Activity和一個Fragment,然後最重要的事情是,它將這個包的具體實現的那個activity分開來了,這樣就可以讓我們很好的借(chao)鑑(xi)。
首先分析MainActivity.java
在看原始碼的時候才發現,很多的功能其實谷歌已經幫我們實現了,就是說我們要知道有哪些API,這是比較重要的。
這幾行是關於如何確定圖片選取器的樣式的。比如是否顯示拍攝圖片按鈕,設定最大可選擇圖片數量、選擇模式、預設選擇樣式。Intent intent = new Intent(MainActivity.this, MultiImageSelectorActivity.class); // 是否顯示拍攝圖片 intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, showCamera); // 最大可選擇圖片數量 intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, maxNum); // 選擇模式 intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, selectedMode); // 預設選擇
判空,以防出錯:
if(mSelectPath != null && mSelectPath.size()>0){
intent.putExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, mSelectPath);
}
startActivityForResult()
參考:
http://www.cnblogs.com/lijunamneg/archive/2013/02/05/2892616.html
startActivityForResult( )
可以一次性完成這項任務,當程式執行到這段程式碼的時候,假若從T1Activity跳轉到下一個Text2Activity,而當這個Text2Activity呼叫了finish()方法以後,程式會自動跳轉回T1Activity,並呼叫前一個T1Activity中的onActivityResult( )方法。
現在轉向MultiImageSelectorActivity.java
mSubmitButton = (Button) findViewById(R.id.commit);
if(resultList == null || resultList.size()<=0){
mSubmitButton.setText(R.string.action_done);
mSubmitButton.setEnabled(false);
}else{
updateDoneText();
mSubmitButton.setEnabled(true);
}
這表明其實我們看到的那個Done按鈕,確切來說是被程式碼控制的一個東西。
這時我想起返回的那個圖示,估計也是一個按鈕。
resultList儲存著一些關於圖片選取的資訊。
@Override
public void onCameraShot(File imageFile) {
if(imageFile != null) {
// notify system
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Intent data = new Intent();
resultList.add(imageFile.getAbsolutePath());
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
}
sendBroadcast是一個API,呼叫後向系統傳送廣播。
http://blog.csdn.net/luoshengyang/article/details/6744448
Uri.fromFile(imageFile)
imageFile也就這樣獲得了圖片路徑,然後被新增至resultList中。
setResult函式必須在Activity被finish之前呼叫。
http://www.cnblogs.com/lijunamneg/archive/2013/02/05/2892616.html
mFolderPopupWindow = new ListPopupWindow(getActivity());
mFolderPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
mFolderPopupWindow.setAdapter(mFolderAdapter);
這一段展示的是:
然後:
mGridView = (GridView) view.findViewById(R.id.grid);
mGridView.setAdapter(mImageAdapter);
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mImageAdapter.isShowCamera()) {
// 如果顯示照相機,則第一個Grid顯示為照相機,處理特殊邏輯
if (i == 0) {
showCameraAction();
} else {
// 正常操作
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
} else {
// 正常操作
Image image = (Image) adapterView.getAdapter().getItem(i);
selectImageFromGrid(image, mode);
}
}
});
這一段是展示手機擁有的圖片:
現在基本分析完畢。只剩下尋找檔案的Adapter內容。
AppCompatActivity和Activity之間不能相互跳轉,只好放棄圖片左右輪播的效果。轉而實現單個檔案展示的效果。
FragmentActivity和Activity之間是可以相互跳轉的。看來,我還是很好運的。
接下來繼續分析這幾個開源框架:
可以繼續分析瞭解到Intent是如何傳輸資料的。
程式碼如下:
Bundle bundle = new Bundle();
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
bundle.putStringArrayList(MultiImageSelectorFragment.EXTRA_DEFAULT_SELECTED_LIST, resultList);
於是我們便得到以下的資料傳遞方式:
在MultiImageSelectorFragment中有一個叫bindData的函式:
void bindData(final Image data){
if(data == null) return;
// 處理單選和多選狀態
if(showSelectIndicator){
indicator.setVisibility(View.VISIBLE);
if(mSelectedImages.contains(data)){
// 設定選中狀態
indicator.setImageResource(R.drawable.btn_selected);
mask.setVisibility(View.VISIBLE);
}else{
// 未選擇
indicator.setImageResource(R.drawable.btn_unselected);
mask.setVisibility(View.GONE);
}
}else{
indicator.setVisibility(View.GONE);
}
File imageFile = new File(data.path);
if (imageFile.exists()) {
// 顯示圖片
Picasso.with(mContext)
.load(imageFile)
.placeholder(R.drawable.default_error)
.tag(MultiImageSelectorFragment.TAG)
.resize(mGridWidth, mGridWidth)
.centerCrop()
.into(image);
}else{
image.setImageResource(R.drawable.default_error);
}
}
這個函式中有判斷多選和單選的狀態、顯示圖片的方法,使用的是開源的Picasso包,這樣佔用的記憶體會小一些。在此之前我寫的關於圖片載入的程式碼是一些大神自己開發的,自然有些慢,而這個包比較成熟,估計可以改寫我的掃描圖片的程式碼。
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if(id == LOADER_ALL) {
CursorLoader cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[3]+"=? OR "+IMAGE_PROJECTION[3]+"=? ",
new String[]{"image/jpeg", "image/png"}, IMAGE_PROJECTION[2] + " DESC");
return cursorLoader;
}else if(id == LOADER_CATEGORY){
CursorLoader cursorLoader = new CursorLoader(getActivity(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[0]+" like '%"+args.getString("path")+"%'",
null, IMAGE_PROJECTION[2] + " DESC");
return cursorLoader;
}
return null;
}
這是關於如何掃描圖片的程式碼,因為我在之前呼叫的一個圖片庫中,也有差不多的方法,所以我相信,就是這一段讓我們獲得了SD卡中的圖片。然後我們需要展示,在這個框架中,作者並沒有傻乎乎地去用new來構造一個函式,而是通過putIntent的方法來實現各個元件之間傳遞資料的方法。這樣這些也在網易雲課堂有所講解。
目前的問題是我的專案無法在Activity和Fragment之間通訊,其中Activity傳入到Fragment是用的setArguments(Object)和getArguments(),但是對於Fragment如何傳回資料給Activity一直沒有好的解決方案。
於是有人提出通過設定回撥函式來實現。所以我便在原始碼中找到了:
/**
* 回撥介面
*/
public interface Callback{
void onSingleImageSelected(String path);
void onImageSelected(String path);
void onImageUnselected(String path);
void onCameraShot(File imageFile);
}
這邊是Fragment和Activity的對話所用到的方法。
具體實現:
@Override
public void onSingleImageSelected(String path) {
Intent data = new Intent();
resultList.add(path);
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
@Override
public void onImageSelected(String path) {
if(!resultList.contains(path)) {
resultList.add(path);
}
// 有圖片之後,改變按鈕狀態
if(resultList.size() > 0){
updateDoneText();
if(!mSubmitButton.isEnabled()){
mSubmitButton.setEnabled(true);
}
}
}
@Override
public void onImageUnselected(String path) {
if(resultList.contains(path)){
resultList.remove(path);
}
updateDoneText();
// 當為選擇圖片時候的狀態
if(resultList.size() == 0){
mSubmitButton.setText(R.string.action_done);
mSubmitButton.setEnabled(false);
}
}
@Override
public void onCameraShot(File imageFile) {
if(imageFile != null) {
// notify system
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
Intent data = new Intent();
resultList.add(imageFile.getAbsolutePath());
data.putStringArrayListExtra(EXTRA_RESULT, resultList);
setResult(RESULT_OK, data);
finish();
}
}
我也可以在我的專案中實現一個回撥函式。通過這個回撥函式來實現Fragment對Activity的通訊。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (Callback) activity;
}catch (ClassCastException e){
throw new ClassCastException("The Activity must implement MultiImageSelectorFragment.Callback interface...");
}
}
這也很重要,這是為了防止使用者忘記實現回撥函式而設定的異常。
為了能夠重新整理Fragment的資料,可以在一些方法上重新再載入一次佈局,通過Bundle的傳遞和getSupportFragmentManager()的方法得以實現圖片的返回鍵取消選擇功能。
getSupportFragmentManager().beginTransaction()
.replace(R.id.image_grid, Fragment.instantiate(this, ImageSelectFragment.class.getName(), bundle))
.commit();