├── 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 │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ └── image_bg.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ ├── image_item.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── imnjh │ │ │ │ └── imagepickersample │ │ │ │ ├── cache │ │ │ │ ├── Cache.java │ │ │ │ ├── CacheManager.java │ │ │ │ ├── InnerCache.java │ │ │ │ └── LocalCache.java │ │ │ │ ├── app │ │ │ │ └── PickerApplication.java │ │ │ │ ├── widget │ │ │ │ └── ExpandGridView.java │ │ │ │ ├── ScaleTypeFillCenterInside.java │ │ │ │ ├── imageloader │ │ │ │ ├── GlideImageLoader.java │ │ │ │ └── FrescoImageLoader.java │ │ │ │ ├── adapter │ │ │ │ └── PickAdapter.java │ │ │ │ ├── SingleFileLimitInterceptor.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── imnjh │ │ │ └── imagepicker │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── imnjh │ │ └── imagepicker │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── imagepicker ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── xml │ │ │ └── provider_paths.xml │ │ ├── drawable-xxhdpi │ │ │ ├── ic_general_back.png │ │ │ ├── ic_takephoto_ok.png │ │ │ ├── ic_album_tick_blue.png │ │ │ ├── ic_takephoto_again.png │ │ │ ├── ic_takephoto_quit.png │ │ │ ├── bg_spinner_default.9.png │ │ │ ├── ic_attachment_camera.png │ │ │ ├── ic_general_cancel_left.png │ │ │ └── ic_maillist_mailbox_arrow.png │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── attrs.xml │ │ │ └── styles.xml │ │ ├── drawable-v21 │ │ │ └── bg_toolbar_spinner.xml │ │ ├── drawable │ │ │ ├── ic_arrow_down_white.xml │ │ │ ├── bg_toolbar_spinner.xml │ │ │ └── bs_selectable.xml │ │ ├── anim │ │ │ └── slide_in_bottom.xml │ │ ├── layout │ │ │ ├── common_toolbar_spinner.xml │ │ │ ├── activity_capture_temp.xml │ │ │ ├── item_picker_capture.xml │ │ │ ├── item_album_selected.xml │ │ │ ├── picker_photo_item.xml │ │ │ ├── album_list_item.xml │ │ │ ├── activity_crop_image.xml │ │ │ ├── activity_photo_picker.xml │ │ │ ├── picker_bottom_layout.xml │ │ │ ├── activity_photo_confirm.xml │ │ │ └── activity_picker_preview.xml │ │ └── values-v19 │ │ │ └── styles.xml │ │ ├── java │ │ └── com │ │ │ └── imnjh │ │ │ └── imagepicker │ │ │ ├── PickerAction.java │ │ │ ├── PhotoLoadListener.java │ │ │ ├── FileChooseInterceptor.java │ │ │ ├── widget │ │ │ ├── subsamplingview │ │ │ │ ├── decoder │ │ │ │ │ ├── DecoderFactory.java │ │ │ │ │ ├── CompatDecoderFactory.java │ │ │ │ │ ├── ImageDecoder.java │ │ │ │ │ ├── ImageRegionDecoder.java │ │ │ │ │ ├── SkiaImageDecoder.java │ │ │ │ │ └── SkiaImageRegionDecoder.java │ │ │ │ ├── ImageViewState.java │ │ │ │ ├── OnImageEventListener.java │ │ │ │ └── ImageSource.java │ │ │ ├── GridInsetDecoration.java │ │ │ ├── SquareRelativeLayout.java │ │ │ ├── ClipImageLayout.java │ │ │ ├── PreviewViewPager.java │ │ │ ├── ClipImageBorderView.java │ │ │ ├── PicturePreviewPageView.java │ │ │ ├── PickerBottomLayout.java │ │ │ ├── CheckBox.java │ │ │ └── ClipZoomImageView.java │ │ │ ├── ImageLoader.java │ │ │ ├── util │ │ │ ├── CollectionUtils.java │ │ │ ├── DeviceCompat.java │ │ │ ├── UriUtil.java │ │ │ ├── FileUtil.java │ │ │ ├── SystemUtil.java │ │ │ └── LogUtils.java │ │ │ ├── control │ │ │ ├── BaseLoaderController.java │ │ │ ├── AlbumController.java │ │ │ └── PhotoController.java │ │ │ ├── activity │ │ │ ├── BasePickerActivity.java │ │ │ ├── CaptureTempActivity.java │ │ │ ├── CaptureConfirmActivity.java │ │ │ └── CropImageActivity.java │ │ │ ├── PickerConfig.java │ │ │ ├── model │ │ │ ├── Photo.java │ │ │ └── Album.java │ │ │ ├── loader │ │ │ ├── PhotoLoader.java │ │ │ └── AlbumLoader.java │ │ │ ├── adapter │ │ │ ├── AlbumAdapter.java │ │ │ ├── CommonHeaderFooterAdapter.java │ │ │ ├── PhotoAdapter.java │ │ │ └── BaseRecycleCursorAdapter.java │ │ │ ├── SImagePicker.java │ │ │ └── CapturePhotoHelper.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .gitignore └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /imagepicker/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':imagepicker' 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_general_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_general_back.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_ok.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_album_tick_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_album_tick_blue.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_again.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_again.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_quit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_takephoto_quit.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/bg_spinner_default.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/bg_spinner_default.9.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_attachment_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_attachment_camera.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SImagePicker 3 | 图片过大,超过200k上限。 4 | 5 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_general_cancel_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_general_cancel_left.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-xxhdpi/ic_maillist_mailbox_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martin90s/ImagePicker/HEAD/imagepicker/src/main/res/drawable-xxhdpi/ic_maillist_mailbox_arrow.png -------------------------------------------------------------------------------- /imagepicker/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56dp 4 | 48dp 5 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable-v21/bg_toolbar_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/PickerAction.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import java.util.ArrayList; 4 | 5 | public interface PickerAction { 6 | void proceedResultAndFinish(ArrayList selected, boolean original, int resultCode); 7 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/black 4 | @color/black 5 | @color/black 6 | 7 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable/ic_arrow_down_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/PhotoLoadListener.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.net.Uri; 6 | 7 | public interface PhotoLoadListener { 8 | void onLoadComplete(ArrayList photoUris); 9 | 10 | void onLoadError(); 11 | } 12 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable/bg_toolbar_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/image_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/common_toolbar_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/drawable/bs_selectable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/activity_capture_temp.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/values-v19/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/FileChooseInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.content.Context; 6 | import android.os.Parcelable; 7 | 8 | /** 9 | * Created by Martin on 2017/1/17. 10 | */ 11 | public interface FileChooseInterceptor extends Parcelable { 12 | boolean onFileChosen(Context context, ArrayList selectedPic, boolean original, 13 | int resultCode, PickerAction action); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/imnjh/imagepicker/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/item_picker_capture.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | imagePicker 3 | 确定 4 | 取消 5 | 发送 6 | 原图 7 | (%1$s) 8 | (%1$d) 9 | 最多选择 %1$d 张图片 10 | 所有图片 11 | 12 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/decoder/DecoderFactory.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget.subsamplingview.decoder; 2 | 3 | /** 4 | * Interface for decoder (and region decoder) factories. 5 | * @param the class of decoder that will be produced. 6 | */ 7 | public interface DecoderFactory { 8 | /** 9 | * Produce a new instance of a decoder with type {@link T}. 10 | * @return a new instance of your decoder. 11 | */ 12 | T make() throws IllegalAccessException, InstantiationException; 13 | } 14 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/ImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.widget.ImageView; 6 | 7 | /** 8 | * Created by Martin on 2017/1/17. 9 | */ 10 | 11 | public interface ImageLoader { 12 | void bindImage(ImageView imageView, Uri uri, int width, int height); 13 | 14 | void bindImage(ImageView imageView, Uri uri); 15 | 16 | 17 | ImageView createImageView(Context context); 18 | 19 | ImageView createFakeImageView(Context context); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/cache/Cache.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.cache; 2 | 3 | import java.io.File; 4 | 5 | import com.imnjh.imagepicker.util.FileUtil; 6 | 7 | /** 8 | * Created by Martin on 2017/1/17. 9 | */ 10 | public abstract class Cache { 11 | 12 | public abstract boolean exist(String fileName); 13 | 14 | public abstract String getAbsolutePath(String fileName); 15 | 16 | public abstract File getDirectory(); 17 | 18 | public abstract boolean deleteCacheItem(String fileName); 19 | 20 | public void clear() { 21 | FileUtil.deleteDirectory(getDirectory()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/item_album_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /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 /Users/niejunhong/Develop/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /imagepicker/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 /Users/niejunhong/Develop/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/decoder/CompatDecoderFactory.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget.subsamplingview.decoder; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | 6 | /** 7 | * Compatibility factory to instantiate decoders with empty public constructors. 8 | * 9 | * @param The base type of the decoder this factory will produce. 10 | */ 11 | public class CompatDecoderFactory implements DecoderFactory { 12 | private Class clazz; 13 | 14 | public CompatDecoderFactory(@NonNull Class clazz) { 15 | this.clazz = clazz; 16 | } 17 | 18 | @Override 19 | public T make() throws IllegalAccessException, InstantiationException { 20 | return clazz.newInstance(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/util/CollectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.util; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by Martin on 2017/1/17. 8 | */ 9 | public class CollectionUtils { 10 | 11 | private CollectionUtils() {} 12 | 13 | public static boolean isEmpty(Collection collection) { 14 | return collection == null || collection.isEmpty(); 15 | } 16 | 17 | public static void removeRange(List list, int start, int count) { 18 | for (int i = start + count - 1; i >= start; i--) { 19 | list.remove(i); 20 | } 21 | } 22 | 23 | public static boolean containsAny(Collection all, Collection keys) { 24 | for (Object key : keys) { 25 | if (all.contains(key)) { 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/picker_photo_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/imnjh/imagepicker/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.imnjh.imagepicker", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /imagepicker/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | group = 'com.imnjh.imagepicker' 4 | 5 | 6 | android { 7 | compileSdkVersion rootProject.ext.compileSdkVersion 8 | buildToolsVersion rootProject.ext.buildToolsVersion 9 | 10 | defaultConfig { 11 | minSdkVersion rootProject.ext.minSdk 12 | targetSdkVersion rootProject.ext.targetSdk 13 | versionCode 4 14 | versionName "1.3.2" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile 'com.android.support:appcompat-v7:' + rootProject.ext.appcompat7Version 27 | compile 'com.android.support:recyclerview-v7:' + rootProject.ext.recyclerviewVersion 28 | } 29 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/control/BaseLoaderController.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.control; 2 | 3 | import android.app.Activity; 4 | import android.app.LoaderManager; 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | 8 | /** 9 | * Created by Martin on 2017/1/17. 10 | */ 11 | public abstract class BaseLoaderController implements LoaderManager.LoaderCallbacks { 12 | 13 | protected static final int PHOTO_LOADER_ID = 1; 14 | protected static final int ALBUM_LOADER_ID = 2; 15 | 16 | protected Context context; 17 | 18 | protected LoaderManager loaderManager; 19 | 20 | protected void onCreate(Activity activity) { 21 | context = activity; 22 | loaderManager = activity.getLoaderManager(); 23 | } 24 | 25 | public void onDestroy() { 26 | loaderManager.destroyLoader(getLoaderId()); 27 | } 28 | 29 | protected abstract int getLoaderId(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "com.imnjh.imagepickersample" 9 | minSdkVersion rootProject.ext.minSdk 10 | targetSdkVersion rootProject.ext.targetSdk 11 | versionCode 1 12 | versionName "1.0" 13 | ndk { 14 | abiFilters "armeabi" 15 | } 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile project(':imagepicker') 27 | compile 'com.github.bumptech.glide:glide:3.7.0' 28 | compile 'com.facebook.fresco:fresco:0.10.0+' 29 | compile 'com.facebook.fresco:imagepipeline-okhttp3:0.10.0+' 30 | } -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/album_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #48BAF3 5 | #40A7DA 6 | #FF4081 7 | #F3F6F8 8 | #D8D8D8 9 | 10 | #AA000000 11 | #FFFFFF 12 | #000000 13 | #8F8F8F 14 | #48BAF3 15 | #666666 16 | #E5E5E5 17 | #4C000000 18 | #4CFFFFFF 19 | #333333 20 | #D8D8D8 21 | #F3F6F8 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/app/PickerApplication.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.app; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.imnjh.imagepicker.PickerConfig; 7 | import com.imnjh.imagepicker.SImagePicker; 8 | import com.imnjh.imagepickersample.R; 9 | import com.imnjh.imagepickersample.imageloader.FrescoImageLoader; 10 | 11 | /** 12 | * Created by Martin on 2017/1/17. 13 | */ 14 | 15 | public class PickerApplication extends Application { 16 | 17 | private static Application instance; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | instance = this; 23 | SImagePicker.init(new PickerConfig.Builder().setAppContext(this) 24 | .setImageLoader(new FrescoImageLoader()) 25 | .setToolbaseColor(getResources().getColor(R.color.colorPrimary)).build()); 26 | } 27 | 28 | 29 | public static Context getAppContext() { 30 | return instance; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/widget/ExpandGridView.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ViewGroup; 6 | import android.widget.GridView; 7 | 8 | public class ExpandGridView extends GridView { 9 | public ExpandGridView(Context context) { 10 | super(context); 11 | } 12 | 13 | public ExpandGridView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | } 16 | 17 | public ExpandGridView(Context context, AttributeSet attrs, int defStyleAttr) { 18 | super(context, attrs, defStyleAttr); 19 | } 20 | 21 | 22 | @Override 23 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 24 | int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, 25 | MeasureSpec.AT_MOST); 26 | super.onMeasure(widthMeasureSpec, expandSpec); 27 | ViewGroup.LayoutParams params = getLayoutParams(); 28 | params.height = getMeasuredHeight(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/GridInsetDecoration.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | import com.imnjh.imagepicker.util.SystemUtil; 9 | 10 | /** 11 | * Created by Martin on 2017/1/17. 12 | */ 13 | public class GridInsetDecoration extends RecyclerView.ItemDecoration { 14 | 15 | private int offset; 16 | 17 | public GridInsetDecoration() { 18 | offset = SystemUtil.dp(1); 19 | } 20 | 21 | @Override 22 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 23 | RecyclerView.State state) { 24 | GridLayoutManager.LayoutParams layoutParams = 25 | (GridLayoutManager.LayoutParams) view.getLayoutParams(); 26 | int position = layoutParams.getViewAdapterPosition(); 27 | if (position == RecyclerView.NO_POSITION) { 28 | outRect.set(0, 0, 0, 0); 29 | return; 30 | } 31 | outRect.set(offset, offset, offset, offset); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/activity/BasePickerActivity.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.activity; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | 10 | /** 11 | * Created by Martin on 2017/1/16. 12 | */ 13 | 14 | public abstract class BasePickerActivity extends AppCompatActivity { 15 | protected View contentView; 16 | 17 | @Override 18 | protected void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 21 | if (getLayoutResId() != 0) { 22 | contentView = inflater.inflate(getLayoutResId(), null, false); 23 | } 24 | if (contentView != null) { 25 | setContentView(contentView); 26 | } 27 | } 28 | 29 | @Override 30 | protected void onDestroy() { 31 | super.onDestroy(); 32 | } 33 | 34 | protected abstract int getLayoutResId(); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/ScaleTypeFillCenterInside.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample; 2 | 3 | import android.graphics.Matrix; 4 | import android.graphics.Rect; 5 | 6 | import com.facebook.drawee.drawable.ScalingUtils; 7 | 8 | /** 9 | * Created on 6/6/16. 10 | * 11 | * @author yingyi.xu@rush.im (Yingyi Xu) 12 | */ 13 | public class ScaleTypeFillCenterInside extends ScalingUtils.AbstractScaleType { 14 | 15 | public static final ScalingUtils.ScaleType INSTANCE = new ScaleTypeFillCenterInside(); 16 | 17 | @Override 18 | public void getTransformImpl( 19 | Matrix outTransform, 20 | Rect parentRect, 21 | int childWidth, 22 | int childHeight, 23 | float focusX, 24 | float focusY, 25 | float scaleX, 26 | float scaleY) { 27 | float scale = Math.min(scaleX, scaleY); 28 | float dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f; 29 | float dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f; 30 | outTransform.setScale(scale, scale); 31 | outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/values/attrs.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 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/decoder/ImageDecoder.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget.subsamplingview.decoder; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.net.Uri; 6 | 7 | /** 8 | * Interface for image decoding classes, allowing the default {@link android.graphics.BitmapRegionDecoder} 9 | * based on the Skia library to be replaced with a custom class. 10 | */ 11 | public interface ImageDecoder { 12 | 13 | /** 14 | * Decode an image. When possible, initial setup work once in this method. This method 15 | * must return the dimensions of the image. The URI can be in one of the following formats: 16 | * File: file:///scard/picture.jpg 17 | * Asset: file:///android_asset/picture.png 18 | * Resource: android.resource://com.example.app/drawable/picture 19 | * @param context Application context. A reference may be held, but must be cleared on recycle. 20 | * @param uri URI of the image. 21 | * @return Dimensions of the image. 22 | * @throws Exception if initialisation fails. 23 | */ 24 | Bitmap decode(Context context, Uri uri) throws Exception; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/imageloader/GlideImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.imageloader; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.widget.ImageView; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.imnjh.imagepicker.ImageLoader; 9 | import com.imnjh.imagepickersample.R; 10 | import com.imnjh.imagepickersample.app.PickerApplication; 11 | 12 | /** 13 | * Created by Martin on 2017/1/18. 14 | */ 15 | 16 | public class GlideImageLoader implements ImageLoader { 17 | @Override 18 | public void bindImage(ImageView imageView, Uri uri, int width, int height) { 19 | Glide.with(PickerApplication.getAppContext()).load(uri).placeholder(R.mipmap.ic_launcher) 20 | .error(R.mipmap.ic_launcher).override(width, height).dontAnimate().into(imageView); 21 | } 22 | 23 | @Override 24 | public void bindImage(ImageView imageView, Uri uri) { 25 | Glide.with(PickerApplication.getAppContext()).load(uri).placeholder(R.mipmap.ic_launcher) 26 | .error(R.mipmap.ic_launcher).dontAnimate().into(imageView); 27 | } 28 | 29 | @Override 30 | public ImageView createImageView(Context context) { 31 | ImageView imageView = new ImageView(context); 32 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 33 | return imageView; 34 | } 35 | 36 | @Override 37 | public ImageView createFakeImageView(Context context) { 38 | return new ImageView(context); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/activity_crop_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 15 | 26 | 27 | 38 | 39 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/activity_photo_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | 19 | 20 | 25 | 26 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/PickerConfig.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker; 2 | 3 | import android.content.Context; 4 | 5 | 6 | /** 7 | * Created by Martin on 2017/1/17. 8 | */ 9 | 10 | public class PickerConfig { 11 | 12 | private ImageLoader imageLoader; 13 | private Context appContext; 14 | private int toolbarColor; 15 | 16 | private PickerConfig(Builder builder) { 17 | this.imageLoader = builder.imageLoader; 18 | this.appContext = builder.context; 19 | this.toolbarColor = builder.toolbarColor; 20 | } 21 | 22 | public ImageLoader getImageLoader() { 23 | return imageLoader; 24 | } 25 | 26 | public Context getAppContext() { 27 | return appContext; 28 | } 29 | 30 | public int getToolbarColor() { 31 | return toolbarColor; 32 | } 33 | 34 | public static class Builder { 35 | 36 | private ImageLoader imageLoader; 37 | private Context context; 38 | private int toolbarColor; 39 | 40 | public Builder() {} 41 | 42 | public Builder setAppContext(Context context) { 43 | this.context = context; 44 | return this; 45 | } 46 | 47 | public Builder setImageLoader(ImageLoader imageLoader) { 48 | this.imageLoader = imageLoader; 49 | return this; 50 | } 51 | 52 | public Builder setToolbaseColor(int color) { 53 | this.toolbarColor = color; 54 | return this; 55 | } 56 | 57 | public PickerConfig build() { 58 | return new PickerConfig(this); 59 | } 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/ImageViewState.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 David Morrissey 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.imnjh.imagepicker.widget.subsamplingview; 18 | 19 | import java.io.Serializable; 20 | 21 | import android.graphics.PointF; 22 | 23 | /** 24 | * Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate. 25 | */ 26 | public class ImageViewState implements Serializable { 27 | 28 | private float scale; 29 | 30 | private float centerX; 31 | 32 | private float centerY; 33 | 34 | private int orientation; 35 | 36 | public ImageViewState(float scale, PointF center, int orientation) { 37 | this.scale = scale; 38 | this.centerX = center.x; 39 | this.centerY = center.y; 40 | this.orientation = orientation; 41 | } 42 | 43 | public float getScale() { 44 | return scale; 45 | } 46 | 47 | public PointF getCenter() { 48 | return new PointF(centerX, centerY); 49 | } 50 | 51 | public int getOrientation() { 52 | return orientation; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/cache/CacheManager.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.cache; 2 | 3 | import com.imnjh.imagepickersample.app.PickerApplication; 4 | import com.imnjh.imagepicker.util.SystemUtil; 5 | 6 | import java.io.File; 7 | /** 8 | * Created by Martin on 2017/1/17. 9 | */ 10 | public class CacheManager { 11 | 12 | public static final String ROOT_STORE = "PickerSample"; 13 | 14 | private InnerCache imageInnerCache; 15 | 16 | private LocalCache imageCache; 17 | 18 | private static CacheManager instance; 19 | 20 | private CacheManager() { 21 | init(); 22 | } 23 | 24 | public static synchronized CacheManager getInstance() { 25 | if (instance == null) { 26 | instance = new CacheManager(); 27 | } 28 | return instance; 29 | } 30 | 31 | 32 | private void init() { 33 | initRootCache(SystemUtil.getStoreDir(PickerApplication.getAppContext())); 34 | } 35 | 36 | private boolean initRootCache(File storage) { 37 | File cacheRoot = new File(storage, ROOT_STORE); 38 | boolean isMkRoot = true; 39 | if (!cacheRoot.exists()) { 40 | isMkRoot = cacheRoot.mkdirs(); 41 | } else if (!cacheRoot.isDirectory()) { 42 | isMkRoot = false; 43 | } 44 | if (!isMkRoot) { 45 | return false; 46 | } 47 | return true; 48 | } 49 | 50 | public InnerCache getImageInnerCache() { 51 | if (imageInnerCache == null) { 52 | imageInnerCache = new InnerCache(); 53 | 54 | } 55 | return imageInnerCache; 56 | } 57 | 58 | public LocalCache getImageCache() { 59 | if (imageCache == null) { 60 | imageCache = new LocalCache("Image"); 61 | } 62 | return imageCache; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/picker_bottom_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 23 | 24 | 33 | 34 | 35 | 36 | 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## http://stackoverflow.com/questions/16736856/what-should-be-in-my-gitignore-for-an-android-studio-project 2 | ## https://raw.githubusercontent.com/github/gitignore/master/Android.gitignore 3 | 4 | gradle.properties 5 | 6 | # Built application files 7 | *.apk 8 | *.ap_ 9 | 10 | # Files for the Dalvik VM 11 | *.dex 12 | 13 | # Java class files 14 | *.class 15 | 16 | # Generated files 17 | bin/ 18 | gen/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Windows thumbnail db 36 | Thumbs.db 37 | 38 | # OSX files 39 | .DS_Store 40 | 41 | # Eclipse project files 42 | .classpath 43 | .project 44 | .metadata/ 45 | 46 | # Android Studio 47 | *.iml 48 | .idea 49 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 50 | 51 | # Gradle files 52 | .gradle/ 53 | .gradle 54 | build/ 55 | 56 | #NDK 57 | obj/ 58 | 59 | 60 | # Optional - for older project format, add this section to your gitignore file: 61 | /*/out 62 | /*/*/build 63 | /*/*/production 64 | *.iws 65 | *.ipr 66 | *~ 67 | *.swp 68 | 69 | 70 | # Update: Since Android Studio 1.0, new projects are created with this gitignore file: 71 | /local.properties 72 | /.idea/workspace.xml 73 | /.idea/libraries 74 | /build 75 | 76 | 77 | # Intellij IDEA (see https://intellij-support.jetbrains.com/entries/23393067) 78 | .idea/workspace.xml 79 | .idea/tasks.xml 80 | .idea/datasources.xml 81 | .idea/dataSources.ids 82 | 83 | gradle/ 84 | gradlew 85 | gradlew.bat 86 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/SquareRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ViewGroup; 6 | import android.widget.ImageView; 7 | import android.widget.RelativeLayout; 8 | 9 | import com.imnjh.imagepicker.R; 10 | 11 | 12 | /** 13 | * Created by Martin on 2017/1/17. 14 | */ 15 | public class SquareRelativeLayout extends RelativeLayout { 16 | 17 | public ImageView photo; 18 | public CheckBox checkBox; 19 | 20 | public SquareRelativeLayout(Context context) { 21 | super(context); 22 | } 23 | 24 | public SquareRelativeLayout(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | } 27 | 28 | public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 29 | super(context, attrs, defStyleAttr); 30 | } 31 | 32 | @Override 33 | protected void onFinishInflate() { 34 | super.onFinishInflate(); 35 | checkBox = (CheckBox) findViewById(R.id.checkbox); 36 | } 37 | 38 | public void setPhotoView(ImageView imageView) { 39 | ViewGroup.LayoutParams layoutParams = 40 | new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 41 | ViewGroup.LayoutParams.MATCH_PARENT); 42 | this.photo = imageView; 43 | addView(imageView, 0, layoutParams); 44 | } 45 | 46 | @Override 47 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 48 | setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 49 | getDefaultSize(0, heightMeasureSpec)); 50 | int childWidthSize = getMeasuredWidth(); 51 | heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec( 52 | childWidthSize, MeasureSpec.EXACTLY); 53 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/ClipImageLayout.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.AttributeSet; 7 | import android.util.TypedValue; 8 | import android.widget.RelativeLayout; 9 | 10 | /** 11 | * Created by Martin on 2017/1/17. 12 | */ 13 | public class ClipImageLayout extends RelativeLayout { 14 | private ClipZoomImageView zoomImageView; 15 | private ClipImageBorderView clipImageView; 16 | private int horizontalPadding = 0; 17 | 18 | public ClipImageLayout(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | 21 | zoomImageView = new ClipZoomImageView(context); 22 | clipImageView = new ClipImageBorderView(context); 23 | 24 | android.view.ViewGroup.LayoutParams lp = new LayoutParams( 25 | android.view.ViewGroup.LayoutParams.MATCH_PARENT, 26 | android.view.ViewGroup.LayoutParams.MATCH_PARENT); 27 | 28 | this.addView(zoomImageView, lp); 29 | this.addView(clipImageView, lp); 30 | 31 | horizontalPadding = (int) TypedValue.applyDimension( 32 | TypedValue.COMPLEX_UNIT_DIP, horizontalPadding, getResources() 33 | .getDisplayMetrics()); 34 | zoomImageView.setHorizontalPadding(horizontalPadding); 35 | clipImageView.setHorizontalPadding(horizontalPadding); 36 | } 37 | 38 | public void setImageDrawable(Drawable drawable) { 39 | zoomImageView.setImageDrawable(drawable); 40 | } 41 | 42 | public void setImageBitmap(Bitmap bitmap) { 43 | zoomImageView.setImageBitmap(bitmap); 44 | } 45 | 46 | public void setHorizontalPadding(int mHorizontalPadding) { 47 | this.horizontalPadding = mHorizontalPadding; 48 | } 49 | 50 | public Bitmap clip() { 51 | return zoomImageView.clip(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /imagepicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 17 | 20 | 21 | 22 | 26 | 27 | 31 | 32 | 36 | 37 | 41 | 42 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/activity_photo_confirm.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 27 | 28 | 35 | 36 | 43 | 44 | 45 | 50 | 51 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/util/DeviceCompat.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.util.Properties; 7 | 8 | import android.os.Build; 9 | import android.os.Environment; 10 | 11 | 12 | /** 13 | * Created by Martin on 2017/1/17. 14 | */ 15 | public final class DeviceCompat { 16 | 17 | private static final String MANUFACTURE_NAME_SONY = "sony"; 18 | 19 | public enum ROM { 20 | SONY, 21 | OTHERS 22 | } 23 | 24 | private static ROM rom; 25 | 26 | private static BuildProperties prop; 27 | 28 | static { 29 | try { 30 | prop = BuildProperties.newInstance(); 31 | if (MANUFACTURE_NAME_SONY.equalsIgnoreCase(Build.MANUFACTURER)) { 32 | rom = ROM.SONY; 33 | } else { 34 | rom = ROM.OTHERS; 35 | } 36 | } catch (final IOException e) { 37 | rom = ROM.OTHERS; 38 | } 39 | } 40 | 41 | private DeviceCompat() {} 42 | 43 | public static ROM getROM() { 44 | return rom; 45 | } 46 | 47 | private static class BuildProperties { 48 | 49 | private final Properties properties; 50 | 51 | private BuildProperties() throws IOException { 52 | properties = new Properties(); 53 | try { 54 | properties 55 | .load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); 56 | for (Object key : properties.keySet()) { 57 | if (key instanceof String) { 58 | String keyStr = (String) key; 59 | LogUtils.d("properties", keyStr + " : " + properties.getProperty(keyStr)); 60 | } 61 | } 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | public int size() { 68 | return properties.size(); 69 | } 70 | 71 | public static BuildProperties newInstance() throws IOException { 72 | return new BuildProperties(); 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/cache/InnerCache.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.cache; 2 | 3 | import java.io.File; 4 | 5 | import android.content.Context; 6 | import android.os.Environment; 7 | 8 | import com.imnjh.imagepickersample.app.PickerApplication; 9 | import com.imnjh.imagepicker.util.FileUtil; 10 | 11 | /** 12 | * Created by Martin on 2017/1/17. 13 | */ 14 | public class InnerCache extends Cache { 15 | 16 | private File innerCache; 17 | 18 | public InnerCache() { 19 | innerCache = getCacheDirCreateIfNotExist(); 20 | } 21 | 22 | private File getCacheDirCreateIfNotExist() { 23 | File cachePath = new File(getInnerCacheDir(PickerApplication.getAppContext())); 24 | if (!cachePath.isDirectory()) { 25 | try { 26 | cachePath.mkdirs(); 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | try { 31 | new File(cachePath, ".nomedia").createNewFile(); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | return cachePath; 37 | } 38 | 39 | private String getInnerCacheDir(Context context) { 40 | String cachePath; 41 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) 42 | || !Environment.isExternalStorageRemovable()) { 43 | cachePath = context.getExternalCacheDir().getPath(); 44 | } else { 45 | cachePath = context.getCacheDir().getPath(); 46 | } 47 | return cachePath; 48 | } 49 | 50 | 51 | public boolean exist(String fileName) { 52 | String path = innerCache + File.separator + fileName; 53 | return FileUtil.exist(path); 54 | } 55 | 56 | @Override 57 | public String getAbsolutePath(String fileName) { 58 | return getDirectory().getAbsolutePath() + File.separator + fileName; 59 | } 60 | 61 | @Override 62 | public File getDirectory() { 63 | return getCacheDirCreateIfNotExist(); 64 | } 65 | 66 | @Override 67 | public boolean deleteCacheItem(String fileName) { 68 | String filePath = getAbsolutePath(fileName); 69 | return FileUtil.deleteFile(filePath); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/model/Photo.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.model; 2 | 3 | import android.content.ContentUris; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.provider.MediaStore; 9 | 10 | /** 11 | * Created by Martin on 2017/1/17. 12 | */ 13 | public class Photo implements Parcelable { 14 | 15 | private final long id; 16 | private final String displayName; 17 | private final String filePath; 18 | 19 | 20 | public Photo(long id, String displayName, String filePath) { 21 | this.id = id; 22 | this.displayName = displayName; 23 | this.filePath = filePath; 24 | } 25 | 26 | 27 | public long getId() { 28 | return id; 29 | } 30 | 31 | public Uri buildContentUri() { 32 | return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); 33 | } 34 | 35 | public String getFilePath() { 36 | return filePath; 37 | } 38 | 39 | public static Photo fromCursor(Cursor cursor) { 40 | return new Photo(cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)), 41 | cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)), 42 | cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))); 43 | } 44 | 45 | @Override 46 | public int describeContents() { 47 | return 0; 48 | } 49 | 50 | @Override 51 | public void writeToParcel(Parcel dest, int flags) { 52 | dest.writeLong(this.id); 53 | dest.writeString(this.displayName); 54 | dest.writeString(this.filePath); 55 | } 56 | 57 | protected Photo(Parcel in) { 58 | this.id = in.readLong(); 59 | this.displayName = in.readString(); 60 | this.filePath = in.readString(); 61 | } 62 | 63 | public static final Creator CREATOR = new Creator() { 64 | @Override 65 | public Photo createFromParcel(Parcel source) { 66 | return new Photo(source); 67 | } 68 | 69 | @Override 70 | public Photo[] newArray(int size) { 71 | return new Photo[size]; 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/loader/PhotoLoader.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.loader; 2 | 3 | import android.content.Context; 4 | import android.content.CursorLoader; 5 | import android.net.Uri; 6 | import android.provider.MediaStore; 7 | 8 | import com.imnjh.imagepicker.model.Album; 9 | 10 | 11 | /** 12 | * Created by Martin on 2017/1/17. 13 | */ 14 | public class PhotoLoader extends CursorLoader { 15 | private static final String[] PROJECTION = {MediaStore.Images.Media._ID, 16 | MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATA, 17 | MediaStore.Images.Media.DATE_TAKEN,}; 18 | private static final String ORDER_BY = MediaStore.Images.Media.DATE_TAKEN + " DESC"; 19 | private static final String SELECTION_SIZE = MediaStore.Images.Media.SIZE + " > ? or " 20 | + MediaStore.Images.Media.SIZE + " is null"; 21 | 22 | private PhotoLoader(Context context, Uri uri, String[] projection, String selection, 23 | String[] selectionArgs, String sortOrder) { 24 | super(context, uri, projection, selection, selectionArgs, sortOrder); 25 | } 26 | 27 | /** 28 | * get image by album 29 | * 30 | * @param context 31 | * @param album 32 | * @return 33 | */ 34 | public static CursorLoader newInstance(Context context, Album album) { 35 | return newInstance(context, album, 0); 36 | } 37 | 38 | /** 39 | * get image and filter by size 40 | * 41 | * @param context 42 | * @param album 43 | * @param minSize The minimum size of the file in bytes 44 | * @return 45 | */ 46 | public static CursorLoader newInstance(Context context, Album album, long minSize) { 47 | if (album == null || album.isAll()) { 48 | return new PhotoLoader(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJECTION, 49 | SELECTION_SIZE, new String[] {minSize + ""}, ORDER_BY); 50 | } 51 | return new PhotoLoader(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJECTION, 52 | MediaStore.Images.Media.BUCKET_ID + " = ? and (" + SELECTION_SIZE + ")", new String[] { 53 | album.getId(), minSize + ""}, ORDER_BY); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/adapter/PickAdapter.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.adapter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.content.Context; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.BaseAdapter; 11 | import android.widget.ImageView; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 15 | import com.imnjh.imagepickersample.R; 16 | import com.imnjh.imagepickersample.app.PickerApplication; 17 | 18 | /** 19 | * Created by Martin on 2017/1/18. 20 | */ 21 | 22 | public class PickAdapter extends BaseAdapter { 23 | 24 | private LayoutInflater layoutInflater; 25 | private List data = new ArrayList<>(); 26 | 27 | public PickAdapter(Context context) { 28 | layoutInflater = LayoutInflater.from(context); 29 | } 30 | 31 | public void setNewData(List data) { 32 | this.data.clear(); 33 | if (data != null) { 34 | this.data.addAll(data); 35 | } 36 | notifyDataSetChanged(); 37 | } 38 | 39 | 40 | @Override 41 | public int getCount() { 42 | return data.size(); 43 | } 44 | 45 | @Override 46 | public Object getItem(int position) { 47 | return data.get(position); 48 | } 49 | 50 | @Override 51 | public long getItemId(int position) { 52 | return position; 53 | } 54 | 55 | @Override 56 | public View getView(int position, View convertView, ViewGroup parent) { 57 | ViewHolder holder; 58 | if (convertView == null) { 59 | convertView = layoutInflater.inflate(R.layout.image_item, null, false); 60 | holder = new ViewHolder(); 61 | holder.imageView = (ImageView) convertView.findViewById(R.id.image); 62 | convertView.setTag(holder); 63 | } else { 64 | holder = (ViewHolder) convertView.getTag(); 65 | } 66 | holder.imageView.setImageResource(R.drawable.ic_album_tick_blue); 67 | Glide.with(PickerApplication.getAppContext()).load(data.get(position)).skipMemoryCache(true) 68 | .diskCacheStrategy(DiskCacheStrategy.NONE) 69 | .dontAnimate().into(holder.imageView); 70 | return convertView; 71 | } 72 | 73 | 74 | class ViewHolder { 75 | public ImageView imageView; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/PreviewViewPager.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.view.ViewPager; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.ViewConfiguration; 9 | 10 | /** 11 | * Created by Martin on 2017/1/17. 12 | */ 13 | public class PreviewViewPager extends ViewPager { 14 | 15 | private int touchSlop; 16 | private boolean scrollEnabled = true; 17 | private boolean isDisallowIntercept = false; 18 | 19 | 20 | public PreviewViewPager(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public PreviewViewPager(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(); 27 | } 28 | 29 | private void init() { 30 | final ViewConfiguration vc = ViewConfiguration.get(getContext()); 31 | touchSlop = vc.getScaledTouchSlop(); 32 | } 33 | 34 | public void setScrollEnabled(boolean scrollEnabled) { 35 | this.scrollEnabled = scrollEnabled; 36 | } 37 | 38 | public boolean isDisallowIntercept() { 39 | return isDisallowIntercept; 40 | } 41 | 42 | @Override 43 | public boolean onInterceptTouchEvent(MotionEvent event) { 44 | if (!scrollEnabled || event.getPointerCount() > 1) { 45 | return false; 46 | } 47 | try { 48 | return super.onInterceptTouchEvent(event); 49 | } catch (IllegalArgumentException e) { 50 | return false; 51 | } 52 | } 53 | 54 | @Override 55 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 56 | isDisallowIntercept = disallowIntercept; 57 | super.requestDisallowInterceptTouchEvent(disallowIntercept); 58 | } 59 | 60 | // fix index out of range in event dispatch 61 | // see https://code.google.com/p/android/issues/detail?id=60464 62 | @Override 63 | public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { 64 | if (ev.getPointerCount() > 1 && isDisallowIntercept) { 65 | requestDisallowInterceptTouchEvent(false); 66 | boolean handled = super.dispatchTouchEvent(ev); 67 | requestDisallowInterceptTouchEvent(true); 68 | return handled; 69 | } else { 70 | return super.dispatchTouchEvent(ev); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/loader/AlbumLoader.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.loader; 2 | 3 | import android.content.Context; 4 | import android.content.CursorLoader; 5 | import android.database.Cursor; 6 | import android.database.MatrixCursor; 7 | import android.database.MergeCursor; 8 | import android.net.Uri; 9 | import android.provider.MediaStore; 10 | 11 | import com.imnjh.imagepicker.model.Album; 12 | 13 | /** 14 | * Created by Martin on 2017/1/17. 15 | */ 16 | public class AlbumLoader extends CursorLoader { 17 | 18 | private static final String[] PROJECTION = {MediaStore.Images.Media.BUCKET_ID, 19 | MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media._ID, 20 | "count(bucket_id) as cou"}; 21 | private static final String BUCKET_GROUP_BY = ") GROUP BY 1,(2"; 22 | private static final String BUCKET_ORDER_BY = "MAX(datetaken) DESC"; 23 | private static final String MEDIA_ID_DUMMY = String.valueOf(-1); 24 | private static final String IS_LARGE_SIZE = " _size > ? or _size is null"; 25 | 26 | public static CursorLoader newInstance(Context context) { 27 | return newInstance(context, 0); 28 | } 29 | 30 | public static CursorLoader newInstance(Context context, long minByte) { 31 | return new AlbumLoader(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJECTION, 32 | IS_LARGE_SIZE + BUCKET_GROUP_BY, new String[] {minByte + ""}, 33 | BUCKET_ORDER_BY); 34 | } 35 | 36 | private AlbumLoader(Context context, Uri uri, String[] projection, String selection, 37 | String[] selectionArgs, String sortOrder) { 38 | super(context, uri, projection, selection, selectionArgs, sortOrder); 39 | } 40 | 41 | 42 | @Override 43 | public Cursor loadInBackground() { 44 | Cursor albums = super.loadInBackground(); 45 | MatrixCursor allAlbum = new MatrixCursor(PROJECTION); 46 | 47 | long count = 0; 48 | if (albums.getCount() > 0) { 49 | while (albums.moveToNext()) { 50 | count += albums.getLong(3); 51 | } 52 | } 53 | allAlbum.addRow( 54 | new String[] { 55 | Album.ALBUM_ID_ALL, 56 | getContext().getString(Album.ALBUM_NAME_ALL_RES_ID), 57 | MEDIA_ID_DUMMY, 58 | count + ""}); 59 | return new MergeCursor(new Cursor[] {allAlbum, albums}); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/decoder/ImageRegionDecoder.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget.subsamplingview.decoder; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Point; 6 | import android.graphics.Rect; 7 | import android.net.Uri; 8 | 9 | /** 10 | * Interface for image decoding classes, allowing the default {@link android.graphics.BitmapRegionDecoder} 11 | * based on the Skia library to be replaced with a custom class. 12 | */ 13 | public interface ImageRegionDecoder { 14 | 15 | /** 16 | * Initialise the decoder. When possible, initial setup work once in this method. This method 17 | * must return the dimensions of the image. The URI can be in one of the following formats: 18 | * File: file:///scard/picture.jpg 19 | * Asset: file:///android_asset/picture.png 20 | * Resource: android.resource://com.example.app/drawable/picture 21 | * @param context Application context. A reference may be held, but must be cleared on recycle. 22 | * @param uri URI of the image. 23 | * @return Dimensions of the image. 24 | * @throws Exception if initialisation fails. 25 | */ 26 | Point init(Context context, Uri uri) throws Exception; 27 | 28 | /** 29 | * Decode a region of the image with the given sample size. This method is called off the UI thread so it can safely 30 | * load the image on the current thread. It is called from an {@link android.os.AsyncTask} running in a single 31 | * threaded executor, and while a synchronization lock is held on this object, so will never be called concurrently 32 | * even if the decoder implementation supports it. 33 | * @param sRect Source image rectangle to decode. 34 | * @param sampleSize Sample size. 35 | * @return The decoded region. I t is safe to return null if decoding fails. 36 | */ 37 | Bitmap decodeRegion(Rect sRect, int sampleSize); 38 | 39 | /** 40 | * Status check. Should return false before initialisation and after recycle. 41 | * @return true if the decoder is ready to be used. 42 | */ 43 | boolean isReady(); 44 | 45 | /** 46 | * This method will be called when the decoder is no longer required. It should clean up any resources still in use. 47 | */ 48 | void recycle(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/adapter/AlbumAdapter.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.adapter; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.CursorAdapter; 9 | import android.widget.TextView; 10 | 11 | import com.imnjh.imagepicker.R; 12 | import com.imnjh.imagepicker.model.Album; 13 | 14 | 15 | /** 16 | * Created by Martin on 2017/1/17. 17 | */ 18 | public class AlbumAdapter extends CursorAdapter { 19 | 20 | private final LayoutInflater layoutInflater; 21 | 22 | public AlbumAdapter(Context context, Cursor c) { 23 | super(context, c, false); 24 | layoutInflater = LayoutInflater.from(context); 25 | } 26 | 27 | @Override 28 | public View newView(Context context, Cursor cursor, ViewGroup parent) { 29 | View view = layoutInflater.inflate(R.layout.album_list_item, parent, false); 30 | AlbumViewHolder albumViewHolder = new AlbumViewHolder(view); 31 | view.setTag(albumViewHolder); 32 | return view; 33 | } 34 | 35 | @Override 36 | public View getView(int position, View convertView, ViewGroup parent) { 37 | if (!getCursor().moveToPosition(position)) { 38 | throw new IllegalStateException("couldn't move cursor to position " + position); 39 | } 40 | if (convertView == null) { 41 | convertView = 42 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_album_selected, 43 | parent, false); 44 | } 45 | TextView albumName = (TextView) convertView.findViewById(android.R.id.text1); 46 | Album album = Album.valueOf(getCursor()); 47 | albumName.setText(album.getDisplayName()); 48 | return convertView; 49 | } 50 | 51 | @Override 52 | public void bindView(View view, Context context, Cursor cursor) { 53 | AlbumViewHolder viewHolder = (AlbumViewHolder) view.getTag(); 54 | Album album = Album.valueOf(cursor); 55 | viewHolder.albumTitle.setText(album.getDisplayName()); 56 | viewHolder.photoCount.setText(context.getResources().getString(R.string.bracket_num, 57 | album.getCount())); 58 | } 59 | 60 | 61 | 62 | static class AlbumViewHolder { 63 | 64 | TextView albumTitle; 65 | 66 | TextView photoCount; 67 | 68 | public AlbumViewHolder(View itemView) { 69 | albumTitle = (TextView) itemView.findViewById(R.id.album_name); 70 | photoCount = (TextView) itemView.findViewById(R.id.photo_count); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/control/AlbumController.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.control; 2 | 3 | import android.app.Activity; 4 | import android.content.Loader; 5 | import android.database.Cursor; 6 | import android.os.Bundle; 7 | import android.support.v7.widget.AppCompatSpinner; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | 11 | import com.imnjh.imagepicker.adapter.AlbumAdapter; 12 | import com.imnjh.imagepicker.loader.AlbumLoader; 13 | import com.imnjh.imagepicker.model.Album; 14 | 15 | 16 | /** 17 | * Created by Martin on 2017/1/17. 18 | */ 19 | public class AlbumController extends BaseLoaderController 20 | implements AdapterView.OnItemSelectedListener { 21 | 22 | private AlbumAdapter albumAdapter; 23 | private OnDirectorySelectListener directorySelectListener; 24 | 25 | public void onCreate(Activity activity, AppCompatSpinner spinner, 26 | OnDirectorySelectListener directorySelectListener) { 27 | super.onCreate(activity); 28 | this.albumAdapter = new AlbumAdapter(activity, null); 29 | this.directorySelectListener = directorySelectListener; 30 | spinner.setAdapter(albumAdapter); 31 | spinner.setOnItemSelectedListener(this); 32 | } 33 | 34 | @Override 35 | protected int getLoaderId() { 36 | return ALBUM_LOADER_ID; 37 | } 38 | 39 | @Override 40 | public Loader onCreateLoader(int id, Bundle args) { 41 | return AlbumLoader.newInstance(context); 42 | } 43 | 44 | @Override 45 | public void onLoadFinished(Loader loader, Cursor data) { 46 | albumAdapter.swapCursor(data); 47 | } 48 | 49 | @Override 50 | public void onLoaderReset(Loader loader) { 51 | albumAdapter.swapCursor(null); 52 | } 53 | 54 | @Override 55 | public void onDestroy() { 56 | super.onDestroy(); 57 | } 58 | 59 | 60 | public void loadAlbums() { 61 | loaderManager.initLoader(getLoaderId(), null, this); 62 | } 63 | 64 | @Override 65 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 66 | if (directorySelectListener != null) { 67 | Cursor cursor = (Cursor) parent.getItemAtPosition(position); 68 | Album album = Album.valueOf(cursor); 69 | directorySelectListener.onSelect(album); 70 | } 71 | } 72 | 73 | @Override 74 | public void onNothingSelected(AdapterView parent) { 75 | 76 | } 77 | 78 | 79 | public interface OnDirectorySelectListener { 80 | void onSelect(Album album); 81 | 82 | void onReset(Album album); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/ClipImageBorderView.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Paint.Style; 8 | import android.util.AttributeSet; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | 12 | import com.imnjh.imagepicker.R; 13 | 14 | /** 15 | * Created by Martin on 2017/1/17. 16 | */ 17 | public class ClipImageBorderView extends View { 18 | private int horizontalPadding; 19 | private int verticalPadding; 20 | private int width; 21 | private int borderColor = Color.WHITE; 22 | private int bgColor; 23 | private int borderWidth = 1; 24 | 25 | private Paint paint; 26 | 27 | public ClipImageBorderView(Context context) { 28 | this(context, null); 29 | } 30 | 31 | public ClipImageBorderView(Context context, AttributeSet attrs) { 32 | this(context, attrs, 0); 33 | } 34 | 35 | public ClipImageBorderView(Context context, AttributeSet attrs, int defStyle) { 36 | super(context, attrs, defStyle); 37 | borderWidth = (int) TypedValue.applyDimension( 38 | TypedValue.COMPLEX_UNIT_DIP, borderWidth, getResources() 39 | .getDisplayMetrics()); 40 | bgColor = context.getResources().getColor(R.color.crop_image_bg); 41 | paint = new Paint(); 42 | paint.setAntiAlias(true); 43 | } 44 | 45 | @Override 46 | protected void onDraw(Canvas canvas) { 47 | super.onDraw(canvas); 48 | width = getWidth() - 2 * horizontalPadding; 49 | verticalPadding = (getHeight() - width) / 2; 50 | paint.setColor(bgColor); 51 | paint.setStyle(Style.FILL); 52 | canvas.drawRect(0, 0, horizontalPadding, getHeight(), paint); 53 | canvas.drawRect(getWidth() - horizontalPadding, 0, getWidth(), getHeight(), paint); 54 | canvas.drawRect(horizontalPadding, 0, getWidth() - horizontalPadding, verticalPadding, paint); 55 | canvas.drawRect(horizontalPadding, getHeight() - verticalPadding, getWidth() 56 | - horizontalPadding, getHeight(), paint); 57 | paint.setColor(borderColor); 58 | paint.setStrokeWidth(borderWidth); 59 | paint.setStyle(Style.STROKE); 60 | canvas.drawRect(horizontalPadding, verticalPadding, getWidth() 61 | - horizontalPadding, getHeight() - verticalPadding, paint); 62 | } 63 | 64 | public void setHorizontalPadding(int mHorizontalPadding) { 65 | this.horizontalPadding = mHorizontalPadding; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/imnjh/imagepickersample/cache/LocalCache.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepickersample.cache; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import com.imnjh.imagepicker.util.FileUtil; 7 | import com.imnjh.imagepicker.util.LogUtils; 8 | import com.imnjh.imagepicker.util.SystemUtil; 9 | import com.imnjh.imagepickersample.app.PickerApplication; 10 | 11 | /** 12 | * Created by Martin on 2017/1/17. 13 | */ 14 | public final class LocalCache extends Cache { 15 | 16 | private final String dataName; 17 | 18 | private final String dataPath; 19 | 20 | /** 21 | * @param dataName cache directory name 22 | */ 23 | public LocalCache(String dataName) { 24 | this.dataName = dataName; 25 | StringBuilder pathBuilder = new StringBuilder(getRushCachePath()); 26 | pathBuilder.append(File.separatorChar); 27 | pathBuilder.append(getDataName()); 28 | pathBuilder.append(File.separatorChar); 29 | dataPath = pathBuilder.toString(); 30 | initCacheRoot(dataPath); 31 | } 32 | 33 | 34 | 35 | private String getRushCachePath() { 36 | File dataDir = SystemUtil.getStoreDir(PickerApplication.getAppContext()); 37 | return getDirectoryCreateIfNotExist( 38 | dataDir.getPath() + File.separator + CacheManager.ROOT_STORE) 39 | .getPath(); 40 | } 41 | 42 | private File getDirectoryCreateIfNotExist(String pathStr) { 43 | File file = new File(pathStr); 44 | if (!file.isDirectory()) { 45 | file.mkdirs(); 46 | } 47 | return file; 48 | } 49 | 50 | @Override 51 | public File getDirectory() { 52 | return getDirectoryCreateIfNotExist(dataPath); 53 | } 54 | 55 | 56 | @Override 57 | public String getAbsolutePath(String fileName) { 58 | return getDirectory() + File.separator + fileName; 59 | } 60 | 61 | @Override 62 | public boolean deleteCacheItem(String fileName) { 63 | String filePath = getAbsolutePath(fileName); 64 | return FileUtil.deleteFile(filePath); 65 | } 66 | 67 | @Override 68 | public boolean exist(String relativePath) { 69 | String filePath = getAbsolutePath(relativePath); 70 | boolean ret = FileUtil.exist(filePath); 71 | return ret; 72 | } 73 | 74 | public String getDataName() { 75 | return dataName; 76 | } 77 | 78 | 79 | private void initCacheRoot(String root) { 80 | getDirectoryCreateIfNotExist(root); 81 | File ignoreFile = new File(root, ".nomedia"); 82 | try { 83 | ignoreFile.createNewFile(); 84 | } catch (IOException e) { 85 | LogUtils.e(LocalCache.class.getSimpleName(), "Failed to create ignore file.", e); 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/activity/CaptureTempActivity.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.activity; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import android.app.Activity; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | 11 | import com.imnjh.imagepicker.CapturePhotoHelper; 12 | import com.imnjh.imagepicker.R; 13 | 14 | /** 15 | * Created by Martin on 2017/1/17. 16 | */ 17 | 18 | public class CaptureTempActivity extends BasePickerActivity { 19 | 20 | public static final String CAPTURE_URI = "capture_uri"; 21 | 22 | private CapturePhotoHelper capturePhotoHelper; 23 | 24 | private Uri uri; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | capturePhotoHelper = new CapturePhotoHelper(this); 30 | if (savedInstanceState == null) { 31 | uri = getIntent().getParcelableExtra(CAPTURE_URI); 32 | capturePhotoHelper.capturePhoto(uri); 33 | } else { 34 | uri = (Uri) savedInstanceState.get(CAPTURE_URI); 35 | } 36 | if (uri == null) { 37 | finishWith(Activity.RESULT_CANCELED); 38 | } 39 | } 40 | 41 | @Override 42 | protected void onResume() { 43 | super.onResume(); 44 | } 45 | 46 | @Override 47 | protected void onSaveInstanceState(Bundle outState) { 48 | super.onSaveInstanceState(outState); 49 | outState.putParcelable(CAPTURE_URI, uri); 50 | } 51 | 52 | @Override 53 | protected int getLayoutResId() { 54 | return R.layout.activity_capture_temp; 55 | } 56 | 57 | @Override 58 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 59 | super.onActivityResult(requestCode, resultCode, data); 60 | if (resultCode == RESULT_OK) { 61 | if (requestCode == CaptureConfirmActivity.REQUEST_CODE_CONFIRM) { 62 | finishWith(RESULT_OK); 63 | } else if (requestCode == CapturePhotoHelper.CAPTURE_PHOTO_REQUEST_CODE) { 64 | CaptureConfirmActivity.launch(this, uri); 65 | } 66 | } else if (resultCode == RESULT_CANCELED) { 67 | finishWith(RESULT_CANCELED); 68 | } else if (resultCode == CaptureConfirmActivity.RESULT_CODE_RETRY) { 69 | File photoFile = new File(uri.getPath()); 70 | if (photoFile.exists()) { 71 | photoFile.delete(); 72 | } 73 | try { 74 | photoFile.createNewFile(); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | capturePhotoHelper.capturePhoto(uri); 79 | } 80 | } 81 | 82 | private void finishWith(int resultCode) { 83 | setResult(resultCode); 84 | finish(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/model/Album.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.model; 2 | 3 | import android.content.ContentUris; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.provider.MediaStore; 9 | import android.support.annotation.StringRes; 10 | 11 | import com.imnjh.imagepicker.R; 12 | 13 | 14 | /** 15 | * Created by Martin on 2017/1/17. 16 | */ 17 | public class Album implements Parcelable { 18 | 19 | public static final String ALBUM_ID_ALL = String.valueOf(-1); 20 | public @StringRes static final int ALBUM_NAME_ALL_RES_ID = R.string.general_all_pictures; 21 | 22 | private final String id; 23 | private final long coverId; 24 | private final String displayName; 25 | private final long count; 26 | 27 | 28 | public Album(String id, long coverId, String albumName, long count) { 29 | this.id = id; 30 | this.coverId = coverId; 31 | this.displayName = albumName; 32 | this.count = count; 33 | } 34 | 35 | public static Album valueOf(Cursor cursor) { 36 | return new Album( 37 | cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID)), 38 | cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)), 39 | cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)), 40 | cursor.getLong(3)); 41 | } 42 | 43 | 44 | public String getId() { 45 | return id; 46 | } 47 | 48 | @Override 49 | public int describeContents() { 50 | return 0; 51 | } 52 | 53 | @Override 54 | public void writeToParcel(Parcel dest, int flags) { 55 | dest.writeString(this.id); 56 | dest.writeLong(this.coverId); 57 | dest.writeString(this.displayName); 58 | dest.writeLong(this.count); 59 | } 60 | 61 | protected Album(Parcel in) { 62 | this.id = in.readString(); 63 | this.coverId = in.readLong(); 64 | this.displayName = in.readString(); 65 | this.count = in.readLong(); 66 | } 67 | 68 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 69 | @Override 70 | public Album createFromParcel(Parcel source) { 71 | return new Album(source); 72 | } 73 | 74 | @Override 75 | public Album[] newArray(int size) { 76 | return new Album[size]; 77 | } 78 | }; 79 | 80 | public boolean isAll() { 81 | return ALBUM_ID_ALL.equals(id); 82 | } 83 | 84 | public Uri buildCoverUri() { 85 | return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, coverId); 86 | } 87 | 88 | public String getDisplayName() { 89 | return displayName; 90 | } 91 | 92 | public long getCount() { 93 | return count; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 22 | 23 | 33 | 34 | 35 | 42 | 43 | 47 | 48 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/subsamplingview/OnImageEventListener.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget.subsamplingview; 2 | 3 | /** 4 | * Created by niejunhong on 16/6/20. 5 | */ 6 | /** 7 | * An event listener, allowing subclasses and activities to be notified of significant events. 8 | */ 9 | public class OnImageEventListener { 10 | 11 | /** 12 | * Called when the dimensions of the image and view are known, and either a preview image, 13 | * the full size image, or base layer tiles are loaded. This indicates the scale and translate 14 | * are known and the next draw will display an image. This event can be used to hide a loading 15 | * graphic, or inform a subclass that it is safe to draw overlays. 16 | */ 17 | public void onReady() {}; 18 | 19 | /** 20 | * Called when the full size image is ready. When using tiling, this means the lowest resolution 21 | * base layer of tiles are loaded, and when tiling is disabled, the image bitmap is loaded. 22 | * This event could be used as a trigger to enable gestures if you wanted interaction disabled 23 | * while only a preview is displayed, otherwise for most cases {@link #onReady()} is the best 24 | * event to listen to. 25 | */ 26 | public void onImageLoaded(int width, int height) {}; 27 | 28 | /** 29 | * Called when a preview image could not be loaded. This method cannot be relied upon; certain 30 | * encoding types of supported image formats can result in corrupt or blank images being loaded 31 | * and displayed with no detectable error. The view will continue to load the full size image. 32 | * 33 | * @param e The exception thrown. This error is logged by the view. 34 | */ 35 | public void onPreviewLoadError(Exception e) {}; 36 | 37 | /** 38 | * Indicates an error initiliasing the decoder when using a tiling, or when loading the full 39 | * size bitmap when tiling is disabled. This method cannot be relied upon; certain encoding 40 | * types of supported image formats can result in corrupt or blank images being loaded and 41 | * displayed with no detectable error. 42 | * 43 | * @param e The exception thrown. This error is also logged by the view. 44 | */ 45 | public void onImageLoadError(Exception e) {}; 46 | 47 | /** 48 | * Called when an image tile could not be loaded. This method cannot be relied upon; certain 49 | * encoding types of supported image formats can result in corrupt or blank images being loaded 50 | * and displayed with no detectable error. Most cases where an unsupported file is used will 51 | * result in an error caught by {@link #onImageLoadError(Exception)}. 52 | * 53 | * @param e The exception thrown. This error is logged by the view. 54 | */ 55 | public void onTileLoadError(Exception e) {}; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/util/UriUtil.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.util; 2 | 3 | import android.net.Uri; 4 | import android.provider.ContactsContract; 5 | import android.provider.MediaStore; 6 | import android.support.annotation.Nullable; 7 | 8 | /** 9 | * Created by Martin on 2017/1/17. 10 | */ 11 | 12 | public class UriUtil { 13 | public static final String HTTP_SCHEME = "http"; 14 | public static final String HTTPS_SCHEME = "https"; 15 | public static final String LOCAL_FILE_SCHEME = "file"; 16 | public static final String LOCAL_CONTENT_SCHEME = "content"; 17 | private static final String LOCAL_CONTACT_IMAGE_PREFIX; 18 | public static final String LOCAL_ASSET_SCHEME = "asset"; 19 | public static final String LOCAL_RESOURCE_SCHEME = "res"; 20 | public static final String DATA_SCHEME = "data"; 21 | 22 | public UriUtil() {} 23 | 24 | public static boolean isNetworkUri(@Nullable Uri uri) { 25 | String scheme = getSchemeOrNull(uri); 26 | return "https".equals(scheme) || "http".equals(scheme); 27 | } 28 | 29 | public static boolean isLocalFileUri(@Nullable Uri uri) { 30 | String scheme = getSchemeOrNull(uri); 31 | return "file".equals(scheme); 32 | } 33 | 34 | public static boolean isLocalContentUri(@Nullable Uri uri) { 35 | String scheme = getSchemeOrNull(uri); 36 | return "content".equals(scheme); 37 | } 38 | 39 | public static boolean isLocalContactUri(Uri uri) { 40 | return isLocalContentUri(uri) && "com.android.contacts".equals(uri.getAuthority()) 41 | && !uri.getPath().startsWith(LOCAL_CONTACT_IMAGE_PREFIX); 42 | } 43 | 44 | public static boolean isLocalCameraUri(Uri uri) { 45 | String uriString = uri.toString(); 46 | return uriString.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()) 47 | || uriString.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString()); 48 | } 49 | 50 | public static boolean isLocalAssetUri(@Nullable Uri uri) { 51 | String scheme = getSchemeOrNull(uri); 52 | return "asset".equals(scheme); 53 | } 54 | 55 | public static boolean isLocalResourceUri(@Nullable Uri uri) { 56 | String scheme = getSchemeOrNull(uri); 57 | return "res".equals(scheme); 58 | } 59 | 60 | public static boolean isDataUri(@Nullable Uri uri) { 61 | return "data".equals(getSchemeOrNull(uri)); 62 | } 63 | 64 | @Nullable 65 | public static String getSchemeOrNull(@Nullable Uri uri) { 66 | return uri == null ? null : uri.getScheme(); 67 | } 68 | 69 | public static Uri parseUriOrNull(@Nullable String uriAsString) { 70 | return uriAsString != null ? Uri.parse(uriAsString) : null; 71 | } 72 | 73 | static { 74 | LOCAL_CONTACT_IMAGE_PREFIX = 75 | Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "display_photo").getPath(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /imagepicker/src/main/java/com/imnjh/imagepicker/widget/PicturePreviewPageView.java: -------------------------------------------------------------------------------- 1 | package com.imnjh.imagepicker.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.PointF; 5 | import android.util.AttributeSet; 6 | import android.view.ViewGroup; 7 | import android.widget.FrameLayout; 8 | 9 | import com.imnjh.imagepicker.util.SystemUtil; 10 | import com.imnjh.imagepicker.widget.subsamplingview.ImageSource; 11 | import com.imnjh.imagepicker.widget.subsamplingview.OnImageEventListener; 12 | import com.imnjh.imagepicker.widget.subsamplingview.SubsamplingScaleImageView; 13 | 14 | 15 | /** 16 | * File description 17 | */ 18 | public class PicturePreviewPageView extends FrameLayout { 19 | 20 | /** 21 | * if aspect ratio is grater than 3 22 | * load picture as long image 23 | */ 24 | private static final int LONG_IMG_ASPECT_RATIO = 3; 25 | private static final int LONG_IMG_MINIMUM_LENGTH = 1500; 26 | 27 | private SubsamplingScaleImageView originImageView; 28 | 29 | public PicturePreviewPageView(Context context) { 30 | super(context); 31 | init(context); 32 | } 33 | 34 | public PicturePreviewPageView(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | init(context); 37 | } 38 | 39 | public PicturePreviewPageView(Context context, AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | init(context); 42 | } 43 | 44 | private void init(Context context) { 45 | originImageView = new SubsamplingScaleImageView(context); 46 | addView(originImageView, ViewGroup.LayoutParams.MATCH_PARENT, 47 | ViewGroup.LayoutParams.MATCH_PARENT); 48 | originImageView.setOnImageEventListener(new OnImageEventListener() { 49 | @Override 50 | public void onImageLoaded(int width, int height) { 51 | adjustPictureScale(originImageView, width, height); 52 | } 53 | }); 54 | } 55 | 56 | public void setMaxScale(float maxScale) { 57 | originImageView.setMaxScale(maxScale); 58 | } 59 | 60 | public void setOnClickListener(OnClickListener listener) { 61 | originImageView.setOnClickListener(listener); 62 | } 63 | 64 | public void setOriginImage(ImageSource imageSource) { 65 | originImageView.setImage(imageSource); 66 | } 67 | 68 | public SubsamplingScaleImageView getOriginImageView() { 69 | return originImageView; 70 | } 71 | 72 | private static void adjustPictureScale(SubsamplingScaleImageView view, int width, int height) { 73 | if (height >= LONG_IMG_MINIMUM_LENGTH 74 | && height / width >= LONG_IMG_ASPECT_RATIO) { 75 | float scale = SystemUtil.displaySize.x / (float) width; 76 | float centerX = SystemUtil.displaySize.x / 2; 77 | view.setScaleAndCenterWithAnim(scale, new PointF(centerX, 0.0f)); 78 | view.setDoubleTapZoomScale(scale); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /imagepicker/src/main/res/layout/activity_picker_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 14 | 15 | 18 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 48 | 49 | 58 | 59 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 20 | 21 | 26 | 27 |