├── .gitignore
├── LICENSE.txt
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── sample.apk
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── stream
│ │ └── customimagepickerapp
│ │ ├── MainActivity.java
│ │ └── Units.java
│ └── res
│ ├── drawable
│ ├── bg_gradient_dusk.xml
│ ├── bg_moon.xml
│ ├── bg_mountains_geometric.xml
│ ├── bg_rectangle_outline_white_pressed.xml
│ └── bg_stars.xml
│ ├── layout
│ ├── activity_main.xml
│ └── bottom_sheet.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-ldpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── customimagepicker
├── .gitignore
├── build.gradle
├── gradlew
├── gradlew.bat
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── stream
│ │ ├── customimagepicker
│ │ ├── CustomImagePicker.java
│ │ └── ImageAdapter.java
│ │ └── jess
│ │ └── ui
│ │ ├── ScrollBarDrawable.java
│ │ ├── TwoWayAbsListView.java
│ │ ├── TwoWayAdapterView.java
│ │ └── TwoWayGridView.java
│ └── res
│ ├── anim
│ ├── popup_hide.xml
│ └── popup_show.xml
│ ├── drawable
│ ├── bg_dialog.xml
│ ├── bg_translucent_white.xml
│ ├── bg_transparent.xml
│ ├── ic_action_image.png
│ ├── ic_local_see_black_48dp.png
│ ├── icon_image_default.xml
│ └── list_selector.xml
│ ├── layout
│ └── bottom_sheet_default.xml
│ └── values
│ ├── attrs.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── Custom-Recent-Images-Cover.gif
├── Recent Images 1 Row.png
└── Recent Images 2 Rows.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | build/*
3 | */app.iml
4 |
5 | # Crashlytics configuations
6 | com_crashlytics_export_strings.xml
7 |
8 | # Local configuration file (sdk path, etc)
9 | local.properties
10 |
11 | # Gradle generated files
12 | .gradle/
13 |
14 | # Signing files
15 | .signing/
16 |
17 | # User-specific configurations
18 | .idea/*
19 | .idea/libraries/
20 | .idea/workspace.xml
21 | .idea/tasks.xml
22 | .idea/.name
23 | .idea/compiler.xml
24 | .idea/copyright/profiles_settings.xml
25 | .idea/encodings.xml
26 | .idea/misc.xml
27 | .idea/modules.xml
28 | .idea/scopes/scope_settings.xml
29 | .idea/vcs.xml
30 | *.iml
31 |
32 | # OS-specific files
33 | .DS_Store
34 | .DS_Store?
35 | ._*
36 | .Spotlight-V100
37 | .Trashes
38 | ehthumbs.db
39 | Thumbs.db
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Amirhossein Naghshzan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/searchy2/CustomImagePicker/releases) [](https://github.com/searchy2/CustomImagePicker) [](https://github.com/searchy2/CustomImagePicker) [](https://github.com/searchy2/CustomImagePicker) [](https://github.com/searchy2/CustomImagePicker) [](https://github.com/searchy2/CustomImagePicker) [](https://github.com/searchy2/CustomImagePicker)
2 | # Custom Image Picker
3 |
4 | 
5 |
6 | Custom Image Picker is a popup image picker for quick selection of recent images. This photo chooser does not require a custom image provider. Custom Image Picker works great for choosing images in a chat app, selecting a profile image, or any quick image selection needs.
7 |
8 | ### Single Row
9 | 
10 |
11 | ### Double Row
12 | 
13 |
14 | This library is part of the [Custom UI](http://rayliverified.com/index.php/code/) collection of beautiful, minimalistic, and customizable Android UI components.
15 |
16 | # Gradle Dependency
17 |
18 | Add this line to your `build.gradle` project. Use the latest release version for the version code.
19 |
20 | ```java
21 | repositories {
22 | maven { url 'https://jitpack.io' }
23 | }
24 | compile 'com.github.searchy2:CustomImagePicker:latest-version'
25 | ```
26 | # Usage
27 |
28 | ### Code
29 |
30 | Custom Image Picker uses a Dialog popup to display the image gallery. Copy the following code into your project and you will have a working image picker. It's that simple!
31 |
32 | First, initialize the bottom sheet layout of our image picker gallery and create the popup dialogue.
33 |
34 | ```java
35 | final View bottomSheet = getLayoutInflater().inflate(R.layout.bottom_sheet, null);
36 | final Dialog mBottomSheetDialog = new Dialog(this, R.style.MaterialDialogSheet);
37 | mBottomSheetDialog.setContentView(bottomSheet);
38 | mBottomSheetDialog.setCancelable(true);
39 | mBottomSheetDialog.getWindow().setLayout(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
40 | mBottomSheetDialog.getWindow().setGravity(Gravity.BOTTOM);
41 | ```
42 |
43 | Next, create the image gallery adapter and show it to the user.
44 |
45 | ```java
46 | CustomImagePicker imagePicker = new CustomImagePicker();
47 | imagePicker.setHeight(100);
48 | imagePicker.setWidth(100);
49 | ImageAdapter adapter = imagePicker.getAdapter(MainActivity.this);
50 |
51 | TwoWayGridView gridview = bottomSheet.findViewById(R.id.gridview);
52 | gridview.getLayoutParams().height = Units.dpToPx(mContext, 200);
53 | gridview.setNumRows(2);
54 | gridview.setAdapter(adapter);
55 | gridview.setOnItemClickListener(new TwoWayAdapterView.OnItemClickListener() {
56 | public void onItemClick(TwoWayAdapterView parent, View v, int position, long id) {
57 | imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
58 | //Do what you want with the returned imageUri here.
59 | mBottomSheetDialog.dismiss();
60 | }
61 | });
62 |
63 | mBottomSheetDialog.show();
64 | ```
65 |
66 | That's it! Now you have a fancy new image picker!
67 |
68 | # Customization
69 |
70 | ### Layouts
71 |
72 | Custom Image Picker is highly customizable. The default `bottom_sheet_default.xml` layout contains only an image gallery but you can design your own image picker layout or use an existing layout. Please view the sample app for an example image layout with action sheet buttons that capture a photo and open the device image gallery.
73 |
74 | ### Builder Attributes
75 |
76 | `getAdapter(Context context)` - default method to get adapter
77 |
78 | `getAdapter(Context context, String columns, String sort)` - filter available images and sort in `Descending` or `Ascending` order.
79 |
80 | `setDrawable(int drawable)` - set drawable resource Id to replace default placeholder image.
81 |
82 | `setHeight(int height)` - set image height (dp).
83 |
84 | `setWidth(int width)` - set image width (dp).
85 |
86 | `setPadding(int padding)` - set image padding (dp).
87 |
88 | `setSize(int size)` - set 1, 2, 3, 4 for image thumbnail quality. 1 is the best quality and 4 is the lowest quality. Default 1.
89 |
90 |
91 | # Features Wishlist
92 |
93 | These features would make this library even more awesome. You can contribute to this library by developing any of the features below. Or, if you really want to see a feature developed, you can sponsor me to develop the feature.
94 |
95 | ---
96 |
97 | >None
98 |
99 | If you have any ideas for marking this library even more awesome, please let me know!
100 |
101 | ---
102 |
103 | Pull requests are most welcome!
104 |
105 | If you've fixed a bug or have a feature you've added, just create a pull request. If you've found a bug, file an issue. If you have any questions or would like to discuss upcoming features, please get in touch. You can get in touch with me in the Contact section below.
106 |
107 | # ★ Acknowledgements ★
108 | **♥ Developer ♥**
109 |
110 | [Ray Li](https://rayliverified.com)
111 |
112 | **♥ Designer ♥**
113 |
114 | [Ray Li](https://rayliverified.com)
115 |
116 | **♥ Inspiration ♥**
117 | Image selection code from https://github.com/amirarcane/recent-images
118 |
119 | # ★ Get in Touch ★
120 |
121 |
123 |
124 |
125 |
127 |
128 |
129 |
131 |
132 |
133 |
135 |
136 |
137 |
139 |
140 |
141 |
143 |
144 |
145 | # Apps Using This Library
146 |
147 | Want to be here? You can include yourself by making a `pull request`.
148 |
149 |
155 |
156 | #### Search Terms
157 | android, image, photo, picker, select, selector, chooser, gallery, viewer
158 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | build/*
3 | */app.iml
4 |
5 | # Crashlytics configuations
6 | com_crashlytics_export_strings.xml
7 |
8 | # Local configuration file (sdk path, etc)
9 | local.properties
10 |
11 | # Gradle generated files
12 | .gradle/
13 |
14 | # Signing files
15 | .signing/
16 |
17 | # User-specific configurations
18 | .idea/*
19 | .idea/libraries/
20 | .idea/workspace.xml
21 | .idea/tasks.xml
22 | .idea/.name
23 | .idea/compiler.xml
24 | .idea/copyright/profiles_settings.xml
25 | .idea/encodings.xml
26 | .idea/misc.xml
27 | .idea/modules.xml
28 | .idea/scopes/scope_settings.xml
29 | .idea/vcs.xml
30 | *.iml
31 |
32 | # OS-specific files
33 | .DS_Store
34 | .DS_Store?
35 | ._*
36 | .Spotlight-V100
37 | .Trashes
38 | ehthumbs.db
39 | Thumbs.db
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 | defaultConfig {
7 | applicationId "stream.recentimagesapp"
8 | minSdkVersion 15
9 | versionCode 1
10 | versionName "1.0.0"
11 | targetSdkVersion 28
12 | vectorDrawables.useSupportLibrary = true
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | repositories {
24 | maven { url 'https://jitpack.io' }
25 | }
26 | implementation fileTree(dir: 'libs', include: ['*.jar'])
27 | implementation project(':customimagepicker')
28 | implementation 'com.github.searchy2:CustomButton:1.9.0'
29 |
30 | implementation 'androidx.appcompat:appcompat:1.1.0'
31 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
32 | implementation 'com.github.bumptech.glide:glide:4.10.0'
33 | }
34 |
--------------------------------------------------------------------------------
/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/Amir/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/sample.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/sample.apk
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/stream/customimagepickerapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package stream.customimagepickerapp;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.app.Activity;
6 | import android.app.Dialog;
7 | import android.content.ContentUris;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.pm.PackageManager;
11 | import android.net.Uri;
12 | import android.os.Build;
13 | import android.os.Bundle;
14 | import android.provider.MediaStore;
15 | import android.util.Log;
16 | import android.view.Gravity;
17 | import android.view.View;
18 | import android.widget.ImageView;
19 | import android.widget.LinearLayout;
20 | import android.widget.Toast;
21 |
22 | import androidx.annotation.NonNull;
23 | import androidx.appcompat.app.AppCompatActivity;
24 | import androidx.core.app.ActivityCompat;
25 |
26 | import com.bumptech.glide.Glide;
27 |
28 | import stream.custombutton.CustomButton;
29 | import stream.customimagepicker.CustomImagePicker;
30 | import stream.customimagepicker.ImageAdapter;
31 | import stream.jess.ui.TwoWayAdapterView;
32 | import stream.jess.ui.TwoWayGridView;
33 |
34 | public class MainActivity extends AppCompatActivity {
35 |
36 | @SuppressLint("InlinedApi")
37 | private static String[] permissionList = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
38 | private static final int CAPTURE_IMAGE = 0;
39 | private static final int SELECT_PHOTO = 1;
40 |
41 | private Uri imageUri;
42 | private ImageView mImage;
43 | private Context mContext;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.activity_main);
49 |
50 | mContext = getApplicationContext();
51 |
52 | //Permissions need to be granted at runtime on Marshmallow
53 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
54 | CheckPermissions();
55 | }
56 |
57 | mImage = findViewById(R.id.imageView);
58 |
59 | //Initialize Image Picker Dialogue Popup.
60 | final View bottomSheet = getLayoutInflater().inflate(R.layout.bottom_sheet, null);
61 | final Dialog mBottomSheetDialog = new Dialog(this, R.style.MaterialDialogSheet);
62 | mBottomSheetDialog.setContentView(bottomSheet);
63 | mBottomSheetDialog.setCancelable(true);
64 | mBottomSheetDialog.getWindow().setLayout(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
65 | mBottomSheetDialog.getWindow().setGravity(Gravity.BOTTOM);
66 |
67 | //Initialize Image Picker Menu Actions.
68 | LinearLayout layoutCamera = bottomSheet.findViewById(R.id.btn_camera);
69 | LinearLayout layoutGallery = bottomSheet.findViewById(R.id.btn_gallery);
70 | layoutCamera.setOnClickListener(new View.OnClickListener() {
71 | @Override
72 | public void onClick(View view) {
73 | Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
74 | startActivityForResult(cameraIntent, CAPTURE_IMAGE);
75 | mBottomSheetDialog.dismiss();
76 | }
77 | });
78 | layoutGallery.setOnClickListener(new View.OnClickListener() {
79 | @Override
80 | public void onClick(View view) {
81 | Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
82 | photoPickerIntent.setType("image/*");
83 | startActivityForResult(photoPickerIntent, SELECT_PHOTO);
84 | mBottomSheetDialog.dismiss();
85 | }
86 | });
87 |
88 | CustomButton btn1 = findViewById(R.id.btn1);
89 | btn1.setOnClickListener(new View.OnClickListener() {
90 | @Override
91 | public void onClick(View v) {
92 |
93 | CustomImagePicker imagePicker = new CustomImagePicker();
94 | imagePicker.setHeight(100);
95 | imagePicker.setWidth(100);
96 | ImageAdapter adapter = imagePicker.getAdapter(MainActivity.this);
97 |
98 | TwoWayGridView gridview = bottomSheet.findViewById(R.id.gridview);
99 | gridview.getLayoutParams().height = Units.dpToPx(mContext, 100);
100 | gridview.setNumRows(1);
101 | gridview.setAdapter(adapter);
102 | gridview.setOnItemClickListener(new TwoWayAdapterView.OnItemClickListener() {
103 | public void onItemClick(TwoWayAdapterView parent, View v, int position, long id) {
104 | imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
105 | Glide.with(mContext).load(imageUri).into(mImage);
106 | mBottomSheetDialog.dismiss();
107 | }
108 | });
109 |
110 | mBottomSheetDialog.show();
111 | }
112 | });
113 |
114 | CustomButton btn2 = findViewById(R.id.btn2);
115 | btn2.setOnClickListener(new View.OnClickListener() {
116 | @Override
117 | public void onClick(View v) {
118 |
119 | CustomImagePicker imagePicker = new CustomImagePicker();
120 | imagePicker.setHeight(100);
121 | imagePicker.setWidth(100);
122 | ImageAdapter adapter = imagePicker.getAdapter(MainActivity.this);
123 |
124 | TwoWayGridView gridview = bottomSheet.findViewById(R.id.gridview);
125 | gridview.getLayoutParams().height = Units.dpToPx(mContext, 200);
126 | gridview.setNumRows(2);
127 | gridview.setAdapter(adapter);
128 | gridview.setOnItemClickListener(new TwoWayAdapterView.OnItemClickListener() {
129 | public void onItemClick(TwoWayAdapterView parent, View v, int position, long id) {
130 | imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
131 | Glide.with(mContext).load(imageUri).into(mImage);
132 | mBottomSheetDialog.dismiss();
133 | }
134 | });
135 |
136 | mBottomSheetDialog.show();
137 | }
138 | });
139 | }
140 |
141 | @Override
142 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
143 | super.onActivityResult(requestCode, resultCode, data);
144 | switch (requestCode) {
145 | case CAPTURE_IMAGE:
146 | case SELECT_PHOTO:
147 | if (resultCode == Activity.RESULT_OK) {
148 | imageUri = data.getData();
149 | }
150 | break;
151 | }
152 | if (imageUri != null) {
153 | Log.d("ImageURI", String.valueOf(imageUri));
154 | Glide.with(mContext).load(imageUri).into(mImage);
155 | }
156 | }
157 |
158 | @Override
159 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
160 | //noinspection SwitchStatementWithTooFewBranches
161 | switch (requestCode) {
162 | case 1:
163 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
164 | Toast.makeText(this, "Thanks!",
165 | Toast.LENGTH_SHORT).show();
166 | } else {
167 | //If user denies Storage Permission, explain why permission is needed and prompt again.
168 | Toast.makeText(this, "Storage access is needed to display images.",
169 | Toast.LENGTH_SHORT).show();
170 | CheckPermissions();
171 | }
172 | break;
173 | default:
174 | break;
175 | }
176 | }
177 |
178 | public void CheckPermissions()
179 | {
180 | if (!IsPermissionsEnabled(mContext, permissionList))
181 | {
182 | ActivityCompat.requestPermissions(this, permissionList, 1);
183 | }
184 | }
185 |
186 | public boolean IsPermissionEnabled(Context context, String permission)
187 | {
188 | if (Build.VERSION.SDK_INT >= 23) {
189 | return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
190 | }
191 | else
192 | {
193 | return true;
194 | }
195 | }
196 |
197 | public boolean IsPermissionsEnabled(Context context, String[] permissionList)
198 | {
199 | for (String permission : permissionList)
200 | {
201 | if (!IsPermissionEnabled(context, permission))
202 | {
203 | return false;
204 | }
205 | }
206 |
207 | return true;
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/app/src/main/java/stream/customimagepickerapp/Units.java:
--------------------------------------------------------------------------------
1 | package stream.customimagepickerapp;
2 |
3 | import android.content.Context;
4 | import android.util.DisplayMetrics;
5 |
6 | public class Units {
7 |
8 | /**
9 | * Converts dp to pixels.
10 | */
11 | public static int dpToPx(Context context, int dp) {
12 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
13 | int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
14 | return px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_gradient_dusk.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_moon.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
15 |
18 |
25 |
29 |
36 |
39 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_mountains_geometric.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
14 |
17 |
20 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
44 |
47 |
50 |
53 |
56 |
59 |
62 |
65 |
68 |
71 |
74 |
77 |
80 |
83 |
86 |
89 |
92 |
95 |
98 |
101 |
104 |
107 |
110 |
113 |
116 |
119 |
122 |
125 |
128 |
131 |
134 |
137 |
140 |
143 |
146 |
149 |
152 |
155 |
158 |
161 |
164 |
167 |
170 |
173 |
176 |
179 |
182 |
185 |
188 |
191 |
194 |
197 |
200 |
203 |
206 |
209 |
212 |
215 |
218 |
221 |
224 |
227 |
230 |
233 |
236 |
239 |
242 |
245 |
248 |
251 |
254 |
257 |
260 |
263 |
266 |
269 |
272 |
275 |
278 |
281 |
284 |
287 |
290 |
293 |
296 |
299 |
302 |
305 |
308 |
311 |
314 |
317 |
320 |
323 |
326 |
329 |
332 |
335 |
338 |
341 |
344 |
347 |
350 |
353 |
356 |
359 |
362 |
365 |
368 |
371 |
374 |
377 |
380 |
383 |
386 |
389 |
392 |
395 |
398 |
401 |
404 |
407 |
410 |
413 |
416 |
419 |
422 |
425 |
428 |
431 |
434 |
437 |
440 |
443 |
446 |
449 |
452 |
455 |
458 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rectangle_outline_white_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_stars.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
14 |
17 |
20 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
44 |
47 |
50 |
53 |
56 |
59 |
62 |
65 |
68 |
71 |
74 |
77 |
80 |
83 |
86 |
89 |
92 |
95 |
98 |
101 |
104 |
107 |
110 |
113 |
116 |
119 |
122 |
125 |
128 |
131 |
134 |
137 |
140 |
143 |
146 |
149 |
152 |
155 |
158 |
161 |
164 |
167 |
170 |
173 |
176 |
179 |
182 |
185 |
188 |
191 |
194 |
197 |
200 |
203 |
206 |
209 |
212 |
215 |
218 |
221 |
224 |
227 |
230 |
233 |
236 |
239 |
242 |
245 |
248 |
251 |
254 |
257 |
260 |
263 |
266 |
269 |
272 |
275 |
278 |
281 |
284 |
287 |
290 |
293 |
296 |
299 |
302 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
19 |
20 |
26 |
27 |
33 |
34 |
46 |
47 |
55 |
56 |
60 |
61 |
65 |
66 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/bottom_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
28 |
29 |
36 |
37 |
47 |
48 |
55 |
56 |
63 |
64 |
65 |
75 |
76 |
83 |
84 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #efebef
4 | #181808
5 |
6 |
7 | #FFFFFF
8 | #c8ffffff
9 | #00FFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Custom Image Picker
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
34 |
35 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | maven { url "https://maven.google.com" }
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.2'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
11 | }
12 | }
13 | allprojects {
14 | repositories {
15 | jcenter()
16 | maven { url "https://maven.google.com" }
17 | }
18 | }
--------------------------------------------------------------------------------
/customimagepicker/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | build/*
3 | */app.iml
4 |
5 | # Crashlytics configuations
6 | com_crashlytics_export_strings.xml
7 |
8 | # Local configuration file (sdk path, etc)
9 | local.properties
10 |
11 | # Gradle generated files
12 | .gradle/
13 |
14 | # Signing files
15 | .signing/
16 |
17 | # User-specific configurations
18 | .idea/*
19 | .idea/libraries/
20 | .idea/workspace.xml
21 | .idea/tasks.xml
22 | .idea/.name
23 | .idea/compiler.xml
24 | .idea/copyright/profiles_settings.xml
25 | .idea/encodings.xml
26 | .idea/misc.xml
27 | .idea/modules.xml
28 | .idea/scopes/scope_settings.xml
29 | .idea/vcs.xml
30 | *.iml
31 |
32 | # OS-specific files
33 | .DS_Store
34 | .DS_Store?
35 | ._*
36 | .Spotlight-V100
37 | .Trashes
38 | ehthumbs.db
39 | Thumbs.db
--------------------------------------------------------------------------------
/customimagepicker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | group='com.github.searchy2'
5 | version = '2.2.1'
6 |
7 | android {
8 | compileSdkVersion 28
9 |
10 | defaultConfig {
11 | minSdkVersion 14
12 | targetSdkVersion 28
13 | vectorDrawables.useSupportLibrary = true
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | implementation fileTree(dir: 'libs', include: ['*.jar'])
25 | implementation 'androidx.appcompat:appcompat:1.1.0'
26 | implementation 'com.github.bumptech.glide:glide:4.10.0'
27 | }
--------------------------------------------------------------------------------
/customimagepicker/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/customimagepicker/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 |
--------------------------------------------------------------------------------
/customimagepicker/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/Amir/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 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/java/stream/customimagepicker/CustomImagePicker.java:
--------------------------------------------------------------------------------
1 | package stream.customimagepicker;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.provider.MediaStore;
6 |
7 | public class CustomImagePicker {
8 |
9 | public static final String ASCENDING = " ASC";
10 | public static final String DESCENDING = " DESC";
11 | public static final String DATE_TAKEN = "datetaken";
12 |
13 | /**
14 | * @param context - default method to get adapter.
15 | * @return
16 | */
17 | public ImageAdapter getAdapter(Context context) {
18 | return getAdapter(context, DATE_TAKEN, DESCENDING);
19 | }
20 |
21 | /**
22 | * @param context
23 | * @param columns - set image sort based on columns.
24 | * @param sort - set ASCENDING, DESCENDING sort order based on columns.
25 | * @return
26 | */
27 | public ImageAdapter getAdapter(Context context, String columns, String sort) {
28 | Cursor mImageCursor = null;
29 | try {
30 | String[] projection = new String[]{MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA,
31 | MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, MediaStore.Images.ImageColumns.DATE_TAKEN, MediaStore.Images.ImageColumns.MIME_TYPE};
32 | mImageCursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, columns + sort);
33 | } catch (Exception e) {
34 | e.printStackTrace();
35 | }
36 | ImageAdapter mAdapter = new ImageAdapter(context, mImageCursor);
37 | return mAdapter;
38 | }
39 |
40 | /**
41 | * @param drawable - set drawable resource Id to replace default placeholder image.
42 | */
43 | public void setDrawable(int drawable) {
44 | ImageAdapter.DRAWABLE = drawable;
45 | }
46 |
47 | /**
48 | * @param height - set image height (dp).
49 | */
50 | public void setHeight(int height) {
51 | ImageAdapter.IMAGE_HEIGHT = height;
52 | }
53 |
54 | /**
55 | * @param width - set image width (dp).
56 | */
57 | public void setWidth(int width) {
58 | ImageAdapter.IMAGE_WIDTH = width;
59 | }
60 |
61 | /**
62 | * @param padding - set image padding (dp).
63 | */
64 | public void setPadding(int padding) {
65 | ImageAdapter.IMAGE_PADDING = padding;
66 | }
67 |
68 | /**
69 | * @param kind - set 1, 2, 3, 4 for image thumbnail quality.
70 | * 1 is the best quality and 4 is the lowest quality. Default 1.
71 | */
72 | public void setKind(int kind) {
73 | ImageAdapter.KIND = kind;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/java/stream/customimagepicker/ImageAdapter.java:
--------------------------------------------------------------------------------
1 | package stream.customimagepicker;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.net.Uri;
6 | import android.provider.MediaStore;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.CursorAdapter;
10 | import android.widget.ImageView;
11 |
12 | import com.bumptech.glide.Glide;
13 | import com.bumptech.glide.request.RequestOptions;
14 | import stream.jess.ui.TwoWayAbsListView;
15 |
16 | public class ImageAdapter extends CursorAdapter {
17 |
18 | public static final int IMAGE_ID_COLUMN = 0;
19 | public static final int IMAGE_DATA_COLUMN = 1;
20 | public static final int IMAGE_NAME_COLUMN = 2;
21 | public static final int IMAGE_DATE_COLUMN = 3;
22 | public static final int IMAGE_TYPE_COLUMN = 4;
23 |
24 | public static float IMAGE_WIDTH = 100;
25 | public static float IMAGE_HEIGHT = 100;
26 | public static float IMAGE_PADDING = 0;
27 |
28 | private final Context mContext;
29 | private float mScale;
30 | private int mImageWidth;
31 | private int mImageHeight;
32 | private int mImagePadding;
33 |
34 | public ImageView.ScaleType SCALE_TYPE = ImageView.ScaleType.CENTER_CROP;
35 | public static int KIND = MediaStore.Images.Thumbnails.MINI_KIND;
36 |
37 | public static int DRAWABLE = R.drawable.icon_image_default;
38 |
39 | public ImageAdapter(Context context, Cursor c) {
40 | this(context, c, true);
41 | }
42 |
43 | public ImageAdapter(Context context, Cursor c, boolean autoRequery) {
44 | super(context, c, autoRequery);
45 | mContext = context;
46 | mScale = mContext.getResources().getDisplayMetrics().density;
47 | mImageWidth = (int) (IMAGE_WIDTH * mScale);
48 | mImageHeight = (int) (IMAGE_HEIGHT * mScale);
49 | mImagePadding = (int) (IMAGE_PADDING * mScale);
50 | }
51 |
52 | @Override
53 | public int getItemViewType(int position) {
54 | return 0;
55 | }
56 |
57 | @Override
58 | public int getViewTypeCount() {
59 | return 1;
60 | }
61 |
62 | @Override
63 | public void bindView(View view, Context context, Cursor cursor) {
64 | String path = cursor.getString(IMAGE_DATA_COLUMN);
65 | RequestOptions requestOptions = new RequestOptions().placeholder(DRAWABLE);
66 | Glide.with(context)
67 | .load(Uri.parse("file://" + path))
68 | .apply(requestOptions)
69 | .into((ImageView) view);
70 | }
71 |
72 | @Override
73 | public View newView(Context context, Cursor cursor, ViewGroup parent) {
74 | ImageView imageView = new ImageView(mContext.getApplicationContext());
75 | imageView.setLayoutParams(new TwoWayAbsListView.LayoutParams(mImageWidth, mImageHeight));
76 | imageView.setPadding(mImagePadding, mImagePadding, mImagePadding, mImagePadding);
77 | imageView.setScaleType(SCALE_TYPE);
78 | return imageView;
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/java/stream/jess/ui/ScrollBarDrawable.java:
--------------------------------------------------------------------------------
1 | package stream.jess.ui;
2 | /*
3 | * A modified version of the Android ScrollBarDrawable
4 | *
5 | * Copyright 2012 Jess Anders
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | /*
21 | * Copyright (C) 2006 The Android Open Source Project
22 | *
23 | * Licensed under the Apache License, Version 2.0 (the "License");
24 | * you may not use this file except in compliance with the License.
25 | * You may obtain a copy of the License at
26 | *
27 | * http://www.apache.org/licenses/LICENSE-2.0
28 | *
29 | * Unless required by applicable law or agreed to in writing, software
30 | * distributed under the License is distributed on an "AS IS" BASIS,
31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 | * See the License for the specific language governing permissions and
33 | * limitations under the License.
34 | */
35 |
36 | import android.graphics.Canvas;
37 | import android.graphics.ColorFilter;
38 | import android.graphics.PixelFormat;
39 | import android.graphics.Rect;
40 | import android.graphics.drawable.Drawable;
41 |
42 | /**
43 | * This is only used by View for displaying its scroll bars. It should probably
44 | * be moved in to the view package since it is used in that lower-level layer.
45 | * For now, we'll hide it so it can be cleaned up later.
46 | * {@hide}
47 | */
48 | public class ScrollBarDrawable extends Drawable {
49 | private Drawable mVerticalTrack;
50 | private Drawable mHorizontalTrack;
51 | private Drawable mVerticalThumb;
52 | private Drawable mHorizontalThumb;
53 | private int mRange;
54 | private int mOffset;
55 | private int mExtent;
56 | private boolean mVertical;
57 | private boolean mChanged;
58 | private boolean mRangeChanged;
59 | private final Rect mTempBounds = new Rect();
60 | private boolean mAlwaysDrawHorizontalTrack;
61 | private boolean mAlwaysDrawVerticalTrack;
62 |
63 | public ScrollBarDrawable() {
64 | }
65 |
66 | /**
67 | * Indicate whether the horizontal scrollbar track should always be drawn regardless of the
68 | * extent. Defaults to false.
69 | *
70 | * @param alwaysDrawTrack Set to true if the track should always be drawn
71 | */
72 | public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
73 | mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
74 | }
75 |
76 | /**
77 | * Indicate whether the vertical scrollbar track should always be drawn regardless of the
78 | * extent. Defaults to false.
79 | *
80 | * @param alwaysDrawTrack Set to true if the track should always be drawn
81 | */
82 | public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
83 | mAlwaysDrawVerticalTrack = alwaysDrawTrack;
84 | }
85 |
86 | /**
87 | * Indicates whether the vertical scrollbar track should always be drawn regardless of the
88 | * extent.
89 | */
90 | public boolean getAlwaysDrawVerticalTrack() {
91 | return mAlwaysDrawVerticalTrack;
92 | }
93 |
94 | /**
95 | * Indicates whether the horizontal scrollbar track should always be drawn regardless of the
96 | * extent.
97 | */
98 | public boolean getAlwaysDrawHorizontalTrack() {
99 | return mAlwaysDrawHorizontalTrack;
100 | }
101 |
102 | public void setParameters(int range, int offset, int extent, boolean vertical) {
103 | if (mVertical != vertical) {
104 | mChanged = true;
105 | }
106 |
107 | if (mRange != range || mOffset != offset || mExtent != extent) {
108 | mRangeChanged = true;
109 | }
110 |
111 | mRange = range;
112 | mOffset = offset;
113 | mExtent = extent;
114 | mVertical = vertical;
115 | }
116 |
117 | @Override
118 | public void draw(Canvas canvas) {
119 | final boolean vertical = mVertical;
120 | final int extent = mExtent;
121 | final int range = mRange;
122 |
123 | boolean drawTrack = true;
124 | boolean drawThumb = true;
125 | if (extent <= 0 || range <= extent) {
126 | drawTrack = vertical ? mAlwaysDrawVerticalTrack : mAlwaysDrawHorizontalTrack;
127 | drawThumb = false;
128 | }
129 |
130 | Rect r = getBounds();
131 | if (canvas.quickReject(r.left, r.top, r.right, r.bottom,
132 | Canvas.EdgeType.AA)) {
133 | return;
134 | }
135 | if (drawTrack) {
136 | drawTrack(canvas, r, vertical);
137 | }
138 |
139 | if (drawThumb) {
140 | int size = vertical ? r.height() : r.width();
141 | int thickness = vertical ? r.width() : r.height();
142 | int length = Math.round((float) size * extent / range);
143 | int offset = Math.round((float) (size - length) * mOffset / (range - extent));
144 |
145 | // avoid the tiny thumb
146 | int minLength = thickness * 2;
147 | if (length < minLength) {
148 | length = minLength;
149 | }
150 | // avoid the too-big thumb
151 | if (offset + length > size) {
152 | offset = size - length;
153 | }
154 |
155 | drawThumb(canvas, r, offset, length, vertical);
156 | }
157 | }
158 |
159 | @Override
160 | protected void onBoundsChange(Rect bounds) {
161 | super.onBoundsChange(bounds);
162 | mChanged = true;
163 | }
164 |
165 | protected void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
166 | Drawable track;
167 | if (vertical) {
168 | track = mVerticalTrack;
169 | } else {
170 | track = mHorizontalTrack;
171 | }
172 | if (track != null) {
173 | if (mChanged) {
174 | track.setBounds(bounds);
175 | }
176 | track.draw(canvas);
177 | }
178 | }
179 |
180 | protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
181 | final Rect thumbRect = mTempBounds;
182 | final boolean changed = mRangeChanged || mChanged;
183 | if (changed) {
184 | if (vertical) {
185 | thumbRect.set(bounds.left, bounds.top + offset,
186 | bounds.right, bounds.top + offset + length);
187 | } else {
188 | thumbRect.set(bounds.left + offset, bounds.top,
189 | bounds.left + offset + length, bounds.bottom);
190 | }
191 | }
192 |
193 | if (vertical) {
194 | final Drawable thumb = mVerticalThumb;
195 | if (changed) thumb.setBounds(thumbRect);
196 | thumb.draw(canvas);
197 | } else {
198 | final Drawable thumb = mHorizontalThumb;
199 | if (changed) thumb.setBounds(thumbRect);
200 | thumb.draw(canvas);
201 | }
202 | }
203 |
204 | public void setVerticalThumbDrawable(Drawable thumb) {
205 | if (thumb != null) {
206 | mVerticalThumb = thumb;
207 | }
208 | }
209 |
210 | public void setVerticalTrackDrawable(Drawable track) {
211 | mVerticalTrack = track;
212 | }
213 |
214 | public void setHorizontalThumbDrawable(Drawable thumb) {
215 | if (thumb != null) {
216 | mHorizontalThumb = thumb;
217 | }
218 | }
219 |
220 | public void setHorizontalTrackDrawable(Drawable track) {
221 | mHorizontalTrack = track;
222 | }
223 |
224 | public int getSize(boolean vertical) {
225 | if (vertical) {
226 | return (mVerticalTrack != null ?
227 | mVerticalTrack : mVerticalThumb).getIntrinsicWidth();
228 | } else {
229 | return (mHorizontalTrack != null ?
230 | mHorizontalTrack : mHorizontalThumb).getIntrinsicHeight();
231 | }
232 | }
233 |
234 | @Override
235 | public void setAlpha(int alpha) {
236 | if (mVerticalTrack != null) {
237 | mVerticalTrack.setAlpha(alpha);
238 | }
239 | mVerticalThumb.setAlpha(alpha);
240 | if (mHorizontalTrack != null) {
241 | mHorizontalTrack.setAlpha(alpha);
242 | }
243 | mHorizontalThumb.setAlpha(alpha);
244 | }
245 |
246 | @Override
247 | public void setColorFilter(ColorFilter cf) {
248 | if (mVerticalTrack != null) {
249 | mVerticalTrack.setColorFilter(cf);
250 | }
251 | mVerticalThumb.setColorFilter(cf);
252 | if (mHorizontalTrack != null) {
253 | mHorizontalTrack.setColorFilter(cf);
254 | }
255 | mHorizontalThumb.setColorFilter(cf);
256 | }
257 |
258 | @Override
259 | public int getOpacity() {
260 | return PixelFormat.TRANSLUCENT;
261 | }
262 |
263 | @Override
264 | public String toString() {
265 | return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
266 | " extent=" + mExtent + (mVertical ? " V" : " H");
267 | }
268 | }
269 |
270 |
271 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/java/stream/jess/ui/TwoWayAdapterView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * A modified version of the Android AdapterView
3 | *
4 | * Copyright 2012 Jess Anders
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | /*
20 | * Copyright (C) 2006 The Android Open Source Project
21 | *
22 | * Licensed under the Apache License, Version 2.0 (the "License");
23 | * you may not use this file except in compliance with the License.
24 | * You may obtain a copy of the License at
25 | *
26 | * http://www.apache.org/licenses/LICENSE-2.0
27 | *
28 | * Unless required by applicable law or agreed to in writing, software
29 | * distributed under the License is distributed on an "AS IS" BASIS,
30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | * See the License for the specific language governing permissions and
32 | * limitations under the License.
33 | */
34 |
35 | package stream.jess.ui;
36 |
37 |
38 | import android.content.Context;
39 | import android.database.DataSetObserver;
40 | import android.os.Handler;
41 | import android.os.Parcelable;
42 | import android.os.SystemClock;
43 | import android.util.AttributeSet;
44 | import android.util.SparseArray;
45 | import android.view.ContextMenu;
46 | import android.view.SoundEffectConstants;
47 | import android.view.View;
48 | import android.view.ViewDebug;
49 | import android.view.ViewGroup;
50 | import android.view.ContextMenu.ContextMenuInfo;
51 | import android.widget.Adapter;
52 |
53 |
54 | /**
55 | * A TwoWayAdapterView is a view whose children are determined by an {@link Adapter}.
56 | *
57 | *
58 | * See {@link TwoWayAbsListView} and {@link TwoWayGridView} for commonly used subclasses of TwoWayAdapterView.
59 | */
60 | public abstract class TwoWayAdapterView extends ViewGroup {
61 |
62 | /**
63 | * The item view type returned by {@link Adapter#getItemViewType(int)} when
64 | * the adapter does not want the item's view recycled.
65 | */
66 | public static final int ITEM_VIEW_TYPE_IGNORE = -1;
67 |
68 | /**
69 | * The item view type returned by {@link Adapter#getItemViewType(int)} when
70 | * the item is a header or footer.
71 | */
72 | public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
73 |
74 | protected Context mContext = null;
75 |
76 | /**
77 | * Whether the view displays its items vertically or horizontally
78 | */
79 | protected boolean mIsVertical = true;
80 |
81 | /**
82 | * The position of the first child displayed
83 | */
84 | @ViewDebug.ExportedProperty
85 | int mFirstPosition = 0;
86 |
87 | /**
88 | * The offset in pixels from the top of the AdapterView to the top or left
89 | * of the view to select during the next layout.
90 | */
91 | int mSpecificTop;
92 |
93 | /**
94 | * Position from which to start looking for mSyncRowId
95 | */
96 | int mSyncPosition;
97 |
98 | /**
99 | * Row id to look for when data has changed
100 | */
101 | long mSyncRowId = INVALID_ROW_ID;
102 |
103 | /**
104 | * Size of the view when mSyncPosition and mSyncRowId where set
105 | */
106 | long mSyncSize;
107 |
108 | /**
109 | * True if we need to sync to mSyncRowId
110 | */
111 | boolean mNeedSync = false;
112 |
113 | /**
114 | * Indicates whether to sync based on the selection or position. Possible
115 | * values are {@link #SYNC_SELECTED_POSITION} or
116 | * {@link #SYNC_FIRST_POSITION}.
117 | */
118 | int mSyncMode;
119 |
120 | /**
121 | * Our height after the last layout
122 | */
123 | private int mLayoutHeight;
124 |
125 | /**
126 | * Our width after the last layout
127 | */
128 | private int mLayoutWidth;
129 |
130 | /**
131 | * Sync based on the selected child
132 | */
133 | static final int SYNC_SELECTED_POSITION = 0;
134 |
135 | /**
136 | * Sync based on the first child displayed
137 | */
138 | static final int SYNC_FIRST_POSITION = 1;
139 |
140 | /**
141 | * Maximum amount of time to spend in {@link #findSyncPosition()}
142 | */
143 | static final int SYNC_MAX_DURATION_MILLIS = 100;
144 |
145 | /**
146 | * Indicates that this view is currently being laid out.
147 | */
148 | boolean mInLayout = false;
149 |
150 | /**
151 | * The listener that receives notifications when an item is selected.
152 | */
153 | OnItemSelectedListener mOnItemSelectedListener;
154 |
155 | /**
156 | * The listener that receives notifications when an item is clicked.
157 | */
158 | OnItemClickListener mOnItemClickListener;
159 |
160 | /**
161 | * The listener that receives notifications when an item is long clicked.
162 | */
163 | OnItemLongClickListener mOnItemLongClickListener;
164 |
165 | /**
166 | * True if the data has changed since the last layout
167 | */
168 | boolean mDataChanged;
169 |
170 | /**
171 | * The position within the adapter's data set of the item to select
172 | * during the next layout.
173 | */
174 | @ViewDebug.ExportedProperty
175 | int mNextSelectedPosition = INVALID_POSITION;
176 |
177 | /**
178 | * The item id of the item to select during the next layout.
179 | */
180 | long mNextSelectedRowId = INVALID_ROW_ID;
181 |
182 | /**
183 | * The position within the adapter's data set of the currently selected item.
184 | */
185 | @ViewDebug.ExportedProperty
186 | int mSelectedPosition = INVALID_POSITION;
187 |
188 | /**
189 | * The item id of the currently selected item.
190 | */
191 | long mSelectedRowId = INVALID_ROW_ID;
192 |
193 | /**
194 | * View to show if there are no items to show.
195 | */
196 | private View mEmptyView;
197 |
198 | /**
199 | * The number of items in the current adapter.
200 | */
201 | @ViewDebug.ExportedProperty
202 | int mItemCount;
203 |
204 | /**
205 | * The number of items in the adapter before a data changed event occured.
206 | */
207 | int mOldItemCount;
208 |
209 | /**
210 | * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
211 | * number of items in the current adapter.
212 | */
213 | public static final int INVALID_POSITION = -1;
214 |
215 | /**
216 | * Represents an empty or invalid row id
217 | */
218 | public static final long INVALID_ROW_ID = Long.MIN_VALUE;
219 |
220 | /**
221 | * The last selected position we used when notifying
222 | */
223 | int mOldSelectedPosition = INVALID_POSITION;
224 |
225 | /**
226 | * The id of the last selected position we used when notifying
227 | */
228 | long mOldSelectedRowId = INVALID_ROW_ID;
229 |
230 | /**
231 | * Indicates what focusable state is requested when calling setFocusable().
232 | * In addition to this, this view has other criteria for actually
233 | * determining the focusable state (such as whether its empty or the text
234 | * filter is shown).
235 | *
236 | * @see #setFocusable(boolean)
237 | * @see #checkFocus()
238 | */
239 | private boolean mDesiredFocusableState;
240 | private boolean mDesiredFocusableInTouchModeState;
241 |
242 | private SelectionNotifier mSelectionNotifier;
243 | /**
244 | * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
245 | * This is used to layout the children during a layout pass.
246 | */
247 | boolean mBlockLayoutRequests = false;
248 |
249 | public TwoWayAdapterView(Context context) {
250 | super(context);
251 | mContext = context;
252 | }
253 |
254 | public TwoWayAdapterView(Context context, AttributeSet attrs) {
255 | super(context, attrs);
256 | mContext = context;
257 | }
258 |
259 | public TwoWayAdapterView(Context context, AttributeSet attrs, int defStyle) {
260 | super(context, attrs, defStyle);
261 | mContext = context;
262 | }
263 |
264 |
265 | /**
266 | * Interface definition for a callback to be invoked when an item in this
267 | * AdapterView has been clicked.
268 | */
269 | public interface OnItemClickListener {
270 |
271 | /**
272 | * Callback method to be invoked when an item in this AdapterView has
273 | * been clicked.
274 | *
275 | * Implementers can call getItemAtPosition(position) if they need
276 | * to access the data associated with the selected item.
277 | *
278 | * @param parent The AdapterView where the click happened.
279 | * @param view The view within the AdapterView that was clicked (this
280 | * will be a view provided by the adapter)
281 | * @param position The position of the view in the adapter.
282 | * @param id The row id of the item that was clicked.
283 | */
284 | void onItemClick(TwoWayAdapterView> parent, View view, int position, long id);
285 | }
286 |
287 | /**
288 | * Register a callback to be invoked when an item in this AdapterView has
289 | * been clicked.
290 | *
291 | * @param listener The callback that will be invoked.
292 | */
293 | public void setOnItemClickListener(OnItemClickListener listener) {
294 | mOnItemClickListener = listener;
295 | }
296 |
297 | /**
298 | * @return The callback to be invoked with an item in this AdapterView has
299 | * been clicked, or null id no callback has been set.
300 | */
301 | public final OnItemClickListener getOnItemClickListener() {
302 | return mOnItemClickListener;
303 | }
304 |
305 | /**
306 | * Call the OnItemClickListener, if it is defined.
307 | *
308 | * @param view The view within the AdapterView that was clicked.
309 | * @param position The position of the view in the adapter.
310 | * @param id The row id of the item that was clicked.
311 | * @return True if there was an assigned OnItemClickListener that was
312 | * called, false otherwise is returned.
313 | */
314 | public boolean performItemClick(View view, int position, long id) {
315 | if (mOnItemClickListener != null) {
316 | playSoundEffect(SoundEffectConstants.CLICK);
317 | mOnItemClickListener.onItemClick(this, view, position, id);
318 | return true;
319 | }
320 |
321 | return false;
322 | }
323 |
324 | /**
325 | * Interface definition for a callback to be invoked when an item in this
326 | * view has been clicked and held.
327 | */
328 | public interface OnItemLongClickListener {
329 | /**
330 | * Callback method to be invoked when an item in this view has been
331 | * clicked and held.
332 | *
333 | * Implementers can call getItemAtPosition(position) if they need to access
334 | * the data associated with the selected item.
335 | *
336 | * @param parent The AbsListView where the click happened
337 | * @param view The view within the AbsListView that was clicked
338 | * @param position The position of the view in the list
339 | * @param id The row id of the item that was clicked
340 | *
341 | * @return true if the callback consumed the long click, false otherwise
342 | */
343 | boolean onItemLongClick(TwoWayAdapterView> parent, View view, int position, long id);
344 | }
345 |
346 |
347 | /**
348 | * Register a callback to be invoked when an item in this AdapterView has
349 | * been clicked and held
350 | *
351 | * @param listener The callback that will run
352 | */
353 | public void setOnItemLongClickListener(OnItemLongClickListener listener) {
354 | if (!isLongClickable()) {
355 | setLongClickable(true);
356 | }
357 | mOnItemLongClickListener = listener;
358 | }
359 |
360 | /**
361 | * @return The callback to be invoked with an item in this AdapterView has
362 | * been clicked and held, or null id no callback as been set.
363 | */
364 | public final OnItemLongClickListener getOnItemLongClickListener() {
365 | return mOnItemLongClickListener;
366 | }
367 |
368 | /**
369 | * Interface definition for a callback to be invoked when
370 | * an item in this view has been selected.
371 | */
372 | public interface OnItemSelectedListener {
373 | /**
374 | * Callback method to be invoked when an item in this view has been
375 | * selected.
376 | *
377 | * Impelmenters can call getItemAtPosition(position) if they need to access the
378 | * data associated with the selected item.
379 | *
380 | * @param parent The AdapterView where the selection happened
381 | * @param view The view within the AdapterView that was clicked
382 | * @param position The position of the view in the adapter
383 | * @param id The row id of the item that is selected
384 | */
385 | void onItemSelected(TwoWayAdapterView> parent, View view, int position, long id);
386 |
387 | /**
388 | * Callback method to be invoked when the selection disappears from this
389 | * view. The selection can disappear for instance when touch is activated
390 | * or when the adapter becomes empty.
391 | *
392 | * @param parent The AdapterView that now contains no selected item.
393 | */
394 | void onNothingSelected(TwoWayAdapterView> parent);
395 | }
396 |
397 |
398 | /**
399 | * Register a callback to be invoked when an item in this AdapterView has
400 | * been selected.
401 | *
402 | * @param listener The callback that will run
403 | */
404 | public void setOnItemSelectedListener(OnItemSelectedListener listener) {
405 | mOnItemSelectedListener = listener;
406 | }
407 |
408 | public final OnItemSelectedListener getOnItemSelectedListener() {
409 | return mOnItemSelectedListener;
410 | }
411 |
412 | /**
413 | * Extra menu information provided to the
414 | * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
415 | * callback when a context menu is brought up for this AdapterView.
416 | *
417 | */
418 | public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
419 |
420 | public AdapterContextMenuInfo(View targetView, int position, long id) {
421 | this.targetView = targetView;
422 | this.position = position;
423 | this.id = id;
424 | }
425 |
426 | /**
427 | * The child view for which the context menu is being displayed. This
428 | * will be one of the children of this AdapterView.
429 | */
430 | public View targetView;
431 |
432 | /**
433 | * The position in the adapter for which the context menu is being
434 | * displayed.
435 | */
436 | public int position;
437 |
438 | /**
439 | * The row id of the item for which the context menu is being displayed.
440 | */
441 | public long id;
442 | }
443 |
444 | /**
445 | * Returns the adapter currently associated with this widget.
446 | *
447 | * @return The adapter used to provide this view's content.
448 | */
449 | public abstract T getAdapter();
450 |
451 | /**
452 | * Sets the adapter that provides the data and the views to represent the data
453 | * in this widget.
454 | *
455 | * @param adapter The adapter to use to create this view's content.
456 | */
457 | public abstract void setAdapter(T adapter);
458 |
459 | /**
460 | * This method is not supported and throws an UnsupportedOperationException when called.
461 | *
462 | * @param child Ignored.
463 | *
464 | * @throws UnsupportedOperationException Every time this method is invoked.
465 | */
466 | @Override
467 | public void addView(View child) {
468 | throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
469 | }
470 |
471 | /**
472 | * This method is not supported and throws an UnsupportedOperationException when called.
473 | *
474 | * @param child Ignored.
475 | * @param index Ignored.
476 | *
477 | * @throws UnsupportedOperationException Every time this method is invoked.
478 | */
479 | @Override
480 | public void addView(View child, int index) {
481 | throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
482 | }
483 |
484 | /**
485 | * This method is not supported and throws an UnsupportedOperationException when called.
486 | *
487 | * @param child Ignored.
488 | * @param params Ignored.
489 | *
490 | * @throws UnsupportedOperationException Every time this method is invoked.
491 | */
492 | @Override
493 | public void addView(View child, LayoutParams params) {
494 | throw new UnsupportedOperationException("addView(View, LayoutParams) "
495 | + "is not supported in AdapterView");
496 | }
497 |
498 | /**
499 | * This method is not supported and throws an UnsupportedOperationException when called.
500 | *
501 | * @param child Ignored.
502 | * @param index Ignored.
503 | * @param params Ignored.
504 | *
505 | * @throws UnsupportedOperationException Every time this method is invoked.
506 | */
507 | @Override
508 | public void addView(View child, int index, LayoutParams params) {
509 | throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
510 | + "is not supported in AdapterView");
511 | }
512 |
513 | /**
514 | * This method is not supported and throws an UnsupportedOperationException when called.
515 | *
516 | * @param child Ignored.
517 | *
518 | * @throws UnsupportedOperationException Every time this method is invoked.
519 | */
520 | @Override
521 | public void removeView(View child) {
522 | throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
523 | }
524 |
525 | /**
526 | * This method is not supported and throws an UnsupportedOperationException when called.
527 | *
528 | * @param index Ignored.
529 | *
530 | * @throws UnsupportedOperationException Every time this method is invoked.
531 | */
532 | @Override
533 | public void removeViewAt(int index) {
534 | throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
535 | }
536 |
537 | /**
538 | * This method is not supported and throws an UnsupportedOperationException when called.
539 | *
540 | * @throws UnsupportedOperationException Every time this method is invoked.
541 | */
542 | @Override
543 | public void removeAllViews() {
544 | throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
545 | }
546 |
547 | @Override
548 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
549 | mLayoutHeight = getHeight();
550 | mLayoutWidth = getWidth();
551 | }
552 |
553 | /**
554 | * Return the position of the currently selected item within the adapter's data set
555 | *
556 | * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
557 | */
558 | @ViewDebug.CapturedViewProperty
559 | public int getSelectedItemPosition() {
560 | return mNextSelectedPosition;
561 | }
562 |
563 | /**
564 | * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
565 | * if nothing is selected.
566 | */
567 | @ViewDebug.CapturedViewProperty
568 | public long getSelectedItemId() {
569 | return mNextSelectedRowId;
570 | }
571 |
572 | /**
573 | * @return The view corresponding to the currently selected item, or null
574 | * if nothing is selected
575 | */
576 | public abstract View getSelectedView();
577 |
578 | /**
579 | * @return The data corresponding to the currently selected item, or
580 | * null if there is nothing selected.
581 | */
582 | public Object getSelectedItem() {
583 | T adapter = getAdapter();
584 | int selection = getSelectedItemPosition();
585 | if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
586 | return adapter.getItem(selection);
587 | } else {
588 | return null;
589 | }
590 | }
591 |
592 | /**
593 | * @return The number of items owned by the Adapter associated with this
594 | * AdapterView. (This is the number of data items, which may be
595 | * larger than the number of visible view.)
596 | */
597 | @ViewDebug.CapturedViewProperty
598 | public int getCount() {
599 | return mItemCount;
600 | }
601 |
602 | /**
603 | * Get the position within the adapter's data set for the view, where view is a an adapter item
604 | * or a descendant of an adapter item.
605 | *
606 | * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
607 | * AdapterView at the time of the call.
608 | * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
609 | * if the view does not correspond to a list item (or it is not currently visible).
610 | */
611 | public int getPositionForView(View view) {
612 | View listItem = view;
613 | try {
614 | View v;
615 | while (!(v = (View) listItem.getParent()).equals(this)) {
616 | listItem = v;
617 | }
618 | } catch (ClassCastException e) {
619 | // We made it up to the window without find this list view
620 | return INVALID_POSITION;
621 | }
622 |
623 | // Search the children for the list item
624 | final int childCount = getChildCount();
625 | for (int i = 0; i < childCount; i++) {
626 | if (getChildAt(i).equals(listItem)) {
627 | return mFirstPosition + i;
628 | }
629 | }
630 |
631 | // Child not found!
632 | return INVALID_POSITION;
633 | }
634 |
635 | /**
636 | * Returns the position within the adapter's data set for the first item
637 | * displayed on screen.
638 | *
639 | * @return The position within the adapter's data set
640 | */
641 | public int getFirstVisiblePosition() {
642 | return mFirstPosition;
643 | }
644 |
645 | /**
646 | * Returns the position within the adapter's data set for the last item
647 | * displayed on screen.
648 | *
649 | * @return The position within the adapter's data set
650 | */
651 | public int getLastVisiblePosition() {
652 | return mFirstPosition + getChildCount() - 1;
653 | }
654 |
655 | /**
656 | * Sets the currently selected item. To support accessibility subclasses that
657 | * override this method must invoke the overriden super method first.
658 | *
659 | * @param position Index (starting at 0) of the data item to be selected.
660 | */
661 | public abstract void setSelection(int position);
662 |
663 | /**
664 | * Sets the view to show if the adapter is empty
665 | */
666 | public void setEmptyView(View emptyView) {
667 | mEmptyView = emptyView;
668 |
669 | final T adapter = getAdapter();
670 | final boolean empty = ((adapter == null) || adapter.isEmpty());
671 | updateEmptyStatus(empty);
672 | }
673 |
674 | /**
675 | * When the current adapter is empty, the AdapterView can display a special view
676 | * call the empty view. The empty view is used to provide feedback to the user
677 | * that no data is available in this AdapterView.
678 | *
679 | * @return The view to show if the adapter is empty.
680 | */
681 | public View getEmptyView() {
682 | return mEmptyView;
683 | }
684 |
685 | /**
686 | * Indicates whether this view is in filter mode. Filter mode can for instance
687 | * be enabled by a user when typing on the keyboard.
688 | *
689 | * @return True if the view is in filter mode, false otherwise.
690 | */
691 | boolean isInFilterMode() {
692 | return false;
693 | }
694 |
695 | @Override
696 | public void setFocusable(boolean focusable) {
697 | final T adapter = getAdapter();
698 | final boolean empty = adapter == null || adapter.getCount() == 0;
699 |
700 | mDesiredFocusableState = focusable;
701 | if (!focusable) {
702 | mDesiredFocusableInTouchModeState = false;
703 | }
704 |
705 | super.setFocusable(focusable && (!empty || isInFilterMode()));
706 | }
707 |
708 | @Override
709 | public void setFocusableInTouchMode(boolean focusable) {
710 | final T adapter = getAdapter();
711 | final boolean empty = adapter == null || adapter.getCount() == 0;
712 |
713 | mDesiredFocusableInTouchModeState = focusable;
714 | if (focusable) {
715 | mDesiredFocusableState = true;
716 | }
717 |
718 | super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
719 | }
720 |
721 | void checkFocus() {
722 | final T adapter = getAdapter();
723 | final boolean empty = adapter == null || adapter.getCount() == 0;
724 | final boolean focusable = !empty || isInFilterMode();
725 | // The order in which we set focusable in touch mode/focusable may matter
726 | // for the client, see View.setFocusableInTouchMode() comments for more
727 | // details
728 | super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
729 | super.setFocusable(focusable && mDesiredFocusableState);
730 | if (mEmptyView != null) {
731 | updateEmptyStatus((adapter == null) || adapter.isEmpty());
732 | }
733 | }
734 |
735 | /**
736 | * Update the status of the list based on the empty parameter. If empty is true and
737 | * we have an empty view, display it. In all the other cases, make sure that the listview
738 | * is VISIBLE and that the empty view is GONE (if it's not null).
739 | */
740 | private void updateEmptyStatus(boolean empty) {
741 | if (isInFilterMode()) {
742 | empty = false;
743 | }
744 |
745 | if (empty) {
746 | if (mEmptyView != null) {
747 | mEmptyView.setVisibility(View.VISIBLE);
748 | setVisibility(View.GONE);
749 | } else {
750 | // If the caller just removed our empty view, make sure the list view is visible
751 | setVisibility(View.VISIBLE);
752 | }
753 |
754 | // We are now GONE, so pending layouts will not be dispatched.
755 | // Force one here to make sure that the state of the list matches
756 | // the state of the adapter.
757 | if (mDataChanged) {
758 | this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
759 | }
760 | } else {
761 | if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
762 | setVisibility(View.VISIBLE);
763 | }
764 | }
765 |
766 | /**
767 | * Gets the data associated with the specified position in the list.
768 | *
769 | * @param position Which data to get
770 | * @return The data associated with the specified position in the list
771 | */
772 | public Object getItemAtPosition(int position) {
773 | T adapter = getAdapter();
774 | return (adapter == null || position < 0) ? null : adapter.getItem(position);
775 | }
776 |
777 | public long getItemIdAtPosition(int position) {
778 | T adapter = getAdapter();
779 | return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
780 | }
781 |
782 | @Override
783 | public void setOnClickListener(OnClickListener l) {
784 | throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
785 | + "You probably want setOnItemClickListener instead");
786 | }
787 |
788 | /**
789 | * Override to prevent freezing of any views created by the adapter.
790 | */
791 | @Override
792 | protected void dispatchSaveInstanceState(SparseArray container) {
793 | dispatchFreezeSelfOnly(container);
794 | }
795 |
796 | /**
797 | * Override to prevent thawing of any views created by the adapter.
798 | */
799 | @Override
800 | protected void dispatchRestoreInstanceState(SparseArray container) {
801 | dispatchThawSelfOnly(container);
802 | }
803 |
804 | class AdapterDataSetObserver extends DataSetObserver {
805 |
806 | private Parcelable mInstanceState = null;
807 |
808 | @Override
809 | public void onChanged() {
810 | mDataChanged = true;
811 | mOldItemCount = mItemCount;
812 | mItemCount = getAdapter().getCount();
813 |
814 | // Detect the case where a cursor that was previously invalidated has
815 | // been repopulated with new data.
816 | if (TwoWayAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
817 | && mOldItemCount == 0 && mItemCount > 0) {
818 | TwoWayAdapterView.this.onRestoreInstanceState(mInstanceState);
819 | mInstanceState = null;
820 | } else {
821 | rememberSyncState();
822 | }
823 | checkFocus();
824 | requestLayout();
825 | }
826 |
827 | @Override
828 | public void onInvalidated() {
829 | mDataChanged = true;
830 |
831 | if (TwoWayAdapterView.this.getAdapter().hasStableIds()) {
832 | // Remember the current state for the case where our hosting activity is being
833 | // stopped and later restarted
834 | mInstanceState = TwoWayAdapterView.this.onSaveInstanceState();
835 | }
836 |
837 | // Data is invalid so we should reset our state
838 | mOldItemCount = mItemCount;
839 | mItemCount = 0;
840 | mSelectedPosition = INVALID_POSITION;
841 | mSelectedRowId = INVALID_ROW_ID;
842 | mNextSelectedPosition = INVALID_POSITION;
843 | mNextSelectedRowId = INVALID_ROW_ID;
844 | mNeedSync = false;
845 | checkSelectionChanged();
846 |
847 | checkFocus();
848 | requestLayout();
849 | }
850 |
851 | public void clearSavedState() {
852 | mInstanceState = null;
853 | }
854 | }
855 |
856 | private class SelectionNotifier extends Handler implements Runnable {
857 | public void run() {
858 | if (mDataChanged) {
859 | // Data has changed between when this SelectionNotifier
860 | // was posted and now. We need to wait until the AdapterView
861 | // has been synched to the new data.
862 | post(this);
863 | } else {
864 | fireOnSelected();
865 | }
866 | }
867 | }
868 |
869 | void selectionChanged() {
870 | if (mOnItemSelectedListener != null) {
871 | if (mInLayout || mBlockLayoutRequests) {
872 | // If we are in a layout traversal, defer notification
873 | // by posting. This ensures that the view tree is
874 | // in a consistent state and is able to accomodate
875 | // new layout or invalidate requests.
876 | if (mSelectionNotifier == null) {
877 | mSelectionNotifier = new SelectionNotifier();
878 | }
879 | mSelectionNotifier.post(mSelectionNotifier);
880 | } else {
881 | fireOnSelected();
882 | }
883 | }
884 |
885 | // we fire selection events here not in View
886 | /* taken out for backward compatibility
887 | if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
888 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
889 | }*/
890 | }
891 |
892 | private void fireOnSelected() {
893 | if (mOnItemSelectedListener == null)
894 | return;
895 |
896 | int selection = this.getSelectedItemPosition();
897 | if (selection >= 0) {
898 | View v = getSelectedView();
899 | mOnItemSelectedListener.onItemSelected(this, v, selection,
900 | getAdapter().getItemId(selection));
901 | } else {
902 | mOnItemSelectedListener.onNothingSelected(this);
903 | }
904 | }
905 |
906 | /* taken out for backward compatibility
907 | @Override
908 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
909 | boolean populated = false;
910 | // This is an exceptional case which occurs when a window gets the
911 | // focus and sends a focus event via its focused child to announce
912 | // current focus/selection. AdapterView fires selection but not focus
913 | // events so we change the event type here.
914 | if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
915 | event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
916 | }
917 |
918 | // we send selection events only from AdapterView to avoid
919 | // generation of such event for each child
920 | View selectedView = getSelectedView();
921 | if (selectedView != null) {
922 | populated = selectedView.dispatchPopulateAccessibilityEvent(event);
923 | }
924 |
925 | if (!populated) {
926 | if (selectedView != null) {
927 | event.setEnabled(selectedView.isEnabled());
928 | }
929 | event.setItemCount(getCount());
930 | event.setCurrentItemIndex(getSelectedItemPosition());
931 | }
932 |
933 | return populated;
934 | }*/
935 |
936 | @Override
937 | protected boolean canAnimate() {
938 | return super.canAnimate() && mItemCount > 0;
939 | }
940 |
941 | void handleDataChanged() {
942 | final int count = mItemCount;
943 | boolean found = false;
944 |
945 | if (count > 0) {
946 |
947 | int newPos;
948 |
949 | // Find the row we are supposed to sync to
950 | if (mNeedSync) {
951 | // Update this first, since setNextSelectedPositionInt inspects
952 | // it
953 | mNeedSync = false;
954 |
955 | // See if we can find a position in the new data with the same
956 | // id as the old selection
957 | newPos = findSyncPosition();
958 | if (newPos >= 0) {
959 | // Verify that new selection is selectable
960 | int selectablePos = lookForSelectablePosition(newPos, true);
961 | if (selectablePos == newPos) {
962 | // Same row id is selected
963 | setNextSelectedPositionInt(newPos);
964 | found = true;
965 | }
966 | }
967 | }
968 | if (!found) {
969 | // Try to use the same position if we can't find matching data
970 | newPos = getSelectedItemPosition();
971 |
972 | // Pin position to the available range
973 | if (newPos >= count) {
974 | newPos = count - 1;
975 | }
976 | if (newPos < 0) {
977 | newPos = 0;
978 | }
979 |
980 | // Make sure we select something selectable -- first look down
981 | int selectablePos = lookForSelectablePosition(newPos, true);
982 | if (selectablePos < 0) {
983 | // Looking down didn't work -- try looking up
984 | selectablePos = lookForSelectablePosition(newPos, false);
985 | }
986 | if (selectablePos >= 0) {
987 | setNextSelectedPositionInt(selectablePos);
988 | checkSelectionChanged();
989 | found = true;
990 | }
991 | }
992 | }
993 | if (!found) {
994 | // Nothing is selected
995 | mSelectedPosition = INVALID_POSITION;
996 | mSelectedRowId = INVALID_ROW_ID;
997 | mNextSelectedPosition = INVALID_POSITION;
998 | mNextSelectedRowId = INVALID_ROW_ID;
999 | mNeedSync = false;
1000 | checkSelectionChanged();
1001 | }
1002 | }
1003 |
1004 | void checkSelectionChanged() {
1005 | if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1006 | selectionChanged();
1007 | mOldSelectedPosition = mSelectedPosition;
1008 | mOldSelectedRowId = mSelectedRowId;
1009 | }
1010 | }
1011 |
1012 | /**
1013 | * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1014 | * and then alternates between moving up and moving down until 1) we find the right position, or
1015 | * 2) we run out of time, or 3) we have looked at every position
1016 | *
1017 | * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1018 | * be found
1019 | */
1020 | int findSyncPosition() {
1021 | int count = mItemCount;
1022 |
1023 | if (count == 0) {
1024 | return INVALID_POSITION;
1025 | }
1026 |
1027 | long idToMatch = mSyncRowId;
1028 | int seed = mSyncPosition;
1029 |
1030 | // If there isn't a selection don't hunt for it
1031 | if (idToMatch == INVALID_ROW_ID) {
1032 | return INVALID_POSITION;
1033 | }
1034 |
1035 | // Pin seed to reasonable values
1036 | seed = Math.max(0, seed);
1037 | seed = Math.min(count - 1, seed);
1038 |
1039 | long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1040 |
1041 | long rowId;
1042 |
1043 | // first position scanned so far
1044 | int first = seed;
1045 |
1046 | // last position scanned so far
1047 | int last = seed;
1048 |
1049 | // True if we should move down on the next iteration
1050 | boolean next = false;
1051 |
1052 | // True when we have looked at the first item in the data
1053 | boolean hitFirst;
1054 |
1055 | // True when we have looked at the last item in the data
1056 | boolean hitLast;
1057 |
1058 | // Get the item ID locally (instead of getItemIdAtPosition), so
1059 | // we need the adapter
1060 | T adapter = getAdapter();
1061 | if (adapter == null) {
1062 | return INVALID_POSITION;
1063 | }
1064 |
1065 | while (SystemClock.uptimeMillis() <= endTime) {
1066 | rowId = adapter.getItemId(seed);
1067 | if (rowId == idToMatch) {
1068 | // Found it!
1069 | return seed;
1070 | }
1071 |
1072 | hitLast = last == count - 1;
1073 | hitFirst = first == 0;
1074 |
1075 | if (hitLast && hitFirst) {
1076 | // Looked at everything
1077 | break;
1078 | }
1079 |
1080 | if (hitFirst || (next && !hitLast)) {
1081 | // Either we hit the top, or we are trying to move down
1082 | last++;
1083 | seed = last;
1084 | // Try going up next time
1085 | next = false;
1086 | } else if (hitLast) {
1087 | // Either we hit the bottom, or we are trying to move up
1088 | first--;
1089 | seed = first;
1090 | // Try going down next time
1091 | next = true;
1092 | }
1093 |
1094 | }
1095 |
1096 | return INVALID_POSITION;
1097 | }
1098 |
1099 | /**
1100 | * Find a position that can be selected (i.e., is not a separator).
1101 | *
1102 | * @param position The starting position to look at.
1103 | * @param lookDown Whether to look down for other positions.
1104 | * @return The next selectable position starting at position and then searching either up or
1105 | * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1106 | */
1107 | int lookForSelectablePosition(int position, boolean lookDown) {
1108 | return position;
1109 | }
1110 |
1111 | /**
1112 | * Utility to keep mSelectedPosition and mSelectedRowId in sync
1113 | * @param position Our current position
1114 | */
1115 | void setSelectedPositionInt(int position) {
1116 | mSelectedPosition = position;
1117 | mSelectedRowId = getItemIdAtPosition(position);
1118 | }
1119 |
1120 | /**
1121 | * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1122 | * @param position Intended value for mSelectedPosition the next time we go
1123 | * through layout
1124 | */
1125 | void setNextSelectedPositionInt(int position) {
1126 | mNextSelectedPosition = position;
1127 | mNextSelectedRowId = getItemIdAtPosition(position);
1128 | // If we are trying to sync to the selection, update that too
1129 | if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1130 | mSyncPosition = position;
1131 | mSyncRowId = mNextSelectedRowId;
1132 | }
1133 | }
1134 |
1135 | /**
1136 | * Remember enough information to restore the screen state when the data has
1137 | * changed.
1138 | *
1139 | */
1140 | void rememberSyncState() {
1141 | if (getChildCount() > 0) {
1142 | mNeedSync = true;
1143 | if (mIsVertical) {
1144 | mSyncSize = mLayoutHeight;
1145 | } else {
1146 | mSyncSize = mLayoutWidth;
1147 | }
1148 | if (mSelectedPosition >= 0) {
1149 | // Sync the selection state
1150 | View v = getChildAt(mSelectedPosition - mFirstPosition);
1151 | mSyncRowId = mNextSelectedRowId;
1152 | mSyncPosition = mNextSelectedPosition;
1153 | if (v != null) {
1154 | if (mIsVertical) {
1155 | mSpecificTop = v.getTop();
1156 | } else {
1157 | mSpecificTop = v.getLeft();
1158 | }
1159 | }
1160 | mSyncMode = SYNC_SELECTED_POSITION;
1161 | } else {
1162 | // Sync the based on the offset of the first view
1163 | View v = getChildAt(0);
1164 | T adapter = getAdapter();
1165 | if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1166 | mSyncRowId = adapter.getItemId(mFirstPosition);
1167 | } else {
1168 | mSyncRowId = NO_ID;
1169 | }
1170 | mSyncPosition = mFirstPosition;
1171 | if (v != null) {
1172 | if (mIsVertical) {
1173 | mSpecificTop = v.getTop();
1174 | } else {
1175 | mSpecificTop = v.getLeft();
1176 | }
1177 | }
1178 | mSyncMode = SYNC_FIRST_POSITION;
1179 | }
1180 | }
1181 | }
1182 |
1183 | /**
1184 | * Offset the vertical location of all children of this view by the specified number of pixels.
1185 | *
1186 | * @param offset the number of pixels to offset
1187 | *
1188 | */
1189 | public void offsetChildrenTopAndBottom(int offset) {
1190 | final int count = getChildCount();
1191 |
1192 | for (int i = 0; i < count; i++) {
1193 | final View v = getChildAt(i);
1194 | v.offsetTopAndBottom(offset);
1195 | }
1196 | }
1197 |
1198 | /**
1199 | * Offset the horizontal location of all children of this view by the specified number of pixels.
1200 | *
1201 | * @param offset the number of pixels to offset
1202 | *
1203 | */
1204 | public void offsetChildrenLeftAndRight(int offset) {
1205 | final int count = getChildCount();
1206 |
1207 | for (int i = 0; i < count; i++) {
1208 | final View v = getChildAt(i);
1209 | v.offsetLeftAndRight(offset);
1210 | }
1211 | }
1212 |
1213 | protected void setIsVertical(boolean vertical) {
1214 | mIsVertical = vertical;
1215 | }
1216 |
1217 | protected boolean isVertical() {
1218 | return mIsVertical;
1219 | }
1220 |
1221 |
1222 | }
1223 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/anim/popup_hide.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/anim/popup_show.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/bg_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/bg_translucent_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/bg_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/ic_action_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/customimagepicker/src/main/res/drawable/ic_action_image.png
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/ic_local_see_black_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/customimagepicker/src/main/res/drawable/ic_local_see_black_48dp.png
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/icon_image_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
22 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/drawable/list_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/layout/bottom_sheet_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
28 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
51 |
52 |
54 |
55 |
57 |
58 |
59 |
60 |
63 |
64 |
66 |
67 |
68 |
70 |
71 |
73 |
74 |
82 |
83 |
84 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Custom Image Picker
3 |
4 |
--------------------------------------------------------------------------------
/customimagepicker/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 02 22:13:43 CST 2019
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-5.4.1-all.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 |
--------------------------------------------------------------------------------
/screenshots/Custom-Recent-Images-Cover.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/screenshots/Custom-Recent-Images-Cover.gif
--------------------------------------------------------------------------------
/screenshots/Recent Images 1 Row.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/screenshots/Recent Images 1 Row.png
--------------------------------------------------------------------------------
/screenshots/Recent Images 2 Rows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rayliverified/CustomImagePicker/d293dceece6df0e53d0bb1a67f62bdc2bf387a8a/screenshots/Recent Images 2 Rows.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':customimagepicker'
--------------------------------------------------------------------------------