├── .gitignore
├── .travis.yml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── gun0912
│ │ └── tedbottompickerdemo
│ │ ├── MainActivity.java
│ │ └── MyApplication.java
│ └── res
│ ├── layout
│ ├── activity_main.xml
│ └── image_item.xml
│ ├── 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-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── demo.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot1.jpeg
├── screenshot_multi_select.jpeg
├── settings.gradle
└── tedbottompicker
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── gun0912
│ └── tedbottompicker
│ ├── GridSpacingItemDecoration.java
│ ├── TedBottomPicker.java
│ ├── TedBottomSheetDialogFragment.java
│ ├── TedRxBottomPicker.java
│ ├── adapter
│ └── GalleryAdapter.java
│ ├── util
│ └── RealPathUtil.java
│ └── view
│ ├── TedEmptyRecyclerView.java
│ ├── TedSquareFrameLayout.java
│ └── TedSquareImageView.java
└── res
├── drawable-hdpi
├── ic_camera.png
└── ic_gallery.png
├── drawable-xhdpi
├── ic_camera.png
├── ic_clear.png
└── ic_gallery.png
├── drawable-xxhdpi
├── ic_camera.png
└── ic_gallery.png
├── drawable-xxxhdpi
├── gallery_photo_selected.xml
├── ic_camera.png
├── ic_gallery.png
└── img_error.png
├── layout
├── tedbottompicker_content_view.xml
├── tedbottompicker_grid_item.xml
└── tedbottompicker_selected_item.xml
├── values-ko
└── strings.xml
├── values
├── attrs.xml
├── colors.xml
├── dimens.xml
├── strings.xml
└── style.xml
└── xml
└── provider_paths.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | ###Android###
2 |
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 |
31 | ###OSX###
32 |
33 | .DS_Store
34 | .AppleDouble
35 | .LSOverride
36 |
37 | # Icon must end with two \r
38 | Icon
39 |
40 | # Thumbnails
41 | ._*
42 |
43 | # Files that might appear on external disk
44 | .Spotlight-V100
45 | .Trashes
46 |
47 | # Directories potentially created on remote AFP share
48 | .AppleDB
49 | .AppleDesktop
50 | Network Trash Folder
51 | Temporary Items
52 | .apdisk
53 |
54 |
55 | ###Linux###
56 |
57 | *~
58 |
59 | # KDE directory preferences
60 | .directory
61 |
62 |
63 | ###Windows###
64 |
65 | # Windows image file caches
66 | Thumbs.db
67 | ehthumbs.db
68 |
69 | # Folder config file
70 | Desktop.ini
71 |
72 | # Recycle Bin used on file shares
73 | $RECYCLE.BIN/
74 |
75 | # Windows Installer files
76 | *.cab
77 | *.msi
78 | *.msm
79 | *.msp
80 |
81 | # Windows shortcuts
82 | *.lnk
83 |
84 |
85 | ###IntelliJ###
86 |
87 | *.iml
88 | *.ipr
89 | *.iws
90 | .idea/
91 |
92 |
93 | ###Gradle###
94 |
95 | .gradle
96 | build/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 | language: android
3 | jdk: oraclejdk8
4 | android:
5 | components:
6 | - tools
7 | - platform-tools
8 | - tools
9 | - android-27
10 | - build-tools-28.0.3
11 | - extra-android-m2repository
12 | - extra-google-m2repository
13 | script:
14 | - ./gradlew build
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [THIS LIBRARY IS DEPRECATED] Please use [TedImagePicker](https://github.com/ParkSangGwon/TedImagePicker)
2 |
3 | # What is TedBottomPicker?
4 | In Google's Material Design, Google introduce **Bottom sheets**.([Components – Bottom sheets](https://material.google.com/components/bottom-sheets.html))
5 | **Bottom sheets** slide up from the bottom of the screen to reveal more content.
6 |
7 | If you want pick image from gallery or take picture, this library can help easily.
8 | **TedBottomPicker** provide 3 options:
9 |
10 | 1. Take a picture by camera(using `MediaStore.ACTION_IMAGE_CAPTURE` intent)
11 | 2. Get image from gallery(using `Intent.ACTION_PICK` intent)
12 | 3. Get image from recent image(using `MediaStore.Images.Media.EXTERNAL_CONTENT_URI` cursor)
13 |
14 |
15 | **TedBottomPicker** is simple image picker using bottom sheet.
16 |
17 |
18 | ## TedImagePicker
19 | - If you want full screen image picker, use [TedImagePicker](https://github.com/ParkSangGwon/TedImagePicker)
20 | - TedImagePicker is simple/beautiful/smart image picker
21 |
22 | .
23 |
24 |
25 | ## Demo
26 | 1. Show Bottom Sheet.
27 | 2. Pick Image
28 |
29 | ### Single/Multi Select
30 |
31 |  
32 | 
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## Setup
40 |
41 |
42 | ### Gradle
43 | [](https://search.maven.org/search?q=g:%22io.github.ParkSangGwon%22%20AND%20a:%tedbottompicker%22)
44 | ```javascript
45 | dependencies {
46 | implementation 'gun0912.ted:tedbottompicker:x.y.z'
47 | //implementation 'gun0912.ted:tedbottompicker:2.0.1'
48 | }
49 |
50 | ```
51 |
52 | If you think this library is useful, please press star button at upside.
53 |
54 |
55 |
56 |
57 |
58 |
59 | ## How to use
60 | ### 1. Check Permission
61 | You have to grant `WRITE_EXTERNAL_STORAGE` permission from user.
62 | If your targetSDK version is 23+, you have to check permission and request permission to user.
63 | Because after Marshmallow(6.0), you have to not only declare permissions in `AndroidManifest.xml` but also request permissions at runtime.
64 | There are so many permission check library in [Android-Arsenal](http://android-arsenal.com/tag/235?sort=rating)
65 | I recommend [TedPermission](https://github.com/ParkSangGwon/TedPermission)
66 | **TedPermission** is super simple and smart permission check library.
67 |
68 |
69 |
70 | ### 2. Start TedBottomPicker
71 | - TedBottomPicker support `RxJava` and `Listener` style
72 |
73 | ### RxJava
74 | **You have to use TedRxBottomPicker**
75 | - Single image
76 | ```java
77 | TedRxBottomPicker.with(MainActivity.this)
78 | .show()
79 | .subscribe(uri ->
80 | // here is selected image uri
81 | }, Throwable::printStackTrace);
82 |
83 |
84 | ```
85 | - Multi image
86 | ```java
87 |
88 | TedRxBottomPicker.with(MainActivity.this)
89 | //.setPeekHeight(getResources().getDisplayMetrics().heightPixels/2)
90 | .setPeekHeight(1600)
91 | .showTitle(false)
92 | .setCompleteButtonText("Done")
93 | .setEmptySelectionText("No Select")
94 | .setSelectedUriList(selectedUriList)
95 | .showMultiImage()
96 | .subscribe(uris -> {
97 | // here is selected image uri list
98 | }, Throwable::printStackTrace);
99 |
100 | ```
101 |
102 |
103 | ### Listener
104 | **You have to use TedBottomPicker**
105 | - Single image
106 | ```java
107 | TedBottomPicker.with(MainActivity.this)
108 | .show(new TedBottomSheetDialogFragment.OnImageSelectedListener() {
109 | @Override
110 | public void onImageSelected(Uri uri) {
111 | // here is selected image uri
112 | }
113 | });
114 |
115 | ```
116 |
117 | - Multi image
118 | ```java
119 | TedBottomPicker.with(MainActivity.this)
120 | .setPeekHeight(1600)
121 | .showTitle(false)
122 | .setCompleteButtonText("Done")
123 | .setEmptySelectionText("No Select")
124 | .setSelectedUriList(selectedUriList)
125 | .showMultiImage(new TedBottomSheetDialogFragment.OnMultiImageSelectedListener() {
126 | @Override
127 | public void onImagesSelected(List uriList) {
128 | // here is selected image uri list
129 | }
130 | });
131 | ```
132 |
133 |
134 | ## Customize
135 | You can customize something ...
136 |
137 | ### Function
138 |
139 | #### Common
140 |
141 | * `showVideoMedia()` : Not only load image, but also load video
142 | * `setPreviewMaxCount(Int) (default: 25)`
143 | * `setPeekHeight(Int)`
144 | * `setPeekHeightResId(R.dimen.xxx)`
145 | * `showCameraTile(Boolean) (default: true)`
146 | * `setCameraTile(R.drawable.xxx or Drawable)`
147 | * `setCameraTileBackgroundResId(R.color.xxx)`
148 | * `setGalleryTile(R.drawable.xxx or Drawable)`
149 | * `showGalleryTile(Boolean) (default: true)`
150 | * `setGalleryTileBackgroundResId(R.color.xxx)`
151 | * `setSpacing(Int)`
152 | * `setSpacingResId(R.dimen.xxx)`
153 | * `setOnErrorListener(OnErrorListener)`
154 | * `setTitle(String or R.string.xxx) (default: 'Select Image','사진 선택')`
155 | * `showTitle(Boolean) (default: true)`
156 | * `setTitleBackgroundResId(R.color.xxx)`
157 | * `setImageProvider(ImageProvider)`
158 | : If you want load grid image yourself, you can use your ImageProvider
159 |
160 | #### Single Select
161 | * `setSelectedUri(Uri)`
162 |
163 | #### Multi Select
164 | * `setDeSelectIcon(R.drawable.xxx or Drawable)`
165 | * `setSelectedForeground(R.drawable.xxx or Drawable)`
166 | * `setSelectMaxCount(Int)`
167 | * `setSelectMinCount(Int)`
168 | * `setCompleteButtonText(String or R.string.xxx) (default: 'Done','완료')`
169 | * `setEmptySelectionText(String or R.string.xxx) (default: 'No Image','이미지가 선택되지 않았습니다')`
170 | * `setSelectMaxCountErrorText(String or R.string.xxx)`
171 | * `setSelectMinCountErrorText(String or R.string.xxx)`
172 | * `setSelectedUriList(ArrayList)`
173 |
174 |
175 |
176 |
177 |
178 | ## Thanks
179 | * [Flipboard-bottomsheet](https://github.com/Flipboard/bottomsheet) - Android component which presents a dismissible view from the bottom of the screen
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 | ## License
188 | ```code
189 | Copyright 2017 Ted Park
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.```
202 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 |
5 | compileSdkVersion 27
6 |
7 |
8 | defaultConfig {
9 | applicationId "gun0912.tedbottompickerdemo"
10 | minSdkVersion 16
11 | targetSdkVersion 26
12 | versionCode 1
13 | versionName "1.0.0"
14 | multiDexEnabled true
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | abortOnError false
25 | }
26 |
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | }
32 |
33 |
34 | repositories {
35 |
36 |
37 | }
38 |
39 | dependencies {
40 | implementation fileTree(include: ['*.jar'], dir: 'libs')
41 | testImplementation 'junit:junit:4.12'
42 | implementation 'com.android.support:appcompat-v7:27.1.1'
43 | implementation project(':tedbottompicker')
44 | implementation 'com.android.support:multidex:1.0.3'
45 |
46 | implementation 'gun0912.ted:tedpermission:2.2.0'
47 | implementation 'com.github.bumptech.glide:glide:4.7.1'
48 | annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
49 |
50 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
51 | releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
52 |
53 | implementation "io.reactivex.rxjava2:rxjava:2.2.6"
54 | }
55 |
--------------------------------------------------------------------------------
/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/TedPark/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/gun0912/tedbottompickerdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompickerdemo;
2 |
3 | import android.Manifest;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.util.Log;
8 | import android.util.TypedValue;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.FrameLayout;
14 | import android.widget.ImageView;
15 | import android.widget.Toast;
16 |
17 | import com.bumptech.glide.Glide;
18 | import com.bumptech.glide.RequestManager;
19 | import com.bumptech.glide.request.RequestOptions;
20 | import com.gun0912.tedpermission.PermissionListener;
21 | import com.gun0912.tedpermission.TedPermission;
22 |
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | import gun0912.tedbottompicker.TedBottomPicker;
27 | import gun0912.tedbottompicker.TedRxBottomPicker;
28 | import io.reactivex.disposables.Disposable;
29 |
30 | public class MainActivity extends AppCompatActivity {
31 |
32 | private ImageView iv_image;
33 | private List selectedUriList;
34 | private Uri selectedUri;
35 | private Disposable singleImageDisposable;
36 | private Disposable multiImageDisposable;
37 | private ViewGroup mSelectedImagesContainer;
38 | private RequestManager requestManager;
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_main);
44 |
45 | iv_image = findViewById(R.id.iv_image);
46 | mSelectedImagesContainer = findViewById(R.id.selected_photos_container);
47 | requestManager = Glide.with(this);
48 | setSingleShowButton();
49 | setMultiShowButton();
50 | setRxSingleShowButton();
51 | setRxMultiShowButton();
52 |
53 | }
54 |
55 | private void setSingleShowButton() {
56 |
57 | Button btnSingleShow = findViewById(R.id.btn_single_show);
58 | btnSingleShow.setOnClickListener(view -> {
59 | PermissionListener permissionlistener = new PermissionListener() {
60 | @Override
61 | public void onPermissionGranted() {
62 |
63 | TedBottomPicker.with(MainActivity.this)
64 | //.setPeekHeight(getResources().getDisplayMetrics().heightPixels/2)
65 | .setSelectedUri(selectedUri)
66 | //.showVideoMedia()
67 | .setPeekHeight(1200)
68 | .show(uri -> {
69 | Log.d("ted", "uri: " + uri);
70 | Log.d("ted", "uri.getPath(): " + uri.getPath());
71 | selectedUri = uri;
72 |
73 | iv_image.setVisibility(View.VISIBLE);
74 | mSelectedImagesContainer.setVisibility(View.GONE);
75 |
76 | requestManager
77 | .load(uri)
78 | .into(iv_image);
79 | });
80 |
81 |
82 | }
83 |
84 | @Override
85 | public void onPermissionDenied(ArrayList deniedPermissions) {
86 | Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
87 | }
88 |
89 |
90 | };
91 |
92 | checkPermission(permissionlistener);
93 | });
94 | }
95 |
96 | private void setMultiShowButton() {
97 |
98 | Button btnMultiShow = findViewById(R.id.btn_multi_show);
99 | btnMultiShow.setOnClickListener(view -> {
100 |
101 | PermissionListener permissionlistener = new PermissionListener() {
102 | @Override
103 | public void onPermissionGranted() {
104 |
105 | TedBottomPicker.with(MainActivity.this)
106 | //.setPeekHeight(getResources().getDisplayMetrics().heightPixels/2)
107 | .setPeekHeight(1600)
108 | .showTitle(false)
109 | .setCompleteButtonText("Done")
110 | .setEmptySelectionText("No Select")
111 | .setSelectedUriList(selectedUriList)
112 | .showMultiImage(uriList -> {
113 | selectedUriList = uriList;
114 | showUriList(uriList);
115 | });
116 |
117 |
118 | }
119 |
120 | @Override
121 | public void onPermissionDenied(ArrayList deniedPermissions) {
122 | Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
123 | }
124 |
125 |
126 | };
127 |
128 | checkPermission(permissionlistener);
129 |
130 | });
131 |
132 | }
133 |
134 |
135 | private void setRxSingleShowButton() {
136 |
137 | Button btnSingleShow = findViewById(R.id.btn_rx_single_show);
138 | btnSingleShow.setOnClickListener(view -> {
139 | PermissionListener permissionlistener = new PermissionListener() {
140 | @Override
141 | public void onPermissionGranted() {
142 |
143 | singleImageDisposable = TedRxBottomPicker.with(MainActivity.this)
144 | //.setPeekHeight(getResources().getDisplayMetrics().heightPixels/2)
145 | .setSelectedUri(selectedUri)
146 | //.showVideoMedia()
147 | .setPeekHeight(1200)
148 | .show()
149 | .subscribe(uri -> {
150 | selectedUri = uri;
151 |
152 | iv_image.setVisibility(View.VISIBLE);
153 | mSelectedImagesContainer.setVisibility(View.GONE);
154 |
155 | requestManager
156 | .load(uri)
157 | .into(iv_image);
158 | }, Throwable::printStackTrace);
159 |
160 |
161 | }
162 |
163 | @Override
164 | public void onPermissionDenied(ArrayList deniedPermissions) {
165 | Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
166 | }
167 |
168 |
169 | };
170 |
171 | checkPermission(permissionlistener);
172 | });
173 | }
174 |
175 |
176 | private void setRxMultiShowButton() {
177 |
178 | Button btnRxMultiShow = findViewById(R.id.btn_rx_multi_show);
179 | btnRxMultiShow.setOnClickListener(view -> {
180 | PermissionListener permissionlistener = new PermissionListener() {
181 | @Override
182 | public void onPermissionGranted() {
183 |
184 | multiImageDisposable = TedRxBottomPicker.with(MainActivity.this)
185 | //.setPeekHeight(getResources().getDisplayMetrics().heightPixels/2)
186 | .setPeekHeight(1600)
187 | .showTitle(false)
188 | .setCompleteButtonText("Done")
189 | .setEmptySelectionText("No Select")
190 | .setSelectedUriList(selectedUriList)
191 | .showMultiImage()
192 | .subscribe(uris -> {
193 | selectedUriList = uris;
194 | showUriList(uris);
195 | }, Throwable::printStackTrace);
196 |
197 |
198 | }
199 |
200 | @Override
201 | public void onPermissionDenied(ArrayList deniedPermissions) {
202 | Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
203 | }
204 |
205 |
206 | };
207 |
208 | checkPermission(permissionlistener);
209 | });
210 |
211 | }
212 |
213 | private void checkPermission(PermissionListener permissionlistener) {
214 | TedPermission.with(MainActivity.this)
215 | .setPermissionListener(permissionlistener)
216 | .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
217 | .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
218 | .check();
219 | }
220 |
221 | private void showUriList(List uriList) {
222 | // Remove all views before
223 | // adding the new ones.
224 | mSelectedImagesContainer.removeAllViews();
225 |
226 | iv_image.setVisibility(View.GONE);
227 | mSelectedImagesContainer.setVisibility(View.VISIBLE);
228 |
229 | int widthPixel = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
230 | int heightPixel = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());
231 |
232 |
233 | for (Uri uri : uriList) {
234 |
235 | View imageHolder = LayoutInflater.from(this).inflate(R.layout.image_item, null);
236 | ImageView thumbnail = imageHolder.findViewById(R.id.media_image);
237 |
238 | requestManager
239 | .load(uri.toString())
240 | .apply(new RequestOptions().fitCenter())
241 | .into(thumbnail);
242 |
243 | mSelectedImagesContainer.addView(imageHolder);
244 |
245 | thumbnail.setLayoutParams(new FrameLayout.LayoutParams(widthPixel, heightPixel));
246 |
247 | }
248 |
249 | }
250 |
251 | @Override
252 | protected void onDestroy() {
253 | if (singleImageDisposable != null && !singleImageDisposable.isDisposed()) {
254 | singleImageDisposable.dispose();
255 | }
256 | if (multiImageDisposable != null && !multiImageDisposable.isDisposed()) {
257 | multiImageDisposable.dispose();
258 | }
259 | super.onDestroy();
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/app/src/main/java/gun0912/tedbottompickerdemo/MyApplication.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompickerdemo;
2 |
3 | import android.app.Application;
4 | import android.support.multidex.MultiDexApplication;
5 |
6 | import com.squareup.leakcanary.LeakCanary;
7 |
8 | public class MyApplication extends MultiDexApplication {
9 |
10 | @Override
11 | public void onCreate() {
12 | super.onCreate();
13 | setLeakCanary();
14 | }
15 |
16 | private void setLeakCanary() {
17 | if (LeakCanary.isInAnalyzerProcess(this)) {
18 | // This process is dedicated to LeakCanary for heap analysis.
19 | // You should not init your app in this process.
20 | return;
21 | }
22 | LeakCanary.install(this);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
40 |
41 |
48 |
49 |
50 |
57 |
58 |
59 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/image_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TedBottomPickerDemo
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.3'
10 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.16.0'
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | google()
18 | }
19 |
20 | group = GROUP
21 | version = VERSION_NAME
22 |
23 | plugins.withId("com.vanniktech.maven.publish") {
24 | mavenPublish {
25 | sonatypeHost = "S01"
26 | }
27 | }
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/demo.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | POM_ARTIFACT_ID=tedbottompicker
20 | POM_NAME=TedBottomPicker
21 |
22 | GROUP=io.github.ParkSangGwon
23 | VERSION_NAME=2.0.1
24 |
25 | POM_PACKAGING=aar
26 |
27 | POM_DESCRIPTION=TedBottomPicker is simple image picker using bottom sheet
28 | POM_INCEPTION_YEAR=2021
29 |
30 | POM_URL=https://github.com/ParkSangGwon/TedBottomPicker
31 | POM_SCM_URL=https://github.com/ParkSangGwon/TedBottomPicker
32 | POM_SCM_CONNECTION=scm:git:git://github.com/ParkSangGwon/TedBottomPicker.git
33 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/ParkSangGwon/TedBottomPicker.git
34 |
35 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
36 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
37 | POM_LICENCE_DIST=repo
38 |
39 | POM_DEVELOPER_ID=ParkSangGwon
40 | POM_DEVELOPER_NAME=ParkSangGwon
41 | POM_DEVELOPER_URL=https://github.com/ParkSangGwon
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jul 01 22:46:06 KST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshot1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/screenshot1.jpeg
--------------------------------------------------------------------------------
/screenshot_multi_select.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/screenshot_multi_select.jpeg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':tedbottompicker'
2 |
--------------------------------------------------------------------------------
/tedbottompicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/tedbottompicker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 |
4 | android {
5 |
6 | compileSdkVersion 27
7 |
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 24
12 | versionCode 1
13 | versionName "1.0"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | lintOptions {
22 | abortOnError false
23 | }
24 |
25 |
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 |
31 | }
32 |
33 |
34 |
35 | configurations {
36 | javadocDeps
37 | }
38 |
39 | repositories {
40 | mavenCentral()
41 | google()
42 | }
43 |
44 |
45 | dependencies {
46 | implementation fileTree(include: ['*.jar'], dir: 'libs')
47 | testCompile 'junit:junit:4.12'
48 | implementation 'com.android.support:appcompat-v7:27.1.1'
49 | api 'com.android.support:design:27.1.1'
50 | implementation 'com.android.support:support-annotations:27.1.1'
51 | javadocDeps 'com.android.support:support-annotations:27.1.1'
52 | implementation 'com.github.bumptech.glide:glide:4.9.0'
53 | annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
54 | implementation 'gun0912.ted:tedonactivityresult:1.0.6'
55 | implementation "io.reactivex.rxjava2:rxjava:2.2.6"
56 | }
57 |
58 | apply plugin: "com.vanniktech.maven.publish"
--------------------------------------------------------------------------------
/tedbottompicker/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/TedPark/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep public class * implements com.bumptech.glide.module.GlideModule
19 | -keep public class * extends com.bumptech.glide.module.AppGlideModule
20 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
21 | **[] $VALUES;
22 | public *;
23 | }
24 |
25 | # for DexGuard only
26 | -keepresourcexmlelements manifest/application/meta-data@value=GlideModule
--------------------------------------------------------------------------------
/tedbottompicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/GridSpacingItemDecoration.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker;
2 |
3 | import android.graphics.Rect;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by TedPark on 2016. 8. 30..
9 | */
10 |
11 | /**
12 | * https://gist.github.com/liangzhitao/e57df3c3232ee446d464
13 | */
14 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
15 |
16 | private int spanCount;
17 | private int spacing;
18 | private boolean includeEdge;
19 |
20 |
21 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
22 | this.spanCount = spanCount;
23 | this.spacing = spacing;
24 | this.includeEdge = includeEdge;
25 |
26 | }
27 |
28 | @Override
29 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
30 | int position = parent.getChildAdapterPosition(view); // item position
31 |
32 | if (position >= 0) {
33 | int column = position % spanCount; // item column
34 |
35 | if (includeEdge) {
36 | outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
37 | outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
38 |
39 | if (position < spanCount) { // top edge
40 | outRect.top = spacing;
41 | }
42 | outRect.bottom = spacing; // item bottom
43 | } else {
44 | outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
45 | outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
46 | if (position >= spanCount) {
47 | outRect.top = spacing; // item top
48 | }
49 | }
50 | } else {
51 | outRect.left = 0;
52 | outRect.right = 0;
53 | outRect.top = 0;
54 | outRect.bottom = 0;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/TedBottomPicker.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker;
2 |
3 | import android.support.v4.app.FragmentActivity;
4 |
5 | public class TedBottomPicker extends TedBottomSheetDialogFragment {
6 |
7 | public static Builder with(FragmentActivity fragmentActivity) {
8 | return new Builder(fragmentActivity);
9 | }
10 |
11 | public static class Builder extends BaseBuilder {
12 |
13 | private Builder(FragmentActivity fragmentActivity) {
14 | super(fragmentActivity);
15 | }
16 |
17 | public void show(OnImageSelectedListener onImageSelectedListener) {
18 | this.onImageSelectedListener = onImageSelectedListener;
19 | create().show(fragmentActivity.getSupportFragmentManager());
20 | }
21 |
22 | public void showMultiImage(OnMultiImageSelectedListener onMultiImageSelectedListener) {
23 | this.onMultiImageSelectedListener = onMultiImageSelectedListener;
24 | create().show(fragmentActivity.getSupportFragmentManager());
25 | }
26 | }
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/TedBottomSheetDialogFragment.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.Dialog;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.content.pm.ResolveInfo;
9 | import android.graphics.drawable.Drawable;
10 | import android.media.MediaScannerConnection;
11 | import android.net.Uri;
12 | import android.os.Build;
13 | import android.os.Bundle;
14 | import android.os.Environment;
15 | import android.provider.MediaStore;
16 | import android.support.annotation.ColorRes;
17 | import android.support.annotation.DimenRes;
18 | import android.support.annotation.DrawableRes;
19 | import android.support.annotation.IntDef;
20 | import android.support.annotation.NonNull;
21 | import android.support.annotation.Nullable;
22 | import android.support.annotation.StringRes;
23 | import android.support.design.widget.BottomSheetBehavior;
24 | import android.support.design.widget.BottomSheetDialogFragment;
25 | import android.support.design.widget.CoordinatorLayout;
26 | import android.support.v4.app.FragmentActivity;
27 | import android.support.v4.app.FragmentManager;
28 | import android.support.v4.app.FragmentTransaction;
29 | import android.support.v4.content.ContextCompat;
30 | import android.support.v4.content.FileProvider;
31 | import android.support.v7.widget.GridLayoutManager;
32 | import android.support.v7.widget.RecyclerView;
33 | import android.text.TextUtils;
34 | import android.view.LayoutInflater;
35 | import android.view.View;
36 | import android.widget.Button;
37 | import android.widget.FrameLayout;
38 | import android.widget.ImageView;
39 | import android.widget.LinearLayout;
40 | import android.widget.TextView;
41 | import android.widget.Toast;
42 |
43 | import com.bumptech.glide.Glide;
44 | import com.bumptech.glide.request.RequestOptions;
45 | import com.gun0912.tedonactivityresult.TedOnActivityResult;
46 | import com.gun0912.tedonactivityresult.listener.OnActivityResultListener;
47 |
48 | import java.io.File;
49 | import java.io.IOException;
50 | import java.lang.annotation.Retention;
51 | import java.lang.annotation.RetentionPolicy;
52 | import java.text.SimpleDateFormat;
53 | import java.util.ArrayList;
54 | import java.util.Date;
55 | import java.util.List;
56 | import java.util.Locale;
57 |
58 | import gun0912.tedbottompicker.adapter.GalleryAdapter;
59 | import gun0912.tedbottompicker.util.RealPathUtil;
60 |
61 | public class TedBottomSheetDialogFragment extends BottomSheetDialogFragment {
62 |
63 | private static final String EXTRA_CAMERA_IMAGE_URI = "camera_image_uri";
64 | private static final String EXTRA_CAMERA_SELECTED_IMAGE_URI = "camera_selected_image_uri";
65 | public BaseBuilder builder;
66 | private GalleryAdapter imageGalleryAdapter;
67 | private View view_title_container;
68 | private TextView tv_title;
69 | private Button btn_done;
70 |
71 | private FrameLayout selected_photos_container_frame;
72 | private LinearLayout selected_photos_container;
73 |
74 | private TextView selected_photos_empty;
75 | private List selectedUriList;
76 | private List tempUriList;
77 | private Uri cameraImageUri;
78 | private RecyclerView rc_gallery;
79 | private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {
80 |
81 |
82 | @Override
83 | public void onStateChanged(@NonNull View bottomSheet, int newState) {
84 | if (newState == BottomSheetBehavior.STATE_HIDDEN) {
85 | dismissAllowingStateLoss();
86 | }
87 |
88 |
89 | }
90 |
91 | @Override
92 | public void onSlide(@NonNull View bottomSheet, float slideOffset) {
93 | }
94 | };
95 |
96 | @Override
97 | public void onCreate(@Nullable Bundle savedInstanceState) {
98 | super.onCreate(savedInstanceState);
99 | setupSavedInstanceState(savedInstanceState);
100 | }
101 |
102 | private void setupSavedInstanceState(Bundle savedInstanceState) {
103 | if (savedInstanceState == null) {
104 | cameraImageUri = builder.selectedUri;
105 | tempUriList = builder.selectedUriList;
106 | } else {
107 | cameraImageUri = savedInstanceState.getParcelable(EXTRA_CAMERA_IMAGE_URI);
108 | tempUriList = savedInstanceState.getParcelableArrayList(EXTRA_CAMERA_SELECTED_IMAGE_URI);
109 | }
110 | }
111 |
112 | @Override
113 | public void onSaveInstanceState(Bundle outState) {
114 | outState.putParcelable(EXTRA_CAMERA_IMAGE_URI, cameraImageUri);
115 | outState.putParcelableArrayList(EXTRA_CAMERA_SELECTED_IMAGE_URI, new ArrayList<>(selectedUriList));
116 | super.onSaveInstanceState(outState);
117 | }
118 |
119 | public void show(FragmentManager fragmentManager) {
120 | FragmentTransaction ft = fragmentManager.beginTransaction();
121 | ft.add(this, getTag());
122 | ft.commitAllowingStateLoss();
123 | }
124 |
125 |
126 | @Override
127 | public void setupDialog(Dialog dialog, int style) {
128 | super.setupDialog(dialog, style);
129 | View contentView = View.inflate(getContext(), R.layout.tedbottompicker_content_view, null);
130 | dialog.setContentView(contentView);
131 | CoordinatorLayout.LayoutParams layoutParams =
132 | (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
133 | CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
134 | if (behavior instanceof BottomSheetBehavior) {
135 | ((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback);
136 | if (builder != null && builder.peekHeight > 0) {
137 | ((BottomSheetBehavior) behavior).setPeekHeight(builder.peekHeight);
138 | }
139 |
140 | }
141 |
142 | if (builder == null) {
143 | dismissAllowingStateLoss();
144 | return;
145 | }
146 | initView(contentView);
147 |
148 | setTitle();
149 | setRecyclerView();
150 | setSelectionView();
151 |
152 | selectedUriList = new ArrayList<>();
153 |
154 |
155 | if (builder.onImageSelectedListener != null && cameraImageUri != null) {
156 | addUri(cameraImageUri);
157 | } else if (builder.onMultiImageSelectedListener != null && tempUriList != null) {
158 | for (Uri uri : tempUriList) {
159 | addUri(uri);
160 | }
161 | }
162 |
163 | setDoneButton();
164 | checkMultiMode();
165 | }
166 |
167 | private void setSelectionView() {
168 |
169 | if (builder.emptySelectionText != null) {
170 | selected_photos_empty.setText(builder.emptySelectionText);
171 | }
172 |
173 |
174 | }
175 |
176 | private void setDoneButton() {
177 |
178 | if (builder.completeButtonText != null) {
179 | btn_done.setText(builder.completeButtonText);
180 | }
181 |
182 | btn_done.setOnClickListener(new View.OnClickListener() {
183 | @Override
184 | public void onClick(View view) {
185 |
186 | onMultiSelectComplete();
187 |
188 |
189 | }
190 | });
191 | }
192 |
193 | private void onMultiSelectComplete() {
194 |
195 | if (selectedUriList.size() < builder.selectMinCount) {
196 | String message;
197 | if (builder.selectMinCountErrorText != null) {
198 | message = builder.selectMinCountErrorText;
199 | } else {
200 | message = String.format(getResources().getString(R.string.select_min_count), builder.selectMinCount);
201 | }
202 |
203 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
204 | return;
205 | }
206 |
207 |
208 | builder.onMultiImageSelectedListener.onImagesSelected(selectedUriList);
209 | dismissAllowingStateLoss();
210 | }
211 |
212 | private void checkMultiMode() {
213 | if (!isMultiSelect()) {
214 | btn_done.setVisibility(View.GONE);
215 | selected_photos_container_frame.setVisibility(View.GONE);
216 | }
217 |
218 | }
219 |
220 | private void initView(View contentView) {
221 |
222 | view_title_container = contentView.findViewById(R.id.view_title_container);
223 | rc_gallery = contentView.findViewById(R.id.rc_gallery);
224 | tv_title = contentView.findViewById(R.id.tv_title);
225 | btn_done = contentView.findViewById(R.id.btn_done);
226 |
227 | selected_photos_container_frame = contentView.findViewById(R.id.selected_photos_container_frame);
228 | selected_photos_container = contentView.findViewById(R.id.selected_photos_container);
229 | selected_photos_empty = contentView.findViewById(R.id.selected_photos_empty);
230 | }
231 |
232 | private void setRecyclerView() {
233 |
234 | GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 3);
235 | rc_gallery.setLayoutManager(gridLayoutManager);
236 | rc_gallery.addItemDecoration(new GridSpacingItemDecoration(gridLayoutManager.getSpanCount(), builder.spacing, builder.includeEdgeSpacing));
237 | updateAdapter();
238 | }
239 |
240 | private void updateAdapter() {
241 |
242 | imageGalleryAdapter = new GalleryAdapter(
243 | getActivity()
244 | , builder);
245 | rc_gallery.setAdapter(imageGalleryAdapter);
246 | imageGalleryAdapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {
247 | @Override
248 | public void onItemClick(View view, int position) {
249 |
250 | GalleryAdapter.PickerTile pickerTile = imageGalleryAdapter.getItem(position);
251 |
252 | switch (pickerTile.getTileType()) {
253 | case GalleryAdapter.PickerTile.CAMERA:
254 | startCameraIntent();
255 | break;
256 | case GalleryAdapter.PickerTile.GALLERY:
257 | startGalleryIntent();
258 | break;
259 | case GalleryAdapter.PickerTile.IMAGE:
260 | if (pickerTile.getImageUri() != null) {
261 | complete(pickerTile.getImageUri());
262 | }
263 |
264 | break;
265 |
266 | default:
267 | errorMessage();
268 | }
269 |
270 | }
271 | });
272 | }
273 |
274 | private void complete(final Uri uri) {
275 | if (isMultiSelect()) {
276 | if (selectedUriList.contains(uri)) {
277 | removeImage(uri);
278 | } else {
279 | addUri(uri);
280 | }
281 |
282 | } else {
283 | builder.onImageSelectedListener.onImageSelected(uri);
284 | dismissAllowingStateLoss();
285 | }
286 |
287 | }
288 |
289 | private void addUri(final Uri uri) {
290 | if (selectedUriList.size() == builder.selectMaxCount) {
291 | String message;
292 | if (builder.selectMaxCountErrorText != null) {
293 | message = builder.selectMaxCountErrorText;
294 | } else {
295 | message = String.format(getResources().getString(R.string.select_max_count), builder.selectMaxCount);
296 | }
297 |
298 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
299 | return;
300 | }
301 |
302 |
303 | selectedUriList.add(uri);
304 |
305 | final View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.tedbottompicker_selected_item, null);
306 | ImageView thumbnail = rootView.findViewById(R.id.selected_photo);
307 | ImageView iv_close = rootView.findViewById(R.id.iv_close);
308 | rootView.setTag(uri);
309 |
310 | selected_photos_container.addView(rootView, 0);
311 |
312 |
313 | int px = (int) getResources().getDimension(R.dimen.tedbottompicker_selected_image_height);
314 | thumbnail.setLayoutParams(new FrameLayout.LayoutParams(px, px));
315 |
316 | if (builder.imageProvider == null) {
317 | Glide.with(getActivity())
318 | .load(uri)
319 | .thumbnail(0.1f)
320 | .apply(new RequestOptions()
321 | .centerCrop()
322 | .placeholder(R.drawable.ic_gallery)
323 | .error(R.drawable.img_error))
324 | .into(thumbnail);
325 | } else {
326 | builder.imageProvider.onProvideImage(thumbnail, uri);
327 | }
328 |
329 |
330 | if (builder.deSelectIconDrawable != null) {
331 | iv_close.setImageDrawable(builder.deSelectIconDrawable);
332 | }
333 |
334 | iv_close.setOnClickListener(new View.OnClickListener() {
335 | @Override
336 | public void onClick(View v) {
337 | removeImage(uri);
338 |
339 | }
340 | });
341 |
342 |
343 | updateSelectedView();
344 | imageGalleryAdapter.setSelectedUriList(selectedUriList, uri);
345 |
346 | }
347 |
348 | private void removeImage(Uri uri) {
349 |
350 | selectedUriList.remove(uri);
351 |
352 |
353 | for (int i = 0; i < selected_photos_container.getChildCount(); i++) {
354 | View childView = selected_photos_container.getChildAt(i);
355 |
356 |
357 | if (childView.getTag().equals(uri)) {
358 | selected_photos_container.removeViewAt(i);
359 | break;
360 | }
361 | }
362 |
363 | updateSelectedView();
364 | imageGalleryAdapter.setSelectedUriList(selectedUriList, uri);
365 | }
366 |
367 | private void updateSelectedView() {
368 |
369 | if (selectedUriList == null || selectedUriList.size() == 0) {
370 | selected_photos_empty.setVisibility(View.VISIBLE);
371 | selected_photos_container.setVisibility(View.GONE);
372 | } else {
373 | selected_photos_empty.setVisibility(View.GONE);
374 | selected_photos_container.setVisibility(View.VISIBLE);
375 | }
376 |
377 | }
378 |
379 | private void startCameraIntent() {
380 | Intent cameraInent;
381 | File mediaFile;
382 |
383 | if (builder.mediaType == BaseBuilder.MediaType.IMAGE) {
384 | cameraInent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
385 | mediaFile = getImageFile();
386 | } else {
387 | cameraInent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
388 | mediaFile = getVideoFile();
389 | }
390 |
391 | if (cameraInent.resolveActivity(getActivity().getPackageManager()) == null) {
392 | errorMessage("This Application do not have Camera Application");
393 | return;
394 | }
395 |
396 |
397 | Uri photoURI = FileProvider.getUriForFile(getContext(), getContext().getApplicationContext().getPackageName() + ".provider", mediaFile);
398 |
399 | List resolvedIntentActivities = getContext().getPackageManager().queryIntentActivities(cameraInent, PackageManager.MATCH_DEFAULT_ONLY);
400 | for (ResolveInfo resolvedIntentInfo : resolvedIntentActivities) {
401 | String packageName = resolvedIntentInfo.activityInfo.packageName;
402 | getContext().grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
403 | }
404 |
405 | cameraInent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
406 |
407 | TedOnActivityResult.with(getActivity())
408 | .setIntent(cameraInent)
409 | .setListener(new OnActivityResultListener() {
410 | @Override
411 | public void onActivityResult(int resultCode, Intent data) {
412 | if (resultCode == Activity.RESULT_OK) {
413 | onActivityResultCamera(cameraImageUri);
414 | }
415 | }
416 | })
417 | .startActivityForResult();
418 | }
419 |
420 | private File getImageFile() {
421 | // Create an image file name
422 | File imageFile = null;
423 | try {
424 | String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
425 | String imageFileName = "JPEG_" + timeStamp + "_";
426 | File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
427 |
428 | if (!storageDir.exists())
429 | storageDir.mkdirs();
430 |
431 | imageFile = File.createTempFile(
432 | imageFileName, /* prefix */
433 | ".jpg", /* suffix */
434 | storageDir /* directory */
435 | );
436 |
437 |
438 | // Save a file: path for use with ACTION_VIEW intents
439 | cameraImageUri = Uri.fromFile(imageFile);
440 | } catch (IOException e) {
441 | e.printStackTrace();
442 | errorMessage("Could not create imageFile for camera");
443 | }
444 |
445 |
446 | return imageFile;
447 | }
448 |
449 | private File getVideoFile() {
450 | // Create an image file name
451 | File videoFile = null;
452 | try {
453 | String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
454 | String imageFileName = "VIDEO_" + timeStamp + "_";
455 | File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
456 |
457 | if (!storageDir.exists())
458 | storageDir.mkdirs();
459 |
460 | videoFile = File.createTempFile(
461 | imageFileName, /* prefix */
462 | ".mp4", /* suffix */
463 | storageDir /* directory */
464 | );
465 |
466 |
467 | // Save a file: path for use with ACTION_VIEW intents
468 | cameraImageUri = Uri.fromFile(videoFile);
469 | } catch (IOException e) {
470 | e.printStackTrace();
471 | errorMessage("Could not create imageFile for camera");
472 | }
473 |
474 |
475 | return videoFile;
476 | }
477 |
478 | private void errorMessage(String message) {
479 | String errorMessage = message == null ? "Something wrong." : message;
480 |
481 | if (builder.onErrorListener == null) {
482 | Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
483 | } else {
484 | builder.onErrorListener.onError(errorMessage);
485 | }
486 | }
487 |
488 | private void startGalleryIntent() {
489 | Intent galleryIntent;
490 | Uri uri;
491 | if (builder.mediaType == BaseBuilder.MediaType.IMAGE) {
492 | galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
493 | galleryIntent.setType("image/*");
494 | } else {
495 | galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
496 | galleryIntent.setType("video/*");
497 |
498 | }
499 |
500 | if (galleryIntent.resolveActivity(getActivity().getPackageManager()) == null) {
501 | errorMessage("This Application do not have Gallery Application");
502 | return;
503 | }
504 |
505 | TedOnActivityResult.with(getActivity())
506 | .setIntent(galleryIntent)
507 | .setListener(new OnActivityResultListener() {
508 | @Override
509 | public void onActivityResult(int resultCode, Intent data) {
510 | if (resultCode == Activity.RESULT_OK) {
511 | onActivityResultGallery(data);
512 | }
513 | }
514 | })
515 | .startActivityForResult();
516 | }
517 |
518 | private void errorMessage() {
519 | errorMessage(null);
520 | }
521 |
522 | private void setTitle() {
523 |
524 | if (!builder.showTitle) {
525 | tv_title.setVisibility(View.GONE);
526 |
527 | if (!isMultiSelect()) {
528 | view_title_container.setVisibility(View.GONE);
529 | }
530 |
531 | return;
532 | }
533 |
534 | if (!TextUtils.isEmpty(builder.title)) {
535 | tv_title.setText(builder.title);
536 | }
537 |
538 | if (builder.titleBackgroundResId > 0) {
539 | tv_title.setBackgroundResource(builder.titleBackgroundResId);
540 | }
541 |
542 | }
543 |
544 | private boolean isMultiSelect() {
545 | return builder.onMultiImageSelectedListener != null;
546 | }
547 |
548 | private void onActivityResultCamera(final Uri cameraImageUri) {
549 |
550 | MediaScannerConnection.scanFile(getContext(), new String[]{cameraImageUri.getPath()}, new String[]{"image/jpeg"}, new MediaScannerConnection.MediaScannerConnectionClient() {
551 | @Override
552 | public void onMediaScannerConnected() {
553 |
554 | }
555 |
556 | @Override
557 | public void onScanCompleted(String s, Uri uri) {
558 | getActivity().runOnUiThread(new Runnable() {
559 | @Override
560 | public void run() {
561 | updateAdapter();
562 | complete(cameraImageUri);
563 | }
564 | });
565 |
566 | }
567 | });
568 | }
569 |
570 |
571 | private void onActivityResultGallery(Intent data) {
572 | Uri temp = data.getData();
573 |
574 | if (temp == null) {
575 | errorMessage();
576 | }
577 |
578 | String realPath = RealPathUtil.getRealPath(getActivity(), temp);
579 |
580 | Uri selectedImageUri;
581 | try {
582 | selectedImageUri = Uri.fromFile(new File(realPath));
583 | } catch (Exception ex) {
584 | selectedImageUri = Uri.parse(realPath);
585 | }
586 |
587 | complete(selectedImageUri);
588 |
589 | }
590 |
591 |
592 | public interface OnMultiImageSelectedListener {
593 | void onImagesSelected(List uriList);
594 | }
595 |
596 | public interface OnImageSelectedListener {
597 | void onImageSelected(Uri uri);
598 | }
599 |
600 | public interface OnErrorListener {
601 | void onError(String message);
602 | }
603 |
604 | public interface ImageProvider {
605 | void onProvideImage(ImageView imageView, Uri imageUri);
606 | }
607 |
608 | public abstract static class BaseBuilder {
609 |
610 | public int previewMaxCount = 25;
611 | public Drawable cameraTileDrawable;
612 | public Drawable galleryTileDrawable;
613 | public Drawable selectedForegroundDrawable;
614 | public ImageProvider imageProvider;
615 | public boolean showCamera = true;
616 | public boolean showGallery = true;
617 | public int cameraTileBackgroundResId = R.color.tedbottompicker_camera;
618 | public int galleryTileBackgroundResId = R.color.tedbottompicker_gallery;
619 | @MediaType
620 | public int mediaType = MediaType.IMAGE;
621 | protected FragmentActivity fragmentActivity;
622 | OnImageSelectedListener onImageSelectedListener;
623 | OnMultiImageSelectedListener onMultiImageSelectedListener;
624 | OnErrorListener onErrorListener;
625 | private String title;
626 | private boolean showTitle = true;
627 | private List selectedUriList;
628 | private Uri selectedUri;
629 | private Drawable deSelectIconDrawable;
630 | private int spacing = 1;
631 | private boolean includeEdgeSpacing = false;
632 | private int peekHeight = -1;
633 | private int titleBackgroundResId;
634 | private int selectMaxCount = Integer.MAX_VALUE;
635 | private int selectMinCount = 0;
636 | private String completeButtonText;
637 | private String emptySelectionText;
638 | private String selectMaxCountErrorText;
639 | private String selectMinCountErrorText;
640 |
641 | public BaseBuilder(@NonNull FragmentActivity fragmentActivity) {
642 |
643 | this.fragmentActivity = fragmentActivity;
644 |
645 | setCameraTile(R.drawable.ic_camera);
646 | setGalleryTile(R.drawable.ic_gallery);
647 | setSpacingResId(R.dimen.tedbottompicker_grid_layout_margin);
648 | }
649 |
650 | public T setCameraTile(@DrawableRes int cameraTileResId) {
651 | setCameraTile(ContextCompat.getDrawable(fragmentActivity, cameraTileResId));
652 | return (T) this;
653 | }
654 |
655 | public BaseBuilder setGalleryTile(@DrawableRes int galleryTileResId) {
656 | setGalleryTile(ContextCompat.getDrawable(fragmentActivity, galleryTileResId));
657 | return this;
658 | }
659 |
660 | public T setSpacingResId(@DimenRes int dimenResId) {
661 | this.spacing = fragmentActivity.getResources().getDimensionPixelSize(dimenResId);
662 | return (T) this;
663 | }
664 |
665 | public T setCameraTile(Drawable cameraTileDrawable) {
666 | this.cameraTileDrawable = cameraTileDrawable;
667 | return (T) this;
668 | }
669 |
670 | public T setGalleryTile(Drawable galleryTileDrawable) {
671 | this.galleryTileDrawable = galleryTileDrawable;
672 | return (T) this;
673 | }
674 |
675 | public T setDeSelectIcon(@DrawableRes int deSelectIconResId) {
676 | setDeSelectIcon(ContextCompat.getDrawable(fragmentActivity, deSelectIconResId));
677 | return (T) this;
678 | }
679 |
680 | public T setDeSelectIcon(Drawable deSelectIconDrawable) {
681 | this.deSelectIconDrawable = deSelectIconDrawable;
682 | return (T) this;
683 | }
684 |
685 | public T setSelectedForeground(@DrawableRes int selectedForegroundResId) {
686 | setSelectedForeground(ContextCompat.getDrawable(fragmentActivity, selectedForegroundResId));
687 | return (T) this;
688 | }
689 |
690 | public T setSelectedForeground(Drawable selectedForegroundDrawable) {
691 | this.selectedForegroundDrawable = selectedForegroundDrawable;
692 | return (T) this;
693 | }
694 |
695 | public T setPreviewMaxCount(int previewMaxCount) {
696 | this.previewMaxCount = previewMaxCount;
697 | return (T) this;
698 | }
699 |
700 | public T setSelectMaxCount(int selectMaxCount) {
701 | this.selectMaxCount = selectMaxCount;
702 | return (T) this;
703 | }
704 |
705 | public T setSelectMinCount(int selectMinCount) {
706 | this.selectMinCount = selectMinCount;
707 | return (T) this;
708 | }
709 |
710 | public T setOnImageSelectedListener(OnImageSelectedListener onImageSelectedListener) {
711 | this.onImageSelectedListener = onImageSelectedListener;
712 | return (T) this;
713 | }
714 |
715 | public T setOnMultiImageSelectedListener(OnMultiImageSelectedListener onMultiImageSelectedListener) {
716 | this.onMultiImageSelectedListener = onMultiImageSelectedListener;
717 | return (T) this;
718 | }
719 |
720 | public T setOnErrorListener(OnErrorListener onErrorListener) {
721 | this.onErrorListener = onErrorListener;
722 | return (T) this;
723 | }
724 |
725 | public T showCameraTile(boolean showCamera) {
726 | this.showCamera = showCamera;
727 | return (T) this;
728 | }
729 |
730 | public T showGalleryTile(boolean showGallery) {
731 | this.showGallery = showGallery;
732 | return (T) this;
733 | }
734 |
735 | public T setSpacing(int spacing) {
736 | this.spacing = spacing;
737 | return (T) this;
738 | }
739 |
740 | public T setIncludeEdgeSpacing(boolean includeEdgeSpacing) {
741 | this.includeEdgeSpacing = includeEdgeSpacing;
742 | return (T) this;
743 | }
744 |
745 | public T setPeekHeight(int peekHeight) {
746 | this.peekHeight = peekHeight;
747 | return (T) this;
748 | }
749 |
750 | public T setPeekHeightResId(@DimenRes int dimenResId) {
751 | this.peekHeight = fragmentActivity.getResources().getDimensionPixelSize(dimenResId);
752 | return (T) this;
753 | }
754 |
755 | public T setCameraTileBackgroundResId(@ColorRes int colorResId) {
756 | this.cameraTileBackgroundResId = colorResId;
757 | return (T) this;
758 | }
759 |
760 | public T setGalleryTileBackgroundResId(@ColorRes int colorResId) {
761 | this.galleryTileBackgroundResId = colorResId;
762 | return (T) this;
763 | }
764 |
765 | public T setTitle(String title) {
766 | this.title = title;
767 | return (T) this;
768 | }
769 |
770 | public T setTitle(@StringRes int stringResId) {
771 | this.title = fragmentActivity.getResources().getString(stringResId);
772 | return (T) this;
773 | }
774 |
775 | public T showTitle(boolean showTitle) {
776 | this.showTitle = showTitle;
777 | return (T) this;
778 | }
779 |
780 | public T setCompleteButtonText(String completeButtonText) {
781 | this.completeButtonText = completeButtonText;
782 | return (T) this;
783 | }
784 |
785 | public T setCompleteButtonText(@StringRes int completeButtonResId) {
786 | this.completeButtonText = fragmentActivity.getResources().getString(completeButtonResId);
787 | return (T) this;
788 | }
789 |
790 | public T setEmptySelectionText(String emptySelectionText) {
791 | this.emptySelectionText = emptySelectionText;
792 | return (T) this;
793 | }
794 |
795 | public T setEmptySelectionText(@StringRes int emptySelectionResId) {
796 | this.emptySelectionText = fragmentActivity.getResources().getString(emptySelectionResId);
797 | return (T) this;
798 | }
799 |
800 | public T setSelectMaxCountErrorText(String selectMaxCountErrorText) {
801 | this.selectMaxCountErrorText = selectMaxCountErrorText;
802 | return (T) this;
803 | }
804 |
805 | public T setSelectMaxCountErrorText(@StringRes int selectMaxCountErrorResId) {
806 | this.selectMaxCountErrorText = fragmentActivity.getResources().getString(selectMaxCountErrorResId);
807 | return (T) this;
808 | }
809 |
810 | public T setSelectMinCountErrorText(String selectMinCountErrorText) {
811 | this.selectMinCountErrorText = selectMinCountErrorText;
812 | return (T) this;
813 | }
814 |
815 | public T setSelectMinCountErrorText(@StringRes int selectMinCountErrorResId) {
816 | this.selectMinCountErrorText = fragmentActivity.getResources().getString(selectMinCountErrorResId);
817 | return (T) this;
818 | }
819 |
820 | public T setTitleBackgroundResId(@ColorRes int colorResId) {
821 | this.titleBackgroundResId = colorResId;
822 | return (T) this;
823 | }
824 |
825 | public T setImageProvider(ImageProvider imageProvider) {
826 | this.imageProvider = imageProvider;
827 | return (T) this;
828 | }
829 |
830 | public T setSelectedUriList(List selectedUriList) {
831 | this.selectedUriList = selectedUriList;
832 | return (T) this;
833 | }
834 |
835 | public T setSelectedUri(Uri selectedUri) {
836 | this.selectedUri = selectedUri;
837 | return (T) this;
838 | }
839 |
840 | public T showVideoMedia() {
841 | this.mediaType = MediaType.VIDEO;
842 | return (T) this;
843 | }
844 |
845 | public TedBottomSheetDialogFragment create() {
846 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
847 | && ContextCompat.checkSelfPermission(fragmentActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
848 | throw new RuntimeException("Missing required WRITE_EXTERNAL_STORAGE permission. Did you remember to request it first?");
849 | }
850 |
851 | if (onImageSelectedListener == null && onMultiImageSelectedListener == null) {
852 | throw new RuntimeException("You have to use setOnImageSelectedListener() or setOnMultiImageSelectedListener() for receive selected Uri");
853 | }
854 |
855 | TedBottomSheetDialogFragment customBottomSheetDialogFragment = new TedBottomSheetDialogFragment();
856 | customBottomSheetDialogFragment.builder = (T) this;
857 | return customBottomSheetDialogFragment;
858 | }
859 |
860 | @Retention(RetentionPolicy.SOURCE)
861 | @IntDef({MediaType.IMAGE, MediaType.VIDEO})
862 | public @interface MediaType {
863 | int IMAGE = 1;
864 | int VIDEO = 2;
865 | }
866 |
867 |
868 | }
869 |
870 |
871 | }
872 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/TedRxBottomPicker.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker;
2 |
3 | import android.net.Uri;
4 | import android.support.v4.app.FragmentActivity;
5 |
6 | import java.util.List;
7 |
8 | import io.reactivex.Single;
9 |
10 | public class TedRxBottomPicker extends TedBottomSheetDialogFragment {
11 |
12 | public static Builder with(FragmentActivity fragmentActivity) {
13 | return new Builder(fragmentActivity);
14 | }
15 |
16 |
17 | public static class Builder extends BaseBuilder {
18 |
19 | private Builder(FragmentActivity fragmentActivity) {
20 | super(fragmentActivity);
21 | }
22 |
23 | @Override
24 | public Builder setOnImageSelectedListener(OnImageSelectedListener onImageSelectedListener) {
25 | throw new RuntimeException("You have to use show() method. Or read usage document");
26 | }
27 |
28 | @Override
29 | public Builder setOnMultiImageSelectedListener(OnMultiImageSelectedListener onMultiImageSelectedListener) {
30 | throw new RuntimeException("You have to use showMultiImage() method. Or read usage document");
31 | }
32 |
33 | public Single show() {
34 | return Single.create(emitter -> {
35 | onImageSelectedListener = emitter::onSuccess;
36 | onErrorListener = message -> emitter.onError(new Exception(message));
37 | create().show(fragmentActivity.getSupportFragmentManager());
38 | });
39 | }
40 |
41 | public Single> showMultiImage() {
42 | return Single.create(emitter -> {
43 | onMultiImageSelectedListener = emitter::onSuccess;
44 | onErrorListener = message -> emitter.onError(new Exception(message));
45 | create().show(fragmentActivity.getSupportFragmentManager());
46 | });
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/adapter/GalleryAdapter.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker.adapter;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.graphics.drawable.Drawable;
6 | import android.net.Uri;
7 | import android.provider.MediaStore;
8 | import android.support.annotation.IntDef;
9 | import android.support.annotation.NonNull;
10 | import android.support.annotation.Nullable;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 |
16 | import com.bumptech.glide.Glide;
17 | import com.bumptech.glide.request.RequestOptions;
18 |
19 | import java.io.File;
20 | import java.lang.annotation.Retention;
21 | import java.lang.annotation.RetentionPolicy;
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | import gun0912.tedbottompicker.R;
26 | import gun0912.tedbottompicker.TedBottomSheetDialogFragment;
27 | import gun0912.tedbottompicker.view.TedSquareFrameLayout;
28 | import gun0912.tedbottompicker.view.TedSquareImageView;
29 |
30 | /**
31 | * Created by TedPark on 2016. 8. 30..
32 | */
33 | public class GalleryAdapter extends RecyclerView.Adapter {
34 |
35 |
36 | private ArrayList pickerTiles;
37 | private Context context;
38 | private TedBottomSheetDialogFragment.BaseBuilder builder;
39 | private OnItemClickListener onItemClickListener;
40 | private List selectedUriList;
41 |
42 |
43 | public GalleryAdapter(Context context, TedBottomSheetDialogFragment.BaseBuilder builder) {
44 |
45 | this.context = context;
46 | this.builder = builder;
47 |
48 | pickerTiles = new ArrayList<>();
49 | selectedUriList = new ArrayList<>();
50 |
51 | if (builder.showCamera) {
52 | pickerTiles.add(new PickerTile(PickerTile.CAMERA));
53 | }
54 |
55 | if (builder.showGallery) {
56 | pickerTiles.add(new PickerTile(PickerTile.GALLERY));
57 | }
58 |
59 | Cursor cursor = null;
60 | try {
61 | String[] columns;
62 | String orderBy;
63 | Uri uri;
64 | if (builder.mediaType == TedBottomSheetDialogFragment.BaseBuilder.MediaType.IMAGE) {
65 | uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
66 | columns = new String[]{MediaStore.Images.Media.DATA};
67 | orderBy = MediaStore.Images.Media.DATE_ADDED + " DESC";
68 | } else {
69 | uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
70 | columns = new String[]{MediaStore.Video.VideoColumns.DATA};
71 | orderBy = MediaStore.Video.VideoColumns.DATE_ADDED + " DESC";
72 | }
73 |
74 |
75 |
76 |
77 | cursor = context.getApplicationContext().getContentResolver().query(uri, columns, null, null, orderBy);
78 | //imageCursor = sContext.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, orderBy);
79 |
80 |
81 | if (cursor != null) {
82 |
83 | int count = 0;
84 | while (cursor.moveToNext() && count < builder.previewMaxCount) {
85 |
86 | String dataIndex;
87 | if (builder.mediaType == TedBottomSheetDialogFragment.BaseBuilder.MediaType.IMAGE) {
88 | dataIndex = MediaStore.Images.Media.DATA;
89 | }else{
90 | dataIndex = MediaStore.Video.VideoColumns.DATA;
91 | }
92 | String imageLocation = cursor.getString(cursor.getColumnIndex(dataIndex));
93 | File imageFile = new File(imageLocation);
94 | pickerTiles.add(new PickerTile(Uri.fromFile(imageFile)));
95 | count++;
96 |
97 | }
98 |
99 | }
100 |
101 | } catch (Exception e) {
102 | e.printStackTrace();
103 | } finally {
104 | if (cursor != null && !cursor.isClosed()) {
105 | cursor.close();
106 | }
107 | }
108 |
109 |
110 | }
111 |
112 | public void setSelectedUriList(List selectedUriList, @NonNull Uri uri) {
113 | this.selectedUriList = selectedUriList;
114 |
115 | int position = -1;
116 |
117 |
118 | PickerTile pickerTile;
119 | for (int i = 0; i < pickerTiles.size(); i++) {
120 | pickerTile = pickerTiles.get(i);
121 | if (pickerTile.isImageTile() && uri.equals(pickerTile.getImageUri())) {
122 | position = i;
123 | break;
124 | }
125 | }
126 |
127 |
128 | if (position > 0) {
129 | notifyItemChanged(position);
130 | }
131 |
132 |
133 | }
134 |
135 | @NonNull
136 | @Override
137 | public GalleryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
138 | View view = View.inflate(context, R.layout.tedbottompicker_grid_item, null);
139 | return new GalleryViewHolder(view);
140 | }
141 |
142 | @Override
143 | public void onBindViewHolder(@NonNull final GalleryViewHolder holder, int position) {
144 |
145 | PickerTile pickerTile = getItem(position);
146 |
147 |
148 | boolean isSelected = false;
149 |
150 | if (pickerTile.isCameraTile()) {
151 | holder.iv_thumbnail.setBackgroundResource(builder.cameraTileBackgroundResId);
152 | holder.iv_thumbnail.setImageDrawable(builder.cameraTileDrawable);
153 | } else if (pickerTile.isGalleryTile()) {
154 | holder.iv_thumbnail.setBackgroundResource(builder.galleryTileBackgroundResId);
155 | holder.iv_thumbnail.setImageDrawable(builder.galleryTileDrawable);
156 |
157 | } else {
158 | Uri uri = pickerTile.getImageUri();
159 | if (builder.imageProvider == null) {
160 | Glide.with(context)
161 | .load(uri)
162 | .thumbnail(0.1f)
163 | .apply(new RequestOptions().centerCrop()
164 | .placeholder(R.drawable.ic_gallery)
165 | .error(R.drawable.img_error))
166 | .into(holder.iv_thumbnail);
167 | } else {
168 | builder.imageProvider.onProvideImage(holder.iv_thumbnail, uri);
169 | }
170 |
171 |
172 | isSelected = selectedUriList.contains(uri);
173 |
174 |
175 | }
176 |
177 |
178 | if (holder.root != null) {
179 |
180 | Drawable foregroundDrawable;
181 |
182 | if (builder.selectedForegroundDrawable != null) {
183 | foregroundDrawable = builder.selectedForegroundDrawable;
184 | } else {
185 | foregroundDrawable = ContextCompat.getDrawable(context, R.drawable.gallery_photo_selected);
186 | }
187 |
188 | holder.root.setForeground(isSelected ? foregroundDrawable : null);
189 | }
190 |
191 |
192 | if (onItemClickListener != null) {
193 | holder.itemView.setOnClickListener(new View.OnClickListener() {
194 | @Override
195 | public void onClick(View view) {
196 | onItemClickListener.onItemClick(holder.itemView, holder.getAdapterPosition());
197 | }
198 | });
199 | }
200 | }
201 |
202 | public PickerTile getItem(int position) {
203 | return pickerTiles.get(position);
204 | }
205 |
206 | @Override
207 | public int getItemCount() {
208 | return pickerTiles.size();
209 | }
210 |
211 | public void setOnItemClickListener(
212 | OnItemClickListener onItemClickListener) {
213 | this.onItemClickListener = onItemClickListener;
214 | }
215 |
216 | public interface OnItemClickListener {
217 | void onItemClick(View view, int position);
218 | }
219 |
220 |
221 | public static class PickerTile {
222 |
223 | public static final int IMAGE = 1;
224 | public static final int CAMERA = 2;
225 | public static final int GALLERY = 3;
226 | private final Uri imageUri;
227 | private final
228 | @TileType
229 | int tileType;
230 |
231 | private PickerTile(@SpecialTileType int tileType) {
232 | this(null, tileType);
233 | }
234 |
235 | private PickerTile(@Nullable Uri imageUri, @TileType int tileType) {
236 | this.imageUri = imageUri;
237 | this.tileType = tileType;
238 | }
239 |
240 | PickerTile(@NonNull Uri imageUri) {
241 | this(imageUri, IMAGE);
242 | }
243 |
244 | @Nullable
245 | public Uri getImageUri() {
246 | return imageUri;
247 | }
248 |
249 | @TileType
250 | public int getTileType() {
251 | return tileType;
252 | }
253 |
254 | @Override
255 | public String toString() {
256 | if (isImageTile()) {
257 | return "ImageTile: " + imageUri;
258 | } else if (isCameraTile()) {
259 | return "CameraTile";
260 | } else if (isGalleryTile()) {
261 | return "PickerTile";
262 | } else {
263 | return "Invalid item";
264 | }
265 | }
266 |
267 | private boolean isImageTile() {
268 | return tileType == IMAGE;
269 | }
270 |
271 | private boolean isCameraTile() {
272 | return tileType == CAMERA;
273 | }
274 |
275 | private boolean isGalleryTile() {
276 | return tileType == GALLERY;
277 | }
278 |
279 | @IntDef({IMAGE, CAMERA, GALLERY})
280 | @Retention(RetentionPolicy.SOURCE)
281 | private @interface TileType {
282 | }
283 |
284 | @IntDef({CAMERA, GALLERY})
285 | @Retention(RetentionPolicy.SOURCE)
286 | private @interface SpecialTileType {
287 | }
288 | }
289 |
290 | class GalleryViewHolder extends RecyclerView.ViewHolder {
291 |
292 | TedSquareFrameLayout root;
293 |
294 |
295 | TedSquareImageView iv_thumbnail;
296 |
297 | private GalleryViewHolder(View view) {
298 | super(view);
299 | root = view.findViewById(R.id.root);
300 | iv_thumbnail = view.findViewById(R.id.iv_thumbnail);
301 |
302 | }
303 |
304 | }
305 |
306 |
307 | }
308 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/util/RealPathUtil.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ContentUris;
5 | import android.content.Context;
6 | import android.content.CursorLoader;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.os.Environment;
11 | import android.provider.DocumentsContract;
12 | import android.provider.MediaStore;
13 |
14 | /**
15 | * Created by TedPark on 2016. 11. 5..
16 | */
17 |
18 | public class RealPathUtil {
19 |
20 | public static String getRealPath(Context context, Uri uri) {
21 | String realPath;
22 | // SDK < API11
23 | if (Build.VERSION.SDK_INT < 19) {
24 | realPath = RealPathUtil.getRealPathFromURI_API11to18(context, uri);
25 | }
26 | // SDK > 19 (Android 4.4)
27 | else {
28 | realPath = RealPathUtil.getRealPathFromURI_API19(context, uri);
29 | }
30 |
31 | return realPath;
32 | }
33 |
34 | @SuppressLint("NewApi")
35 | public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
36 |
37 | // check here to KITKAT or new version
38 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
39 |
40 | // DocumentProvider
41 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
42 |
43 | // ExternalStorageProvider
44 | if (isExternalStorageDocument(uri)) {
45 | final String docId = DocumentsContract.getDocumentId(uri);
46 | final String[] split = docId.split(":");
47 | final String type = split[0];
48 |
49 | if ("primary".equalsIgnoreCase(type)) {
50 | return Environment.getExternalStorageDirectory() + "/"
51 | + split[1];
52 | }
53 | }
54 | // DownloadsProvider
55 | else if (isDownloadsDocument(uri)) {
56 |
57 | final String id = DocumentsContract.getDocumentId(uri);
58 | final Uri contentUri = ContentUris.withAppendedId(
59 | Uri.parse("content://downloads/public_downloads"),
60 | Long.valueOf(id));
61 |
62 | return getDataColumn(context, contentUri, null, null);
63 | }
64 | // MediaProvider
65 | else if (isMediaDocument(uri)) {
66 | final String docId = DocumentsContract.getDocumentId(uri);
67 | final String[] split = docId.split(":");
68 | final String type = split[0];
69 |
70 | Uri contentUri = null;
71 | if ("image".equals(type)) {
72 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
73 | } else if ("video".equals(type)) {
74 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
75 | } else if ("audio".equals(type)) {
76 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
77 | }
78 |
79 | final String selection = "_id=?";
80 | final String[] selectionArgs = new String[] { split[1] };
81 |
82 | return getDataColumn(context, contentUri, selection,
83 | selectionArgs);
84 | }
85 | }
86 | // MediaStore (and general)
87 | else if ("content".equalsIgnoreCase(uri.getScheme())) {
88 |
89 | // Return the remote address
90 | if (isGooglePhotosUri(uri))
91 | return uri.getLastPathSegment();
92 |
93 | return getDataColumn(context, uri, null, null);
94 | }
95 | // File
96 | else if ("file".equalsIgnoreCase(uri.getScheme())) {
97 | return uri.getPath();
98 | }
99 |
100 | return null;
101 | }
102 |
103 | /**
104 | * Get the value of the data column for this Uri. This is useful for
105 | * MediaStore Uris, and other file-based ContentProviders.
106 | *
107 | * @param context
108 | * The context.
109 | * @param uri
110 | * The Uri to query.
111 | * @param selection
112 | * (Optional) Filter used in the query.
113 | * @param selectionArgs
114 | * (Optional) Selection arguments used in the query.
115 | * @return The value of the _data column, which is typically a file path.
116 | */
117 | public static String getDataColumn(Context context, Uri uri,
118 | String selection, String[] selectionArgs) {
119 |
120 | Cursor cursor = null;
121 | final String column = "_data";
122 | final String[] projection = { column };
123 |
124 | try {
125 | cursor = context.getContentResolver().query(uri, projection,
126 | selection, selectionArgs, null);
127 | if (cursor != null && cursor.moveToFirst()) {
128 | final int index = cursor.getColumnIndexOrThrow(column);
129 | return cursor.getString(index);
130 | }
131 | } finally {
132 | if (cursor != null)
133 | cursor.close();
134 | }
135 | return null;
136 | }
137 |
138 | /**
139 | * @param uri
140 | * The Uri to check.
141 | * @return Whether the Uri authority is ExternalStorageProvider.
142 | */
143 | public static boolean isExternalStorageDocument(Uri uri) {
144 | return "com.android.externalstorage.documents".equals(uri
145 | .getAuthority());
146 | }
147 |
148 | /**
149 | * @param uri
150 | * The Uri to check.
151 | * @return Whether the Uri authority is DownloadsProvider.
152 | */
153 | public static boolean isDownloadsDocument(Uri uri) {
154 | return "com.android.providers.downloads.documents".equals(uri
155 | .getAuthority());
156 | }
157 |
158 | /**
159 | * @param uri
160 | * The Uri to check.
161 | * @return Whether the Uri authority is MediaProvider.
162 | */
163 | public static boolean isMediaDocument(Uri uri) {
164 | return "com.android.providers.media.documents".equals(uri
165 | .getAuthority());
166 | }
167 |
168 | /**
169 | * @param uri
170 | * The Uri to check.
171 | * @return Whether the Uri authority is Google Photos.
172 | */
173 | public static boolean isGooglePhotosUri(Uri uri) {
174 | return "com.google.android.apps.photos.content".equals(uri
175 | .getAuthority());
176 | }
177 |
178 | @SuppressLint("NewApi")
179 | public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
180 | String[] proj = { MediaStore.Images.Media.DATA };
181 | String result = null;
182 |
183 | CursorLoader cursorLoader = new CursorLoader(
184 | context,
185 | contentUri, proj, null, null, null);
186 | Cursor cursor = cursorLoader.loadInBackground();
187 |
188 | if(cursor != null){
189 | int column_index =
190 | cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
191 | cursor.moveToFirst();
192 | result = cursor.getString(column_index);
193 | }
194 | return result;
195 | }
196 |
197 | public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri){
198 | String[] proj = { MediaStore.Images.Media.DATA };
199 | Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
200 | int column_index
201 | = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
202 | cursor.moveToFirst();
203 | return cursor.getString(column_index);
204 | }
205 | }
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/view/TedEmptyRecyclerView.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker.view;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | public class TedEmptyRecyclerView extends RecyclerView {
11 | @Nullable
12 | View emptyView;
13 |
14 | public TedEmptyRecyclerView(Context context) {
15 | super(context);
16 | }
17 |
18 | public TedEmptyRecyclerView(Context context, AttributeSet attrs) {
19 | super(context, attrs);
20 | }
21 |
22 | public TedEmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
23 | super(context, attrs, defStyle);
24 | }
25 |
26 | void checkIfEmpty() {
27 | if (emptyView != null) {
28 |
29 | emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
30 | }
31 | }
32 |
33 | final @NonNull
34 | AdapterDataObserver observer = new AdapterDataObserver() {
35 | @Override
36 | public void onChanged() {
37 | super.onChanged();
38 | checkIfEmpty();
39 | }
40 | };
41 |
42 |
43 |
44 | @Override
45 | public void setAdapter(@Nullable Adapter adapter) {
46 | final Adapter oldAdapter = getAdapter();
47 | if (oldAdapter != null) {
48 | oldAdapter.unregisterAdapterDataObserver(observer);
49 | }
50 | super.setAdapter(adapter);
51 | if (adapter != null) {
52 | adapter.registerAdapterDataObserver(observer);
53 | }
54 | }
55 |
56 | public void setEmptyView(@Nullable View emptyView) {
57 | this.emptyView = emptyView;
58 | checkIfEmpty();
59 | }
60 | }
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/view/TedSquareFrameLayout.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.widget.FrameLayout;
7 |
8 | import gun0912.tedbottompicker.R;
9 |
10 | /**
11 | * Created by Gil on 09/06/2014.
12 | */
13 | public class TedSquareFrameLayout extends FrameLayout {
14 |
15 |
16 | private static boolean mMatchHeightToWidth;
17 | private static boolean mMatchWidthToHeight;
18 |
19 | public TedSquareFrameLayout(Context context) {
20 | super(context);
21 | }
22 |
23 | public TedSquareFrameLayout(Context context, AttributeSet attrs) {
24 | super(context, attrs);
25 |
26 | TypedArray a = context.getTheme().obtainStyledAttributes(
27 | attrs,
28 | R.styleable.TedBottomPickerSquareView,
29 | 0, 0);
30 |
31 | try {
32 | mMatchHeightToWidth = a.getBoolean(R.styleable.TedBottomPickerSquareView_matchHeightToWidth, false);
33 | mMatchWidthToHeight = a.getBoolean(R.styleable.TedBottomPickerSquareView_matchWidthToHeight, false);
34 | } finally {
35 | a.recycle();
36 | }
37 | }
38 |
39 |
40 |
41 | //Squares the thumbnail
42 | @Override
43 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec){
44 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
45 | if(mMatchHeightToWidth){
46 | setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
47 | } else if(mMatchWidthToHeight){
48 | setMeasuredDimension(heightMeasureSpec, heightMeasureSpec);
49 | }
50 | }
51 |
52 | @Override
53 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
54 |
55 | if(mMatchHeightToWidth){
56 | super.onSizeChanged(w, w,oldw,oldh);
57 | } else if(mMatchWidthToHeight){
58 | super.onSizeChanged(h, h,oldw,oldh);
59 | }
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/java/gun0912/tedbottompicker/view/TedSquareImageView.java:
--------------------------------------------------------------------------------
1 | package gun0912.tedbottompicker.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.drawable.Drawable;
7 | import android.util.AttributeSet;
8 | import android.widget.ImageView;
9 |
10 | import gun0912.tedbottompicker.R;
11 |
12 |
13 | /**
14 | * Created by Gil on 09/06/2014.
15 | */
16 | public class TedSquareImageView extends ImageView {
17 |
18 | String fit_mode;
19 | private Drawable foreground;
20 |
21 | public TedSquareImageView(Context context) {
22 | super(context);
23 | }
24 |
25 | public TedSquareImageView(Context context, AttributeSet attrs) {
26 | super(context, attrs);
27 |
28 | TypedArray a = context.getTheme().obtainStyledAttributes(
29 | attrs,
30 | R.styleable.TedBottomPickerImageView,
31 | 0, 0);
32 |
33 | Drawable foreground = a.getDrawable(R.styleable.TedBottomPickerImageView_foreground);
34 | if (foreground != null) {
35 | setForeground(foreground);
36 | }
37 |
38 |
39 | try {
40 | fit_mode = a.getString(R.styleable.TedBottomPickerImageView_fit_mode);
41 |
42 | } finally {
43 | a.recycle();
44 | }
45 | }
46 |
47 |
48 | //Squares the thumbnail
49 | @Override
50 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
51 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
52 |
53 |
54 | if ("height".equals(fit_mode)) {
55 | setMeasuredDimension(heightMeasureSpec, heightMeasureSpec);
56 |
57 | } else {
58 | setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
59 |
60 | }
61 |
62 |
63 | if (foreground != null) {
64 | foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
65 | invalidate();
66 | }
67 |
68 |
69 | }
70 |
71 |
72 | /**
73 | * Supply a Drawable that is to be rendered on top of all of the child views
74 | * in the frame layout.
75 | *
76 | * @param drawable The Drawable to be drawn on top of the children.
77 | */
78 | public void setForeground(Drawable drawable) {
79 | if (foreground == drawable) {
80 | return;
81 | }
82 | if (foreground != null) {
83 | foreground.setCallback(null);
84 | unscheduleDrawable(foreground);
85 | }
86 |
87 | foreground = drawable;
88 |
89 | if (drawable != null) {
90 | drawable.setCallback(this);
91 | if (drawable.isStateful()) {
92 | drawable.setState(getDrawableState());
93 | }
94 | }
95 | requestLayout();
96 | invalidate();
97 | }
98 |
99 |
100 | @Override
101 | protected boolean verifyDrawable(Drawable who) {
102 | return super.verifyDrawable(who) || who == foreground;
103 | }
104 |
105 | @Override
106 | public void jumpDrawablesToCurrentState() {
107 | super.jumpDrawablesToCurrentState();
108 | if (foreground != null)
109 | foreground.jumpToCurrentState();
110 | }
111 |
112 | @Override
113 | protected void drawableStateChanged() {
114 | super.drawableStateChanged();
115 | if (foreground != null && foreground.isStateful()) {
116 | foreground.setState(getDrawableState());
117 | }
118 | }
119 |
120 | @Override
121 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
122 | super.onSizeChanged(w, h, oldw, oldh);
123 | if (foreground != null) {
124 | foreground.setBounds(0, 0, w, h);
125 | invalidate();
126 | }
127 | }
128 |
129 | @Override
130 | public void draw(Canvas canvas) {
131 | super.draw(canvas);
132 |
133 | if (foreground != null) {
134 | foreground.draw(canvas);
135 | }
136 | }
137 |
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-hdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-hdpi/ic_camera.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-hdpi/ic_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-hdpi/ic_gallery.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xhdpi/ic_camera.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xhdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xhdpi/ic_clear.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xhdpi/ic_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xhdpi/ic_gallery.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xxhdpi/ic_camera.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxhdpi/ic_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xxhdpi/ic_gallery.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxxhdpi/gallery_photo_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxxhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xxxhdpi/ic_camera.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxxhdpi/ic_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xxxhdpi/ic_gallery.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/drawable-xxxhdpi/img_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ParkSangGwon/TedBottomPicker/399069d438b13a37fb829d94dc233aa52e05f693/tedbottompicker/src/main/res/drawable-xxxhdpi/img_error.png
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/layout/tedbottompicker_content_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
15 |
16 |
17 |
27 |
28 |
38 |
39 |
40 |
41 |
70 |
71 |
72 |
79 |
80 |
88 |
89 |
98 |
99 |
100 |
108 |
109 |
110 |
119 |
120 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/layout/tedbottompicker_grid_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/layout/tedbottompicker_selected_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 사진 선택
6 | 이미지가 선택되지 않았습니다
7 | 완료
8 |
9 |
10 |
11 | %1$d개까지 선택 가능합니다
12 | %1$d개이상 선택하셔야 합니다
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000
4 | #ccc
5 | #f0f0f0
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1dp
4 |
5 | 90dp
6 |
7 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Select Image
4 | No Image
5 | Done
6 |
7 |
8 | Already %1$d images selected
9 | You have to choice %1$d images
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tedbottompicker/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------