├── .gitignore
├── .idea
├── .name
├── codeStyles
│ └── codeStyleConfig.xml
└── vcs.xml
├── Jenkinsfile
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── pereira
│ │ └── agnaldo
│ │ └── previewimgcol
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── pereira
│ │ │ └── agnaldo
│ │ │ └── previewimgcol
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ ├── landscape_01.jpg
│ │ ├── landscape_02.jpg
│ │ ├── landscape_03.jpg
│ │ ├── landscape_04.jpg
│ │ ├── landscape_05.jpg
│ │ ├── landscape_06.jpg
│ │ ├── landscape_07.jpg
│ │ └── landscape_08.jpg
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── pereira
│ └── agnaldo
│ └── previewimgcol
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── previewimgcol
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── pereira
│ │ └── agnaldo
│ │ └── previewimgcol
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── pereira
│ │ │ └── agnaldo
│ │ │ └── previewimgcol
│ │ │ ├── Blur.kt
│ │ │ ├── GlideListener.kt
│ │ │ ├── ImageCollectionView.kt
│ │ │ └── PreviewImage.kt
│ └── res
│ │ ├── drawable
│ │ └── blur.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── boolean.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── integer.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── pereira
│ └── agnaldo
│ └── previewimgcol
│ └── ExampleUnitTest.kt
├── screenshot
├── sample.gif
└── screenshot.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | .idea/codeStyles/Project.xml
16 | .idea/gradle.xml
17 | .idea/jarRepositories.xml
18 | .idea/runConfigurations.xml
19 | .idea/
20 | .idea/*
21 | .idea/*.*
22 | .idea/compiler.xml
23 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Preview Images Collections
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | stage 'Checkout'
2 | node('slave') {
3 | deleteDir()
4 | checkout scm
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Preview Image Collection
2 |
3 | ## Introduction
4 | *Preview Image Collection* is a library to draw a collage with a number of images like facebook preview album
5 |
6 | 
7 | 
8 |
9 | ## Install
10 |
11 | **Step 1**. Add the JitPack repository to your build file
12 | Add it in your root build.gradle at the end of repositories:
13 | ```
14 | allprojects {
15 | repositories {
16 | ...
17 | maven { url 'https://jitpack.io' }
18 | }
19 | }
20 | ```
21 |
22 | If you are using newest versions of gradle this config should be done on settings.gradle
23 | ```
24 | dependencyResolutionManagement {
25 | repositories {
26 | ...
27 | maven { url 'https://jitpack.io' }
28 | }
29 | }
30 | ```
31 |
32 | **Step 2.** Add the dependency
33 | ```
34 | dependencies {
35 | implementation 'com.github.AgnaldoNP:PreviewImageCollection:2.3'
36 | }
37 | ```
38 | [](https://jitpack.io/#AgnaldoNP/PreviewImageCollection)
39 |
40 | ## Usage
41 |
42 | Sample of usage
43 | ```xml
44 |
56 |
57 | ```
58 |
59 | ### Options
60 |
61 | | Property | Value type | Default |
62 | |-----------------------------|-------------------|-------------|
63 | | backgroundColor | color | #FFFFFF |
64 | | baseRowHeight | dimension | 150dp |
65 | | imageMargin | dimension | 1dp |
66 | | pinchToZoom | boolean | true |
67 | | showExternalBorderMargins | boolean | true |
68 | | maxImagePerRow | integer | 3 |
69 | | maxRows | integer | 3 |
70 | | previewImageScaleType | enum | center_crop |
71 | | previewDistributeEvenly | boolean | false |
72 | | previewCornerRadius | dimension | 0dp |
73 |
74 | > On Version 2.0 `imageScaleType` was changed to `previewImageScaleType`
75 |
76 | ### Programmatically
77 | ```kotlin
78 | var collectionView = findViewById(R.id.imageCollectionView)
79 |
80 | collectionView.maxRows = ImageCollectionView.NO_ROW_LIMITS
81 | collectionView.maxRows = 10
82 |
83 | collectionView.maxImagePerRow =3
84 |
85 | collectionView.imageMargin = 10
86 |
87 | collectionView.baseImageHeight = 150
88 |
89 | collectionView.mBackgroundColor = Color.WHITE
90 |
91 | collectionView.pinchToZoom = true
92 |
93 | collectionView.showExternalBorderMargins = true
94 |
95 | val bitmap = ...
96 | collectionView.addImage(bitmap)
97 |
98 | val bitmap2 = ...
99 | collectionView.addImage(bitmap2, object : ImageCollectionView.OnImageClickListener {
100 | override fun onClick(bitmap: Bitmap, imageView: ImageView) {
101 | Toast.makeText(imageView.context, "Test Click on image ...", Toast.LENGTH_LONG).show()
102 | }
103 | })
104 | // or simply
105 | collectionView.addImage(bitmap2, { bitmap: Bitmap?, imageView: ImageView? ->
106 | Toast.makeText(context, "Test Click on image ...", Toast.LENGTH_LONG).show()
107 | })
108 |
109 | collectionView.setOnMoreClicked(object : ImageCollectionView.OnMoreClickListener {
110 | override fun onMoreClicked(bitmaps: List) {
111 | Toast.makeText(collectionView.context, "on more clicked ", Toast.LENGTH_LONG).show()
112 | }
113 | })
114 | // or simply
115 | collectionView.setOnMoreClicked { bitmaps ->
116 | Toast.makeText(collectionView.context, "on more clicked ", Toast.LENGTH_LONG).show()
117 | }
118 | ```
119 |
120 | ```java
121 | ImageCollectionView collectionView = (ImageCollectionView) findViewById(R.id.imageCollectionView);
122 |
123 | Bitmap bitmap = ...;
124 | imageCollectionView.addImage(bitmap);
125 |
126 | Bitmap bitmap2 = ...;
127 | imageCollectionView.addImage(bitmap, (bmp, imageView) -> {
128 | Toast.makeText(context, "Test Click image 08", Toast.LENGTH_LONG).show();
129 | });
130 |
131 | imageCollectionView.setOnMoreClicked(bitmaps -> {
132 | Toast.makeText(context, "OnMoreClicked", Toast.LENGTH_LONG).show();
133 | });
134 | ```
135 |
136 |
137 | ## Contributions and Support
138 |
139 | This project made use of [Zoomy](https://github.com/imablanco/Zoomy) by [Álvaro Blanco](https://github.com/imablanco) to enable "pinch to zoom" functionality.
140 |
141 | Contributions are welcome. Create a new pull request in order to submit your fixes and they shall be merged after moderation. In case of any issues, bugs or any suggestions, either create a new issue or post comments in already active relevant issues
142 |
143 | ## Please consider supporting me
144 | ETH Address
145 | * [0xe61d524595D3bCC92DaD9bCC965B87394F9069d8](https://etherscan.io/address/0xe61d524595D3bCC92DaD9bCC965B87394F9069d8)
146 |
147 | --
148 |
149 | ETH / SHIB / BNB / SLP / IOTX / DODGE (BEP20 or ERC20)
150 | * 0x0d620a663692ac8797c289c5715228c5f19f9f7a
151 |
152 | --
153 |
154 | DOGE
155 | * (Main net) DQXW3DH2Jwe3zuCAcNAL7xLr1cSx7b7Pmt
156 | * (BEP20) 0x0d620a663692ac8797c289c5715228c5f19f9f7a
157 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 31
9 | buildToolsVersion "29.0.2"
10 | defaultConfig {
11 | applicationId "pereira.agnaldo.previewimgcol"
12 | minSdkVersion 21
13 | targetSdkVersion 31
14 | versionCode 7
15 | versionName "1.6"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 |
18 | multiDexEnabled true
19 |
20 | javaCompileOptions {
21 | annotationProcessorOptions {
22 | arguments = [
23 | "androidManifestFile": "$projectDir/src/main/AndroidManifest.xml".toString()
24 | ]
25 | }
26 | }
27 | }
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 |
35 | compileOptions {
36 | encoding "UTF-8"
37 | sourceCompatibility JavaVersion.VERSION_1_8
38 | targetCompatibility JavaVersion.VERSION_1_8
39 | }
40 |
41 | android.compileOptions.sourceCompatibility 1.8
42 | android.compileOptions.targetCompatibility 1.8
43 |
44 | packagingOptions {
45 | exclude 'META-INF/NOTICE.txt'
46 | exclude 'META-INF/LICENSE.txt'
47 | exclude 'META-INF/LICENSE'
48 | exclude 'META-INF/LICENSE-FIREBASE.txt'
49 | exclude 'protobuf.meta'
50 | }
51 | }
52 |
53 | dependencies {
54 | implementation project(":previewimgcol")
55 |
56 | implementation fileTree(dir: 'libs', include: ['*.jar'])
57 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10"
58 | implementation 'androidx.appcompat:appcompat:1.4.0'
59 | implementation 'androidx.core:core-ktx:1.7.0'
60 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
61 | testImplementation 'junit:junit:4.13.2'
62 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
64 |
65 | implementation 'com.github.ivan200:PhotoBarcode:1.1.1'
66 | }
67 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/pereira/agnaldo/previewimgcol/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("pereira.agnaldo.previewimgcol", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/pereira/agnaldo/previewimgcol/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.graphics.Color
7 | import android.os.Bundle
8 | import android.widget.* // ktlint-disable no-wildcard-imports
9 | import android.widget.SeekBar.OnSeekBarChangeListener
10 | import androidx.appcompat.app.AppCompatActivity
11 | import com.ivan200.photobarcodelib.PhotoBarcodeScannerBuilder
12 | import kotlinx.android.synthetic.main.activity_main.view.*
13 |
14 | class MainActivity : AppCompatActivity() {
15 |
16 | private lateinit var collectionView: ImageCollectionView
17 | private lateinit var backgroundColor: SeekBar
18 | private lateinit var baseRowHeight: SeekBar
19 | private lateinit var imageMargin: SeekBar
20 | private lateinit var maxImagePerRow: SeekBar
21 | private lateinit var maxRows: SeekBar
22 | private lateinit var cornerRadius: SeekBar
23 | private lateinit var pinchToZoom: CheckBox
24 | private lateinit var showExternalBoards: CheckBox
25 | private lateinit var distributeEvenly: CheckBox
26 |
27 | private lateinit var addPhoto: Button
28 | private lateinit var clearPhotos: Button
29 |
30 | val Int.dp: Int
31 | get() = (this / Resources.getSystem().displayMetrics.density).toInt()
32 | val Int.px: Int
33 | get() = (this * Resources.getSystem().displayMetrics.density).toInt()
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(R.layout.activity_main)
38 |
39 | collectionView = findViewById(R.id.imageCollectionView)
40 |
41 | backgroundColor = findViewById(R.id.color)
42 | baseRowHeight = findViewById(R.id.baseRowHeight)
43 | imageMargin = findViewById(R.id.imageMargin)
44 | maxImagePerRow = findViewById(R.id.maxImagePerRow)
45 | maxRows = findViewById(R.id.maxRows)
46 | cornerRadius = findViewById(R.id.cornerRadius)
47 | pinchToZoom = findViewById(R.id.pinchToZoom)
48 | showExternalBoards = findViewById(R.id.showExternalBorderMargins)
49 | distributeEvenly = findViewById(R.id.distributeEvenly)
50 | addPhoto = findViewById(R.id.add_photo)
51 | clearPhotos = findViewById(R.id.clear_photos)
52 |
53 | backgroundColor.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
54 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
55 | collectionView.mBackgroundColor = if (progress == 0) Color.TRANSPARENT else
56 | Color.HSVToColor(floatArrayOf(progress.toFloat(), 100f, 100f))
57 | }
58 |
59 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
60 | }
61 |
62 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
63 | }
64 | })
65 |
66 | baseRowHeight.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
67 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
68 | collectionView.baseImageHeight = if (progress <= 2) 120.px else progress.px
69 | }
70 |
71 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
72 | }
73 |
74 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
75 | }
76 | })
77 |
78 | imageMargin.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
79 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
80 | collectionView.imageMargin = progress.px
81 | }
82 |
83 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
84 | }
85 |
86 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
87 | }
88 | })
89 |
90 | maxImagePerRow.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
91 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
92 | collectionView.maxImagePerRow = if (progress == 0) 3 else progress
93 | }
94 |
95 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
96 | }
97 |
98 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
99 | }
100 | })
101 |
102 | maxRows.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
103 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
104 | collectionView.maxRows =
105 | if (progress == 0) ImageCollectionView.NO_ROW_LIMITS else progress
106 | }
107 |
108 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
109 | }
110 |
111 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
112 | }
113 | })
114 |
115 | cornerRadius.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
116 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
117 | collectionView.previewCornerRadius =
118 | (collectionView.width * (progress / 100F)).toInt()
119 | }
120 |
121 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
122 | }
123 |
124 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
125 | }
126 | })
127 |
128 | pinchToZoom.setOnCheckedChangeListener { buttonView, isChecked ->
129 | collectionView.pinchToZoom = isChecked
130 | }
131 |
132 | showExternalBoards.setOnCheckedChangeListener { buttonView, isChecked ->
133 | collectionView.showExternalBorderMargins = isChecked
134 | }
135 |
136 | distributeEvenly.setOnCheckedChangeListener { buttonView, isChecked ->
137 | collectionView.previewDistributeEvenly = isChecked
138 | }
139 |
140 | addPhoto.setOnClickListener {
141 | PhotoBarcodeScannerBuilder()
142 | .withActivity(this)
143 | .withTakingPictureMode()
144 | .withAutoFocus(true)
145 | .withFocusOnTap(true)
146 | .withCameraLockRotate(false)
147 | .withThumbnails(false)
148 | .withCameraTryFixOrientation(true)
149 | .withImageLargerSide(1200)
150 | .withPictureListener { file ->
151 | if (file.exists()) {
152 | val bitmap = BitmapFactory.decodeFile(file.absolutePath)
153 | collectionView.addImage(bitmap)
154 | file.delete()
155 | }
156 | }.build().start()
157 | }
158 |
159 | clearPhotos.setOnClickListener {
160 | collectionView.clearImages()
161 | }
162 |
163 | collectionView.addImage(
164 | BitmapFactory.decodeResource(resources, R.drawable.landscape_08),
165 | object : ImageCollectionView.OnImageClickListener {
166 | override fun onClick(bitmap: Bitmap, imageView: ImageView) {
167 | Toast.makeText(imageView.context, "Test Click image 08", Toast.LENGTH_LONG)
168 | .show()
169 | }
170 | }
171 | )
172 | collectionView.addImage(
173 | BitmapFactory.decodeResource(resources, R.drawable.landscape_01),
174 | object : ImageCollectionView.OnImageLongClickListener {
175 | override fun onLongClick(bitmap: Bitmap, imageView: ImageView) {
176 | Toast.makeText(imageView.context, "Long Click", Toast.LENGTH_LONG)
177 | .show()
178 | }
179 | }
180 | )
181 | collectionView.addImageK(
182 | R.drawable.landscape_02,
183 | { bitmap: Bitmap?, imageView: ImageView? ->
184 | Toast.makeText(this, "landscape_02", Toast.LENGTH_LONG).show()
185 | }
186 | )
187 |
188 | collectionView.addImage(BitmapFactory.decodeResource(resources, R.drawable.landscape_05))
189 |
190 | collectionView.addImage(BitmapFactory.decodeResource(resources, R.drawable.landscape_03))
191 | collectionView.addImage(BitmapFactory.decodeResource(resources, R.drawable.landscape_04))
192 | collectionView.addImage(BitmapFactory.decodeResource(resources, R.drawable.landscape_06))
193 | collectionView.addImage(BitmapFactory.decodeResource(resources, R.drawable.landscape_07))
194 | collectionView.addImage(
195 | "https://d1dwhi9yny5dep.cloudfront.net/cm_live/c4d403788761d42233b6675.desktop-gallery-large.jpg",
196 | R.drawable.blur
197 | )
198 | collectionView.addImage(
199 | "https://d1dwhi9yny5dep.cloudfront.net/cm_live/80f4beb96361d422359321c.desktop-gallery-large.jpg",
200 | R.drawable.blur
201 | )
202 |
203 | collectionView.setOnMoreClicked(object : ImageCollectionView.OnMoreClickListener {
204 | override fun onMoreClicked(bitmaps: List) {
205 | Toast.makeText(collectionView.context, "oi oi oi oi ", Toast.LENGTH_LONG)
206 | .show()
207 | }
208 | })
209 |
210 | collectionView.setOnMoreClicked { bitmaps -> }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_01.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_02.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_03.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_04.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_05.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_06.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_07.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/landscape_08.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/drawable/landscape_08.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
25 |
26 |
32 |
33 |
40 |
41 |
42 |
49 |
50 |
55 |
56 |
63 |
64 |
65 |
72 |
73 |
78 |
79 |
86 |
87 |
88 |
96 |
97 |
102 |
103 |
110 |
111 |
112 |
120 |
121 |
126 |
127 |
134 |
135 |
136 |
144 |
145 |
150 |
151 |
158 |
159 |
160 |
167 |
168 |
175 |
176 |
183 |
184 |
189 |
190 |
195 |
196 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Preview Images Collections
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/pereira/agnaldo/previewimgcol/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | maven { url "https://jitpack.io" }
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:4.1.3'
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | maven { url "https://jitpack.io" }
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Mar 04 10:54:59 BRT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
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 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/previewimgcol/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/previewimgcol/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | android {
5 | compileSdkVersion 31
6 | buildToolsVersion "29.0.3"
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 31
11 | versionCode 11
12 | versionName "2.3"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles 'consumer-rules.pro'
16 |
17 | renderscriptTargetApi 19
18 | renderscriptSupportModeEnabled true
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.jar'])
32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10"
33 | implementation 'androidx.appcompat:appcompat:1.4.0'
34 | implementation 'androidx.core:core-ktx:1.7.0'
35 | testImplementation 'junit:junit:4.13.2'
36 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
38 |
39 | implementation 'io.github.imablanco:zoomy:1.0.0'
40 | implementation ("com.github.bumptech.glide:glide:4.12.0") {
41 | exclude group: "com.android.support"
42 | }
43 | implementation "com.android.support:support-fragment:28.0.0"
44 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
45 | }
46 |
--------------------------------------------------------------------------------
/previewimgcol/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/previewimgcol/consumer-rules.pro
--------------------------------------------------------------------------------
/previewimgcol/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/previewimgcol/src/androidTest/java/pereira/agnaldo/previewimgcol/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("pereira.agnaldo.previewimgcol.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/java/pereira/agnaldo/previewimgcol/Blur.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import android.graphics.Bitmap
4 | import kotlin.math.abs
5 | import kotlin.math.roundToInt
6 |
7 | fun Bitmap.blur(
8 | newWidth: Int = (this.width / 2.0).toInt(),
9 | radius: Int = 10
10 | ): Bitmap {
11 |
12 | val scale = newWidth.toFloat() / this.width
13 |
14 | var sentBitmap = this
15 | val width = (sentBitmap.width * scale).roundToInt()
16 | val height = (sentBitmap.height * scale).roundToInt()
17 | sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false)
18 | val bitmap = sentBitmap.copy(sentBitmap.config, true)
19 | if (radius < 1) {
20 | return this
21 | }
22 | val w = bitmap.width
23 | val h = bitmap.height
24 | val pix = IntArray(w * h)
25 | bitmap.getPixels(pix, 0, w, 0, 0, w, h)
26 | val wm = w - 1
27 | val hm = h - 1
28 | val wh = w * h
29 | val div = radius + radius + 1
30 | val r = IntArray(wh)
31 | val g = IntArray(wh)
32 | val b = IntArray(wh)
33 | var rsum: Int
34 | var gsum: Int
35 | var bsum: Int
36 | var x: Int
37 | var y: Int
38 | var i: Int
39 | var p: Int
40 | var yp: Int
41 | var yi: Int
42 | var yw: Int
43 | val vmin = IntArray(w.coerceAtLeast(h))
44 | var divsum = div + 1 shr 1
45 | divsum *= divsum
46 | val dv = IntArray(256 * divsum)
47 | i = 0
48 | while (i < 256 * divsum) {
49 | dv[i] = i / divsum
50 | i++
51 | }
52 | yi = 0
53 | yw = yi
54 | val stack = Array(div) {
55 | IntArray(
56 | 3
57 | )
58 | }
59 | var stackpointer: Int
60 | var stackstart: Int
61 | var sir: IntArray
62 | var rbs: Int
63 | val r1 = radius + 1
64 | var routsum: Int
65 | var goutsum: Int
66 | var boutsum: Int
67 | var rinsum: Int
68 | var ginsum: Int
69 | var binsum: Int
70 | y = 0
71 | while (y < h) {
72 | bsum = 0
73 | gsum = bsum
74 | rsum = gsum
75 | boutsum = rsum
76 | goutsum = boutsum
77 | routsum = goutsum
78 | binsum = routsum
79 | ginsum = binsum
80 | rinsum = ginsum
81 | i = -radius
82 | while (i <= radius) {
83 | p = pix[yi + wm.coerceAtMost(i.coerceAtLeast(0))]
84 | sir = stack[i + radius]
85 | sir[0] = p and 0xff0000 shr 16
86 | sir[1] = p and 0x00ff00 shr 8
87 | sir[2] = p and 0x0000ff
88 | rbs = r1 - abs(i)
89 | rsum += sir[0] * rbs
90 | gsum += sir[1] * rbs
91 | bsum += sir[2] * rbs
92 | if (i > 0) {
93 | rinsum += sir[0]
94 | ginsum += sir[1]
95 | binsum += sir[2]
96 | } else {
97 | routsum += sir[0]
98 | goutsum += sir[1]
99 | boutsum += sir[2]
100 | }
101 | i++
102 | }
103 | stackpointer = radius
104 | x = 0
105 | while (x < w) {
106 | r[yi] = dv[rsum]
107 | g[yi] = dv[gsum]
108 | b[yi] = dv[bsum]
109 | rsum -= routsum
110 | gsum -= goutsum
111 | bsum -= boutsum
112 | stackstart = stackpointer - radius + div
113 | sir = stack[stackstart % div]
114 | routsum -= sir[0]
115 | goutsum -= sir[1]
116 | boutsum -= sir[2]
117 | if (y == 0) {
118 | vmin[x] = (x + radius + 1).coerceAtMost(wm)
119 | }
120 | p = pix[yw + vmin[x]]
121 | sir[0] = p and 0xff0000 shr 16
122 | sir[1] = p and 0x00ff00 shr 8
123 | sir[2] = p and 0x0000ff
124 | rinsum += sir[0]
125 | ginsum += sir[1]
126 | binsum += sir[2]
127 | rsum += rinsum
128 | gsum += ginsum
129 | bsum += binsum
130 | stackpointer = (stackpointer + 1) % div
131 | sir = stack[stackpointer % div]
132 | routsum += sir[0]
133 | goutsum += sir[1]
134 | boutsum += sir[2]
135 | rinsum -= sir[0]
136 | ginsum -= sir[1]
137 | binsum -= sir[2]
138 | yi++
139 | x++
140 | }
141 | yw += w
142 | y++
143 | }
144 | x = 0
145 | while (x < w) {
146 | bsum = 0
147 | gsum = bsum
148 | rsum = gsum
149 | boutsum = rsum
150 | goutsum = boutsum
151 | routsum = goutsum
152 | binsum = routsum
153 | ginsum = binsum
154 | rinsum = ginsum
155 | yp = -radius * w
156 | i = -radius
157 | while (i <= radius) {
158 | yi = 0.coerceAtLeast(yp) + x
159 | sir = stack[i + radius]
160 | sir[0] = r[yi]
161 | sir[1] = g[yi]
162 | sir[2] = b[yi]
163 | rbs = r1 - abs(i)
164 | rsum += r[yi] * rbs
165 | gsum += g[yi] * rbs
166 | bsum += b[yi] * rbs
167 | if (i > 0) {
168 | rinsum += sir[0]
169 | ginsum += sir[1]
170 | binsum += sir[2]
171 | } else {
172 | routsum += sir[0]
173 | goutsum += sir[1]
174 | boutsum += sir[2]
175 | }
176 | if (i < hm) {
177 | yp += w
178 | }
179 | i++
180 | }
181 | yi = x
182 | stackpointer = radius
183 | y = 0
184 | while (y < h) {
185 |
186 | // Preserve alpha channel: ( 0xff000000 & pix[yi] )
187 | pix[yi] =
188 | -0x1000000 and pix[yi] or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum]
189 | rsum -= routsum
190 | gsum -= goutsum
191 | bsum -= boutsum
192 | stackstart = stackpointer - radius + div
193 | sir = stack[stackstart % div]
194 | routsum -= sir[0]
195 | goutsum -= sir[1]
196 | boutsum -= sir[2]
197 | if (x == 0) {
198 | vmin[y] = (y + r1).coerceAtMost(hm) * w
199 | }
200 | p = x + vmin[y]
201 | sir[0] = r[p]
202 | sir[1] = g[p]
203 | sir[2] = b[p]
204 | rinsum += sir[0]
205 | ginsum += sir[1]
206 | binsum += sir[2]
207 | rsum += rinsum
208 | gsum += ginsum
209 | bsum += binsum
210 | stackpointer = (stackpointer + 1) % div
211 | sir = stack[stackpointer]
212 | routsum += sir[0]
213 | goutsum += sir[1]
214 | boutsum += sir[2]
215 | rinsum -= sir[0]
216 | ginsum -= sir[1]
217 | binsum -= sir[2]
218 | yi += w
219 | y++
220 | }
221 | x++
222 | }
223 | bitmap.setPixels(pix, 0, w, 0, 0, w, h)
224 | return bitmap
225 | }
226 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/java/pereira/agnaldo/previewimgcol/GlideListener.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.drawable.Drawable
5 | import androidx.core.graphics.drawable.toBitmap
6 | import com.bumptech.glide.load.DataSource
7 | import com.bumptech.glide.load.engine.GlideException
8 | import com.bumptech.glide.request.RequestListener
9 | import com.bumptech.glide.request.target.Target
10 | import java.lang.Exception
11 |
12 | class GlideListener(
13 | private val onSuccess: (Bitmap?) -> Unit,
14 | private val onError: ((Exception?) -> Unit)? = null
15 | ) : RequestListener {
16 | override fun onLoadFailed(
17 | e: GlideException?,
18 | model: Any?,
19 | target: Target?,
20 | isFirstResource: Boolean
21 | ): Boolean {
22 | onError?.invoke(e)
23 | return false
24 | }
25 |
26 | override fun onResourceReady(
27 | resource: Drawable?,
28 | model: Any?,
29 | target: Target?,
30 | dataSource: DataSource?,
31 | isFirstResource: Boolean
32 | ): Boolean {
33 | onSuccess.invoke(resource?.toBitmap())
34 | return false
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/java/pereira/agnaldo/previewimgcol/ImageCollectionView.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused")
2 |
3 | package pereira.agnaldo.previewimgcol
4 |
5 | import android.app.Activity
6 | import android.content.Context
7 | import android.graphics.*
8 | import android.graphics.drawable.Drawable
9 | import android.net.Uri
10 | import android.util.AttributeSet
11 | import android.view.ViewGroup
12 | import android.widget.ImageView
13 | import android.widget.LinearLayout
14 | import androidx.annotation.DrawableRes
15 | import com.ablanco.zoomy.Zoomy
16 | import com.bumptech.glide.Glide
17 | import java.io.File
18 |
19 | open class ImageCollectionView @JvmOverloads constructor(
20 | context: Context,
21 | attrs: AttributeSet? = null,
22 | defStyleAttr: Int = 0,
23 | defStyleRes: Int = 0
24 | ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
25 |
26 | companion object {
27 | const val NO_ROW_LIMITS = -1
28 | }
29 |
30 | private val previewImages: ArrayList
31 |
32 | private var onMoreClickListener: OnMoreClickListener? = null
33 | private var onMoreClickListenerUnit: ((List) -> Unit)? = null
34 |
35 | var maxImagePerRow = 3
36 | set(value) {
37 | field = value
38 | clearAndReloadBitmaps()
39 | }
40 |
41 | var maxRows = 3
42 | set(value) {
43 | field = value
44 | clearAndReloadBitmaps()
45 | }
46 |
47 | var baseImageHeight = 300
48 | set(value) {
49 | field = value
50 | clearAndReloadBitmaps()
51 | }
52 |
53 | var previewDistributeEvenly = false
54 | set(value) {
55 | field = value
56 | clearAndReloadBitmaps()
57 | }
58 |
59 | var previewCornerRadius = 0
60 | set(value) {
61 | field = value
62 | clearAndReloadBitmaps()
63 | }
64 |
65 | var imageMargin = 1
66 | set(value) {
67 | field = value
68 | clearAndReloadBitmaps()
69 | }
70 |
71 | var mBackgroundColor = Color.WHITE
72 | set(value) {
73 | field = value
74 | clearAndReloadBitmaps()
75 | }
76 |
77 | var scaleType = ImageView.ScaleType.CENTER_CROP
78 | set(value) {
79 | field = value
80 | clearAndReloadBitmaps()
81 | }
82 |
83 | var pinchToZoom = true
84 | set(value) {
85 | field = value
86 | clearAndReloadBitmaps()
87 | }
88 |
89 | var showExternalBorderMargins = false
90 | set(value) {
91 | field = value
92 | clearAndReloadBitmaps()
93 | }
94 |
95 | init {
96 | orientation = VERTICAL
97 |
98 | previewImages = ArrayList()
99 | getStyles(attrs, defStyleAttr)
100 | }
101 |
102 | private fun getStyles(attrs: AttributeSet?, defStyle: Int) {
103 | attrs?.let {
104 |
105 | val typedArray = context.obtainStyledAttributes(
106 | attrs,
107 | R.styleable.ImageCollectionView, defStyle, R.style.defaultPreviewImageCollection
108 | )
109 |
110 | baseImageHeight = typedArray.getDimensionPixelSize(
111 | R.styleable.ImageCollectionView_baseRowHeight, baseImageHeight
112 | )
113 |
114 | imageMargin = typedArray.getDimensionPixelSize(
115 | R.styleable.ImageCollectionView_imageMargin, imageMargin
116 | )
117 |
118 | previewCornerRadius = typedArray.getDimensionPixelSize(
119 | R.styleable.ImageCollectionView_previewCornerRadius, previewCornerRadius
120 | )
121 |
122 | maxImagePerRow = typedArray.getInteger(
123 | R.styleable.ImageCollectionView_maxImagePerRow, maxImagePerRow
124 | )
125 |
126 | previewDistributeEvenly = typedArray.getBoolean(
127 | R.styleable.ImageCollectionView_previewDistributeEvenly, previewDistributeEvenly
128 | )
129 |
130 | maxRows = typedArray.getInteger(
131 | R.styleable.ImageCollectionView_maxRows, maxRows
132 | )
133 |
134 | val scaleTypeInt = typedArray.getInteger(
135 | R.styleable.ImageCollectionView_previewImageScaleType,
136 | ImageView.ScaleType.CENTER_CROP.ordinal
137 | )
138 |
139 | ImageView.ScaleType.values().firstOrNull() { it.ordinal == scaleTypeInt }?.let {
140 | scaleType = it
141 | }
142 |
143 | mBackgroundColor = typedArray.getColor(
144 | R.styleable.ImageCollectionView_backgroundColor, mBackgroundColor
145 | )
146 |
147 | pinchToZoom = typedArray.getBoolean(
148 | R.styleable.ImageCollectionView_pinchToZoom, pinchToZoom
149 | )
150 |
151 | showExternalBorderMargins = typedArray.getBoolean(
152 | R.styleable.ImageCollectionView_showExternalBorderMargins,
153 | showExternalBorderMargins
154 | )
155 |
156 | typedArray.recycle()
157 | }
158 | }
159 |
160 | fun addImages(bitmaps: ArrayList) =
161 | bitmaps.forEach { bmp -> addImage(bmp) }
162 |
163 | fun addImage(drawableRes: Int) =
164 | addImage(PreviewImage(context, drawableRes), onClick = null, onLongClick = null)
165 |
166 | fun addImage(
167 | drawableRes: Int,
168 | onClick: OnImageClickListener?,
169 | ) = addImage(PreviewImage(context, drawableRes), onClick, null)
170 |
171 | fun addImage(
172 | drawableRes: Int,
173 | onLongClick: OnImageLongClickListener
174 | ) = addImage(PreviewImage(context, drawableRes), null, onLongClick)
175 |
176 | fun addImage(url: String, @DrawableRes placeHolder: Int) =
177 | addImage(
178 | PreviewImage(context, url, placeHolder) {
179 | invalidatePreview()
180 | },
181 | onClick = null, onLongClick = null
182 | )
183 |
184 | fun addImage(
185 | url: String,
186 | @DrawableRes placeHolder: Int,
187 | onClick: OnImageClickListener?,
188 | ) = addImage(
189 | PreviewImage(context, url, placeHolder) {
190 | invalidatePreview()
191 | },
192 | onClick, null
193 | )
194 |
195 | fun addImage(
196 | url: String,
197 | @DrawableRes placeHolder: Int,
198 | onLongClick: OnImageLongClickListener
199 | ) = addImage(
200 | PreviewImage(context, url, placeHolder) {
201 | invalidatePreview()
202 | },
203 | null, onLongClick
204 | )
205 |
206 | fun addImage(
207 | url: String,
208 | @DrawableRes placeHolder: Int,
209 | onClick: OnImageClickListener,
210 | onLongClick: OnImageLongClickListener
211 | ) = addImage(
212 | PreviewImage(context, url, placeHolder) {
213 | invalidatePreview()
214 | },
215 | onClick, onLongClick
216 | )
217 |
218 | fun addImage(
219 | drawableRes: Int,
220 | onClick: OnImageClickListener,
221 | onLongClick: OnImageLongClickListener
222 | ) = addImage(PreviewImage(context, drawableRes), onClick, onLongClick)
223 |
224 | /**
225 | * Due to conflict with java and kotlin apps,
226 | * functions with functional interface will receive K at end
227 | */
228 | fun addImageK(
229 | drawableRes: Int,
230 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
231 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
232 | ) = addImage(PreviewImage(context, drawableRes), onClickUnit, onLongClickUnit)
233 |
234 | fun addImage(drawable: Drawable) =
235 | addImage(PreviewImage(context, drawable), onClick = null, onLongClick = null)
236 |
237 | fun addImage(
238 | drawable: Drawable,
239 | onClick: OnImageClickListener,
240 | ) = addImage(PreviewImage(context, drawable), onClick, null)
241 |
242 | fun addImage(
243 | drawable: Drawable,
244 | onLongClick: OnImageLongClickListener
245 | ) = addImage(PreviewImage(context, drawable), null, onLongClick)
246 |
247 | fun addImage(
248 | drawable: Drawable,
249 | onClick: OnImageClickListener,
250 | onLongClick: OnImageLongClickListener
251 | ) = addImage(PreviewImage(context, drawable), onClick, onLongClick)
252 |
253 | /**
254 | * Due to conflict with java and kotlin apps,
255 | * functions with functional interface will receive K at end
256 | */
257 | fun addImageK(
258 | drawable: Drawable,
259 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
260 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
261 | ) = addImage(PreviewImage(context, drawable), onClickUnit, onLongClickUnit)
262 |
263 | fun addImage(bitmap: Bitmap) =
264 | addImage(PreviewImage(context, bitmap), onClick = null, onLongClick = null)
265 |
266 | fun addImage(
267 | bitmap: Bitmap,
268 | onClick: OnImageClickListener,
269 | ) = addImage(PreviewImage(context, bitmap), onClick)
270 |
271 | fun addImage(
272 | bitmap: Bitmap,
273 | onLongClick: OnImageLongClickListener
274 | ) = addImage(PreviewImage(context, bitmap), null, onLongClick)
275 |
276 | fun addImage(
277 | bitmap: Bitmap,
278 | onClick: OnImageClickListener,
279 | onLongClick: OnImageLongClickListener
280 | ) = addImage(PreviewImage(context, bitmap), onClick, onLongClick)
281 |
282 | /**
283 | * Due to conflict with java and kotlin apps,
284 | * functions with functional interface will receive K at end
285 | */
286 | fun addImageK(
287 | bitmap: Bitmap,
288 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
289 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
290 | ) = addImage(PreviewImage(context, bitmap), onClickUnit, onLongClickUnit)
291 |
292 | fun addImage(bitmapFile: File) =
293 | addImage(PreviewImage(context, bitmapFile), onClick = null, onLongClick = null)
294 |
295 | fun addImage(
296 | bitmapFile: File,
297 | onClick: OnImageClickListener
298 | ) = addImage(PreviewImage(context, bitmapFile), onClick, null)
299 |
300 | fun addImage(
301 | bitmapFile: File,
302 | onLongClick: OnImageLongClickListener
303 | ) = addImage(PreviewImage(context, bitmapFile), null, onLongClick)
304 |
305 | fun addImage(
306 | bitmapFile: File,
307 | onClick: OnImageClickListener,
308 | onLongClick: OnImageLongClickListener
309 | ) = addImage(PreviewImage(context, bitmapFile), onClick, onLongClick)
310 |
311 | /**
312 | * Due to conflict with java and kotlin apps,
313 | * functions with functional interface will receive K at end
314 | */
315 | fun addImageK(
316 | bitmapFile: File,
317 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
318 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
319 | ) = addImage(PreviewImage(context, bitmapFile), onClickUnit, onLongClickUnit)
320 |
321 | fun addImage(bitmapUri: Uri) =
322 | addImage(PreviewImage(context, bitmapUri), onClick = null, onLongClick = null)
323 |
324 | fun addImage(
325 | bitmapUri: Uri,
326 | onClick: OnImageClickListener
327 | ) = addImage(PreviewImage(context, bitmapUri), onClick, null)
328 |
329 | fun addImage(
330 | bitmapUri: Uri,
331 | onClick: OnImageClickListener,
332 | onLongClick: OnImageLongClickListener
333 | ) = addImage(PreviewImage(context, bitmapUri), onClick, onLongClick)
334 |
335 | fun addImage(
336 | bitmapUri: Uri,
337 | onLongClick: OnImageLongClickListener
338 | ) = addImage(PreviewImage(context, bitmapUri), null, onLongClick)
339 |
340 | /**
341 | * Due to conflict with java and kotlin apps,
342 | * functions with functional interface will receive K at end
343 | */
344 | fun addImageK(
345 | bitmapUri: Uri,
346 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
347 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
348 | ) = addImage(PreviewImage(context, bitmapUri), onClickUnit, onLongClickUnit)
349 |
350 | private fun addImage(
351 | previewImage: PreviewImage,
352 | onClick: OnImageClickListener? = null,
353 | onLongClick: OnImageLongClickListener? = null
354 | ) {
355 | previewImage.mOnClick = onClick
356 | previewImage.mOnLongClick = onLongClick
357 | previewImages.add(previewImage)
358 |
359 | afterAddPreviewImage(previewImage)
360 | }
361 |
362 | private fun addImage(
363 | previewImage: PreviewImage,
364 | onClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null,
365 | onLongClickUnit: ((bitmap: Bitmap?, imageView: ImageView?) -> Unit)? = null
366 | ) {
367 | previewImage.mOnClickUnit = onClickUnit
368 | previewImage.mOnLongClickUnit = onLongClickUnit
369 | previewImages.add(previewImage)
370 |
371 | afterAddPreviewImage(previewImage)
372 | }
373 |
374 | private fun afterAddPreviewImage(previewImage: PreviewImage) {
375 | reEvaluateLastRow(previewImage)
376 | removeOutsideMargins()
377 | invalidate()
378 | }
379 |
380 | fun clearImages() {
381 | previewImages.clear()
382 | removeAllViews()
383 | invalidate()
384 | }
385 |
386 | fun removeImage(bitmap: Bitmap) {
387 | findPreviewImage(bitmap)?.let {
388 | previewImages.remove(it)
389 | }
390 | clearAndReloadBitmaps()
391 | invalidate()
392 | }
393 |
394 | private fun findPreviewImage(bitmap: Bitmap): PreviewImage? =
395 | previewImages.firstOrNull { it.isEquals(bitmap) }
396 |
397 | fun setOnMoreClicked(onMoreClickListener: OnMoreClickListener) {
398 | this.onMoreClickListener = onMoreClickListener
399 | }
400 |
401 | fun setOnMoreClicked(onMoreClickListenerUnit: (bitmaps: List) -> Unit) {
402 | this.onMoreClickListenerUnit = onMoreClickListenerUnit
403 | }
404 |
405 | override fun onAttachedToWindow() {
406 | super.onAttachedToWindow()
407 | post { clearAndReloadBitmaps() }
408 | }
409 |
410 | override fun onDetachedFromWindow() {
411 | super.onDetachedFromWindow()
412 | previewImages.forEach { previewImage: PreviewImage ->
413 | previewImage.mImageView?.let {
414 | Zoomy.unregister(it)
415 | }
416 | }
417 | }
418 |
419 | private fun clearAndReloadBitmaps() {
420 | removeAllViews()
421 | extractAndInflateImagesPerLine(previewImages)
422 | removeOutsideMargins()
423 | }
424 |
425 | private fun extractAndInflateImagesPerLine(previewImages: List) {
426 | val maxRowReached = maxRows != NO_ROW_LIMITS && childCount == maxRows
427 |
428 | if (!maxRowReached && previewImages.size > maxImagePerRow) {
429 | val newLine = createNewRow()
430 | addBitmapsToLine(previewImages.subList(0, maxImagePerRow), newLine)
431 |
432 | extractAndInflateImagesPerLine(
433 | previewImages.subList(
434 | maxImagePerRow,
435 | previewImages.size
436 | )
437 | )
438 | return
439 | } else {
440 | if (!maxRowReached) {
441 | val newLine = createNewRow()
442 | addBitmapsToLine(previewImages, newLine)
443 | } else {
444 | addThereAreMore()
445 | }
446 | }
447 | }
448 |
449 | private fun addThereAreMore() = synchronized(this) {
450 | val lastRow = getChildAt(childCount - 1) as LinearLayout
451 | val lastImage = lastRow.getChildAt(lastRow.childCount - 1) as ImageView
452 | lastImage.post {
453 | Zoomy.unregister(lastImage)
454 | val lastImageIndex = childCount * maxImagePerRow
455 |
456 | if (onMoreClickListener != null || onMoreClickListenerUnit != null) {
457 | lastImage.setOnClickListener {
458 | val bitmaps = previewImages.subList(
459 | lastImageIndex - 1,
460 | previewImages.size
461 | ).mapNotNull { it.asBitmap() }.toList()
462 | onMoreClickListener?.onMoreClicked(bitmaps)
463 | onMoreClickListenerUnit?.invoke(bitmaps)
464 | }
465 | }
466 |
467 | val index = (maxRows * maxImagePerRow) - 1
468 | previewImages.takeIf { index < previewImages.size - 1 }?.get(index)?.asBitmap()?.let {
469 | val blurredBitmap = it.blur(lastImage.width.takeIf { w -> w != 0 } ?: it.width / 2)
470 |
471 | val canvas = Canvas(blurredBitmap)
472 |
473 | val paintText = Paint()
474 | paintText.color = Color.WHITE
475 | paintText.style = Paint.Style.FILL_AND_STROKE
476 | paintText.textAlign = Paint.Align.CENTER
477 |
478 | val text = "+".plus(previewImages.size - lastImageIndex + 1)
479 | val textSize = blurredBitmap.width / 4.toFloat()
480 | paintText.textSize = textSize
481 |
482 | val paint = Paint()
483 | paint.color = Color.argb(100, 0, 0, 0)
484 | paint.maskFilter = BlurMaskFilter(300F, BlurMaskFilter.Blur.INNER)
485 |
486 | val rect = Rect(0, 0, canvas.width, canvas.height)
487 | canvas.drawRect(rect, paint)
488 |
489 | canvas.drawText(
490 | text,
491 | rect.centerX().toFloat(),
492 | rect.centerY().toFloat() + textSize / 2f,
493 | paintText
494 | )
495 |
496 | Glide.with(context).load(blurredBitmap).into(lastImage)
497 | }
498 | }
499 | }
500 |
501 | private fun reEvaluateLastRow(previewImage: PreviewImage) {
502 | if (width == 0) {
503 | return
504 | }
505 |
506 | if (childCount == 0) {
507 | createNewRow()
508 | }
509 |
510 | val lineLinearLayout = getChildAt(childCount - 1) as ViewGroup
511 | val lineChildCount = lineLinearLayout.childCount
512 | if (lineChildCount == maxImagePerRow) {
513 | val maxImages = maxRows * maxImagePerRow
514 | val imagesCount = childCount * maxImagePerRow
515 | if (maxRows == NO_ROW_LIMITS || imagesCount < maxImages) {
516 | createNewRow()
517 | reEvaluateLastRow(previewImage)
518 | } else {
519 | addThereAreMore()
520 | }
521 | return
522 | }
523 |
524 | val bitmaps = previewImages.subList(
525 | previewImages.size - lineChildCount - 1, previewImages.size
526 | )
527 | addBitmapsToLine(bitmaps, lineLinearLayout)
528 | }
529 |
530 | private fun invalidatePreview() = synchronized(this) {
531 | try {
532 | for (i in 0 until childCount) {
533 | val line = getChildAt(i) as ViewGroup
534 |
535 | val previewImages = mutableListOf()
536 | for (j in 0 until line.childCount) {
537 | val image = line.getChildAt(j) as? ImageView
538 | val imagePreview = (image?.tag as? PreviewImage)
539 | imagePreview?.let { previewImages.add(it) }
540 | }
541 |
542 | val widthSum = previewImages.sumOf { previewImage -> previewImage.width() }
543 | previewImages.forEachIndexed { index, previewImage ->
544 | (line.getChildAt(index) as? ImageView)?.let { imageView ->
545 | val proportion = (previewImage.width() / widthSum.toFloat())
546 | val widthBmp = if (!previewDistributeEvenly) {
547 | ((width * proportion).toInt()) - (2 * imageMargin)
548 | } else {
549 | (width / previewImages.size) - (2 * imageMargin)
550 | }
551 |
552 | val heightBmp = baseImageHeight - (2 * imageMargin)
553 |
554 | val params = imageView.layoutParams as LayoutParams
555 | params.width = widthBmp
556 | params.height = heightBmp
557 | params.setMargins(imageMargin, imageMargin, imageMargin, imageMargin)
558 | imageView.layoutParams = params
559 |
560 | previewImage.loadImage(imageView)
561 | }
562 |
563 | if (maxRows != NO_ROW_LIMITS && i == childCount - 1 && index == line.childCount - 1) {
564 | addThereAreMore()
565 | }
566 | }
567 | }
568 | } catch (e: Exception) {
569 | if (BuildConfig.DEBUG) {
570 | e.printStackTrace()
571 | }
572 | }
573 |
574 | removeOutsideMargins()
575 | }
576 |
577 | private fun addBitmapsToLine(previewImages: List, rowLinearLayout: ViewGroup) {
578 | if (previewImages.isEmpty())
579 | return
580 |
581 | rowLinearLayout.removeAllViews()
582 | rowLinearLayout.setBackgroundColor(mBackgroundColor)
583 |
584 | val widthSum = previewImages.sumOf { previewImage -> previewImage.width() }
585 |
586 | previewImages.forEach { previewImage: PreviewImage ->
587 | val imageView = ImageView(context)
588 | imageView.tag = previewImage
589 | imageView.scaleType = scaleType
590 | previewImage.loadImage(imageView)
591 |
592 | if (pinchToZoom && context is Activity) {
593 | Zoomy.Builder(context as Activity).target(imageView).tapListener {
594 | previewImage.onClick()
595 | }.longPressListener {
596 | previewImage.onLongClick()
597 | }.register()
598 | }
599 |
600 | val proportion = (previewImage.width() / widthSum.toFloat())
601 | val widthBmp = if (!previewDistributeEvenly) {
602 | ((width * proportion).toInt()) - (2 * imageMargin)
603 | } else {
604 | (width / previewImages.size) - (2 * imageMargin)
605 | }
606 | val heightBmp = baseImageHeight - (2 * imageMargin)
607 |
608 | val params = LayoutParams(widthBmp, heightBmp)
609 | params.setMargins(imageMargin, imageMargin, imageMargin, imageMargin)
610 |
611 | rowLinearLayout.addView(imageView, params)
612 | }
613 | }
614 |
615 | private fun createNewRow(): LinearLayout {
616 | val linearLayout = LinearLayout(context)
617 | linearLayout.orientation = HORIZONTAL
618 |
619 | val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
620 | addView(linearLayout, params)
621 | return linearLayout
622 | }
623 |
624 | private fun removeOutsideMargins() {
625 | if (showExternalBorderMargins)
626 | return
627 |
628 | for (i in 0 until childCount) {
629 | val row = getChildAt(i) as LinearLayout
630 | val rowChildCount = row.childCount
631 | for (j in 0 until rowChildCount) {
632 | val image = row.getChildAt(j)
633 |
634 | val layoutParams = image.layoutParams as LayoutParams
635 | if (i == 0) {
636 | layoutParams.topMargin = 0
637 | layoutParams.height = layoutParams.height + imageMargin
638 | }
639 |
640 | if (i == childCount - 1) {
641 | layoutParams.bottomMargin = 0
642 | layoutParams.height = layoutParams.height + imageMargin
643 | }
644 |
645 | if (j == 0) {
646 | layoutParams.leftMargin = 0
647 | layoutParams.width = layoutParams.width + imageMargin
648 | }
649 |
650 | if (j == rowChildCount - 1) {
651 | layoutParams.rightMargin = 0
652 | layoutParams.width = layoutParams.width + imageMargin
653 | }
654 |
655 | image.layoutParams = layoutParams
656 | }
657 | }
658 |
659 | post { invalidate() }
660 | }
661 |
662 | fun getImageAt(index: Int): Bitmap {
663 | return previewImages[index].asBitmap()!!
664 | }
665 |
666 | fun getNullableImageAt(index: Int): Bitmap? {
667 | return if (index < previewImages.size) previewImages[index].asBitmap() else null
668 | }
669 |
670 | override fun dispatchDraw(canvas: Canvas?) {
671 | if (previewCornerRadius > 0 && canvas != null) {
672 | val clipPath = Path()
673 | clipPath.addRoundRect(
674 | RectF(0F, 0F, width.toFloat(), height.toFloat()),
675 | previewCornerRadius.toFloat(),
676 | previewCornerRadius.toFloat(),
677 | Path.Direction.CW
678 | )
679 | canvas.clipPath(clipPath)
680 | }
681 | super.dispatchDraw(canvas)
682 | }
683 |
684 | interface OnImageClickListener {
685 | fun onClick(bitmap: Bitmap, imageView: ImageView)
686 | }
687 |
688 | interface OnImageLongClickListener {
689 | fun onLongClick(bitmap: Bitmap, imageView: ImageView)
690 | }
691 |
692 | interface OnMoreClickListener {
693 | fun onMoreClicked(bitmaps: List)
694 | }
695 | }
696 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/java/pereira/agnaldo/previewimgcol/PreviewImage.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused", "DEPRECATION")
2 |
3 | package pereira.agnaldo.previewimgcol
4 |
5 | import android.content.Context
6 | import android.graphics.Bitmap
7 | import android.graphics.BitmapFactory
8 | import android.graphics.drawable.Drawable
9 | import android.net.Uri
10 | import android.provider.MediaStore
11 | import android.widget.ImageView
12 | import androidx.core.content.ContextCompat
13 | import androidx.core.graphics.drawable.toBitmap
14 | import com.bumptech.glide.Glide
15 | import com.bumptech.glide.RequestBuilder
16 | import com.bumptech.glide.RequestManager
17 | import com.bumptech.glide.request.target.SimpleTarget
18 | import com.bumptech.glide.request.transition.Transition
19 | import java.io.File
20 |
21 | internal class PreviewImage(private val context: Context) {
22 |
23 | var mOnClick: ImageCollectionView.OnImageClickListener? = null
24 | var mOnClickUnit: ((Bitmap?, ImageView?) -> Unit)? = null
25 |
26 | var mOnLongClick: ImageCollectionView.OnImageLongClickListener? = null
27 | var mOnLongClickUnit: ((Bitmap?, ImageView?) -> Unit)? = null
28 |
29 | var mImageView: ImageView? = null
30 |
31 | private var imageBitmap: Bitmap? = null
32 |
33 | constructor(context: Context, imageBitmap: Bitmap) : this(context) {
34 | this.imageBitmap = imageBitmap
35 | }
36 |
37 | private var imageUri: Uri? = null
38 |
39 | constructor(context: Context, imageUri: Uri) : this(context) {
40 | this.imageUri = imageUri
41 | }
42 |
43 | private var imageResourceId: Int? = null
44 |
45 | constructor(context: Context, imageResourceId: Int) : this(context) {
46 | this.imageResourceId = imageResourceId
47 | }
48 |
49 | private var imageUrl: String? = null
50 | private var placeHolder: Int? = null
51 | private var bitmapLoaded: (() -> Unit)? = null
52 |
53 | constructor(
54 | context: Context,
55 | imageUrl: String,
56 | placeHolder: Int? = null,
57 | bitmapLoaded: () -> Unit
58 | ) : this(context) {
59 | this.imageUrl = imageUrl
60 | this.bitmapLoaded = bitmapLoaded
61 | this.placeHolder = placeHolder
62 | }
63 |
64 | private var imageDrawable: Drawable? = null
65 |
66 | constructor(context: Context, imageDrawable: Drawable) : this(context) {
67 | this.imageDrawable = imageDrawable
68 | }
69 |
70 | private var imageFile: File? = null
71 |
72 | constructor(context: Context, imageFile: File) : this(context) {
73 | this.imageFile = imageFile
74 | }
75 |
76 | fun loadImage(imageView: ImageView) {
77 | this.mImageView = imageView
78 | imageView.post {
79 | Glide.with(context)
80 | .loadImage()
81 | ?.error(placeHolder ?: R.drawable.blur)
82 | ?.placeholder(placeHolder ?: R.drawable.blur)
83 | ?.into(imageView)
84 |
85 | imageView.setOnClickListener { onClick() }
86 | imageView.setOnLongClickListener { onLongClick() }
87 | }
88 | }
89 |
90 | private fun RequestManager.loadImage() = when {
91 | imageBitmap != null -> this.load(imageBitmap)
92 | imageUrl != null -> this.loadUrl(imageUrl!!)
93 | imageUri != null -> this.load(imageUri)
94 | imageFile != null -> this.load(imageFile)
95 | imageDrawable != null -> this.load(imageDrawable)
96 | imageResourceId != null -> this.load(imageResourceId)
97 | else -> null
98 | }
99 |
100 | private fun RequestManager.loadUrl(url: String): RequestBuilder {
101 | Glide.with(context).asBitmap().load(url).into(object : SimpleTarget() {
102 | override fun onResourceReady(resource: Bitmap, transition: Transition?) {
103 | imageBitmap = resource
104 | bitmapLoaded?.invoke()
105 | }
106 | })
107 |
108 | return this.load(url).addListener(
109 | GlideListener(
110 | onSuccess = {
111 | if (imageBitmap == null) {
112 | imageBitmap = it
113 | }
114 | },
115 | onError = {
116 | if (BuildConfig.DEBUG) {
117 | it?.printStackTrace()
118 | }
119 | }
120 | )
121 | )
122 | }
123 |
124 | fun width() = imageBitmap?.width
125 | ?: ContextCompat.getDrawable(context, placeHolder ?: R.drawable.blur)?.intrinsicWidth
126 | ?: 0
127 |
128 | fun asBitmap(): Bitmap? =
129 | when {
130 | imageBitmap != null -> {
131 | imageBitmap
132 | }
133 | imageFile != null -> {
134 | imageBitmap = BitmapFactory.decodeFile(imageFile!!.path)
135 | imageBitmap
136 | }
137 | imageResourceId != null -> {
138 | imageBitmap = ContextCompat.getDrawable(context, imageResourceId!!)?.toBitmap()
139 | imageBitmap
140 | }
141 | imageDrawable != null -> {
142 | imageBitmap = imageDrawable?.toBitmap()
143 | imageBitmap
144 | }
145 | imageUri != null -> {
146 | imageBitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, imageUri)
147 | imageBitmap
148 | }
149 | else -> {
150 | null
151 | }
152 | }
153 |
154 | fun isEquals(bitmap: Bitmap): Boolean = imageBitmap?.equals(bitmap) ?: false
155 |
156 | fun onClick() {
157 | if (mOnClick != null) {
158 | val bitmap = asBitmap()
159 | mOnClickUnit?.invoke(bitmap, mImageView) ?: run {
160 | if (bitmap != null && mImageView != null) {
161 | mOnClick?.onClick(bitmap, mImageView!!)
162 | }
163 | }
164 | }
165 | }
166 |
167 | fun onLongClick(): Boolean {
168 | if (mOnLongClick != null || mOnClickUnit != null) {
169 | val bitmap = asBitmap()
170 | mOnLongClickUnit?.invoke(bitmap, mImageView) ?: run {
171 | if (bitmap != null && mImageView != null) {
172 | mOnLongClick?.onLongClick(bitmap, mImageView!!)
173 | }
174 | }
175 | }
176 |
177 | return true
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/drawable/blur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/previewimgcol/src/main/res/drawable/blur.png
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/boolean.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | true
6 | false
7 |
8 |
9 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FFFFFF
5 |
6 |
7 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 150dp
5 | 1px
6 | 0dp
7 |
8 |
9 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/integer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3
5 | 3
6 |
7 |
8 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PreviewImageCollections
3 |
4 |
--------------------------------------------------------------------------------
/previewimgcol/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/previewimgcol/src/test/java/pereira/agnaldo/previewimgcol/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package pereira.agnaldo.previewimgcol
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/screenshot/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/screenshot/sample.gif
--------------------------------------------------------------------------------
/screenshot/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgnaldoNP/PreviewImageCollection/d84d459e70d70e2ed58bf67f1a57d1571fc66184/screenshot/screenshot.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':previewimgcol'
2 | rootProject.name='Preview Images Collections'
3 |
--------------------------------------------------------------------------------