├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mindinventory │ │ └── overlapimagegalleyviewsample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mindinventory │ │ │ └── overlapimagegalleyviewsample │ │ │ ├── activities │ │ │ └── OverlapImageViewActivity.kt │ │ │ ├── adapters │ │ │ └── RecyclerViewAdapter.kt │ │ │ ├── models │ │ │ └── OverlapImageModel.kt │ │ │ └── utils │ │ │ └── AppUtils.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── mind.png │ │ └── shape_circle.xml │ │ ├── font │ │ ├── lobster_regular.ttf │ │ ├── space_grotesk_light.ttf │ │ ├── space_grotesk_medium.ttf │ │ └── space_grotesk_regular.ttf │ │ ├── layout │ │ ├── activity_overlap_image.xml │ │ └── row_image.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 │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mindinventory │ └── overlapimagegalleyviewsample │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mindinventory │ │ └── overlaprecylcerview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mindinventory │ │ │ └── overlaprecylcerview │ │ │ ├── adapters │ │ │ └── OverlapRecyclerViewAdapter.kt │ │ │ ├── animations │ │ │ └── OverlapRecyclerViewAnimation.kt │ │ │ ├── decoration │ │ │ └── OverlapRecyclerViewDecoration.kt │ │ │ ├── listeners │ │ │ └── OverlapRecyclerViewClickListener.kt │ │ │ └── utils │ │ │ ├── Direction.kt │ │ │ └── TextDrawable.kt │ └── res │ │ ├── anim │ │ ├── bottom_from_top.xml │ │ ├── left_from_right.xml │ │ ├── right_from_left.xml │ │ └── top_from_bottom.xml │ │ └── values │ │ └── integers.xml │ └── test │ └── java │ └── com │ └── mindinventory │ └── overlaprecylcerview │ └── ExampleUnitTest.java ├── media ├── .gitkeep └── OverlapImageView.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | /.idea/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MindInventory 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 | 2 | # OverlapImageGalleryView [![](https://jitpack.io/v/Mindinventory/OverlapImageGalleryView.svg)](https://jitpack.io/#Mindinventory/OverlapImageGalleryView) ![](https://img.shields.io/github/languages/top/Mindinventory/OverlapImageGalleryView) ![](https://img.shields.io/github/license/mindinventory/OverlapImageGalleryView) 3 | 4 | -- 5 | OverlapImageGalleryView is an android library which provides circular image horizontal list with multiple animations and customization. 6 | 7 | ### Preview 8 | ![image](/media/OverlapImageView.gif) 9 | 10 | ### Key features 11 | 12 | * Easy way to integrate it with your recyclerview adapter. 13 | * Overlapping space as you want. 14 | * Number of items to show in gallery as overlapped. 15 | * Different scroll animations. 16 | * Orientation. 17 | * Supported androidx 18 | 19 | ## Usage 20 | ### Dependencies 21 | - **Step 1: Add the JitPack repository in your project build.gradle file** 22 | ```bash 23 | allprojects { 24 | repositories { 25 | ... 26 | maven { url 'https://jitpack.io' } 27 | } 28 | } 29 | ``` 30 | **or** 31 | 32 | **If Android studio version is Arctic Fox or higher then add it in your settings.gradle** 33 | 34 | ```bash 35 | dependencyResolutionManagement { 36 | repositories { 37 | ... 38 | maven { url 'https://jitpack.io' } 39 | } 40 | } 41 | ``` 42 | - **Step 2: Add the dependency in your app module build.gradle file** 43 | ```bash 44 | dependencies { 45 | ... 46 | implementation 'com.github.Mindinventory:OverlapImageGalleryView:x.x.x' 47 | } 48 | ``` 49 | ### Implementation 50 | ```Fragment/Activity 51 | //------limit number of items to be overlapped 52 | private val overlapLimit = 5 53 | 54 | //------set value of item overlapping in percentage between 0 to 100 55 | private val overlapWidthInPercentage = -50 56 | 57 | //------set item decoration for item overlapping 58 | recyclerView.addItemDecoration(OverlapRecyclerViewDecoration(overlapLimit, overlapWidth)) 59 | recyclerView.adapter = mAdapter 60 | mAdapter.setImageList(setDummyArrayList()) 61 | 62 | 63 | //------ Implement OverlapRecyclerViewClickListener interface to get callback of items click. 64 | override fun onNormalItemClicked(adapterPosition: Int) { 65 | toast(this,"Normal item clicked >> $adapterPosition") 66 | } 67 | 68 | override fun onNumberedItemClick(adapterPosition: Int) { 69 | toast(this,"Numbered item clicked >> $adapterPosition") 70 | // Here you can add remaining items in list or open seperate screen. 71 | } 72 | ``` 73 | 74 | ### Library used 75 | * Glide -> implementation 'com.github.bumptech.glide:glide:4.8.0' 76 | 77 | ### Dribble 78 | https://dribbble.com/shots/5790365-Magnetic-Swipe-Animation-code 79 | 80 | ## LICENSE! 81 | 82 | OverlapImageGalleryView is [MIT-licensed](/LICENSE). 83 | 84 | ## Let us know! 85 | If you use our open-source libraries in your project, please make sure to credit us and Give a star to www.mindinventorycom 86 | 87 |

Please feel free to use this component and Let us know if you are interested to building Apps or Designing Products.

88 | 89 | app development 90 | 91 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | buildFeatures { 8 | viewBinding true 9 | } 10 | compileSdkVersion 33 11 | defaultConfig { 12 | applicationId "com.mindinventory.overlapimagegalleyviewsample" 13 | minSdkVersion 21 14 | targetSdkVersion 33 15 | versionCode 1 16 | versionName "1.0" 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation 'androidx.core:core-ktx:1.9.0' 36 | implementation 'androidx.appcompat:appcompat:1.5.1' 37 | implementation 'com.google.android.material:material:1.6.1' 38 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | implementation "com.github.bumptech.glide:glide:4.13.2" 41 | annotationProcessor "com.github.bumptech.glide:compiler:4.13.2" 42 | implementation project(path: ':library') 43 | } 44 | -------------------------------------------------------------------------------- /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/com/mindinventory/overlapimagegalleyviewsample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.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.getTargetContext() 22 | assertEquals("com.mindinventory.overlapimagegalleyviewsample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/mindinventory/overlapimagegalleyviewsample/activities/OverlapImageViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample.activities 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import com.mindinventory.overlapimagegalleyviewsample.adapters.RecyclerViewAdapter 7 | import com.mindinventory.overlapimagegalleyviewsample.models.OverlapImageModel 8 | import com.mindinventory.overlapimagegalleyviewsample.utils.imagesURLS 9 | import com.mindinventory.overlapimagegalleyviewsample.utils.toast 10 | import com.mindinventory.overlaprecyclerview.R 11 | import com.mindinventory.overlaprecyclerview.databinding.ActivityOverlapImageBinding 12 | import com.mindinventory.overlaprecylcerview.animations.OverlapRecyclerViewAnimation 13 | import com.mindinventory.overlaprecylcerview.listeners.OverlapRecyclerViewClickListener 14 | import com.mindinventory.overlaprecylcerview.utils.Direction 15 | 16 | 17 | class OverlapImageViewActivity : AppCompatActivity(), OverlapRecyclerViewClickListener { 18 | 19 | private var _binding: ActivityOverlapImageBinding? = null 20 | private val binding get() = _binding!! 21 | 22 | //------limit number of visibleItems to be overlapped 23 | private val numberOfItemToBeOverlapped = 25 24 | 25 | //------set value of item overlapping in percentage between 0 to 100 26 | private val overlapWidthInPercentage = 25 27 | 28 | //------init RecyclerView adapter 29 | private val topBottomAdapter by lazy { 30 | RecyclerViewAdapter(numberOfItemToBeOverlapped, overlapWidthInPercentage) 31 | } 32 | private val bottomTopAdapter by lazy { 33 | RecyclerViewAdapter(numberOfItemToBeOverlapped, overlapWidthInPercentage) 34 | } 35 | private val leftRightAdapter by lazy { 36 | RecyclerViewAdapter(numberOfItemToBeOverlapped, overlapWidthInPercentage) 37 | } 38 | private val rightLeftAdapter by lazy { 39 | RecyclerViewAdapter(numberOfItemToBeOverlapped, overlapWidthInPercentage) 40 | } 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | _binding = ActivityOverlapImageBinding.inflate(layoutInflater) 45 | setContentView(binding.root) 46 | 47 | registerDirectionRadioButtonListener() 48 | setAnimatedList() 49 | } 50 | 51 | private fun registerDirectionRadioButtonListener() { 52 | with(binding.rgDirection) 53 | { 54 | setOnCheckedChangeListener { group, checkedId -> 55 | run { 56 | when (checkedId) { 57 | R.id.rbLeftToRight -> { 58 | setOrientation(Direction.LEFT_TO_RIGHT) 59 | } 60 | R.id.rbRightToLeft -> { 61 | setOrientation(Direction.RIGHT_TO_LEFT) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | binding.rbLeftToRight.isChecked = true 69 | } 70 | 71 | private fun setOrientation(direction: Direction) { 72 | val topBottomLinearLayoutManager = LinearLayoutManager( 73 | this@OverlapImageViewActivity, 74 | LinearLayoutManager.HORIZONTAL, 75 | direction == Direction.RIGHT_TO_LEFT 76 | ) 77 | 78 | val bottomTopLinearLayoutManager = LinearLayoutManager( 79 | this@OverlapImageViewActivity, 80 | LinearLayoutManager.HORIZONTAL, 81 | direction == Direction.RIGHT_TO_LEFT 82 | ) 83 | 84 | val leftRightLinearLayoutManager = LinearLayoutManager( 85 | this@OverlapImageViewActivity, 86 | LinearLayoutManager.HORIZONTAL, 87 | direction == Direction.RIGHT_TO_LEFT 88 | ) 89 | val rightLeftLinearLayoutManager = LinearLayoutManager( 90 | this@OverlapImageViewActivity, 91 | LinearLayoutManager.HORIZONTAL, 92 | direction == Direction.RIGHT_TO_LEFT 93 | ) 94 | with(binding) 95 | { 96 | rvTopBottom.layoutManager = topBottomLinearLayoutManager 97 | rvBottomTop.layoutManager = bottomTopLinearLayoutManager 98 | rvLeftRight.layoutManager = leftRightLinearLayoutManager 99 | rvRightLeft.layoutManager = rightLeftLinearLayoutManager 100 | } 101 | } 102 | 103 | private fun setAnimatedList() { 104 | with(topBottomAdapter) { 105 | binding.rvTopBottom.addItemDecoration(getItemDecoration()) 106 | binding.rvTopBottom.adapter = this 107 | addAnimation = true 108 | animationType = OverlapRecyclerViewAnimation.TOP_BOTTOM 109 | addAll(getDummyArrayList()) 110 | overlapRecyclerViewClickListener = this@OverlapImageViewActivity 111 | notifyDataSetChanged() 112 | } 113 | 114 | with(bottomTopAdapter) { 115 | binding.rvBottomTop.addItemDecoration(getItemDecoration()) 116 | binding.rvBottomTop.adapter = this 117 | addAnimation = true 118 | animationType = OverlapRecyclerViewAnimation.BOTTOM_UP 119 | addAll(getDummyArrayList()) 120 | overlapRecyclerViewClickListener = this@OverlapImageViewActivity 121 | notifyDataSetChanged() 122 | } 123 | 124 | with(leftRightAdapter) { 125 | binding.rvLeftRight.addItemDecoration(getItemDecoration()) 126 | binding.rvLeftRight.adapter = this 127 | addAnimation = true 128 | animationType = OverlapRecyclerViewAnimation.LEFT_RIGHT 129 | addAll(getDummyArrayList()) 130 | overlapRecyclerViewClickListener = this@OverlapImageViewActivity 131 | notifyDataSetChanged() 132 | } 133 | 134 | with(rightLeftAdapter) { 135 | binding.rvRightLeft.addItemDecoration(getItemDecoration()) 136 | binding.rvRightLeft.adapter = this 137 | addAnimation = true 138 | animationType = OverlapRecyclerViewAnimation.RIGHT_LEFT 139 | addAll(getDummyArrayList()) 140 | overlapRecyclerViewClickListener = this@OverlapImageViewActivity 141 | notifyDataSetChanged() 142 | } 143 | } 144 | 145 | /** 146 | * addItem dummy data to ArrayList 147 | */ 148 | private fun getDummyArrayList(): java.util.ArrayList { 149 | val items = java.util.ArrayList() 150 | 151 | //-----fill data in to array list 152 | for (i in imagesURLS.indices) { 153 | items.add(OverlapImageModel(imagesURLS[i])) 154 | } 155 | // for (i in 0..20) { 156 | // items.add(OverlapImageModel(imageURLs[i % imageURLs.size])) 157 | // } 158 | return items 159 | } 160 | 161 | override fun onNormalItemClicked(adapterPosition: Int) { 162 | toast(this, "Normal item clicked >> $adapterPosition") 163 | } 164 | 165 | override fun onNumberedItemClick(adapterPosition: Int) { 166 | toast(this, "Numbered item clicked >> $adapterPosition") 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/java/com/mindinventory/overlapimagegalleyviewsample/adapters/RecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample.adapters 2 | 3 | import android.graphics.Color 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.bumptech.glide.Priority 9 | import com.bumptech.glide.request.RequestOptions 10 | import com.mindinventory.overlapimagegalleyviewsample.models.OverlapImageModel 11 | import com.mindinventory.overlaprecyclerview.R 12 | import com.mindinventory.overlaprecyclerview.databinding.RowImageBinding 13 | import com.mindinventory.overlaprecylcerview.adapters.OverlapRecyclerViewAdapter 14 | import com.mindinventory.overlaprecylcerview.utils.TextDrawable 15 | 16 | class RecyclerViewAdapter( 17 | overlapLimit: Int, 18 | overlapWidthInPercentage: Int 19 | ) : OverlapRecyclerViewAdapter(overlapLimit, overlapWidthInPercentage) { 20 | 21 | override fun createItemViewHolder(parent: ViewGroup): CustomViewHolder { 22 | val rowImageBinding = RowImageBinding.inflate(LayoutInflater.from(parent.context), parent, false) 23 | return CustomViewHolder(rowImageBinding) 24 | } 25 | 26 | override fun bindItemViewHolder(viewHolder: CustomViewHolder, position: Int) { 27 | val currentImageModel = getVisibleItemAt(position)!! 28 | //----bind data to view 29 | viewHolder.bind(currentImageModel) 30 | } 31 | 32 | override fun getItemCount() = visibleItems.size 33 | 34 | inner class CustomViewHolder(private val rowImageBinding: RowImageBinding) : RecyclerView.ViewHolder(rowImageBinding.root) { 35 | /** 36 | * bind model data to item 37 | */ 38 | fun bind(overlapImageModel: OverlapImageModel) { 39 | with(rowImageBinding){ 40 | if (isLastVisibleItemItem(adapterPosition)) { 41 | //----set text drawable to show count on last imageview 42 | val text = notVisibleItems.size.toString() 43 | val drawable = TextDrawable.builder() 44 | .beginConfig() 45 | .textColor(Color.WHITE) 46 | .width(90) 47 | .height(90) 48 | .endConfig() 49 | .buildRound(text, R.color.light_green) 50 | imageView.setImageDrawable(drawable) 51 | } else { 52 | Glide.with(imageView.context) 53 | .load(overlapImageModel.imageUrl) 54 | .apply(RequestOptions.circleCropTransform().priority(Priority.HIGH)) 55 | .into(imageView) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/mindinventory/overlapimagegalleyviewsample/models/OverlapImageModel.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample.models 2 | 3 | data class OverlapImageModel(val imageUrl: String) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/mindinventory/overlapimagegalleyviewsample/utils/AppUtils.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample.utils 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | 6 | fun toast(context: Context, message: String) { 7 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show() 8 | } 9 | 10 | 11 | val imagesURLS = arrayOf( 12 | "https://seeklogo.com/images/M/mindinventory-logo-7B0B69A9E5-seeklogo.com.png?v=637713601010000000", 13 | "https://randomuser.me/api/portraits/men/94.jpg", 14 | "https://randomuser.me/api/portraits/women/79.jpg", 15 | "https://randomuser.me/api/portraits/women/44.jpg", 16 | "https://randomuser.me/api/portraits/men/46.jpg", 17 | "https://randomuser.me/api/portraits/women/63.jpg", 18 | "https://randomuser.me/api/portraits/women/76.jpg", 19 | "https://randomuser.me/api/portraits/women/75.jpg", 20 | "https://randomuser.me/api/portraits/women/60.jpg", 21 | "https://randomuser.me/api/portraits/women/43.jpg", 22 | "https://randomuser.me/api/portraits/men/22.jpg", 23 | "https://randomuser.me/api/portraits/women/65.jpg", 24 | "https://randomuser.me/api/portraits/women/72.jpg", 25 | "https://randomuser.me/api/portraits/women/71.jpg", 26 | "https://randomuser.me/api/portraits/women/67.jpg", 27 | "https://randomuser.me/api/portraits/women/26.jpg", 28 | "https://randomuser.me/api/portraits/women/79.jpg", 29 | "https://randomuser.me/api/portraits/women/44.jpg", 30 | "https://randomuser.me/api/portraits/women/63.jpg", 31 | "https://randomuser.me/api/portraits/women/76.jpg", 32 | "https://randomuser.me/api/portraits/women/75.jpg", 33 | "https://randomuser.me/api/portraits/women/60.jpg", 34 | "https://randomuser.me/api/portraits/women/43.jpg", 35 | "https://randomuser.me/api/portraits/women/65.jpg", 36 | "https://randomuser.me/api/portraits/women/72.jpg", 37 | "https://randomuser.me/api/portraits/women/71.jpg", 38 | "https://randomuser.me/api/portraits/women/67.jpg", 39 | "https://randomuser.me/api/portraits/women/26.jpg", 40 | ) 41 | -------------------------------------------------------------------------------- /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/mind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/drawable/mind.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/font/lobster_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/font/lobster_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/space_grotesk_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/font/space_grotesk_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/space_grotesk_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/font/space_grotesk_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/space_grotesk_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/font/space_grotesk_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_overlap_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 14 | 15 | 28 | 29 | 42 | 43 | 53 | 54 | 64 | 65 | 75 | 76 | 77 | 90 | 91 | 103 | 104 | 114 | 115 | 128 | 129 | 141 | 142 | 152 | 153 | 166 | 167 | 179 | 180 | 190 | 191 | 204 | 205 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /app/src/main/res/layout/row_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /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/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ef0849 4 | #ef0849 5 | #FF4081 6 | #6CCF98 7 | #ffffff 8 | #8FAE5D 9 | #ef0849 10 | #E4B1C0 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70dp 4 | 100dp 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | OverlapImageGalleryView 3 | Animation Type 4 | Orientation 5 | Left To Right 6 | Right To Left 7 | 8 | OverlayImageView 9 | Top Bottom 10 | Bottom Top 11 | Left Right 12 | Right Left 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/test/java/com/mindinventory/overlapimagegalleyviewsample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlapimagegalleyviewsample 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 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:7.3.0' 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 10 | } 11 | } 12 | 13 | task clean(type: Delete) { 14 | delete rootProject.buildDir 15 | } 16 | -------------------------------------------------------------------------------- /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 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 21 11:25:39 IST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | group='com.github.mindinventory' 7 | 8 | android { 9 | compileSdkVersion 33 10 | buildFeatures { 11 | viewBinding true 12 | } 13 | 14 | defaultConfig { 15 | minSdkVersion 21 16 | targetSdkVersion 33 17 | versionCode 4 18 | versionName "1.0.4" 19 | 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | implementation 'androidx.core:core-ktx:1.9.0' 43 | implementation fileTree(dir: 'libs', include: ['*.jar']) 44 | implementation 'androidx.appcompat:appcompat:1.5.1' 45 | implementation 'com.google.android.material:material:1.6.1' 46 | } -------------------------------------------------------------------------------- /library/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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/mindinventory/overlaprecylcerview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.mindinventory.overlaprecylcerview.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/adapters/OverlapRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.adapters 2 | 3 | import android.view.ViewGroup 4 | import android.view.animation.AnimationUtils 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.mindinventory.overlaprecylcerview.R 7 | import com.mindinventory.overlaprecylcerview.animations.OverlapRecyclerViewAnimation 8 | import com.mindinventory.overlaprecylcerview.decoration.OverlapRecyclerViewDecoration 9 | import com.mindinventory.overlaprecylcerview.listeners.OverlapRecyclerViewClickListener 10 | import java.util.* 11 | 12 | 13 | abstract class OverlapRecyclerViewAdapter( 14 | private var overlapLimit: Int = 0, 15 | private val overlapWidthInPercentage: Int = 0 16 | ) : RecyclerView.Adapter() { 17 | 18 | //S = Model , T = RecyclerView.ViewHolder 19 | 20 | //----list of visible items 21 | protected var visibleItems: MutableList = ArrayList() 22 | 23 | //----list of all items 24 | private var allItems: MutableList = ArrayList() 25 | 26 | //----list of items that are not visible 27 | protected var notVisibleItems: MutableList = ArrayList() 28 | lateinit var overlapRecyclerViewClickListener: OverlapRecyclerViewClickListener 29 | 30 | //to manage animation 31 | var lastPosition = -1 32 | 33 | //is animation enabled by user 34 | var addAnimation = false 35 | 36 | //default animation 37 | var animationType = OverlapRecyclerViewAnimation.LEFT_RIGHT 38 | 39 | /** 40 | * create view holder of recycler view item 41 | */ 42 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): T { 43 | return createItemViewHolder(parent) 44 | } 45 | 46 | 47 | /** 48 | * bind recycler view item with data 49 | */ 50 | override fun onBindViewHolder(viewHolder: T, position: Int) { 51 | if (addAnimation) { 52 | addAnimation(position, viewHolder) 53 | lastPosition = position 54 | } 55 | bindItemViewHolder(viewHolder, position) 56 | 57 | //----manage item click 58 | if (::overlapRecyclerViewClickListener.isInitialized) 59 | viewHolder!!.itemView.setOnClickListener { 60 | 61 | if (isLastVisibleItemItem(position)) { 62 | overlapRecyclerViewClickListener.onNumberedItemClick(position) 63 | } else { 64 | overlapRecyclerViewClickListener.onNormalItemClicked(position) 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * add animation to recyclerview 71 | */ 72 | private fun addAnimation(position: Int, viewHolder: T) { 73 | val animation = when (animationType) { 74 | OverlapRecyclerViewAnimation.LEFT_RIGHT -> { 75 | AnimationUtils.loadAnimation(viewHolder?.itemView?.context, 76 | if (position > lastPosition) 77 | R.anim.right_from_left 78 | else 79 | R.anim.left_from_right) 80 | } 81 | 82 | OverlapRecyclerViewAnimation.RIGHT_LEFT -> { 83 | AnimationUtils.loadAnimation(viewHolder?.itemView?.context, 84 | if (position > lastPosition) 85 | R.anim.left_from_right 86 | else 87 | R.anim.right_from_left) 88 | } 89 | 90 | OverlapRecyclerViewAnimation.TOP_BOTTOM -> { 91 | AnimationUtils.loadAnimation(viewHolder?.itemView?.context, 92 | if (position > lastPosition) 93 | R.anim.bottom_from_top 94 | else 95 | R.anim.top_from_bottom) 96 | } 97 | 98 | OverlapRecyclerViewAnimation.BOTTOM_UP -> { 99 | AnimationUtils.loadAnimation(viewHolder?.itemView?.context, 100 | if (position > lastPosition) 101 | R.anim.top_from_bottom 102 | else 103 | R.anim.bottom_from_top) 104 | } 105 | } 106 | 107 | viewHolder?.itemView?.startAnimation(animation) 108 | } 109 | 110 | /** 111 | * get count for visible count 112 | */ 113 | override fun getItemCount(): Int { 114 | return visibleItems.size 115 | } 116 | 117 | /** 118 | * abstract method to create custom view holder 119 | */ 120 | protected abstract fun createItemViewHolder(parent: ViewGroup): T 121 | 122 | /** 123 | * abstract method to bind custom data 124 | */ 125 | protected abstract fun bindItemViewHolder(viewHolder: T, position: Int) 126 | 127 | 128 | /** 129 | * get visible item at 130 | */ 131 | fun getVisibleItemAt(position: Int): S? { 132 | return if (position != -1 && position <= overlapLimit) visibleItems[position] else null 133 | } 134 | 135 | /** 136 | * add items to list 137 | */ 138 | fun addAll(items: ArrayList, clearPrevious: Boolean = false) { 139 | if (clearPrevious) { 140 | visibleItems.clear() 141 | notVisibleItems.clear() 142 | } 143 | 144 | if (items.size <= overlapLimit) { 145 | overlapLimit = items.size 146 | } 147 | 148 | for (mImageModel in items) { 149 | if (this.visibleItems.size <= overlapLimit) { 150 | this.visibleItems.add(mImageModel) 151 | } else { 152 | this.notVisibleItems.add(mImageModel) 153 | } 154 | } 155 | 156 | this.allItems.addAll(items) 157 | notifyDataSetChanged() 158 | } 159 | 160 | fun removeItem(pos: Int) { 161 | if (pos < 0) { 162 | visibleItems.removeAt(pos) 163 | notifyItemRemoved(pos) 164 | } 165 | } 166 | 167 | fun isLastVisibleItemItem(position: Int): Boolean { 168 | return position == overlapLimit 169 | } 170 | 171 | fun getItemDecoration(): OverlapRecyclerViewDecoration { 172 | return OverlapRecyclerViewDecoration(overlapLimit, overlapWidthInPercentage) 173 | } 174 | 175 | override fun onViewDetachedFromWindow(holder: T) { 176 | super.onViewDetachedFromWindow(holder) 177 | if (addAnimation) 178 | holder?.itemView?.clearAnimation() 179 | } 180 | } -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/animations/OverlapRecyclerViewAnimation.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.animations 2 | 3 | enum class OverlapRecyclerViewAnimation { 4 | TOP_BOTTOM, 5 | BOTTOM_UP, 6 | LEFT_RIGHT, 7 | RIGHT_LEFT 8 | } -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/decoration/OverlapRecyclerViewDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.decoration 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | class OverlapRecyclerViewDecoration( 9 | private val overlapLimit: Int, 10 | private var overlapPercentage: Int 11 | ) : RecyclerView.ItemDecoration() { 12 | 13 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 14 | val width = view.layoutParams.width 15 | 16 | if (overlapPercentage < 0) { 17 | overlapPercentage = 0 18 | } else if (overlapPercentage > 100) { 19 | overlapPercentage = 100 20 | } 21 | val widthPercentage = (overlapPercentage * width * -1) / 100 22 | 23 | //-----get current position of item 24 | val itemPosition = parent.getChildAdapterPosition(view) 25 | 26 | val isReverseLayout = (parent.layoutManager as LinearLayoutManager).reverseLayout 27 | 28 | //-----avoid first item decoration else it will go of the screen 29 | if (itemPosition == 0) { 30 | return 31 | } else { 32 | 33 | if (isReverseLayout) { 34 | when { 35 | itemPosition <= overlapLimit -> outRect.set(0, 0, widthPercentage, 0) 36 | 37 | else -> outRect.set(0, 0, 0, 0) 38 | } 39 | } else { 40 | //-----apply decoration 41 | when { 42 | itemPosition <= overlapLimit -> outRect.set(widthPercentage, 0, 0, 0) 43 | else -> outRect.set(0, 0, 0, 0) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/listeners/OverlapRecyclerViewClickListener.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.listeners 2 | 3 | interface OverlapRecyclerViewClickListener { 4 | fun onNormalItemClicked(adapterPosition: Int) 5 | 6 | fun onNumberedItemClick(adapterPosition: Int) 7 | } -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/utils/Direction.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.utils 2 | 3 | enum class Direction { 4 | LEFT_TO_RIGHT, 5 | RIGHT_TO_LEFT 6 | } -------------------------------------------------------------------------------- /library/src/main/java/com/mindinventory/overlaprecylcerview/utils/TextDrawable.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview.utils 2 | 3 | import android.graphics.* 4 | import android.graphics.drawable.ShapeDrawable 5 | import android.graphics.drawable.shapes.OvalShape 6 | import android.graphics.drawable.shapes.RectShape 7 | import android.graphics.drawable.shapes.RoundRectShape 8 | 9 | class TextDrawable(builder: Builder) : ShapeDrawable(builder.shape) { 10 | 11 | private val textPaint: Paint 12 | private val borderPaint: Paint 13 | private val text: String? 14 | private val color: Int 15 | private val shape: RectShape? 16 | private val height: Int 17 | private val width: Int 18 | private val fontSize: Int 19 | private val radius: Float 20 | private val borderThickness: Int 21 | 22 | init { 23 | 24 | // shape properties 25 | shape = builder.shape 26 | height = builder.height 27 | width = builder.width 28 | radius = builder.radius 29 | 30 | // text and color 31 | text = if (builder.toUpperCase) builder.text?.uppercase() else builder.text 32 | color = builder.color 33 | 34 | // text paint settings 35 | fontSize = builder.fontSize 36 | textPaint = Paint() 37 | textPaint.color = builder.textColor 38 | textPaint.isAntiAlias = true 39 | textPaint.isFakeBoldText = builder.isBold 40 | textPaint.style = Paint.Style.FILL 41 | textPaint.typeface = builder.font 42 | textPaint.textAlign = Paint.Align.CENTER 43 | textPaint.strokeWidth = builder.borderThickness.toFloat() 44 | 45 | // border paint settings 46 | borderThickness = builder.borderThickness 47 | borderPaint = Paint() 48 | borderPaint.color = getDarkerShade(builder.color) 49 | borderPaint.style = Paint.Style.STROKE 50 | borderPaint.strokeWidth = borderThickness.toFloat() 51 | 52 | // drawable paint color 53 | val paint = paint 54 | paint.color = color 55 | 56 | } 57 | 58 | private fun getDarkerShade(color: Int): Int { 59 | return Color.rgb((SHADE_FACTOR * Color.red(color)).toInt(), 60 | (SHADE_FACTOR * Color.green(color)).toInt(), 61 | (SHADE_FACTOR * Color.blue(color)).toInt()) 62 | } 63 | 64 | override fun draw(canvas: Canvas) { 65 | super.draw(canvas) 66 | val r = bounds 67 | 68 | 69 | // draw border 70 | if (borderThickness > 0) { 71 | drawBorder(canvas) 72 | } 73 | 74 | val count = canvas.save() 75 | canvas.translate(r.left.toFloat(), r.top.toFloat()) 76 | 77 | // draw text 78 | val width = if (this.width < 0) r.width() else this.width 79 | val height = if (this.height < 0) r.height() else this.height 80 | val fontSize = if (this.fontSize < 0) Math.min(width, height) / 2 else this.fontSize 81 | textPaint.textSize = fontSize.toFloat() 82 | canvas.drawText(text!!, (width / 2).toFloat(), height / 2 - (textPaint.descent() + textPaint.ascent()) / 2, textPaint) 83 | 84 | canvas.restoreToCount(count) 85 | 86 | } 87 | 88 | private fun drawBorder(canvas: Canvas) { 89 | val rect = RectF(bounds) 90 | rect.inset((borderThickness / 2).toFloat(), (borderThickness / 2).toFloat()) 91 | 92 | when (shape) { 93 | is OvalShape -> canvas.drawOval(rect, borderPaint) 94 | is RoundRectShape -> canvas.drawRoundRect(rect, radius, radius, borderPaint) 95 | else -> canvas.drawRect(rect, borderPaint) 96 | } 97 | } 98 | 99 | override fun setAlpha(alpha: Int) { 100 | textPaint.alpha = alpha 101 | } 102 | 103 | override fun setColorFilter(cf: ColorFilter?) { 104 | textPaint.colorFilter = cf 105 | } 106 | 107 | override fun getOpacity(): Int { 108 | return PixelFormat.TRANSLUCENT 109 | } 110 | 111 | override fun getIntrinsicWidth(): Int { 112 | return width 113 | } 114 | 115 | override fun getIntrinsicHeight(): Int { 116 | return height 117 | } 118 | 119 | class Builder : IConfigBuilder, IShapeBuilder, IBuilder { 120 | 121 | var text: String? = null 122 | 123 | var color: Int = 0 124 | 125 | var borderThickness: Int = 0 126 | 127 | var borderColor: Int = 0 128 | 129 | var width: Int = 0 130 | 131 | var height: Int = 0 132 | 133 | var font: Typeface? = null 134 | 135 | var shape: RectShape? = null 136 | 137 | var textColor: Int = 0 138 | 139 | var fontSize: Int = 0 140 | 141 | var isBold: Boolean = false 142 | 143 | var toUpperCase: Boolean = false 144 | 145 | var radius: Float = 0.toFloat() 146 | 147 | init { 148 | text = "" 149 | color = Color.GRAY 150 | textColor = Color.WHITE 151 | borderThickness = 0 152 | borderColor = 0 153 | width = -1 154 | height = -1 155 | shape = RectShape() 156 | font = Typeface.create("sans-serif-light", Typeface.NORMAL) 157 | fontSize = -1 158 | isBold = false 159 | toUpperCase = false 160 | } 161 | 162 | override fun width(width: Int): IConfigBuilder { 163 | this.width = width 164 | return this 165 | } 166 | 167 | override fun height(height: Int): IConfigBuilder { 168 | this.height = height 169 | return this 170 | } 171 | 172 | override fun textColor(color: Int): IConfigBuilder { 173 | this.textColor = color 174 | return this 175 | } 176 | 177 | override fun withBorder(thickness: Int): IConfigBuilder { 178 | this.borderThickness = thickness 179 | return this 180 | } 181 | 182 | override fun borderColor(color: Int): IConfigBuilder { 183 | this.borderColor= color 184 | return this 185 | } 186 | 187 | override fun useFont(font: Typeface): IConfigBuilder { 188 | this.font = font 189 | return this 190 | } 191 | 192 | override fun fontSize(size: Int): IConfigBuilder { 193 | this.fontSize = size 194 | return this 195 | } 196 | 197 | override fun bold(): IConfigBuilder { 198 | this.isBold = true 199 | return this 200 | } 201 | 202 | override fun toUpperCase(): IConfigBuilder { 203 | this.toUpperCase = true 204 | return this 205 | } 206 | 207 | override fun beginConfig(): IConfigBuilder { 208 | return this 209 | } 210 | 211 | override fun endConfig(): IShapeBuilder { 212 | return this 213 | } 214 | 215 | override fun rect(): IBuilder { 216 | this.shape = RectShape() 217 | return this 218 | } 219 | 220 | override fun round(): IBuilder { 221 | this.shape = OvalShape() 222 | return this 223 | } 224 | 225 | override fun roundRect(radius: Int): IBuilder { 226 | this.radius = radius.toFloat() 227 | val radii = floatArrayOf(radius.toFloat(), radius.toFloat(), radius.toFloat(), radius.toFloat(), radius.toFloat(), radius.toFloat(), radius.toFloat(), radius.toFloat()) 228 | this.shape = RoundRectShape(radii, null, null) 229 | return this 230 | } 231 | 232 | override fun buildRect(text: String, color: Int): TextDrawable { 233 | rect() 234 | return build(text, color) 235 | } 236 | 237 | override fun buildRoundRect(text: String, color: Int, radius: Int): TextDrawable { 238 | roundRect(radius) 239 | return build(text, color) 240 | } 241 | 242 | override fun buildRound(text: String, color: Int): TextDrawable { 243 | round() 244 | return build(text, color) 245 | } 246 | 247 | override fun build(text: String, color: Int): TextDrawable { 248 | this.color = color 249 | this.text = text 250 | return TextDrawable(this) 251 | } 252 | } 253 | 254 | interface IConfigBuilder { 255 | fun width(width: Int): IConfigBuilder 256 | 257 | fun height(height: Int): IConfigBuilder 258 | 259 | fun textColor(color: Int): IConfigBuilder 260 | 261 | fun withBorder(thickness: Int): IConfigBuilder 262 | 263 | fun borderColor(color: Int): IConfigBuilder 264 | 265 | fun useFont(font: Typeface): IConfigBuilder 266 | 267 | fun fontSize(size: Int): IConfigBuilder 268 | 269 | fun bold(): IConfigBuilder 270 | 271 | fun toUpperCase(): IConfigBuilder 272 | 273 | fun endConfig(): IShapeBuilder 274 | } 275 | 276 | interface IBuilder { 277 | 278 | fun build(text: String, color: Int): TextDrawable 279 | } 280 | 281 | interface IShapeBuilder { 282 | 283 | fun beginConfig(): IConfigBuilder 284 | 285 | fun rect(): IBuilder 286 | 287 | fun round(): IBuilder 288 | 289 | fun roundRect(radius: Int): IBuilder 290 | 291 | fun buildRect(text: String, color: Int): TextDrawable 292 | 293 | fun buildRoundRect(text: String, color: Int, radius: Int): TextDrawable 294 | 295 | fun buildRound(text: String, color: Int): TextDrawable 296 | } 297 | 298 | companion object { 299 | private val SHADE_FACTOR = 0.9f 300 | 301 | fun builder(): IShapeBuilder { 302 | return Builder() 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /library/src/main/res/anim/bottom_from_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/anim/left_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/anim/right_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/anim/top_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1000 4 | -------------------------------------------------------------------------------- /library/src/test/java/com/mindinventory/overlaprecylcerview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.mindinventory.overlaprecylcerview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /media/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/media/.gitkeep -------------------------------------------------------------------------------- /media/OverlapImageView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/overlap-image-gallery-view/bf373885b3b4598d4b2bcd51f4ce8abe6c7c051d/media/OverlapImageView.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | rootProject.name = "OverlapImageGalleryView" 17 | include ':app',':library' 18 | --------------------------------------------------------------------------------