├── .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 | [![GitHub release](https://img.shields.io/github/release/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker/releases) [![GitHub Release Date](https://img.shields.io/github/release-date/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) [![Libraries.io for GitHub](https://img.shields.io/librariesio/github/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) [![GitHub issues](https://img.shields.io/github/issues/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) [![API](https://img.shields.io/badge/API-15%2B-blue.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) [![GitHub top language](https://img.shields.io/github/languages/top/searchy2/CustomImagePicker.svg?style=flat-square)](https://github.com/searchy2/CustomImagePicker) 2 | # Custom Image Picker 3 | 4 | ![Screenshots](screenshots/Custom-Recent-Images-Cover.gif) 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 | ![Screenshots](screenshots/Recent%20Images%201%20Row.png) 10 | 11 | ### Double Row 12 | ![Screenshots](screenshots/Recent%20Images%202%20Rows.png) 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 | Ray Li Email 123 | 124 | 125 | Ray Li Website 127 | 128 | 129 | Ray Li Twitter 131 | 132 | 133 | Ray Li LinkedIn 135 | 136 | 137 | Ray Li UpLabs 139 | 140 | 141 | Ray Li GitHub 143 | 144 | 145 | # Apps Using This Library 146 | 147 | Want to be here? You can include yourself by making a `pull request`. 148 | 149 | 150 | 151 | 152 | 153 | 154 |
Crowdfunding Community
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' --------------------------------------------------------------------------------