一款好用的图片选择器

前言

最近项目中需要使用到图片选择器,网上搜索了一下,这方面的库有很多,最终是参考的ImageSelector的源代码实现。在使用的过程中,按照交互需求,我新增了一下功能,并修复了几个bug。最后我自己做了一些封装,采用Builder的设计模式设计实现了一个更加通用的库。在此做个记录,具体实现效果如下所示:

代码地址:https://github.com/yushiwo/Universal-Image-Selector

模块设计

具体代码的逻辑实现,我就不在这边讲了,大家有兴趣可以看下源码~
在对模块结构设计过程中,我参考了Universal-Image-Loader的设计思想。对外暴露ImageSelector.java和ImageSelectorConfigration.java类,用户通过ImageSelectorConfigration.java配置组件相关功能和样式;然后通过ImageSelector.java获取configration初始化组件以及启动组件的相关界面。

ImageSelector

/**
 * @author hzzhengrui
 * @Date 16/10/20
 * @Description
 */
public class ImageSelector {

    private static final String TAG = ImageSelector.class.getSimpleName();

    private static final String WARNING_RE_INIT_CONFIG = "Try to initialize ImageSelector which had already been initialized before. " + "To re-init ImageSelector with new configuration call ImageSelector.destroy() at first.";
    private static final String ERROR_INIT_CONFIG_WITH_NULL = "ImageSelector configuration can not be initialized with null";
    private static final String ERROR_NOT_INIT = "ImageSelector must be init with configuration before using";

    private static ImageSelector sInstance;

    private ImageSelectorConfiguration configuration;

    public static ImageSelector getInstance(){
        if (sInstance == null) {
            synchronized (ImageSelector.class) {
                if (sInstance == null) {
                    sInstance = new ImageSelector();
                }
            }
        }
        return sInstance;
    }

    // TODO: 16/10/20 实现config设置
    public void init(ImageSelectorConfiguration configuration){
        if(configuration == null){
            throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
        }

        if(this.configuration == null){
            this.configuration = configuration;
            ImageSelectorProxy.getInstance().setConfiguration(configuration);
        }else {
            Log.w(TAG, WARNING_RE_INIT_CONFIG);
        }
    }

    public boolean isInited() {
        return configuration != null;
    }

    private void checkConfiguration() {
        if (configuration == null) {
            throw new IllegalStateException(ERROR_NOT_INIT);
        }
    }

    public void destroy() {
        if (configuration != null) {
            configuration = null;
        }
    }

    /**
     * 开启图片选择页面
     * @param activity
     * @param imageList
     */
    public void launchSelector(Activity activity, ArrayList<String> imageList){
        checkConfiguration();
        ImageSelectorActivity.start(activity, imageList);
    }

    /**
     * 开启图片可删除选中图片的预览界面
     * @param activity
     * @param imageList
     * @param position
     */
    public void launchDeletePreview(Activity activity, ArrayList<String> imageList, int position){
        checkConfiguration();
        ImagePreviewActivity.startDeletePreview(activity, imageList, position);
    }

}

ImageSelectorConfiguration

/**
* @author hzzhengrui
* @Date 16/10/20
* @Description
*/
public class ImageSelectorConfiguration {

   /** 最多可以选择图片的数目 */
   public int maxSelectNum = -1;
   /** 图片选择界面的列数 */
   public int spanCount = -1;

   public int selectMode = -1;
   public boolean isShowCamera = true;
   public boolean isEnablePreview = true;

   public boolean isEnableCrop = false;

   public Drawable imageOnLoading = null;
   public int imageResOnLoading = 0;

   public Drawable imageOnError = null;
   public int imageResOnError = 0;

   int titleBarColor = -1;
   int statusBarColor = -1;
   float titleHeight = -1;


   public ImageSelectorConfiguration(final Builder builder) {
       maxSelectNum = builder.maxSelectNum;
       spanCount = builder.spanCount;
       selectMode = builder.selectMode;
       isShowCamera = builder.isShowCamera;
       isEnablePreview = builder.isEnablePreview;
       isEnableCrop = builder.isEnableCrop;
       imageOnLoading = builder.imageOnLoading;
       imageResOnLoading = builder.imageResOnLoading;
       imageOnError = builder.imageOnError;
       imageResOnError = builder.imageResOnError;
       titleBarColor = builder.titleBarColor;
       statusBarColor = builder.statusBarColor;
       titleHeight = builder.titleHeight;
   }

   /**
    * 生成默认的图片选择器配置
    * @param context
    * @return
    */
   public static ImageSelectorConfiguration createDefault(Context context){
       return new Builder(context).build();
   }

   public static class Builder{

       public static final int DEFAULT_MAX_SELECT_NUM = 9;
       public static final int DEFAULT_SPAN_COUNT = 4;
       public static final int DEFAULT_SELECT_MODE = ImageSelectorConstant.MODE_MULTIPLE;


       private Context context;

       /** 最多可以选择图片的数目 */
       private int maxSelectNum = DEFAULT_MAX_SELECT_NUM;
       /** 图片选择界面的列数 */
       private int spanCount = DEFAULT_SPAN_COUNT;

       private int selectMode = DEFAULT_SELECT_MODE;
       private boolean isShowCamera = true;
       private boolean isEnablePreview = true;
       private boolean isEnableCrop = false;

       private Drawable imageOnLoading = null;
       private int imageResOnLoading = 0;

       private Drawable imageOnError = null;
       private int imageResOnError = 0;

       private int titleBarColor = -1;
       private int statusBarColor = -1;
       private float titleHeight = -1;

       public Builder(Context context) {
           this.context = context.getApplicationContext();
       }

       /**
        * 设置做多可选图片的数目
        * @param maxSelectNum
        * @return
        */
       public Builder setMaxSelectNum(int maxSelectNum){
           this.maxSelectNum = maxSelectNum;
           return this;
       }

       /**
        * 设置图片选择页面展示的列数
        * @param spanCount
        * @return
        */
       public Builder setSpanCount(int spanCount){
           this.spanCount = spanCount;
           return this;
       }

       /**
        * 设置选择模式,单选或者多选
        * @param selectMode
        * @return
        */
       public Builder setSelectMode(int selectMode) {
           this.selectMode = selectMode;
           return this;
       }

       /**
        * 设置是否可裁剪
        * @param enableCrop
        * @return
        */
       public Builder setEnableCrop(boolean enableCrop) {
           isEnableCrop = enableCrop;
           return this;
       }

       /**
        * 设置是否支持选择时候预览
        * @param enablePreview
        * @return
        */
       public Builder setEnablePreview(boolean enablePreview) {
           isEnablePreview = enablePreview;
           return this;
       }

       /**
        * 设置是否显示拍照按钮
        * @param showCamera
        * @return
        */
       public Builder setShowCamera(boolean showCamera) {
           isShowCamera = showCamera;
           return this;
       }

       /**
        * 设置图片加载时候的默认图
        * @param
        * @return
        */
       public Builder setImageOnLoading(Drawable image) {
           this.imageOnLoading = image;
           return this;
       }

       public Builder setImageOnLoading(int imageRes){
           this.imageResOnLoading = imageRes;
           return this;
       }

       public Builder setImageOnError(Drawable image) {
           this.imageOnError = image;
           return this;
       }

       public Builder setImageOnError(int imageRes){
           this.imageResOnError = imageRes;
           return this;
       }

       /**
        * 设置选择器titlebar的颜色
        * @param colorRes
        * @return
        */
       public Builder setTitleBarColor(int colorRes){
           this.titleBarColor = colorRes;
           return this;
       }

       /**
        * 设置选择器statusbar的颜色
        * @param colorRes
        * @return
        */
       public Builder setStatusBarColor(int colorRes){
           this.statusBarColor = colorRes;
           return this;
       }

       public Builder setTitleHeight(float titleHeight){
           this.titleHeight = titleHeight;
           return this;
       }

       public ImageSelectorConfiguration build(){
           initEmptyFieldsWithDefaultValues();
           return new ImageSelectorConfiguration(this);
       }

       private void initEmptyFieldsWithDefaultValues() {
           if(maxSelectNum <= 0){
               maxSelectNum = DEFAULT_MAX_SELECT_NUM;
           }
           if(spanCount <= 0){
               spanCount = DEFAULT_SPAN_COUNT;
           }
           if(selectMode <= 0){
               selectMode = DEFAULT_SELECT_MODE;
           }

           if(imageResOnLoading == 0 && imageOnLoading == null){
               imageResOnLoading = R.drawable.uis_ic_placeholder;
           }

           if(imageResOnError == 0 && imageOnError == null){
               imageResOnError = R.drawable.uis_ic_placeholder;
           }

           if(titleBarColor == -1){
               titleBarColor = R.color.uis_black;
           }

           if(statusBarColor == -1){
               statusBarColor = R.color.uis_black;
           }

           if(titleHeight <= 0){
               titleHeight = 48;
           }
       }
   }

}

在组件内部,通过一个代理类ImageSelectorProxy.java,统一处理configration的相关配置

/**
* @author hzzhengrui
* @Date 16/10/24
* @Description
*/
public class ImageSelectorProxy implements IProxy{

   private static final String ERROR_NOT_INIT = "ImageSelector must be init with configuration before using";

   private static ImageSelectorProxy sInstance;

   ImageSelectorConfiguration configuration;

   public static ImageSelectorProxy getInstance(){
       if (sInstance == null) {
           synchronized (ImageSelector.class) {
               if (sInstance == null) {
                   sInstance = new ImageSelectorProxy();
               }
           }
       }
       return sInstance;
   }

   /**
    * 设置图片选择器的配置
    * @param configuration
    */
   public void setConfiguration(ImageSelectorConfiguration configuration){
       this.configuration = configuration;
   }

   private void checkConfiguration() {
       if (configuration == null) {
           throw new IllegalStateException(ERROR_NOT_INIT);
       }
   }

   @Override
   public int getMaxSelectNum() {
       checkConfiguration();
       return configuration.maxSelectNum;
   }

   @Override
   public int getSpanCount() {
       checkConfiguration();
       return configuration.spanCount;
   }

   @Override
   public int getSelectMode() {
       checkConfiguration();
       return configuration.selectMode;
   }

   @Override
   public boolean isEnablePreview() {
       checkConfiguration();
       return configuration.isEnablePreview;
   }

   @Override
   public boolean isShowCamera() {
       checkConfiguration();
       return configuration.isShowCamera;
   }

   @Override
   public boolean isEnableCorp() {
       checkConfiguration();
       return configuration.isEnableCrop;
   }

   @Override
   public Drawable getImageOnLoading(Resources res) {
       checkConfiguration();
       return configuration.imageResOnLoading != 0 ? res.getDrawable(configuration.imageResOnLoading) : configuration.imageOnLoading;
   }

   @Override
   public Drawable getImageOnError(Resources res) {
       checkConfiguration();
       return configuration.imageResOnError != 0 ? res.getDrawable(configuration.imageResOnError) : configuration.imageOnError;
   }

   @Override
   public int getTitleBarColor(Resources res) {
       checkConfiguration();
       return res.getColor(configuration.titleBarColor);
   }

   @Override
   public int getStatusBarColor(Resources res) {
       checkConfiguration();
       return res.getColor(configuration.statusBarColor);
   }

   @Override
   public float getTitleHeight() {
       checkConfiguration();
       return configuration.titleHeight;
   }
}

使用

  • build.gradle配置
compile 'com.netease.imageSelector:android-imageselector-lib:1.0.1'
  • AndroidManifest配置
    // 设置权限
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET"/>
    
    // 注册activity
    <activity
        android:name="com.netease.imageSelector.view.ImageSelectorActivity"
        android:theme="@style/Theme.AppCompat.NoActionBar" />
    <activity
        android:name="com.netease.imageSelector.view.ImagePreviewActivity"
        android:theme="@style/Theme.AppCompat.NoActionBar" />
    <activity
        android:name="com.netease.imageSelector.view.ImageCropActivity"
        android:theme="@style/Theme.AppCompat.NoActionBar" />
  • 初始化
    在需要使用此组件的Activity的onCreate方法中,或者在自定义Application的onCreate方法中初始化。
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 获取默认配置
        ImageSelectorConfiguration configuration = ImageSelectorConfiguration.createDefault(this);

          // 自定义图片选择器
//        ImageSelectorConfiguration configuration = new ImageSelectorConfiguration.Builder(this)
//                .setMaxSelectNum(9)
//                .setSpanCount(4)
//                .setSelectMode(ImageSelectorConstant.MODE_MULTIPLE)
//                .setTitleHeight(48)
//                .build();
          
        ImageSelector.getInstance().init(configuration);
    }
}
  • 启动组件

    • 开启图片选择界面
    /**
     * 开启图片选择页面
     * @param activity
     * @param imageList
     */
    public void launchSelector(Activity activity, ArrayList<String> imageList)
    
    • 开启图片预览界面(带删除功能)
    /**
     * 开启图片可删除选中图片的预览界面
     * @param activity
     * @param imageList
     * @param position
     */
    public void launchDeletePreview(Activity activity, ArrayList<String> imageList, int position)  ImagePreviewActivity.startDeletePreview(activity, imageList, position)
    
  • 处理回调结果
    组件的界面,都是通过startActivityForResult的方式启动,所以,我们只需在自己调起组件的界面,处理返回结果即可。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (data == null) {
        return;
    }
    // 接收图片选择器返回结果,更新所选图片集合
    if (requestCode == REQUEST_PREVIEW || requestCode == REQUEST_IMAGE) {
        ArrayList<String> newFiles = data.getStringArrayListExtra(OUTPUT_LIST);
        if (newFiles != null) {
            updateUI(newFiles);
        }
    }
}

因为时间的原因,还没有实现可配置图片缓存相关,之后有时间会加上~大家在使用库的过程中,有任何问题,欢迎反馈哈~

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,827评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,729评论 2 51
  • 文/古月言 背道而驰,我们会不会相遇在地球另一点?--------题记 上一篇 目录 第二十五章 自转和公转 ...
    古月言阅读 672评论 7 10
  • 失败的确是一件让人难以接受的事。 虽然说失败是成功之母,可在真正的失败面前,一切都显得那么苍白无力,只有“我失败了...
    戚小暖阅读 348评论 2 0