1. 程式人生 > >對Github上一個開源專案圖片選擇器的分析

對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();