├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values-zh
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── me
│ │ │ └── nereo
│ │ │ └── multiimageselector
│ │ │ └── MainActivity.java
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── nereo
│ │ └── multiimageselector
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── multi-image-selector
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── public.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── strings.xml
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── mis_asv.png
│ │ │ │ ├── mis_asy.png
│ │ │ │ ├── mis_btn_selected.png
│ │ │ │ ├── mis_default_check.png
│ │ │ │ ├── mis_default_error.png
│ │ │ │ ├── mis_btn_unselected.png
│ │ │ │ └── mis_text_indicator.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── mis_btn_back.png
│ │ │ │ ├── mis_ic_menu_back.png
│ │ │ │ ├── mis_default_check_s.png
│ │ │ │ └── mis_selector_indicator.png
│ │ │ ├── xml
│ │ │ │ └── mis_file_paths.xml
│ │ │ ├── values-sw360dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values-sw480dp
│ │ │ │ └── dimens.xml
│ │ │ ├── values-sw720dp
│ │ │ │ └── dimens.xml
│ │ │ ├── color
│ │ │ │ ├── mis_default_text_color.xml
│ │ │ │ └── mis_folder_text_color.xml
│ │ │ ├── layout
│ │ │ │ ├── mis_list_item_camera.xml
│ │ │ │ ├── mis_list_item_image.xml
│ │ │ │ ├── mis_activity_default.xml
│ │ │ │ ├── mis_cmp_customer_actionbar.xml
│ │ │ │ ├── mis_fragment_multi_image.xml
│ │ │ │ └── mis_list_item_folder.xml
│ │ │ ├── values-zh
│ │ │ │ └── strings.xml
│ │ │ ├── drawable
│ │ │ │ └── mis_action_btn.xml
│ │ │ └── layout-v14
│ │ │ │ └── mis_fragment_multi_image.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── nereo
│ │ │ │ └── multi_image_selector
│ │ │ │ ├── utils
│ │ │ │ ├── MISFileProvider.java
│ │ │ │ ├── ScreenUtils.java
│ │ │ │ ├── TimeUtils.java
│ │ │ │ └── FileUtils.java
│ │ │ │ ├── bean
│ │ │ │ ├── Folder.java
│ │ │ │ └── Image.java
│ │ │ │ ├── view
│ │ │ │ ├── SquaredImageView.java
│ │ │ │ └── SquareFrameLayout.java
│ │ │ │ ├── MultiImageSelector.java
│ │ │ │ ├── adapter
│ │ │ │ ├── FolderAdapter.java
│ │ │ │ └── ImageGridAdapter.java
│ │ │ │ ├── MultiImageSelectorActivity.java
│ │ │ │ └── MultiImageSelectorFragment.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── nereo
│ │ └── multi_image_selector
│ │ └── ApplicationTest.java
├── project.properties
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── art
├── select_1.png
├── select_2.png
├── select_3.png
└── example_1.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── LICENSE
├── bintray-release.gradle
├── gradlew.bat
├── gradlew
├── README_zh.md
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.iml
3 |
--------------------------------------------------------------------------------
/multi-image-selector/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.iml
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':multi-image-selector'
2 |
--------------------------------------------------------------------------------
/art/select_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/art/select_1.png
--------------------------------------------------------------------------------
/art/select_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/art/select_2.png
--------------------------------------------------------------------------------
/art/select_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/art/select_3.png
--------------------------------------------------------------------------------
/art/example_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/art/example_1.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #21282C
4 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_asv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_asv.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_asy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_asy.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xhdpi/mis_btn_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xhdpi/mis_btn_back.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xhdpi/mis_ic_menu_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xhdpi/mis_ic_menu_back.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_btn_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_btn_selected.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_default_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_default_check.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_default_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_default_error.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xhdpi/mis_default_check_s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xhdpi/mis_default_check_s.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_btn_unselected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_btn_unselected.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xxhdpi/mis_text_indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xxhdpi/mis_text_indicator.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable-xhdpi/mis_selector_indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq549631030/MultiImageSelector/HEAD/multi-image-selector/src/main/res/drawable-xhdpi/mis_selector_indicator.png
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/xml/mis_file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 120dp
4 | 2dp
5 | 72dp
6 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values-sw360dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 96dp
4 | 2dp
5 | 72dp
6 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values-sw480dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 100dp
4 | 2dp
5 | 72dp
6 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values-sw720dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 120dp
4 | 3dp
5 | 82dp
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 20 11:08:16 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/multi-image-selector/project.properties:
--------------------------------------------------------------------------------
1 | #project
2 | project.name=Multi-Image-Selector
3 | project.groupId=com.hx.multi-image-selector
4 | project.artifactId=multi-image-selector
5 | project.siteUrl=https://github.com/qq549631030/MultiImageSelector
6 | project.gitUrl=https://github.com/qq549631030/MultiImageSelector.git
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/utils/MISFileProvider.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.utils;
2 |
3 | import android.support.v4.content.FileProvider;
4 |
5 | /**
6 | * @author huangx
7 | * @date 2017/12/15
8 | */
9 |
10 | public class MISFileProvider extends FileProvider {
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/nereo/multiimageselector/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multiimageselector;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/multi-image-selector/src/androidTest/java/me/nereo/multi_image_selector/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/color/mis_default_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/color/mis_folder_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | *.iml
17 | .idea/
18 | .gradle/
19 | build/
20 | /*/build/
21 | /local.properties
22 | /.idea/workspace.xml
23 | /.idea/libraries
24 | .DS_Store
25 |
26 | # Local configuration file (sdk path, etc)
27 | local.properties
28 |
29 | # Proguard folder generated by Eclipse
30 | proguard/
31 |
32 | # Log Files
33 | *.log
34 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 多图选择
3 |
4 | 设置
5 |
6 | 选择模式
7 | 单选
8 | 多选
9 | 最大选择数量
10 | 默认数量为9
11 | 是否启用照相机
12 | 是
13 | 否
14 | 图片选择
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MultiImageSelector
3 |
4 | Settings
5 | Select Mode
6 | Single-Choice
7 | Multi-Choice
8 | Max Selected Amount
9 | default amount is 9
10 | Enable Camera
11 | Enable
12 | Disable
13 | Select
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\Nereo\Program\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/bean/Folder.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.bean;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * 文件夹
9 | * Created by Nereo on 2015/4/7.
10 | */
11 | public class Folder {
12 | public String name;
13 | public String path;
14 | public Image cover;
15 | public List images;
16 |
17 | @Override
18 | public boolean equals(Object o) {
19 | try {
20 | Folder other = (Folder) o;
21 | return TextUtils.equals(other.path, path);
22 | }catch (ClassCastException e){
23 | e.printStackTrace();
24 | }
25 | return super.equals(o);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '25.0.1'
6 |
7 | defaultConfig {
8 | applicationId "me.nereo.multiimageselector"
9 | minSdkVersion 12
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile project(':multi-image-selector')
25 | compile 'com.android.support:appcompat-v7:25.1.0'
26 | }
27 |
--------------------------------------------------------------------------------
/multi-image-selector/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:/Nereo/Program/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/view/SquaredImageView.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.ImageView;
6 |
7 | /** An image view which always remains square with respect to its width. */
8 | class SquaredImageView extends ImageView {
9 | public SquaredImageView(Context context) {
10 | super(context);
11 | }
12 |
13 | public SquaredImageView(Context context, AttributeSet attrs) {
14 | super(context, attrs);
15 | }
16 |
17 | @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
18 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
19 | setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_list_item_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
17 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/view/SquareFrameLayout.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.FrameLayout;
6 |
7 | /**
8 | * Created by nereo on 15/11/10.
9 | */
10 | public class SquareFrameLayout extends FrameLayout{
11 | public SquareFrameLayout(Context context) {
12 | super(context);
13 | }
14 |
15 | public SquareFrameLayout(Context context, AttributeSet attrs) {
16 | super(context, attrs);
17 | }
18 |
19 | @Override
20 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
21 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
22 | setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/bean/Image.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.bean;
2 |
3 | import android.text.TextUtils;
4 |
5 | /**
6 | * 图片实体
7 | * Created by Nereo on 2015/4/7.
8 | */
9 | public class Image {
10 | public String path;
11 | public String name;
12 | public long time;
13 |
14 | public Image(String path, String name, long time){
15 | this.path = path;
16 | this.name = name;
17 | this.time = time;
18 | }
19 |
20 | @Override
21 | public boolean equals(Object o) {
22 | try {
23 | Image other = (Image) o;
24 | return TextUtils.equals(this.path, other.path);
25 | }catch (ClassCastException e){
26 | e.printStackTrace();
27 | }
28 | return super.equals(o);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 所有照片
3 | 预览
4 | 没有系统相机
5 | 已经达到最高选择数量
6 | 完成
7 | 张
8 | 拍摄照片
9 | 图片错误
10 | 无权限
11 | 权限拒绝
12 | 好
13 | 拒绝
14 | 浏览图片需要您提供浏览存储的权限
15 | 拍摄照片需要您提交摄像头权限
16 | 保存拍照图片需要您提供写存储权限
17 |
18 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/utils/ScreenUtils.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.utils;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.os.Build;
6 | import android.view.Display;
7 | import android.view.WindowManager;
8 |
9 | /**
10 | * 屏幕工具
11 | * Created by nereo on 15/11/19.
12 | * Updated by nereo on 2016/1/19.
13 | */
14 | public class ScreenUtils {
15 |
16 | public static Point getScreenSize(Context context){
17 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
18 | Display display = wm.getDefaultDisplay();
19 | Point out = new Point();
20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
21 | display.getSize(out);
22 | }else{
23 | int width = display.getWidth();
24 | int height = display.getHeight();
25 | out.set(width, height);
26 | }
27 | return out;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/utils/TimeUtils.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.utils;
2 |
3 | import android.media.ExifInterface;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 | import java.util.Locale;
10 |
11 | /**
12 | * 时间处理工具
13 | * Created by Nereo on 2015/4/8.
14 | */
15 | public class TimeUtils {
16 |
17 | public static String timeFormat(long timeMillis, String pattern){
18 | SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.CHINA);
19 | return format.format(new Date(timeMillis));
20 | }
21 |
22 | public static String formatPhotoDate(long time){
23 | return timeFormat(time, "yyyy-MM-dd");
24 | }
25 |
26 | public static String formatPhotoDate(String path){
27 | File file = new File(path);
28 | if(file.exists()){
29 | long time = file.lastModified();
30 | return formatPhotoDate(time);
31 | }
32 | return "1970-01-01";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/drawable/mis_action_btn.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 |
23 |
24 |
25 | -
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Nereo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/bintray-release.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.novoda.bintray-release'
2 |
3 | // This generates sources.jar
4 | task sourcesJar(type: Jar) {
5 | from android.sourceSets.main.java.srcDirs
6 | classifier = 'sources'
7 | }
8 |
9 | task javadoc(type: Javadoc) {
10 | source = android.sourceSets.main.java.srcDirs
11 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
12 | failOnError false
13 | }
14 |
15 | // This generates javadoc.jar
16 | task javadocJar(type: Jar, dependsOn: javadoc) {
17 | classifier = 'javadoc'
18 | from javadoc.destinationDir
19 | }
20 |
21 | artifacts {
22 | archives javadocJar
23 | archives sourcesJar
24 | }
25 |
26 | // javadoc configuration
27 | javadoc {
28 | options {
29 | encoding "UTF-8"
30 | charSet 'UTF-8'
31 | author true
32 | }
33 | }
34 |
35 | afterEvaluate {
36 | Task bintrayUploadTask = tasks.findByName('bintrayUpload')
37 | Task uploadArchivesTask = tasks.findByName('uploadArchives')
38 | if (bintrayUploadTask != null && uploadArchivesTask != null) {
39 | bintrayUploadTask.dependsOn uploadArchivesTask
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_list_item_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
18 |
19 |
27 |
28 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | All Images
3 | Preview
4 | No system camera found
5 | Select images amount is limit
6 | Done
7 | %1$s(%2$d/%3$d)
8 | Shot
9 | Take photo
10 | Image error
11 | Has no permission
12 | Permission Deny
13 | OK
14 | CANCEL
15 | Storage read permission is needed to pick files.
16 | Camera permission is needed to take photo.
17 | Storage write permission is needed to save the image.
18 |
19 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_activity_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_cmp_customer_actionbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
25 |
42 |
43 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_fragment_multi_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
25 |
26 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout-v14/mis_fragment_multi_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
26 |
27 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/multi-image-selector/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion '25.0.1'
6 |
7 | defaultConfig {
8 | minSdkVersion 12
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.2.2"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | lintOptions {
20 | abortOnError false
21 | }
22 | resourcePrefix "mis_"
23 | }
24 |
25 | dependencies {
26 | compile fileTree(include: ['*.jar'], dir: 'libs')
27 | compile 'com.squareup.picasso:picasso:2.5.2'
28 | compile 'com.android.support:appcompat-v7:25.1.0'
29 | }
30 |
31 | tasks.withType(Javadoc) {
32 | options.addStringOption('Xdoclint:none', '-quiet')
33 | options.addStringOption('encoding', 'UTF-8')
34 | }
35 |
36 | apply from: rootProject.projectDir.absolutePath + "/bintray-release.gradle"
37 |
38 | // load properties
39 | Properties properties = new Properties()
40 | File localPropertiesFile = project.file("$rootProject.projectDir.absolutePath /local.properties");
41 | if (localPropertiesFile.exists()) {
42 | properties.load(localPropertiesFile.newDataInputStream())
43 | }
44 | File projectPropertiesFile = project.file("project.properties");
45 | if (projectPropertiesFile.exists()) {
46 | properties.load(projectPropertiesFile.newDataInputStream())
47 | }
48 |
49 | // read properties
50 | def projectName = properties.getProperty("project.name")
51 | def projectGroupId = properties.getProperty("project.groupId")
52 | def projectArtifactId = properties.getProperty("project.artifactId")
53 | def projectVersionName = android.defaultConfig.versionName
54 | def projectSiteUrl = properties.getProperty("project.siteUrl")
55 | def projectGitUrl = properties.getProperty("project.gitUrl")
56 |
57 | def bintray_Org = properties.getProperty("bintray.org")
58 | def bintray_User = properties.getProperty("bintray.user")
59 | def bintrayApikey = properties.getProperty("bintray.apikey")
60 |
61 | publish {
62 | userOrg = bintray_Org
63 | groupId = projectGroupId
64 | artifactId = projectArtifactId
65 | uploadName = projectName
66 | website = projectSiteUrl
67 | repository = projectGitUrl
68 | publishVersion = projectVersionName
69 | bintrayUser = bintray_User
70 | bintrayKey = bintrayApikey
71 | dryRun = true
72 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/res/layout/mis_list_item_folder.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
31 |
32 |
39 |
40 |
49 |
50 |
60 |
61 |
62 |
63 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
20 |
26 |
27 |
32 |
37 |
38 |
39 |
40 |
47 |
48 |
55 |
56 |
63 |
64 |
70 |
71 |
76 |
81 |
82 |
83 |
84 |
89 |
90 |
93 |
94 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/MultiImageSelector.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.os.Build;
9 | import android.support.v4.app.Fragment;
10 | import android.support.v4.content.ContextCompat;
11 | import android.widget.Toast;
12 |
13 | import java.util.ArrayList;
14 |
15 | /**
16 | * 图片选择器
17 | * Created by nereo on 16/3/17.
18 | */
19 | public class MultiImageSelector {
20 |
21 | public static final String EXTRA_RESULT = MultiImageSelectorActivity.EXTRA_RESULT;
22 |
23 | private boolean mShowCamera = true;
24 | private int mMaxCount = 9;
25 | private int mMode = MultiImageSelectorActivity.MODE_MULTI;
26 | private ArrayList mOriginData;
27 | private static MultiImageSelector sSelector;
28 |
29 | @Deprecated
30 | private MultiImageSelector(Context context){
31 |
32 | }
33 |
34 | private MultiImageSelector(){}
35 |
36 | @Deprecated
37 | public static MultiImageSelector create(Context context){
38 | if(sSelector == null){
39 | sSelector = new MultiImageSelector(context);
40 | }
41 | return sSelector;
42 | }
43 |
44 | public static MultiImageSelector create(){
45 | if(sSelector == null){
46 | sSelector = new MultiImageSelector();
47 | }
48 | return sSelector;
49 | }
50 |
51 | public MultiImageSelector showCamera(boolean show){
52 | mShowCamera = show;
53 | return sSelector;
54 | }
55 |
56 | public MultiImageSelector count(int count){
57 | mMaxCount = count;
58 | return sSelector;
59 | }
60 |
61 | public MultiImageSelector single(){
62 | mMode = MultiImageSelectorActivity.MODE_SINGLE;
63 | return sSelector;
64 | }
65 |
66 | public MultiImageSelector multi(){
67 | mMode = MultiImageSelectorActivity.MODE_MULTI;
68 | return sSelector;
69 | }
70 |
71 | public MultiImageSelector origin(ArrayList images){
72 | mOriginData = images;
73 | return sSelector;
74 | }
75 |
76 | public void start(Activity activity, int requestCode){
77 | final Context context = activity;
78 | if(hasPermission(context)) {
79 | activity.startActivityForResult(createIntent(context), requestCode);
80 | }else{
81 | Toast.makeText(context, R.string.mis_error_no_permission, Toast.LENGTH_SHORT).show();
82 | }
83 | }
84 |
85 | public void start(Fragment fragment, int requestCode){
86 | final Context context = fragment.getContext();
87 | if(hasPermission(context)) {
88 | fragment.startActivityForResult(createIntent(context), requestCode);
89 | }else{
90 | Toast.makeText(context, R.string.mis_error_no_permission, Toast.LENGTH_SHORT).show();
91 | }
92 | }
93 |
94 | private boolean hasPermission(Context context){
95 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){
96 | // Permission was added in API Level 16
97 | return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
98 | == PackageManager.PERMISSION_GRANTED;
99 | }
100 | return true;
101 | }
102 |
103 | private Intent createIntent(Context context){
104 | Intent intent = new Intent(context, MultiImageSelectorActivity.class);
105 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, mShowCamera);
106 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, mMaxCount);
107 | if(mOriginData != null){
108 | intent.putStringArrayListExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, mOriginData);
109 | }
110 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, mMode);
111 | return intent;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.utils;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.os.Environment;
6 | import android.text.TextUtils;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | import static android.os.Environment.MEDIA_MOUNTED;
12 |
13 | /**
14 | * 文件操作类
15 | * Created by Nereo on 2015/4/8.
16 | */
17 | public class FileUtils {
18 |
19 | private static final String JPEG_FILE_PREFIX = "IMG_";
20 | private static final String JPEG_FILE_SUFFIX = ".jpg";
21 |
22 | public static File createTmpFile(Context context) throws IOException{
23 | File dir = null;
24 | if(TextUtils.equals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED)) {
25 | dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
26 | if (!dir.exists()) {
27 | dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera");
28 | if (!dir.exists()) {
29 | dir = getCacheDirectory(context, true);
30 | }
31 | }
32 | }else{
33 | dir = getCacheDirectory(context, true);
34 | }
35 | return File.createTempFile(JPEG_FILE_PREFIX, JPEG_FILE_SUFFIX, dir);
36 | }
37 |
38 |
39 | private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
40 |
41 | /**
42 | * Returns application cache directory. Cache directory will be created on SD card
43 | * ("/Android/data/[app_package_name]/cache") if card is mounted and app has appropriate permission. Else -
44 | * Android defines cache directory on device's file system.
45 | *
46 | * @param context Application context
47 | * @return Cache {@link File directory}.
48 | * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
49 | * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
50 | */
51 | public static File getCacheDirectory(Context context) {
52 | return getCacheDirectory(context, true);
53 | }
54 |
55 | /**
56 | * Returns application cache directory. Cache directory will be created on SD card
57 | * ("/Android/data/[app_package_name]/cache") (if card is mounted and app has appropriate permission) or
58 | * on device's file system depending incoming parameters.
59 | *
60 | * @param context Application context
61 | * @param preferExternal Whether prefer external location for cache
62 | * @return Cache {@link File directory}.
63 | * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
64 | * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
65 | */
66 | public static File getCacheDirectory(Context context, boolean preferExternal) {
67 | File appCacheDir = null;
68 | String externalStorageState;
69 | try {
70 | externalStorageState = Environment.getExternalStorageState();
71 | } catch (NullPointerException e) { // (sh)it happens (Issue #660)
72 | externalStorageState = "";
73 | } catch (IncompatibleClassChangeError e) { // (sh)it happens too (Issue #989)
74 | externalStorageState = "";
75 | }
76 | if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState) && hasExternalStoragePermission(context)) {
77 | appCacheDir = getExternalCacheDir(context);
78 | }
79 | if (appCacheDir == null) {
80 | appCacheDir = context.getCacheDir();
81 | }
82 | if (appCacheDir == null) {
83 | String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
84 | appCacheDir = new File(cacheDirPath);
85 | }
86 | return appCacheDir;
87 | }
88 |
89 | /**
90 | * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be
91 | * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has
92 | * appropriate permission. Else - Android defines cache directory on device's file system.
93 | *
94 | * @param context Application context
95 | * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images")
96 | * @return Cache {@link File directory}
97 | */
98 | public static File getIndividualCacheDirectory(Context context, String cacheDir) {
99 | File appCacheDir = getCacheDirectory(context);
100 | File individualCacheDir = new File(appCacheDir, cacheDir);
101 | if (!individualCacheDir.exists()) {
102 | if (!individualCacheDir.mkdir()) {
103 | individualCacheDir = appCacheDir;
104 | }
105 | }
106 | return individualCacheDir;
107 | }
108 |
109 | private static File getExternalCacheDir(Context context) {
110 | File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
111 | File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
112 | if (!appCacheDir.exists()) {
113 | if (!appCacheDir.mkdirs()) {
114 | return null;
115 | }
116 | try {
117 | new File(appCacheDir, ".nomedia").createNewFile();
118 | } catch (IOException e) {
119 | }
120 | }
121 | return appCacheDir;
122 | }
123 |
124 | private static boolean hasExternalStoragePermission(Context context) {
125 | int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
126 | return perm == PackageManager.PERMISSION_GRANTED;
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/adapter/FolderAdapter.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.adapter;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 |
11 | import com.squareup.picasso.Picasso;
12 |
13 | import java.io.File;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import me.nereo.multi_image_selector.R;
18 | import me.nereo.multi_image_selector.bean.Folder;
19 |
20 | /**
21 | * 文件夹Adapter
22 | * Created by Nereo on 2015/4/7.
23 | * Updated by nereo on 2016/1/19.
24 | */
25 | public class FolderAdapter extends BaseAdapter {
26 |
27 | private Context mContext;
28 | private LayoutInflater mInflater;
29 |
30 | private List mFolders = new ArrayList<>();
31 |
32 | int mImageSize;
33 |
34 | int lastSelected = 0;
35 |
36 | public FolderAdapter(Context context){
37 | mContext = context;
38 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
39 | mImageSize = mContext.getResources().getDimensionPixelOffset(R.dimen.mis_folder_cover_size);
40 | }
41 |
42 | /**
43 | * 设置数据集
44 | * @param folders
45 | */
46 | public void setData(List folders) {
47 | if(folders != null && folders.size()>0){
48 | mFolders = folders;
49 | }else{
50 | mFolders.clear();
51 | }
52 | notifyDataSetChanged();
53 | }
54 |
55 | @Override
56 | public int getCount() {
57 | return mFolders.size()+1;
58 | }
59 |
60 | @Override
61 | public Folder getItem(int i) {
62 | if(i == 0) return null;
63 | return mFolders.get(i-1);
64 | }
65 |
66 | @Override
67 | public long getItemId(int i) {
68 | return i;
69 | }
70 |
71 | @Override
72 | public View getView(int i, View view, ViewGroup viewGroup) {
73 | ViewHolder holder;
74 | if(view == null){
75 | view = mInflater.inflate(R.layout.mis_list_item_folder, viewGroup, false);
76 | holder = new ViewHolder(view);
77 | }else{
78 | holder = (ViewHolder) view.getTag();
79 | }
80 | if (holder != null) {
81 | if(i == 0){
82 | holder.name.setText(R.string.mis_folder_all);
83 | holder.path.setText("/sdcard");
84 | holder.size.setText(String.format("%d%s",
85 | getTotalImageSize(), mContext.getResources().getString(R.string.mis_photo_unit)));
86 | if(mFolders.size()>0){
87 | Folder f = mFolders.get(0);
88 | if (f != null) {
89 | Picasso.with(mContext)
90 | .load(new File(f.cover.path))
91 | .error(R.drawable.mis_default_error)
92 | .resizeDimen(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
93 | .centerCrop()
94 | .into(holder.cover);
95 | }else{
96 | holder.cover.setImageResource(R.drawable.mis_default_error);
97 | }
98 | }
99 | }else {
100 | holder.bindData(getItem(i));
101 | }
102 | if(lastSelected == i){
103 | holder.indicator.setVisibility(View.VISIBLE);
104 | }else{
105 | holder.indicator.setVisibility(View.INVISIBLE);
106 | }
107 | }
108 | return view;
109 | }
110 |
111 | private int getTotalImageSize(){
112 | int result = 0;
113 | if(mFolders != null && mFolders.size()>0){
114 | for (Folder f: mFolders){
115 | result += f.images.size();
116 | }
117 | }
118 | return result;
119 | }
120 |
121 | public void setSelectIndex(int i) {
122 | if(lastSelected == i) return;
123 |
124 | lastSelected = i;
125 | notifyDataSetChanged();
126 | }
127 |
128 | public int getSelectIndex(){
129 | return lastSelected;
130 | }
131 |
132 | class ViewHolder{
133 | ImageView cover;
134 | TextView name;
135 | TextView path;
136 | TextView size;
137 | ImageView indicator;
138 | ViewHolder(View view){
139 | cover = (ImageView)view.findViewById(R.id.cover);
140 | name = (TextView) view.findViewById(R.id.name);
141 | path = (TextView) view.findViewById(R.id.path);
142 | size = (TextView) view.findViewById(R.id.size);
143 | indicator = (ImageView) view.findViewById(R.id.indicator);
144 | view.setTag(this);
145 | }
146 |
147 | void bindData(Folder data) {
148 | if(data == null){
149 | return;
150 | }
151 | name.setText(data.name);
152 | path.setText(data.path);
153 | if (data.images != null) {
154 | size.setText(String.format("%d%s", data.images.size(), mContext.getResources().getString(R.string.mis_photo_unit)));
155 | }else{
156 | size.setText("*"+mContext.getResources().getString(R.string.mis_photo_unit));
157 | }
158 | if (data.cover != null) {
159 | // 显示图片
160 | Picasso.with(mContext)
161 | .load(new File(data.cover.path))
162 | .placeholder(R.drawable.mis_default_error)
163 | .resizeDimen(R.dimen.mis_folder_cover_size, R.dimen.mis_folder_cover_size)
164 | .centerCrop()
165 | .into(cover);
166 | }else{
167 | cover.setImageResource(R.drawable.mis_default_error);
168 | }
169 | }
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # MultiImageSelector
2 | 仿微信实现多图选择。支持单选和多选两种模式
3 |
4 | [](https://jitpack.io/#lovetuzitong/MultiImageSelector)
5 |
6 | [English Doc](README.md)
7 |
8 | ###截图
9 |    
10 |
11 | -------------------
12 |
13 | ###运行DEMO
14 |
15 | >./gradlew installDebug
16 |
17 | ###快速开始
18 | * 第0步
19 | 把模块 `multi-image-selector` 作为你的项目依赖添加到工程中. 在项目`build.gradle` 中:
20 | ```java
21 | repositories {
22 | jcenter()
23 | }
24 |
25 | dependencies {
26 | //compile 'com.github.lovetuzitong:MultiImageSelector:1.2'
27 | compile 'com.hx.multi-image-selector:multi-image-selector:1.2.2'
28 | }
29 | ```
30 |
31 | * 第1步
32 | 在你的 `AndroidManifest.xml` 中做如下声明:
33 | ```xml
34 |
35 |
36 |
37 |
42 |
45 |
46 | ```
47 |
48 | * 第2步
49 | 在你的代码中简单调用( 版本`version-1.1`之后支持 ), eg.
50 |
51 | ``` java
52 | // Multi image selector form an Activity
53 | MultiImageSelector.create(Context)
54 | .start(Activity, REQUEST_IMAGE);
55 | ```
56 |
57 | 详细可使用的Api.
58 | ``` java
59 | MultiImageSelector.create(Context)
60 | .showCamera(boolean) // 是否显示相机. 默认为显示
61 | .count(int) // 最大选择图片数量, 默认为9. 只有在选择模式为多选时有效
62 | .single() // 单选模式
63 | .multi() // 多选模式, 默认模式;
64 | .origin(ArrayList) // 默认已选择图片. 只有在选择模式为多选时有效
65 | .start(Activity/Fragment, REQUEST_IMAGE);
66 | ```
67 |
68 | 同样支持老版本的 `Intent` 调用方法:
69 | ```java
70 | Intent intent = new Intent(mContext, MultiImageSelectorActivity.class);
71 | // 是否显示调用相机拍照
72 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, true);
73 | // 最大图片选择数量
74 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, 9);
75 | // 设置模式 (支持 单选/MultiImageSelectorActivity.MODE_SINGLE 或者 多选/MultiImageSelectorActivity.MODE_MULTI)
76 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, MultiImageSelectorActivity.MODE_MULTI);
77 | // 默认选择图片,回填选项(支持String ArrayList)
78 | intent.putStringArrayListExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, defaultDataArray);
79 | startActivityForResult(intent, REQUEST_IMAGE);
80 | ```
81 |
82 | * 第3步
83 | 在你的 `onActivityResult` 方法中接受结果. 例如:
84 | ```java
85 | @Override
86 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
87 | super.onActivityResult(requestCode, resultCode, data);
88 | if(requestCode == REQUEST_IMAGE){
89 | if(resultCode == RESULT_OK){
90 | // 获取返回的图片列表
91 | List path = data.getStringArrayListExtra(MultiImageSelectorActivity.EXTRA_RESULT);
92 | // 处理你自己的逻辑 ....
93 | }
94 | }
95 | }
96 | ```
97 |
98 | * 第4步
99 | 没第4步了,就这样就OK啦~ :)
100 |
101 | -------------------
102 |
103 | ###自定义显示
104 | * 自定义Activity
105 | ```java
106 | class CustomerActivity extends Activity implements MultiImageSelectorFragment.Callback{
107 | @Override
108 | protected void onCreate(Bundle savedInstanceState) {
109 | // 你自己的逻辑...
110 | Bundle bundle = new Bundle();
111 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
112 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
113 | olean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
114 | // 添加主Fragment到Activity
115 | getSupportFragmentManager().beginTransaction()
116 | .add(R.id.image_grid, Fragment.instantiate(this, MultiImageSelectorFragment.class.getName(), bundle))
117 | .commit();
118 | }
119 | @Override
120 | public void onSingleImageSelected(String path) {
121 | // 当选择模式设定为 单选/MODE_SINGLE, 这个方法就会接受到Fragment返回的数据
122 | }
123 |
124 | @Override
125 | public void onImageSelected(String path) {
126 | // 一个图片被选择是触发,这里可以自定义的自己的Actionbar行为
127 | }
128 |
129 | @Override
130 | public void onImageUnselected(String path) {
131 | // 一个图片被反选是触发,这里可以自定义的自己的Actionbar行为
132 | }
133 |
134 | @Override
135 | public void onCameraShot(File imageFile) {
136 | // 当设置了使用摄像头,用户拍照后会返回照片文件
137 | }
138 | }
139 | ```
140 | * 具体可以参考`MultiImageSelectorActivity.java`的实现
141 |
142 | -------------------
143 |
144 | ###更新日志
145 |
146 | * 2016-5-18
147 | 1. 新增. `JitPack` 支持
148 | 2. 新增. 简单的调用方式. 详细参见 `第2步`
149 | 3. Fixed. 修复某些情况下碰到的空指针异常.
150 |
151 | * 2016-1-19
152 | 1. 修复. 无法加载大小为0的图片
153 | 2. 新增. 拍照后通知媒体扫描,加入图库
154 | 3. 修复. 红米无法拍照问题
155 | 4. 优化. 拍照Item的显示
156 |
157 | * 2015-5-5
158 | 1. 修复. 某些图片无法显示. (Issue by[sd6352051](https://github.com/sd6352051), [larry](https://github.com/18611480882))
159 | 2. 修复. `ListPopupWindow` 无法填充父控件
160 | 3. 新增. 选中的遮罩效果.
161 |
162 | * 2015-4-16
163 | 1. 修复. 旋转设备时,程序会崩溃. (Issue by [@Leminity](https://github.com/Leminity))
164 | 2. 修复. 文件夹PopupListView位置错误. (Issue by [@Slock](https://github.com/Slock))
165 | 3. 更改. 演示程序截图.
166 | 4. 更改. Readme 文件.
167 |
168 | * 2015-4-9
169 | 1. 修复. 当设置 `EXTRA_SHOW_CAMERA` 为 `true` 时, 点击第一个Item会混乱的问题.
170 | 2. 新增. 支持初始化图片选择设定。
171 |
172 |
173 | -------------------
174 |
175 | ###感谢
176 |
177 | * [square-picasso](https://github.com/square/picasso) A powerful image downloading and caching library for Android
178 |
179 | -------------------
180 |
181 | ###License
182 | >The MIT License (MIT)
183 |
184 | >Copyright (c) 2015 Nereo
185 |
186 | >Permission is hereby granted, free of charge, to any person obtaining a copy
187 | of this software and associated documentation files (the "Software"), to deal
188 | in the Software without restriction, including without limitation the rights
189 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
190 | copies of the Software, and to permit persons to whom the Software is
191 | furnished to do so, subject to the following conditions:
192 |
193 | >The above copyright notice and this permission notice shall be included in all
194 | copies or substantial portions of the Software.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MultiImageSelector
2 | Image selector for Android device. Support single choice and multi-choice.
3 |
4 | [](https://jitpack.io/#lovetuzitong/MultiImageSelector)
5 |
6 | [中文文档](README_zh.md)
7 |
8 | ###ART
9 |    
10 |
11 | -------------------
12 |
13 | ###Run Demo
14 |
15 | >./gradlew installDebug
16 |
17 | ###Quick Start
18 | * Step 0
19 | Add module `multi-image-selector` as your dependence. in your `build.gradle` :
20 | ```java
21 | repositories {
22 | jcenter()
23 | }
24 |
25 | dependencies {
26 | //compile 'com.github.lovetuzitong:MultiImageSelector:1.2'
27 | compile 'com.hx.multi-image-selector:multi-image-selector:1.2.2'
28 | }
29 | ```
30 |
31 | * Step 1
32 | Set your `AndroidManifest.xml` as below:
33 | ```xml
34 |
35 |
36 |
37 |
42 |
45 |
46 | ```
47 |
48 | * Step 2
49 | Call image selector simplest in your code, eg. ( From `version-1.1` )
50 |
51 | ``` java
52 | // Multi image selector form an Activity
53 | MultiImageSelector.create(Context)
54 | .start(Activity, REQUEST_IMAGE);
55 | ```
56 |
57 | Detail Api.
58 | ``` java
59 | MultiImageSelector.create(Context)
60 | .showCamera(boolean) // show camera or not. true by default
61 | .count(int) // max select image size, 9 by default. used width #.multi()
62 | .single() // single mode
63 | .multi() // multi mode, default mode;
64 | .origin(ArrayList) // original select data set, used width #.multi()
65 | .start(Activity/Fragment, REQUEST_IMAGE);
66 | ```
67 |
68 | Also support traditional `Intent` :
69 | ``` java
70 | Intent intent = new Intent(mContext, MultiImageSelectorActivity.class);
71 | // whether show camera
72 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, true);
73 | // max select image amount
74 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, 9);
75 | // select mode (MultiImageSelectorActivity.MODE_SINGLE OR MultiImageSelectorActivity.MODE_MULTI)
76 | intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, MultiImageSelectorActivity.MODE_MULTI);
77 | // default select images (support array list)
78 | intent.putStringArrayListExtra(MultiImageSelectorActivity.EXTRA_DEFAULT_SELECTED_LIST, defaultDataArray);
79 | startActivityForResult(intent, REQUEST_IMAGE);
80 | ```
81 |
82 | * Step 3
83 | Receive result in your `onActivityResult` Method. eg.
84 | ```java
85 | @Override
86 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
87 | super.onActivityResult(requestCode, resultCode, data);
88 | if(requestCode == REQUEST_IMAGE){
89 | if(resultCode == RESULT_OK){
90 | // Get the result list of select image paths
91 | List path = data.getStringArrayListExtra(MultiImageSelectorActivity.EXTRA_RESULT);
92 | // do your logic ....
93 | }
94 | }
95 | }
96 | ```
97 |
98 | * Step 4
99 | No more steps, just enjoy. :)
100 |
101 | -------------------
102 |
103 | ###Custom Activity Style
104 | * Custome your own Activity
105 | ```java
106 | class CustomerActivity extends Activity implements MultiImageSelectorFragment.Callback{
107 | @Override
108 | protected void onCreate(Bundle savedInstanceState) {
109 | // customer logic here...
110 | Bundle bundle = new Bundle();
111 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
112 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
113 | bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
114 | // Add fragment to your Activity
115 | getSupportFragmentManager().beginTransaction()
116 | .add(R.id.image_grid, Fragment.instantiate(this, MultiImageSelectorFragment.class.getName(), bundle))
117 | .commit();
118 | }
119 | @Override
120 | public void onSingleImageSelected(String path) {
121 | // When select mode set to MODE_SINGLE, this method will received result from fragment
122 | }
123 |
124 | @Override
125 | public void onImageSelected(String path) {
126 | // You can specify your ActionBar behavior here
127 | }
128 |
129 | @Override
130 | public void onImageUnselected(String path) {
131 | // You can specify your ActionBar behavior here
132 | }
133 |
134 | @Override
135 | public void onCameraShot(File imageFile) {
136 | // When user take phone by camera, this method will be called.
137 | }
138 | }
139 | ```
140 | * Take a glance of `MultiImageSelectorActivity.java`
141 |
142 | -------------------
143 |
144 | ###Change Log
145 |
146 | * 2016-5-18
147 | 1. Added. `JitPack` support
148 | 2. Added. Convenient way to call image selector. See `Step 2`
149 | 3. Fixed. Some NPE.
150 |
151 | * 2016-1-19
152 | 1. Fixed. cannot load some 0-size image
153 | 2. Added. When take a new photo, notify media scanner
154 | 3. Fixed. Can't take photo on RED-MI
155 | 4. Fixed. Performance when show Camera-Icon
156 |
157 | * 2015-5-5
158 | 1. Fixed. Can't display some images. (Issue by[sd6352051](https://github.com/sd6352051), [larry](https://github.com/18611480882))
159 | 2. Fixed. `ListPopupWindow` can not fill parent
160 | 3. Added. Add checked mask.
161 |
162 | * 2015-4-16
163 | 1. Fixed. Crack when rotate device. (Issue by [@Leminity](https://github.com/Leminity))
164 | 2. Fixed. PopupListView position error. (Issue by [@Slock](https://github.com/Slock))
165 | 3. Change. Demo application shortcut.
166 | 4. Change. Readme file.
167 |
168 | * 2015-4-9
169 | 1. Fixed. When set `EXTRA_SHOW_CAMERA` to `true`, the first grid item onclick event were messed.
170 | 2. Add. Support initial selected image list.
171 |
172 | -------------------
173 |
174 | ###Thanks
175 |
176 | * [square-picasso](https://github.com/square/picasso) A powerful image downloading and caching library for Android
177 |
178 | -------------------
179 |
180 | ###License
181 | >The MIT License (MIT)
182 |
183 | >Copyright (c) 2015 Nereo
184 |
185 | >Permission is hereby granted, free of charge, to any person obtaining a copy
186 | of this software and associated documentation files (the "Software"), to deal
187 | in the Software without restriction, including without limitation the rights
188 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
189 | copies of the Software, and to permit persons to whom the Software is
190 | furnished to do so, subject to the following conditions:
191 |
192 | >The above copyright notice and this permission notice shall be included in all
193 | copies or substantial portions of the Software.
194 |
--------------------------------------------------------------------------------
/app/src/main/java/me/nereo/multiimageselector/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multiimageselector;
2 |
3 | import android.Manifest;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.support.annotation.NonNull;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.support.v7.app.AlertDialog;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.text.TextUtils;
14 | import android.view.Menu;
15 | import android.view.MenuItem;
16 | import android.view.View;
17 | import android.widget.EditText;
18 | import android.widget.RadioGroup;
19 | import android.widget.TextView;
20 |
21 | import java.util.ArrayList;
22 |
23 | import me.nereo.multi_image_selector.MultiImageSelector;
24 |
25 |
26 | public class MainActivity extends AppCompatActivity {
27 |
28 | private static final int REQUEST_IMAGE = 2;
29 | protected static final int REQUEST_STORAGE_READ_ACCESS_PERMISSION = 101;
30 | protected static final int REQUEST_STORAGE_WRITE_ACCESS_PERMISSION = 102;
31 |
32 | private TextView mResultText;
33 | private RadioGroup mChoiceMode, mShowCamera;
34 | private EditText mRequestNum;
35 |
36 | private ArrayList mSelectPath;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | mResultText = (TextView) findViewById(R.id.result);
44 | mChoiceMode = (RadioGroup) findViewById(R.id.choice_mode);
45 | mShowCamera = (RadioGroup) findViewById(R.id.show_camera);
46 | mRequestNum = (EditText) findViewById(R.id.request_num);
47 |
48 | mChoiceMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
49 | @Override
50 | public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
51 | if(checkedId == R.id.multi){
52 | mRequestNum.setEnabled(true);
53 | }else{
54 | mRequestNum.setEnabled(false);
55 | mRequestNum.setText("");
56 | }
57 | }
58 | });
59 |
60 | View button = findViewById(R.id.button);
61 | if (button != null) {
62 | button.setOnClickListener(new View.OnClickListener() {
63 | @Override
64 | public void onClick(View view) {
65 | pickImage();
66 | }
67 | });
68 | }
69 |
70 | }
71 |
72 | private void pickImage() {
73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN // Permission was added in API Level 16
74 | && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
75 | != PackageManager.PERMISSION_GRANTED) {
76 | requestPermission(Manifest.permission.READ_EXTERNAL_STORAGE,
77 | getString(R.string.mis_permission_rationale),
78 | REQUEST_STORAGE_READ_ACCESS_PERMISSION);
79 | }else {
80 | boolean showCamera = mShowCamera.getCheckedRadioButtonId() == R.id.show;
81 | int maxNum = 9;
82 |
83 | if (!TextUtils.isEmpty(mRequestNum.getText())) {
84 | try {
85 | maxNum = Integer.valueOf(mRequestNum.getText().toString());
86 | } catch (NumberFormatException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 | MultiImageSelector selector = MultiImageSelector.create(MainActivity.this);
91 | selector.showCamera(showCamera);
92 | selector.count(maxNum);
93 | if (mChoiceMode.getCheckedRadioButtonId() == R.id.single) {
94 | selector.single();
95 | } else {
96 | selector.multi();
97 | }
98 | selector.origin(mSelectPath);
99 | selector.start(MainActivity.this, REQUEST_IMAGE);
100 | }
101 | }
102 |
103 | private void requestPermission(final String permission, String rationale, final int requestCode){
104 | if(ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
105 | new AlertDialog.Builder(this)
106 | .setTitle(R.string.mis_permission_dialog_title)
107 | .setMessage(rationale)
108 | .setPositiveButton(R.string.mis_permission_dialog_ok, new DialogInterface.OnClickListener() {
109 | @Override
110 | public void onClick(DialogInterface dialog, int which) {
111 | ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission}, requestCode);
112 | }
113 | })
114 | .setNegativeButton(R.string.mis_permission_dialog_cancel, null)
115 | .create().show();
116 | }else{
117 | ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
118 | }
119 | }
120 |
121 | @Override
122 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
123 | if(requestCode == REQUEST_STORAGE_READ_ACCESS_PERMISSION){
124 | if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
125 | pickImage();
126 | }
127 | } else {
128 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
129 | }
130 | }
131 |
132 | @Override
133 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
134 | super.onActivityResult(requestCode, resultCode, data);
135 | if(requestCode == REQUEST_IMAGE){
136 | if(resultCode == RESULT_OK){
137 | mSelectPath = data.getStringArrayListExtra(MultiImageSelector.EXTRA_RESULT);
138 | StringBuilder sb = new StringBuilder();
139 | for(String p: mSelectPath){
140 | sb.append(p);
141 | sb.append("\n");
142 | }
143 | mResultText.setText(sb.toString());
144 | }
145 | }
146 | }
147 |
148 | @Override
149 | public boolean onCreateOptionsMenu(Menu menu) {
150 | // Inflate the menu; this adds items to the action bar if it is present.
151 | getMenuInflater().inflate(R.menu.menu_main, menu);
152 | return true;
153 | }
154 |
155 | @Override
156 | public boolean onOptionsItemSelected(MenuItem item) {
157 | // Handle action bar item clicks here. The action bar will
158 | // automatically handle clicks on the Home/Up button, so long
159 | // as you specify a parent activity in AndroidManifest.xml.
160 | int id = item.getItemId();
161 |
162 | //noinspection SimplifiableIfStatement
163 | if (id == R.id.action_settings) {
164 | return true;
165 | }
166 |
167 | return super.onOptionsItemSelected(item);
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/MultiImageSelectorActivity.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector;
2 |
3 | import android.content.Intent;
4 | import android.graphics.Color;
5 | import android.net.Uri;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v7.app.ActionBar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.widget.Button;
15 |
16 | import java.io.File;
17 | import java.util.ArrayList;
18 |
19 | /**
20 | * Multi image selector
21 | * Created by Nereo on 2015/4/7.
22 | * Updated by nereo on 2016/1/19.
23 | * Updated by nereo on 2016/5/18.
24 | */
25 | public class MultiImageSelectorActivity extends AppCompatActivity
26 | implements MultiImageSelectorFragment.Callback{
27 |
28 | // Single choice
29 | public static final int MODE_SINGLE = 0;
30 | // Multi choice
31 | public static final int MODE_MULTI = 1;
32 |
33 | /** Max image size,int,{@link #DEFAULT_IMAGE_SIZE} by default */
34 | public static final String EXTRA_SELECT_COUNT = "max_select_count";
35 | /** Select mode,{@link #MODE_MULTI} by default */
36 | public static final String EXTRA_SELECT_MODE = "select_count_mode";
37 | /** Whether show camera,true by default */
38 | public static final String EXTRA_SHOW_CAMERA = "show_camera";
39 | /** Result data set,ArrayList<String>*/
40 | public static final String EXTRA_RESULT = "select_result";
41 | /** Original data set */
42 | public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
43 | // Default image size
44 | private static final int DEFAULT_IMAGE_SIZE = 9;
45 |
46 | private ArrayList resultList = new ArrayList<>();
47 | private Button mSubmitButton;
48 | private int mDefaultCount = DEFAULT_IMAGE_SIZE;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | setTheme(R.style.MIS_NO_ACTIONBAR);
54 | setContentView(R.layout.mis_activity_default);
55 |
56 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
57 | getWindow().setStatusBarColor(Color.BLACK);
58 | }
59 |
60 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
61 | if(toolbar != null){
62 | setSupportActionBar(toolbar);
63 | }
64 |
65 | final ActionBar actionBar = getSupportActionBar();
66 | if (actionBar != null) {
67 | actionBar.setDisplayHomeAsUpEnabled(true);
68 | }
69 |
70 | final Intent intent = getIntent();
71 | mDefaultCount = intent.getIntExtra(EXTRA_SELECT_COUNT, DEFAULT_IMAGE_SIZE);
72 | final int mode = intent.getIntExtra(EXTRA_SELECT_MODE, MODE_MULTI);
73 | final boolean isShow = intent.getBooleanExtra(EXTRA_SHOW_CAMERA, true);
74 | if(mode == MODE_MULTI && intent.hasExtra(EXTRA_DEFAULT_SELECTED_LIST)) {
75 | resultList = intent.getStringArrayListExtra(EXTRA_DEFAULT_SELECTED_LIST);
76 | }
77 |
78 | mSubmitButton = (Button) findViewById(R.id.commit);
79 | if(mode == MODE_MULTI){
80 | updateDoneText(resultList);
81 | mSubmitButton.setVisibility(View.VISIBLE);
82 | mSubmitButton.setOnClickListener(new View.OnClickListener() {
83 | @Override
84 | public void onClick(View view) {
85 | if(resultList != null && resultList.size() >0){
86 | // Notify success
87 | Intent data = new Intent();
88 | data.putStringArrayListExtra(EXTRA_RESULT, resultList);
89 | setResult(RESULT_OK, data);
90 | }else{
91 | setResult(RESULT_CANCELED);
92 | }
93 | finish();
94 | }
95 | });
96 | }else{
97 | mSubmitButton.setVisibility(View.GONE);
98 | }
99 |
100 | if(savedInstanceState == null){
101 | Bundle bundle = new Bundle();
102 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_COUNT, mDefaultCount);
103 | bundle.putInt(MultiImageSelectorFragment.EXTRA_SELECT_MODE, mode);
104 | bundle.putBoolean(MultiImageSelectorFragment.EXTRA_SHOW_CAMERA, isShow);
105 | bundle.putStringArrayList(MultiImageSelectorFragment.EXTRA_DEFAULT_SELECTED_LIST, resultList);
106 |
107 | getSupportFragmentManager().beginTransaction()
108 | .add(R.id.image_grid, Fragment.instantiate(this, MultiImageSelectorFragment.class.getName(), bundle))
109 | .commit();
110 | }
111 |
112 | }
113 |
114 | @Override
115 | public boolean onOptionsItemSelected(MenuItem item) {
116 | switch (item.getItemId()) {
117 | case android.R.id.home:
118 | setResult(RESULT_CANCELED);
119 | finish();
120 | return true;
121 | }
122 | return super.onOptionsItemSelected(item);
123 | }
124 |
125 | /**
126 | * Update done button by select image data
127 | * @param resultList selected image data
128 | */
129 | private void updateDoneText(ArrayList resultList){
130 | int size = 0;
131 | if(resultList == null || resultList.size()<=0){
132 | mSubmitButton.setText(R.string.mis_action_done);
133 | mSubmitButton.setEnabled(false);
134 | }else{
135 | size = resultList.size();
136 | mSubmitButton.setEnabled(true);
137 | }
138 | mSubmitButton.setText(getString(R.string.mis_action_button_string,
139 | getString(R.string.mis_action_done), size, mDefaultCount));
140 | }
141 |
142 | @Override
143 | public void onSingleImageSelected(String path) {
144 | Intent data = new Intent();
145 | resultList.add(path);
146 | data.putStringArrayListExtra(EXTRA_RESULT, resultList);
147 | setResult(RESULT_OK, data);
148 | finish();
149 | }
150 |
151 | @Override
152 | public void onImageSelected(String path) {
153 | if(!resultList.contains(path)) {
154 | resultList.add(path);
155 | }
156 | updateDoneText(resultList);
157 | }
158 |
159 | @Override
160 | public void onImageUnselected(String path) {
161 | if(resultList.contains(path)){
162 | resultList.remove(path);
163 | }
164 | updateDoneText(resultList);
165 | }
166 |
167 | @Override
168 | public void onCameraShot(File imageFile) {
169 | if(imageFile != null) {
170 | // notify system the image has change
171 | sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)));
172 |
173 | Intent data = new Intent();
174 | resultList.add(imageFile.getAbsolutePath());
175 | data.putStringArrayListExtra(EXTRA_RESULT, resultList);
176 | setResult(RESULT_OK, data);
177 | finish();
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/adapter/ImageGridAdapter.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector.adapter;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.os.Build;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.view.WindowManager;
10 | import android.widget.BaseAdapter;
11 | import android.widget.ImageView;
12 |
13 | import com.squareup.picasso.Picasso;
14 |
15 | import java.io.File;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import me.nereo.multi_image_selector.MultiImageSelectorFragment;
20 | import me.nereo.multi_image_selector.R;
21 | import me.nereo.multi_image_selector.bean.Image;
22 |
23 | /**
24 | * 图片Adapter
25 | * Created by Nereo on 2015/4/7.
26 | * Updated by nereo on 2016/1/19.
27 | */
28 | public class ImageGridAdapter extends BaseAdapter {
29 |
30 | private static final int TYPE_CAMERA = 0;
31 | private static final int TYPE_NORMAL = 1;
32 |
33 | private Context mContext;
34 |
35 | private LayoutInflater mInflater;
36 | private boolean showCamera = true;
37 | private boolean showSelectIndicator = true;
38 |
39 | private List mImages = new ArrayList<>();
40 | private List mSelectedImages = new ArrayList<>();
41 |
42 | final int mGridWidth;
43 |
44 | public ImageGridAdapter(Context context, boolean showCamera, int column){
45 | mContext = context;
46 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
47 | this.showCamera = showCamera;
48 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
49 | int width = 0;
50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
51 | Point size = new Point();
52 | wm.getDefaultDisplay().getSize(size);
53 | width = size.x;
54 | }else{
55 | width = wm.getDefaultDisplay().getWidth();
56 | }
57 | mGridWidth = width / column;
58 | }
59 | /**
60 | * 显示选择指示器
61 | * @param b
62 | */
63 | public void showSelectIndicator(boolean b) {
64 | showSelectIndicator = b;
65 | }
66 |
67 | public void setShowCamera(boolean b){
68 | if(showCamera == b) return;
69 |
70 | showCamera = b;
71 | notifyDataSetChanged();
72 | }
73 |
74 | public boolean isShowCamera(){
75 | return showCamera;
76 | }
77 |
78 | /**
79 | * 选择某个图片,改变选择状态
80 | * @param image
81 | */
82 | public void select(Image image) {
83 | if(mSelectedImages.contains(image)){
84 | mSelectedImages.remove(image);
85 | }else{
86 | mSelectedImages.add(image);
87 | }
88 | notifyDataSetChanged();
89 | }
90 |
91 | /**
92 | * 通过图片路径设置默认选择
93 | * @param resultList
94 | */
95 | public void setDefaultSelected(ArrayList resultList) {
96 | for(String path : resultList){
97 | Image image = getImageByPath(path);
98 | if(image != null){
99 | mSelectedImages.add(image);
100 | }
101 | }
102 | if(mSelectedImages.size() > 0){
103 | notifyDataSetChanged();
104 | }
105 | }
106 |
107 | private Image getImageByPath(String path){
108 | if(mImages != null && mImages.size()>0){
109 | for(Image image : mImages){
110 | if(image.path.equalsIgnoreCase(path)){
111 | return image;
112 | }
113 | }
114 | }
115 | return null;
116 | }
117 |
118 | /**
119 | * 设置数据集
120 | * @param images
121 | */
122 | public void setData(List images) {
123 | mSelectedImages.clear();
124 |
125 | if(images != null && images.size()>0){
126 | mImages = images;
127 | }else{
128 | mImages.clear();
129 | }
130 | notifyDataSetChanged();
131 | }
132 |
133 | @Override
134 | public int getViewTypeCount() {
135 | return 2;
136 | }
137 |
138 | @Override
139 | public int getItemViewType(int position) {
140 | if(showCamera){
141 | return position==0 ? TYPE_CAMERA : TYPE_NORMAL;
142 | }
143 | return TYPE_NORMAL;
144 | }
145 |
146 | @Override
147 | public int getCount() {
148 | return showCamera ? mImages.size()+1 : mImages.size();
149 | }
150 |
151 | @Override
152 | public Image getItem(int i) {
153 | if(showCamera){
154 | if(i == 0){
155 | return null;
156 | }
157 | return mImages.get(i-1);
158 | }else{
159 | return mImages.get(i);
160 | }
161 | }
162 |
163 | @Override
164 | public long getItemId(int i) {
165 | return i;
166 | }
167 |
168 | @Override
169 | public View getView(int i, View view, ViewGroup viewGroup) {
170 |
171 | if(isShowCamera()){
172 | if(i == 0){
173 | view = mInflater.inflate(R.layout.mis_list_item_camera, viewGroup, false);
174 | return view;
175 | }
176 | }
177 |
178 | ViewHolder holder;
179 | if(view == null){
180 | view = mInflater.inflate(R.layout.mis_list_item_image, viewGroup, false);
181 | holder = new ViewHolder(view);
182 | }else{
183 | holder = (ViewHolder) view.getTag();
184 | }
185 |
186 | if(holder != null) {
187 | holder.bindData(getItem(i));
188 | }
189 |
190 | return view;
191 | }
192 |
193 | class ViewHolder {
194 | ImageView image;
195 | ImageView indicator;
196 | View mask;
197 |
198 | ViewHolder(View view){
199 | image = (ImageView) view.findViewById(R.id.image);
200 | indicator = (ImageView) view.findViewById(R.id.checkmark);
201 | mask = view.findViewById(R.id.mask);
202 | view.setTag(this);
203 | }
204 |
205 | void bindData(final Image data){
206 | if(data == null) return;
207 | // 处理单选和多选状态
208 | if(showSelectIndicator){
209 | indicator.setVisibility(View.VISIBLE);
210 | if(mSelectedImages.contains(data)){
211 | // 设置选中状态
212 | indicator.setImageResource(R.drawable.mis_btn_selected);
213 | mask.setVisibility(View.VISIBLE);
214 | }else{
215 | // 未选择
216 | indicator.setImageResource(R.drawable.mis_btn_unselected);
217 | mask.setVisibility(View.GONE);
218 | }
219 | }else{
220 | indicator.setVisibility(View.GONE);
221 | }
222 | File imageFile = new File(data.path);
223 | if (imageFile.exists()) {
224 | // 显示图片
225 | Picasso.with(mContext)
226 | .load(imageFile)
227 | .placeholder(R.drawable.mis_default_error)
228 | .tag(MultiImageSelectorFragment.TAG)
229 | .resize(mGridWidth, mGridWidth)
230 | .centerCrop()
231 | .into(image);
232 | }else{
233 | image.setImageResource(R.drawable.mis_default_error);
234 | }
235 | }
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------
/multi-image-selector/src/main/java/me/nereo/multi_image_selector/MultiImageSelectorFragment.java:
--------------------------------------------------------------------------------
1 | package me.nereo.multi_image_selector;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.content.res.Configuration;
10 | import android.database.Cursor;
11 | import android.graphics.Color;
12 | import android.graphics.Point;
13 | import android.graphics.drawable.ColorDrawable;
14 | import android.net.Uri;
15 | import android.os.Build;
16 | import android.os.Bundle;
17 | import android.os.Handler;
18 | import android.provider.MediaStore;
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 | import android.support.v4.app.Fragment;
22 | import android.support.v4.app.LoaderManager;
23 | import android.support.v4.content.ContextCompat;
24 | import android.support.v4.content.CursorLoader;
25 | import android.support.v4.content.FileProvider;
26 | import android.support.v4.content.Loader;
27 | import android.support.v7.app.AlertDialog;
28 | import android.support.v7.widget.ListPopupWindow;
29 | import android.text.TextUtils;
30 | import android.view.LayoutInflater;
31 | import android.view.View;
32 | import android.view.ViewGroup;
33 | import android.widget.AbsListView;
34 | import android.widget.AdapterView;
35 | import android.widget.GridView;
36 | import android.widget.TextView;
37 | import android.widget.Toast;
38 |
39 | import com.squareup.picasso.Picasso;
40 |
41 | import java.io.File;
42 | import java.io.IOException;
43 | import java.util.ArrayList;
44 | import java.util.List;
45 |
46 | import me.nereo.multi_image_selector.adapter.FolderAdapter;
47 | import me.nereo.multi_image_selector.adapter.ImageGridAdapter;
48 | import me.nereo.multi_image_selector.bean.Folder;
49 | import me.nereo.multi_image_selector.bean.Image;
50 | import me.nereo.multi_image_selector.utils.FileUtils;
51 | import me.nereo.multi_image_selector.utils.ScreenUtils;
52 |
53 | /**
54 | * Multi image selector Fragment
55 | * Created by Nereo on 2015/4/7.
56 | * Updated by nereo on 2016/5/18.
57 | */
58 | public class MultiImageSelectorFragment extends Fragment {
59 |
60 | public static final String TAG = "MultiImageSelectorFragment";
61 |
62 | private static final int REQUEST_CAMERA_PERMISSION = 110;
63 | private static final int REQUEST_CAMERA = 100;
64 |
65 | private static final String KEY_TEMP_FILE = "key_temp_file";
66 |
67 | // Single choice
68 | public static final int MODE_SINGLE = 0;
69 | // Multi choice
70 | public static final int MODE_MULTI = 1;
71 |
72 | /** Max image size,int,*/
73 | public static final String EXTRA_SELECT_COUNT = "max_select_count";
74 | /** Select mode,{@link #MODE_MULTI} by default */
75 | public static final String EXTRA_SELECT_MODE = "select_count_mode";
76 | /** Whether show camera,true by default */
77 | public static final String EXTRA_SHOW_CAMERA = "show_camera";
78 | /** Original data set */
79 | public static final String EXTRA_DEFAULT_SELECTED_LIST = "default_list";
80 |
81 | // loaders
82 | private static final int LOADER_ALL = 0;
83 | private static final int LOADER_CATEGORY = 1;
84 |
85 | // image result data set
86 | private ArrayList resultList = new ArrayList<>();
87 | // folder result data set
88 | private ArrayList mResultFolder = new ArrayList<>();
89 |
90 | private GridView mGridView;
91 | private Callback mCallback;
92 |
93 | private ImageGridAdapter mImageAdapter;
94 | private FolderAdapter mFolderAdapter;
95 |
96 | private ListPopupWindow mFolderPopupWindow;
97 |
98 | private TextView mCategoryText;
99 | private View mPopupAnchorView;
100 |
101 | private boolean hasFolderGened = false;
102 |
103 | private File mTmpFile;
104 |
105 | @Override
106 | public void onAttach(Context context) {
107 | super.onAttach(context);
108 | try {
109 | mCallback = (Callback) getActivity();
110 | }catch (ClassCastException e){
111 | throw new ClassCastException("The Activity must implement MultiImageSelectorFragment.Callback interface...");
112 | }
113 | }
114 |
115 | @Override
116 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
117 | return inflater.inflate(R.layout.mis_fragment_multi_image, container, false);
118 | }
119 |
120 | @Override
121 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
122 | super.onViewCreated(view, savedInstanceState);
123 |
124 | final int mode = selectMode();
125 | if(mode == MODE_MULTI) {
126 | ArrayList tmp = getArguments().getStringArrayList(EXTRA_DEFAULT_SELECTED_LIST);
127 | if(tmp != null && tmp.size()>0) {
128 | resultList = tmp;
129 | }
130 | }
131 | mImageAdapter = new ImageGridAdapter(getActivity(), showCamera(), 3);
132 | mImageAdapter.showSelectIndicator(mode == MODE_MULTI);
133 |
134 | mPopupAnchorView = view.findViewById(R.id.footer);
135 |
136 | mCategoryText = (TextView) view.findViewById(R.id.category_btn);
137 | mCategoryText.setText(R.string.mis_folder_all);
138 | mCategoryText.setOnClickListener(new View.OnClickListener() {
139 | @Override
140 | public void onClick(View view) {
141 |
142 | if(mFolderPopupWindow == null){
143 | createPopupFolderList();
144 | }
145 |
146 | if (mFolderPopupWindow.isShowing()) {
147 | mFolderPopupWindow.dismiss();
148 | } else {
149 | mFolderPopupWindow.show();
150 | int index = mFolderAdapter.getSelectIndex();
151 | index = index == 0 ? index : index - 1;
152 | mFolderPopupWindow.getListView().setSelection(index);
153 | }
154 | }
155 | });
156 |
157 | mGridView = (GridView) view.findViewById(R.id.grid);
158 | mGridView.setAdapter(mImageAdapter);
159 | mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
160 | @Override
161 | public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
162 | if (mImageAdapter.isShowCamera()) {
163 | if (i == 0) {
164 | showCameraAction();
165 | } else {
166 | Image image = (Image) adapterView.getAdapter().getItem(i);
167 | selectImageFromGrid(image, mode);
168 | }
169 | } else {
170 | Image image = (Image) adapterView.getAdapter().getItem(i);
171 | selectImageFromGrid(image, mode);
172 | }
173 | }
174 | });
175 | mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
176 | @Override
177 | public void onScrollStateChanged(AbsListView view, int scrollState) {
178 | if (scrollState == SCROLL_STATE_FLING) {
179 | Picasso.with(view.getContext()).pauseTag(TAG);
180 | } else {
181 | Picasso.with(view.getContext()).resumeTag(TAG);
182 | }
183 | }
184 |
185 | @Override
186 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
187 |
188 | }
189 | });
190 |
191 | mFolderAdapter = new FolderAdapter(getActivity());
192 | }
193 |
194 | /**
195 | * Create popup ListView
196 | */
197 | private void createPopupFolderList() {
198 | Point point = ScreenUtils.getScreenSize(getActivity());
199 | int width = point.x;
200 | int height = (int) (point.y * (4.5f/8.0f));
201 | mFolderPopupWindow = new ListPopupWindow(getActivity());
202 | mFolderPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));
203 | mFolderPopupWindow.setAdapter(mFolderAdapter);
204 | mFolderPopupWindow.setContentWidth(width);
205 | mFolderPopupWindow.setWidth(width);
206 | mFolderPopupWindow.setHeight(height);
207 | mFolderPopupWindow.setAnchorView(mPopupAnchorView);
208 | mFolderPopupWindow.setModal(true);
209 | mFolderPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
210 | @Override
211 | public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
212 |
213 | mFolderAdapter.setSelectIndex(i);
214 |
215 | final int index = i;
216 | final AdapterView v = adapterView;
217 |
218 | new Handler().postDelayed(new Runnable() {
219 | @Override
220 | public void run() {
221 | mFolderPopupWindow.dismiss();
222 |
223 | if (index == 0) {
224 | getActivity().getSupportLoaderManager().restartLoader(LOADER_ALL, null, mLoaderCallback);
225 | mCategoryText.setText(R.string.mis_folder_all);
226 | if (showCamera()) {
227 | mImageAdapter.setShowCamera(true);
228 | } else {
229 | mImageAdapter.setShowCamera(false);
230 | }
231 | } else {
232 | Folder folder = (Folder) v.getAdapter().getItem(index);
233 | if (null != folder) {
234 | mImageAdapter.setData(folder.images);
235 | mCategoryText.setText(folder.name);
236 | if (resultList != null && resultList.size() > 0) {
237 | mImageAdapter.setDefaultSelected(resultList);
238 | }
239 | }
240 | mImageAdapter.setShowCamera(false);
241 | }
242 |
243 | mGridView.smoothScrollToPosition(0);
244 | }
245 | }, 100);
246 |
247 | }
248 | });
249 | }
250 |
251 | @Override
252 | public void onSaveInstanceState(Bundle outState) {
253 | super.onSaveInstanceState(outState);
254 | outState.putSerializable(KEY_TEMP_FILE, mTmpFile);
255 | }
256 |
257 | @Override
258 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
259 | super.onViewStateRestored(savedInstanceState);
260 | if (savedInstanceState != null) {
261 | mTmpFile = (File) savedInstanceState.getSerializable(KEY_TEMP_FILE);
262 | }
263 | }
264 |
265 | @Override
266 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
267 | super.onActivityCreated(savedInstanceState);
268 | // load image data
269 | getActivity().getSupportLoaderManager().initLoader(LOADER_ALL, null, mLoaderCallback);
270 | }
271 |
272 | @Override
273 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
274 | super.onActivityResult(requestCode, resultCode, data);
275 | if(requestCode == REQUEST_CAMERA){
276 | if(resultCode == Activity.RESULT_OK) {
277 | if (mTmpFile != null) {
278 | if (mCallback != null) {
279 | mCallback.onCameraShot(mTmpFile);
280 | }
281 | }
282 | }else{
283 | // delete tmp file
284 | while (mTmpFile != null && mTmpFile.exists()){
285 | boolean success = mTmpFile.delete();
286 | if(success){
287 | mTmpFile = null;
288 | }
289 | }
290 | }
291 | }
292 | }
293 |
294 | @Override
295 | public void onConfigurationChanged(Configuration newConfig) {
296 | if(mFolderPopupWindow != null){
297 | if(mFolderPopupWindow.isShowing()){
298 | mFolderPopupWindow.dismiss();
299 | }
300 | }
301 | super.onConfigurationChanged(newConfig);
302 | }
303 |
304 | /**
305 | * Open camera
306 | */
307 | private void showCameraAction() {
308 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN // Permission was added in API Level 16
309 | &&ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA)
310 | != PackageManager.PERMISSION_GRANTED){
311 | requestPermission(Manifest.permission.CAMERA,
312 | getString(R.string.mis_permission_rationale_camera),
313 | REQUEST_CAMERA_PERMISSION);
314 | }else {
315 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
316 | if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
317 | try {
318 | mTmpFile = FileUtils.createTmpFile(getActivity());
319 | } catch (IOException e) {
320 | e.printStackTrace();
321 | }
322 | if (mTmpFile != null && mTmpFile.exists()) {
323 | Uri imageUri = null;
324 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
325 | imageUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".mis.fileprovider", mTmpFile);//通过FileProvider创建一个content类型的Uri
326 | } else {
327 | imageUri = Uri.fromFile(mTmpFile);
328 | }
329 | intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
330 | startActivityForResult(intent, REQUEST_CAMERA);
331 | } else {
332 | Toast.makeText(getActivity(), R.string.mis_error_image_not_exist, Toast.LENGTH_SHORT).show();
333 | }
334 | } else {
335 | Toast.makeText(getActivity(), R.string.mis_msg_no_camera, Toast.LENGTH_SHORT).show();
336 | }
337 | }
338 | }
339 |
340 | private void requestPermission(final String permission, String rationale, final int requestCode){
341 | if(shouldShowRequestPermissionRationale(permission)){
342 | new AlertDialog.Builder(getContext())
343 | .setTitle(R.string.mis_permission_dialog_title)
344 | .setMessage(rationale)
345 | .setPositiveButton(R.string.mis_permission_dialog_ok, new DialogInterface.OnClickListener() {
346 | @Override
347 | public void onClick(DialogInterface dialog, int which) {
348 | requestPermissions(new String[]{permission}, requestCode);
349 | }
350 | })
351 | .setNegativeButton(R.string.mis_permission_dialog_cancel, null)
352 | .create().show();
353 | }else{
354 | requestPermissions(new String[]{permission}, requestCode);
355 | }
356 | }
357 |
358 | @Override
359 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
360 | if(requestCode == REQUEST_CAMERA_PERMISSION){
361 | if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
362 | showCameraAction();
363 | }
364 | } else {
365 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
366 | }
367 | }
368 |
369 | /**
370 | * notify callback
371 | * @param image image data
372 | */
373 | private void selectImageFromGrid(Image image, int mode) {
374 | if(image != null) {
375 | if(mode == MODE_MULTI) {
376 | if (resultList.contains(image.path)) {
377 | resultList.remove(image.path);
378 | if (mCallback != null) {
379 | mCallback.onImageUnselected(image.path);
380 | }
381 | } else {
382 | if(selectImageCount() == resultList.size()){
383 | Toast.makeText(getActivity(), R.string.mis_msg_amount_limit, Toast.LENGTH_SHORT).show();
384 | return;
385 | }
386 | resultList.add(image.path);
387 | if (mCallback != null) {
388 | mCallback.onImageSelected(image.path);
389 | }
390 | }
391 | mImageAdapter.select(image);
392 | }else if(mode == MODE_SINGLE){
393 | if(mCallback != null){
394 | mCallback.onSingleImageSelected(image.path);
395 | }
396 | }
397 | }
398 | }
399 |
400 | private LoaderManager.LoaderCallbacks mLoaderCallback = new LoaderManager.LoaderCallbacks() {
401 |
402 | private final String[] IMAGE_PROJECTION = {
403 | MediaStore.Images.Media.DATA,
404 | MediaStore.Images.Media.DISPLAY_NAME,
405 | MediaStore.Images.Media.DATE_ADDED,
406 | MediaStore.Images.Media.MIME_TYPE,
407 | MediaStore.Images.Media.SIZE,
408 | MediaStore.Images.Media._ID };
409 |
410 | @Override
411 | public Loader onCreateLoader(int id, Bundle args) {
412 | CursorLoader cursorLoader = null;
413 | if(id == LOADER_ALL) {
414 | cursorLoader = new CursorLoader(getActivity(),
415 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
416 | IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[3]+"=? OR "+IMAGE_PROJECTION[3]+"=? ",
417 | new String[]{"image/jpeg", "image/png"}, IMAGE_PROJECTION[2] + " DESC");
418 | }else if(id == LOADER_CATEGORY){
419 | cursorLoader = new CursorLoader(getActivity(),
420 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
421 | IMAGE_PROJECTION[4]+">0 AND "+IMAGE_PROJECTION[0]+" like '%"+args.getString("path")+"%'",
422 | null, IMAGE_PROJECTION[2] + " DESC");
423 | }
424 | return cursorLoader;
425 | }
426 |
427 | private boolean fileExist(String path){
428 | if(!TextUtils.isEmpty(path)){
429 | return new File(path).exists();
430 | }
431 | return false;
432 | }
433 |
434 | @Override
435 | public void onLoadFinished(Loader loader, Cursor data) {
436 | if (data != null) {
437 | if (data.getCount() > 0) {
438 | List images = new ArrayList<>();
439 | data.moveToFirst();
440 | do{
441 | String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
442 | String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
443 | long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));
444 | if(!fileExist(path)){continue;}
445 | Image image = null;
446 | if (!TextUtils.isEmpty(name)) {
447 | image = new Image(path, name, dateTime);
448 | images.add(image);
449 | }
450 | if( !hasFolderGened ) {
451 | // get all folder data
452 | File folderFile = new File(path).getParentFile();
453 | if(folderFile != null && folderFile.exists()){
454 | String fp = folderFile.getAbsolutePath();
455 | Folder f = getFolderByPath(fp);
456 | if(f == null){
457 | Folder folder = new Folder();
458 | folder.name = folderFile.getName();
459 | folder.path = fp;
460 | folder.cover = image;
461 | List imageList = new ArrayList<>();
462 | imageList.add(image);
463 | folder.images = imageList;
464 | mResultFolder.add(folder);
465 | }else {
466 | f.images.add(image);
467 | }
468 | }
469 | }
470 |
471 | }while(data.moveToNext());
472 |
473 | mImageAdapter.setData(images);
474 | if(resultList != null && resultList.size()>0){
475 | mImageAdapter.setDefaultSelected(resultList);
476 | }
477 | if(!hasFolderGened) {
478 | mFolderAdapter.setData(mResultFolder);
479 | hasFolderGened = true;
480 | }
481 | }
482 | }
483 | }
484 |
485 | @Override
486 | public void onLoaderReset(Loader loader) {
487 |
488 | }
489 | };
490 |
491 | private Folder getFolderByPath(String path){
492 | if(mResultFolder != null){
493 | for (Folder folder : mResultFolder) {
494 | if(TextUtils.equals(folder.path, path)){
495 | return folder;
496 | }
497 | }
498 | }
499 | return null;
500 | }
501 |
502 | private boolean showCamera(){
503 | return getArguments() == null || getArguments().getBoolean(EXTRA_SHOW_CAMERA, true);
504 | }
505 |
506 | private int selectMode(){
507 | return getArguments() == null ? MODE_MULTI : getArguments().getInt(EXTRA_SELECT_MODE);
508 | }
509 |
510 | private int selectImageCount(){
511 | return getArguments() == null ? 9 : getArguments().getInt(EXTRA_SELECT_COUNT);
512 | }
513 |
514 | /**
515 | * Callback for host activity
516 | */
517 | public interface Callback{
518 | void onSingleImageSelected(String path);
519 | void onImageSelected(String path);
520 | void onImageUnselected(String path);
521 | void onCameraShot(File imageFile);
522 | }
523 | }
524 |
--------------------------------------------------------------------------------