对Github上一个开源项目图片选择器的分析

由于老板对我的要求,而自身基础不扎实(在此面壁思过),只能借(cao)鉴(xi)网上优秀且成熟的开源框架,并作修改,以方便使用。符合项目预期功能。

MutilmageSecletor:

https://github.com/lovetuzitong/MultiImageSelector/blob/master/README_zh.md

PhotoPicker:

https://github.com/donglua/PhotoPicker

以上是我学习的开源框架。

首先是对这两个框架的大致浏览,发现主要是:

对Github上一个开源项目图片选择器的分析_第1张图片

面对着相似度极高的结构和模型,深刻感到这个世界的恶意,所为开源只不过是服务一群懒人(包括我这位初学者)。

两个框架的主要界面:

甲:

对Github上一个开源项目图片选择器的分析_第2张图片

乙:

对Github上一个开源项目图片选择器的分析_第3张图片

看上去功能也差不多。

开源真是初学者的巨大福音啊。

以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);

这一段展示的是:

对Github上一个开源项目图片选择器的分析_第4张图片

然后:

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);
                }
            }
        });

这一段是展示手机拥有的图片:

对Github上一个开源项目图片选择器的分析_第5张图片

现在基本分析完毕。只剩下寻找文件的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);

于是我们便得到以下的数据传递方式:

对Github上一个开源项目图片选择器的分析_第6张图片

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


你可能感兴趣的:(Android,SDK)