私人订制Android本地图片选择器

效果图

效果图

需求分析

  • 网格布局显示本地图片
  • 支持图片多选
  • 支持选中的图片预览
  • 未选择图片时不可预览
  • 由已选多图变为无图时可退出图片选择页面
  • 图片已选达到上限后依然可以跳转图片选择页面

第三方框架使用

史上最强的安卓图片选择器——GalleryFinal;
简书博客地址——//www.greatytc.com/p/48ddd6756b7a

实现目标

应用并修改第三方框架GalleryFinal源码,实现效果图的样式。

具体实现

  • 导入GalleryFinal源码
    从GalleryFinal的Github仓库中拷贝仓库地址,使用git指令cloneGalleryFinal源代码到本地:
git clone https://github.com/pengjianbo/GalleryFinal.git

选择Android Studio菜单栏File->New->import Module...,导入GalleryFinal源代码到Android Studio:

导入GalleryFinal源码

详情参考:
【Mac流派】程序猿修炼之道(6)-技能篇之git指令
Android Studio导入第三方库的三种方法

  • 写一个GridView
    这里声明一下,选择图片的Activity是GalleryFinal自带的,所以我们这里要写的GridView是用来显示选中并返回的图片,这里的代码就不放出来了,效果图如下:

    未选择时

    选择图片时

    本人实现的可支持最大图片数量是5,在图片选满的时候依然显示“+”,用以跳转图片选择页面选择其他图片。

  • 初始化GalleryFinal配置

    • 通过代码设置图片选择器的标题栏背景颜色,标题文本颜色,浮动按钮颜色;
    • 通过监听事件,达到滚动时不加载图片,停下来时加载图片,实现优化;
    • 初始化功能配置;

    为防止代码分开查看导致逻辑的混乱,将上述配置代码一齐贴上,下面的代码可放在跳转图片选择界面的按钮点击事件中。这里强调一下:mThemeConfig = new ThemeConfig.Builder() .setTitleBarBgColor等方法传参是整型,但是其传入的是颜色值而非资源文件的id。
    代码注释较详细,其他不做过多讲解:

// ------- 声明 -------
// 主题配置
private ThemeConfig mThemeConfig;
// 图片加载器
private cn.finalteam.galleryfinal.ImageLoader mGlidImgLoader;
// 滚动监听事件
private PauseOnScrollListener mPauseOnScrollListener;
// 功能配置
private FunctionConfig mFunctionConfig;
// 核心配置
private CoreConfig mCoreConfig;
// ------- 实现 -------
// 获取标题栏背景颜色
int colorTitleBarBg = ContextCompat.getColor(Activity.this, R.color.titleBarBgColor);
// 标题栏文字颜色
int colorTitleBarText = ContextCompat.getColor(Activity.this, R.color.titleBarTextColor);
// 浮动按钮常规颜色
int colorFabNormal = ContextCompat.getColor(Activity.this, R.color.color_ffaa2a);
// 浮动按钮点击颜色
int colorFabPressed = ContextCompat.getColor(Activity.this, R.color.color_e29428);
// 标题栏按钮颜色
int colorTitleBarIcon = ContextCompat.getColor(MainActivity.this, R.color.colorTitleBarIcon);
// 设置主题
mThemeConfig = new ThemeConfig.Builder()
        .setTitleBarBgColor(colorTitleBarBg) // 设置标题栏背景颜色
        .setTitleBarTextColor(colorTitleBarText)    // 设置标题栏文字颜色
        .setFabNormalColor(colorFabNormal)  // 设置浮动按钮常规颜色
        .setFabPressedColor(colorFabPressed)    // 设置浮动按钮点击颜色
        .setCheckSelectedColor(colorFabNormal)  // 设置选中标记(对勾)的颜色和按钮的颜色相同
        .setTitleBarIconColor(colorTitleBarIcon) // 设置标题栏按钮颜色
        .setIconBack(R.drawable.ic_back) // 设置返回按钮
        .build();
// 初始化图片加载器
mGlidImgLoader = new GlideImageLoader();
// 初始化监听事件
mPauseOnScrollListener = new GlidePauseOnScrollListener(false, true);
// 初始化功能配置
FunctionConfig.Builder funConBuilder = new FunctionConfig.Builder();
// 设置最多可选择5张照图片
funConBuilder.setMutiSelectMaxSize(5);
// 设置图片不可编辑
funConBuilder.setEnableEdit(false);
// 设置图片不可旋转
funConBuilder.setEnableRotate(false);
// 设置图片不可裁剪
funConBuilder.setEnableCrop(false);
// 设置不可通过照相选择照片
funConBuilder.setEnableCamera(false);
// 设置添加过滤集合,过滤掉之前选中的图片
//        funConBuilder.setFilter(mPhotoList);
// 不过滤图片,而是将之前选中的图片设置为选中状态
funConBuilder.setSelected(mPhotoList);
// 设置可预览
funConBuilder.setEnablePreview(true);
// 功能配置
mFunctionConfig = funConBuilder.build();
// 初始化核心配置
mCoreConfig = new CoreConfig.Builder(ReportActivity.this, mGlidImgLoader, mThemeConfig)
        .setFunctionConfig(mFunctionConfig) // 添加功能配置
        .setPauseOnScrollListener(mPauseOnScrollListener) // 滑动停止加载事件
        .setNoAnimcation(true) // 无特效动画
        .build();
// 实例化
GalleryFinalGalleryFinal.init(mCoreConfig);
// 多图片选择打开相册
GalleryFinal.openGalleryMuti(Constants.REQUEST_CODE_GALLERY, mFunctionConfig,
        mOnHandlerResultCallback);
// 初始化图片加载器
initImageLoader(ReportActivity.this);
/**
 * 初始化图片加载器
 *
 * @param context
 */
private void initImageLoader(Context context) {
    // 图片加载器配置
    ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(this);
    // 设置线程优先级
    config.threadPriority(Thread.NORM_PRIORITY - 2);
    // 禁止内存缓存
    config.denyCacheImageMultipleSizesInMemory();
    // 设置磁盘缓存文件名生成器
    config.diskCacheFileNameGenerator(new Md5FileNameGenerator());
    // 设置磁盘缓存大小
    config.diskCacheSize(20 * 1024 * 1024);
    // 设置任务进程执行顺序:先进后出
    config.tasksProcessingOrder(QueueProcessingType.LIFO);
    // 调试使用,若是发布版,需要移除代码
    config.writeDebugLogs();
    // 初始化图片加载器
    ImageLoader.getInstance().init(config.build());
}
  • 选择图片返回的回调实现
/**
 * 回调处理
 */
private GalleryFinal.OnHanlderResultCallback mOnHandlerResultCallback = new GalleryFinal.OnHanlderResultCallback() {
    @Override
    public void onHanlderSuccess(int reqeustCode, List<PhotoInfo> resultList) {
        // 清除原来列表中的图片
        mPhotoList.clear();
        // 返回图片列表
        mPhotoList.addAll(resultList);
        // 刷新页面
        mPhotoAdapter.notifyDataSetChanged();
    }
    @Override
    public void onHanlderFailure(int requestCode, String errorMsg) {
        // 错误提示
        Toast.makeText(Activity.this, errorMsg, Toast.LENGTH_SHORT).show();
    }
};

基于上述代码,可得到效果图如下:

初步效果图

修改源码

通过运行调试,发现框架中有些功能与需求不一致,因此我产生了修改源码的想法,总结需要更改的原功能点如下:

  • 选择完达到上限数量的图片后,无法重新回到图片选择页面


    选择图片数量达到上限时无法进入图片选择页面
  • 无图片选择时,无法点击浮动按钮进行返回


    无图片时无法返回
  • 无图片选择时,预览按钮依然存在


    无图片选择时预览按钮依然存在并可以点击

针对以上需要修改的功能,源码修改如下:

  • 选择图片到达上限依然可以返回图片选择页面
    因图片选择页面的跳转在openGalleryMuti方法里实现:
// 多图片选择打开相册
GalleryFinal.openGalleryMuti(Constants.REQUEST_CODE_GALLERY, mFunctionConfig, 
       mOnHandlerResultCallback);

所以我们来看看openGalleryMuti方法的源码:


openGalleryMuti方法源码

可以发现,源码中有这么一个判断逻辑:

if (config.getSelectedList() != null && config.getSelectedList().size() > config.getMaxSize()) {
    if(callback != null){
        callback.onHanlderFailure(requestCode, mCoreConfig.getContext().getString(R.string.select_max_tips));
    }
    return;
}

其作用是当选中的图片数量超过最大值时,返回打开本地图片选择器失败的提示信息。之前我们提到,需求中我们实际多显示了一张图片:


“添加”图片

且在配置的时候传入的是添加了一张图片以后的图片列表:

// 不过滤图片,而是将之前选中的图片设置为选中状态
funConBuilder.setSelected(mPhotoList);

所以会导致界面无法跳转,我们有三个策略:
1.将多添加的图片放到adapter里面处理,adapter外部保持选中图片数量与选择页面传入图片的数量一致
2.setSelected传入图片列表之前将mPhotoList移除多出的图片
3.注释掉源码中对图片数量上限的判断

  • 无图选择时,点击浮动按钮可以返回
    可能有人不解,为何不点击标题栏的返回按钮返回而要点击浮动按钮返回?其原因是,若之前我选择好图片,但是想想,现在我不想要选择的图片了,这时候我们想把图片清空掉,这时候需要点击浮动按钮,来更新选中图片的列表。
    想到这是浮动按钮的点击事件,所以我们到源码的GallerySelectActivity中浮动按钮的事件回调方法中:
    浮动按钮点击事件

    这段代码仅仅在选中图片的数量大于0的时候才执行操作,所以我们添加一个条件,修改后的代码如下:
if (mSelectPhotoList.size() > 0) {
    if (!GalleryFinal.getFunctionConfig().isEditPhoto()) {
        resultData(mSelectPhotoList);
    } else {
        toPhotoEdit();
    }
} else {
    // 添加的代码,使未选中图片时也可返回
    resultData(mSelectPhotoList);
}
mSelectPhotoList初始化

考虑到选中图片的列表在声明时已经初始化,所以不用担心图片返回的回调事件传入空指针对象。

  • 未选择图片时不显示预览按钮
    通过布局的id——iv_preview在PhotoSelectActivity中查找,在refreshSelectCount方法里找到了对预览按钮可见性的设置:
    refreshSelectCount方法源码

    从源码中可以看到,预览按钮的可见性判断逻辑仅仅与isEnablePreview有关,而没有和选中的图片数量进行关联,所以我们修改代码如下:
public void refreshSelectCount() {
    mTvChooseCount.setText(getString(R.string.selected, mSelectPhotoList.size(), GalleryFinal.getFunctionConfig().getMaxSize()));
    if (mSelectPhotoList.size() > 0 && GalleryFinal.getFunctionConfig().isMutiSelect()) {
        mIvClear.setVisibility(View.VISIBLE);
        if (GalleryFinal.getFunctionConfig().isEnablePreview()) {
            mIvPreView.setVisibility(View.VISIBLE);
        }
    } else {
        mIvClear.setVisibility(View.GONE);
        mIvPreView.setVisibility(View.GONE);
    }
}

当选中图片列表大小为0的时候,隐藏预览按钮;大于0的时候再根据isEnablePreview()来判断是否显示预览按钮。

修改布局和代码逻辑

布局和代码逻辑的修改,其思路与上一节修改源码一样,因需求的效果图功能与GalleryFinal的功能基本一致,逻辑上并不需要做很多的修改,而布局的修改仅涉及到ImageButton变成Button,ImageView变成TextView以及控件位置的调整,在关联控件上和点击事件根据id来判断事件处理上做相应修改即可,在此不做赘述。

总结

使用GalleryFinal订制属于自己的图片选择器并不难,只需要循着需求的功能点,按照代码的逻辑一点点追踪源码并进行修改订制即可。诚恳地说,GalleryFinal框架的可移植性确实很强,在此推荐大家了解一下!

本篇文章的Demo已上传Github,欢迎访问指导!
原创不易,转载请注明链接://www.greatytc.com/p/fd5ebfc4725e,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容