图片选择器的开发与设计

1.Module介绍

先看效果
这里写图片描述

1.1项目分析

仿照ImagePicker、PhotoPicker等开源框架,将图片选择器作为一个单独的功能模块解耦出来,作为一个Module,之后可被任意项目引用并使用。先合适的地方( Activity | Fragment )创建Intent并启动,并通过onActivityResult得到所选图片的Path。
整体采用Builder模式进行构建,可以清晰的管理参数,层次清晰,增加代码的可读性。功能支持选择是否单选、多选、拍照;是否进行裁剪,使用到开源库cropview进行图片的裁剪;采用策略模式,支持灵活切换图片加载框架;使用photoview预览大图、手势缩放等等…

1.2如何使用

唤起相册:

new ImagePicker()
        .pickType(ImagePickType.SINGLE)
        .needCamera(true)
        .maxNum(9)
        .displayer(new GlideImagePickerDisplayer())
        .doCrop(1, 1, 0, 0)
        .start(UploadAvatarActivity.this, REQUEST_CODE);

得到数据:

if (requestCode == REQUEST_CODE_UPDATE_HEAD && resultCode == RESULT_OK && data != null) {
    List<ImageBean> resultList = data.getParcelableArrayListExtra(ImagePicker.INTENT_RESULT_DATA);
    for(ImageBean imageBean : resultList) {
            String path = imageBean.getImagePath();
    }
}

1.3方法说明

  • ImagePicker pickType(ImagePickType mode) 设置选择模式
    模式:ONLY_CAMERA 拍照、 SINGLE 单选模式、 MULTI 多选模式

  • ImagePicker needCamera(boolean b) 是否需要在界面中显示相机入口

  • ImagePicker maxNum(int max) 设置图片选择的最大数量
    注:拍照和单选都是1,即使设置也无效

  • ImagePicker displayer(IImagePickerDisplayer displayer) 设置图片加载器
    注:默认为Glide加载

  • ImagePicker doCrop(int aspectX, int aspectY, int outputX, int outputY) 图片裁剪
    参数说明:aspectX、aspectY 裁剪框宽高
    outputX、outputY图片输出缩放宽高

  • void start(Activity activity, int requestCode) 发起选择图片

  • void start(Fragment fragment, int requestCode) 兼容Fragment

1.4目录结构分析

视图层

类名描述
ImageDataActivity主图片展示页面,ViewStub进行视图管理,监听文件夹列表改变刷新数据等
ImagePagerActivity照片的预览页面
ImageCropActivity裁剪页
ImageFloderPop文件夹列表页,采用PopupWindow设计,灵活调整位置

data数据层

类名描述
ImageContants常量类,包含各个页面跳转的请求码、数据传递键值、文件名前缀等
ImageDataModel图片数据层,图片、文件夹等数据管理类
ImageBean图片实体类
ImageFloderBean图片文件夹实体类
ImagePickerCropParams图片裁剪各参数
ImagePickerOptions图片选择的参数,调用start()方法时,由此类的具体事例进行构建

2.Module目录结构

这里写图片描述

3.部分代码设计

3.1Builder设计


/**
 * 通过build模式构造
 *
 * @author yangjiaming
 */

public class ImagePicker {
    /**
     * 返回结果中包含图片数据的Intent的键值
     */
    public static final String INTENT_RESULT_DATA = "ImageBeans";

    private ImagePickerOptions mOptions;

    public ImagePicker() {
        mOptions = new ImagePickerOptions();
    }

    /**
     * 设置模式
     *
     * @param mode 拍照、多选、单选
     */
    public ImagePicker pickType(ImagePickType mode) {
        mOptions.setType(mode);
        return this;
    }

    /**
     * 设置多选
     *
     * @param maxNum 最大数
     */
    public ImagePicker maxNum(int maxNum) {
        mOptions.setMaxNum(maxNum);
        return this;
    }

    /**
     * 是否需要拍照入口
     */
    public ImagePicker needCamera(boolean b) {
        mOptions.setNeedCamera(b);
        return this;
    }

    public ImagePicker cachePath(String path) {
        mOptions.setCachePath(path);
        return this;
    }

    public ImagePicker doCrop(ImagePickerCropParams cropParams) {
        mOptions.setNeedCrop(cropParams != null);
        mOptions.setCropParams(cropParams);
        return this;
    }

    public ImagePicker doCrop(int aspectX, int aspectY, int outputX, int outputY) {
        mOptions.setNeedCrop(true);
        mOptions.setCropParams(new ImagePickerCropParams(aspectX, aspectY, outputX, outputY));
        return this;
    }

    public ImagePicker displayer(IImagePickerDisplayer displayer) {
        ImageDataModel.getInstance().setDisplayer(displayer);
        return this;
    }

    /**
     * 发起选择图片
     *
     * @param activity    发起的Activity
     * @param requestCode 请求码
     */
    public void start(Activity activity, int requestCode) {
        Intent intent = new Intent(activity, ImageDataActivity.class);
        intent.putExtra(ImageContants.INTENT_KEY_OPTIONS, mOptions);
        activity.startActivityForResult(intent, requestCode);
    }

    /**
     * 发起选择图片
     *
     * @param fragment    发起的Fragment
     * @param requestCode 请求码
     */
    public void start(Fragment fragment, int requestCode) {
        Intent intent = new Intent(fragment.getActivity(), ImageDataActivity.class);
        intent.putExtra(ImageContants.INTENT_KEY_OPTIONS, mOptions);
        fragment.startActivityForResult(intent, requestCode);
    }
}

3.2拍照(7.0适配)

/**
 * 拍照
 *
 * @param activity    发起拍照的帮助类
 * @param requestCode 请求码
 * @param cachePath   缓存地址
 * @return 拍照后图片地址
 */
public static String takePhoto(Activity activity, int requestCode, String cachePath) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    //自定义缓存路径
    File tempFile = getPhotoTempFile(cachePath);
    try {
        // 7.0以上需要适配StickMode
        if (Build.VERSION.SDK_INT >= 24) {
            Uri imageUri = FileProvider.getUriForFile(activity, ImagePickerFileProvider.getAuthorities(activity), tempFile);
            // 添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            // 将拍取的照片保存到指定URI
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            activity.startActivityForResult(intent, requestCode);
        } else {
            // 将拍取的照片保存到指定URI
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
            activity.startActivityForResult(intent, requestCode);
        }
        return tempFile.getAbsolutePath();
    } catch (Exception e) {
        Toast.makeText(activity, R.string.error_can_not_takephoto, Toast.LENGTH_SHORT).show();
        return null;
    }
}

3.3内容提供者获取照片

// 得到一个游标
ContentResolver cr = context.getContentResolver();
Cursor cur = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null);

if (cur != null && cur.moveToFirst()) {
    // 图片总数
    allImgFloder.setNum(cur.getCount());

    // 获取指定列的索引
    int imageIDIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
    int imagePathIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    int imageModifyIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED);
    int imageWidthIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH);
    int imageHeightIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT);
    int floderIdIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_ID);
    int floderNameIndex = cur.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);

    do {
        String imageId = cur.getString(imageIDIndex);
        String imagePath = cur.getString(imagePathIndex);
        String lastModify = cur.getString(imageModifyIndex);
        String width = cur.getString(imageWidthIndex);
        String height = cur.getString(imageHeightIndex);
        String floderId = cur.getString(floderIdIndex);
        String floderName = cur.getString(floderNameIndex);

        if (new File(imagePath).exists()) {
            //创建图片对象
            ImageBean imageBean = new ImageBean();
            imageBean.setImageId(imageId);
            imageBean.setImagePath(imagePath);
            imageBean.setLastModified(ImagePickerComUtils.isNotEmpty(lastModify) ? Long.valueOf(lastModify) : 0);
            imageBean.setWidth(ImagePickerComUtils.isNotEmpty(width) ? Integer.valueOf(width) : 0);
            imageBean.setHeight(ImagePickerComUtils.isNotEmpty(height) ? Integer.valueOf(height) : 0);
            imageBean.setFloderId(floderId);
            mAllImgList.add(imageBean);
            //更新文件夹对象
            ImageFloderBean floderBean;
            if (floderMap.containsKey(floderId)) {
                floderBean = floderMap.get(floderId);
            } else {
                floderBean = new ImageFloderBean(floderId, floderName);
            }
            floderBean.setFirstImgPath(imagePath);
            floderBean.gainNum();
            floderMap.put(floderId, floderBean);
        }

    } while (cur.moveToNext());
    cur.close();
}

源码下载

优秀开源项目

[1]ImagePicker https://github.com/Vanish136/ImagePicker
[2]PictureSelector https://github.com/LuckSiege/PictureSelector
[3]ImagePicker https://github.com/jeasonlzy/ImagePicker
[4]TakePhoto https://github.com/crazycodeboy/TakePhoto
[5]PhotoView https://github.com/chrisbanes/PhotoView
[6]cropview https://github.com/oginotihiro/cropview
[7]大杂烩 https://github.com/AbrahamCaiJin/CommonUILibrary/blob/master/README.md

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值