├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── img_1.jpg │ │ │ │ ├── img_2.jpg │ │ │ │ ├── img_3.jpg │ │ │ │ ├── img_4.jpg │ │ │ │ ├── img_5.jpg │ │ │ │ ├── img_6.jpg │ │ │ │ ├── avatar.png │ │ │ │ └── ic_launcher_background.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 │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── item_footer.xml │ │ │ │ ├── item_header.xml │ │ │ │ ├── item_image_and_content.xml │ │ │ │ ├── item_change_profile_picture.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item_binding_change_profile_picture.xml │ │ │ │ └── item_images.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── recyclerviewbuilder │ │ │ │ └── sample │ │ │ │ ├── models │ │ │ │ ├── User.kt │ │ │ │ ├── Post.kt │ │ │ │ ├── ImageAndContent.kt │ │ │ │ ├── BindingProfilePicture.kt │ │ │ │ ├── ProfilePicture.kt │ │ │ │ └── Images.kt │ │ │ │ ├── viewitems │ │ │ │ ├── FooterViewItem.kt │ │ │ │ ├── HeaderViewItem.kt │ │ │ │ ├── ProfilePictureViewItem.kt │ │ │ │ ├── ImageAndContentViewItem.kt │ │ │ │ ├── ImagesViewItem.kt │ │ │ │ └── ProfilePictureBindingViewItem.kt │ │ │ │ ├── extensions │ │ │ │ └── imageview │ │ │ │ │ └── LoadImage.kt │ │ │ │ ├── BindingActivity.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── recyclerviewbuilder │ │ │ └── sample │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── recyclerviewbuilder │ │ └── sample │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── recyclerviewbuilder ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── recyclerviewbuilder │ │ └── library │ │ ├── ViewItemRepresentable.kt │ │ ├── ViewItemsObserver.kt │ │ ├── BaseAdapterInterface.kt │ │ ├── ViewItem.kt │ │ ├── RecyclerViewBuilderFactory.kt │ │ ├── ScrollListener.kt │ │ ├── LinearRecyclerViewBuilder.kt │ │ ├── GridRecyclerViewBuilder.kt │ │ ├── Adapter.kt │ │ ├── BindingAdapter.kt │ │ ├── AbstractRecyclerViewBuilder.kt │ │ └── RecyclerViewBuilder.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── changelogs ├── Changelog-v2.0.1.md ├── Changelog-v2.0.0.md ├── Changelog-v1.1.1.md ├── Changelog-v1.2.1.md └── Changelog-v1.2.0.md ├── LICENSE ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /recyclerviewbuilder/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':recyclerviewbuilder' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RecyclerViewBuilder 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_4.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_5.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/img_6.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/drawable/avatar.png -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/User.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | class User(var name: String) -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RecyclerViewBuilder 3 | 4 | -------------------------------------------------------------------------------- /changelogs/Changelog-v2.0.1.md: -------------------------------------------------------------------------------- 1 | ## What's new in v2.0.1 2 | 3 | #### _ViewItemRepresentable typealias and ViewItemArrayList typealias are removed 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/Post.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | open class Post(var content: String = "", var time: String) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrreda1995/recyclerview-builder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/ViewItemRepresentable.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | interface ViewItemRepresentable { 4 | val viewItem: AbstractViewItem 5 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 23 10:22:44 EET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/ViewItemsObserver.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | data class ViewItemsObserver( 4 | var viewItemsArrayList: ArrayList = arrayListOf(), 5 | var clearsOnSet: Boolean = false, 6 | var appendToEnd: Boolean = true 7 | ) -------------------------------------------------------------------------------- /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/java/com/recyclerviewbuilder/sample/viewitems/FooterViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import com.recyclerviewbuilder.library.ViewItem 4 | import com.recyclerviewbuilder.library.ViewItemRepresentable 5 | import com.recyclerviewbuilder.sample.R 6 | 7 | class FooterViewItem : ViewItem(R.layout.item_footer) -------------------------------------------------------------------------------- /changelogs/Changelog-v2.0.0.md: -------------------------------------------------------------------------------- 1 | ## What's new in v2.0.0 2 | 3 | ### - Now, no need to specify the the generic data type as ViewItemRepresentable in AbstractViewItem abstract or its sub abstract classes (ViewItem or even BindingViewItem) 4 | 5 | * Note that, you have to remove the ViewItemRepresentable generic data type from all your previous ViewItems starting from version 2.0.0 6 | * See the updated documentation 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/viewitems/HeaderViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import android.view.View 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.sample.R 6 | 7 | class HeaderViewItem: ViewItem(R.layout.item_header) { 8 | 9 | override fun bind(itemView: View, viewItemPosition: Int) { 10 | // do something 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/recyclerviewbuilder/sample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample 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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/ImageAndContent.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | import com.recyclerviewbuilder.library.AbstractViewItem 4 | import com.recyclerviewbuilder.library.ViewItemRepresentable 5 | import com.recyclerviewbuilder.sample.R 6 | import com.recyclerviewbuilder.sample.viewitems.ImageAndContentViewItem 7 | 8 | class ImageAndContent( 9 | var user: User = User("Robert Downey"), 10 | val image: Int = R.drawable.img_3 11 | ) : ViewItemRepresentable, Post("Van Gogh, Died: 29 July 1890", "Mar 9 at 8:15 PM") { 12 | 13 | override val viewItem: AbstractViewItem 14 | get() = ImageAndContentViewItem(this) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/BindingProfilePicture.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | import com.recyclerviewbuilder.library.AbstractViewItem 4 | import com.recyclerviewbuilder.library.ViewItemRepresentable 5 | import com.recyclerviewbuilder.sample.R 6 | import com.recyclerviewbuilder.sample.viewitems.ProfilePictureBindingViewItem 7 | 8 | class BindingProfilePicture( 9 | var user: User = User("Cristian Bale"), 10 | var profilePicture: Int = R.drawable.avatar 11 | ) : ViewItemRepresentable, Post(time = "Mar 9 at 10:05 PM") { 12 | override val viewItem: AbstractViewItem 13 | get() = ProfilePictureBindingViewItem(this) 14 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/ProfilePicture.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | import com.recyclerviewbuilder.library.AbstractViewItem 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.library.ViewItemRepresentable 6 | import com.recyclerviewbuilder.sample.R 7 | import com.recyclerviewbuilder.sample.viewitems.ProfilePictureViewItem 8 | 9 | class ProfilePicture( 10 | var user: User = User("Cristian Bale"), 11 | var profilePicture: Int = R.drawable.avatar 12 | ) : ViewItemRepresentable, Post(time = "Mar 9 at 10:05 PM") { 13 | override val viewItem: AbstractViewItem 14 | get() = ProfilePictureViewItem(this) 15 | } -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/BaseAdapterInterface.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.View 4 | 5 | interface BaseAdapterInterface { 6 | var viewItemsArrayList: ArrayList 7 | 8 | fun notifyDataSetChanged() 9 | fun notifyItemChanged(position: Int) 10 | fun notifyItemInserted(position: Int) 11 | fun notifyItemMoved(fromPosition: Int, toPosition: Int) 12 | fun notifyItemRemoved(position: Int) 13 | fun setOnItemClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) 14 | fun setOnItemLongClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) 15 | fun notifyItemRangeRemoved(positionStart: Int, itemCount: Int) 16 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/recyclerviewbuilder/sample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.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.recyclerviewbuilder.sample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/models/Images.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.models 2 | 3 | import com.recyclerviewbuilder.library.AbstractViewItem 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.library.ViewItemRepresentable 6 | import com.recyclerviewbuilder.sample.R 7 | import com.recyclerviewbuilder.sample.viewitems.ImagesViewItem 8 | 9 | class Images( 10 | var user: User = User("Ben Affleck"), 11 | var images: ArrayList = arrayListOf( 12 | R.drawable.img_1, 13 | R.drawable.img_2, 14 | R.drawable.img_3, 15 | R.drawable.img_4, 16 | R.drawable.img_6 17 | ) 18 | ) : ViewItemRepresentable, Post(time = "Mar 9 at 9:10 PM") { 19 | override val viewItem: AbstractViewItem 20 | get() = ImagesViewItem(this) 21 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /recyclerviewbuilder/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 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/ViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.View 4 | import androidx.databinding.ViewDataBinding 5 | 6 | abstract class AbstractViewItem( 7 | open val layoutResourceId: Int, 8 | open val dataModel: ViewItemRepresentable? = null 9 | ) 10 | 11 | abstract class ViewItem( 12 | override val layoutResourceId: Int, override val dataModel: ViewItemRepresentable? = null 13 | ) : AbstractViewItem(layoutResourceId, dataModel) { 14 | open fun bind(itemView: View, viewItemPosition: Int) {} 15 | } 16 | 17 | abstract class BindingViewItem( 18 | override val layoutResourceId: Int, override val dataModel: ViewItemRepresentable? = null 19 | ) : AbstractViewItem(layoutResourceId, dataModel) where T : ViewDataBinding { 20 | open fun bind(binding: T, viewItemPosition: Int) {} 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/viewitems/ProfilePictureViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import android.view.View 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.library.ViewItemRepresentable 6 | import com.recyclerviewbuilder.sample.R 7 | import com.recyclerviewbuilder.sample.extensions.imageview.load 8 | import com.recyclerviewbuilder.sample.models.ProfilePicture 9 | import kotlinx.android.synthetic.main.item_change_profile_picture.view.* 10 | 11 | class ProfilePictureViewItem( 12 | private val model: ProfilePicture 13 | ) : ViewItem(R.layout.item_change_profile_picture, model) { 14 | 15 | override fun bind(itemView: View, viewItemPosition: Int) { 16 | itemView.userName.text = model.user.name 17 | itemView.time.text = model.time 18 | 19 | itemView.img.load(model.profilePicture) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/extensions/imageview/LoadImage.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.extensions.imageview 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | 6 | enum class ImageViewMode { 7 | NORMAL, CENTER_CROP, FIT_CENTER 8 | } 9 | 10 | fun ImageView.load(url: String, mode: ImageViewMode = ImageViewMode.NORMAL) { 11 | val load = Glide.with(context).load(url) 12 | when (mode) { 13 | ImageViewMode.NORMAL -> load.into(this) 14 | ImageViewMode.CENTER_CROP -> load.centerCrop().into(this) 15 | ImageViewMode.FIT_CENTER -> load.fitCenter().into(this) 16 | } 17 | } 18 | 19 | fun ImageView.load(resourceId: Int, mode: ImageViewMode = ImageViewMode.NORMAL) { 20 | val load = Glide.with(context).load(resourceId) 21 | when (mode) { 22 | ImageViewMode.NORMAL -> load.into(this) 23 | ImageViewMode.CENTER_CROP -> load.centerCrop().into(this) 24 | ImageViewMode.FIT_CENTER -> load.fitCenter().into(this) 25 | } 26 | } -------------------------------------------------------------------------------- /changelogs/Changelog-v1.1.1.md: -------------------------------------------------------------------------------- 1 | ## What's new in v1.1.1 2 | 3 | * setPaginationFeatureEnabled function has been renamed to setPaginationEnabled. 4 | * Fix a bug with LiveData and how it interacts with the lifecycle owner, so, in bindViewItems method; you will pass lifecycleOwner instead of lifecycle 5 | * Introduce DataBinding Support with a new ViewItem type BindingViewItem 6 | * Added the ability to set header / footer for the recyclerview 7 | * The ViewItem now accepts optional models which means you can create a ViewItem without a model at all (can be used for headers / footers for example) 8 | * The bind function in ViewItem / BindingViewItem is not abstract anymore and has default implementation you can override it and Android Studio will provide super.bind call which is completely useless and unneeded so feel free to delete it. 9 | * ViewItem / BindingViewItem will have their position on the "bind" function enabling you to find out quickly which item was interacted with via the position (e.g actions for custom buttons in items) 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/viewitems/ImageAndContentViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import android.view.View 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.library.ViewItemRepresentable 6 | import com.recyclerviewbuilder.sample.R 7 | import com.recyclerviewbuilder.sample.extensions.imageview.ImageViewMode 8 | import com.recyclerviewbuilder.sample.extensions.imageview.load 9 | import com.recyclerviewbuilder.sample.models.ImageAndContent 10 | import kotlinx.android.synthetic.main.item_image_and_content.view.* 11 | 12 | class ImageAndContentViewItem( 13 | private val model: ImageAndContent 14 | ) : ViewItem(R.layout.item_image_and_content, model) { 15 | 16 | override fun bind(itemView: View, viewItemPosition: Int) { 17 | itemView.userName.text = model.user.name 18 | itemView.time.text = model.time 19 | itemView.content.text = model.content 20 | itemView.img.load(model.image, ImageViewMode.CENTER_CROP) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Amr Reda 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/viewitems/ImagesViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import android.view.View 4 | import com.recyclerviewbuilder.library.ViewItem 5 | import com.recyclerviewbuilder.library.ViewItemRepresentable 6 | import com.recyclerviewbuilder.sample.R 7 | import com.recyclerviewbuilder.sample.extensions.imageview.ImageViewMode 8 | import com.recyclerviewbuilder.sample.extensions.imageview.load 9 | import com.recyclerviewbuilder.sample.models.Images 10 | import kotlinx.android.synthetic.main.item_images.view.* 11 | 12 | class ImagesViewItem( 13 | private val model: Images 14 | ) : ViewItem(R.layout.item_images, model) { 15 | 16 | override fun bind(itemView: View, viewItemPosition: Int) { 17 | itemView.userName.text = model.user.name 18 | itemView.time.text = model.time 19 | 20 | itemView.img_1.load(model.images[0], ImageViewMode.CENTER_CROP) 21 | itemView.img_2.load(model.images[1], ImageViewMode.CENTER_CROP) 22 | itemView.img_3.load(model.images[2], ImageViewMode.CENTER_CROP) 23 | itemView.img_4.load(model.images[3], ImageViewMode.CENTER_CROP) 24 | itemView.img_5.load(model.images[4], ImageViewMode.CENTER_CROP) 25 | } 26 | } -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/RecyclerViewBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | class RecyclerViewBuilderFactory(private val recyclerView: RecyclerView) { 6 | 7 | fun buildWithGridLayout( 8 | isDataBindingEnabled: Boolean = false, 9 | columnCount: Int, 10 | orientation: Int? = null, 11 | reverseLayout: Boolean? = null, 12 | canScrollHorizontally: Boolean? = null, 13 | canScrollVertically: Boolean? = null 14 | ): GridRecyclerViewBuilder { 15 | return GridRecyclerViewBuilder(recyclerView, isDataBindingEnabled, columnCount, orientation, reverseLayout, canScrollHorizontally, canScrollVertically) 16 | } 17 | 18 | fun buildWithLinearLayout( 19 | isDataBindingEnabled: Boolean = false, 20 | orientation: Int? = null, 21 | reverseLayout: Boolean? = null, 22 | canScrollHorizontally: Boolean? = null, 23 | canScrollVertically: Boolean? = null 24 | ): LinearRecyclerViewBuilder { 25 | return LinearRecyclerViewBuilder(recyclerView, isDataBindingEnabled, orientation, reverseLayout, canScrollHorizontally, canScrollVertically) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/viewitems/ProfilePictureBindingViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample.viewitems 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.recyclerviewbuilder.library.BindingViewItem 6 | import com.recyclerviewbuilder.library.ViewItemRepresentable 7 | import com.recyclerviewbuilder.sample.R 8 | import com.recyclerviewbuilder.sample.databinding.ItemBindingChangeProfilePictureBinding 9 | import com.recyclerviewbuilder.sample.extensions.imageview.ImageViewMode 10 | import com.recyclerviewbuilder.sample.extensions.imageview.load 11 | import com.recyclerviewbuilder.sample.models.BindingProfilePicture 12 | 13 | class ProfilePictureBindingViewItem( 14 | val model: BindingProfilePicture 15 | ) : BindingViewItem( 16 | R.layout.item_binding_change_profile_picture, model 17 | ) { 18 | override fun bind(binding: ItemBindingChangeProfilePictureBinding, viewItemPosition: Int) { 19 | binding.model = model 20 | } 21 | 22 | companion object { 23 | @JvmStatic 24 | @BindingAdapter("android:src") 25 | fun setImageView(imageView: ImageView, imageResourceId: Int) { 26 | imageView.load(imageResourceId) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /recyclerviewbuilder/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | dataBinding { 9 | enabled = true 10 | } 11 | 12 | compileSdkVersion 28 13 | defaultConfig { 14 | minSdkVersion 15 15 | targetSdkVersion 28 16 | versionCode 1 17 | versionName "1.0" 18 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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 | 33 | implementation 'com.android.support:appcompat-v7:28.0.0' 34 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 35 | 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 39 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 40 | } 41 | repositories { 42 | mavenCentral() 43 | } 44 | -------------------------------------------------------------------------------- /changelogs/Changelog-v1.2.1.md: -------------------------------------------------------------------------------- 1 | ## What's new in v1.2.1 2 | 3 | ### - Bugs fixed 4 | 5 | 6 | ### - Performance enhanced by setting the adapter to use stable ids for each view item 7 | * Note that, you may to override "hashCode" function in your view item to take the advantage of this enhancement correctly like this 8 | ```kotlin 9 | class ProductViewItem(private val model: Product) : ViewItem(R.layout.item_product, model) { 10 | 11 | //something like this 12 | override fun hashCode(): Int { 13 | return model.id 14 | } 15 | 16 | override fun bind(itemView: View, viewItemPosition: Int) { 17 | itemView.titleTextView.text = model.title 18 | } 19 | } 20 | ``` 21 | 22 | 23 | ### - New function was added 24 | 25 | 26 | #### indexOf 27 | * A function returns the index of the given viewItemRepresentable 28 | * Note to use this function successfully, you have to override "equals" function in your model like this 29 | ```kotlin 30 | class Product(val id: Int, val title: String, val date: String): ViewItemRepresentable { 31 | 32 | override val viewItem: AbstractViewItem 33 | get() = ProductViewItem(this) 34 | 35 | //something like this 36 | override fun equals(other: Any?): Boolean { 37 | if (other is Product) { 38 | return other.id == this.id 39 | } 40 | return false 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/ScrollListener.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | fun RecyclerView.addScrollListenerForPagination( 6 | orientation: Int? = null, 7 | reverseLayout: Boolean? = null, 8 | block: () -> Unit 9 | ) { 10 | this.addOnScrollListener(object : RecyclerView.OnScrollListener() { 11 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 12 | super.onScrolled(recyclerView, dx, dy) 13 | 14 | // 1 = down; -1 = up; 0 = up or down 15 | var direction = 1 16 | 17 | reverseLayout?.let { 18 | if (it) { 19 | direction = -1 20 | } 21 | } 22 | 23 | orientation?.let { 24 | when (it) { 25 | RecyclerView.VERTICAL -> { 26 | if (!recyclerView.canScrollVertically(direction)) { 27 | block() 28 | } 29 | } 30 | 31 | RecyclerView.HORIZONTAL -> { 32 | if (!recyclerView.canScrollHorizontally(direction)) { 33 | block() 34 | } 35 | } 36 | } 37 | } ?: run { 38 | if (!recyclerView.canScrollVertically(direction)) { 39 | block() 40 | } 41 | } 42 | } 43 | }) 44 | } -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/LinearRecyclerViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class LinearRecyclerViewBuilder( 7 | recyclerView: RecyclerView, 8 | isDataBindingEnabled: Boolean, 9 | orientation: Int? = null, 10 | reverseLayout: Boolean? = null, 11 | canScrollHorizontally: Boolean? = null, 12 | canScrollVertically: Boolean? = null 13 | ) : RecyclerViewBuilder(recyclerView, isDataBindingEnabled, orientation, reverseLayout) { 14 | 15 | init { 16 | val layoutManager = object : LinearLayoutManager(recyclerView.context) { 17 | 18 | override fun canScrollHorizontally(): Boolean { 19 | canScrollHorizontally?.let { 20 | return it 21 | } ?: run { 22 | return super.canScrollHorizontally() 23 | } 24 | } 25 | 26 | override fun canScrollVertically(): Boolean { 27 | canScrollVertically?.let { 28 | return it 29 | } ?: run { 30 | return super.canScrollVertically() 31 | } 32 | } 33 | } 34 | 35 | orientation?.let { 36 | layoutManager.orientation = it 37 | } 38 | 39 | reverseLayout?.let { 40 | layoutManager.reverseLayout = it 41 | } 42 | 43 | recyclerView.layoutManager = layoutManager 44 | } 45 | } -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/GridRecyclerViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import androidx.recyclerview.widget.GridLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class GridRecyclerViewBuilder( 7 | recyclerView: RecyclerView, 8 | isDataBindingEnabled: Boolean, 9 | columnCount: Int, 10 | orientation: Int? = null, 11 | reverseLayout: Boolean? = null, 12 | canScrollHorizontally: Boolean? = null, 13 | canScrollVertically: Boolean? = null 14 | ) : RecyclerViewBuilder(recyclerView, isDataBindingEnabled, orientation, reverseLayout, columnCount) { 15 | 16 | init { 17 | val layoutManager = object : GridLayoutManager(recyclerView.context, columnCount) { 18 | 19 | override fun canScrollHorizontally(): Boolean { 20 | canScrollHorizontally?.let { 21 | return it 22 | } ?: run { 23 | return super.canScrollHorizontally() 24 | } 25 | } 26 | 27 | override fun canScrollVertically(): Boolean { 28 | canScrollVertically?.let { 29 | return it 30 | } ?: run { 31 | return super.canScrollVertically() 32 | } 33 | } 34 | } 35 | 36 | orientation?.let { 37 | layoutManager.orientation = it 38 | } 39 | 40 | reverseLayout?.let { 41 | layoutManager.reverseLayout = it 42 | } 43 | 44 | recyclerView.layoutManager = layoutManager 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/BindingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.MutableLiveData 6 | import com.recyclerviewbuilder.library.RecyclerViewBuilder 7 | import com.recyclerviewbuilder.library.RecyclerViewBuilderFactory 8 | import com.recyclerviewbuilder.library.ViewItemRepresentable 9 | import com.recyclerviewbuilder.library.ViewItemsObserver 10 | import com.recyclerviewbuilder.sample.models.BindingProfilePicture 11 | import kotlinx.android.synthetic.main.activity_main.* 12 | import kotlinx.coroutines.* 13 | 14 | class BindingActivity : AppCompatActivity() { 15 | 16 | private lateinit var recyclerViewBuilder: RecyclerViewBuilder 17 | 18 | private val viewItems = MutableLiveData() 19 | 20 | private lateinit var models: ArrayList 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_main) 25 | 26 | models = arrayListOf() 27 | models.add(BindingProfilePicture()) 28 | 29 | recyclerViewBuilder = RecyclerViewBuilderFactory(recyclerView) 30 | .buildWithLinearLayout(true) 31 | .bindViewItems(this, viewItems) 32 | 33 | viewItems.value = ViewItemsObserver(ArrayList(models.map { it.viewItem })) 34 | } 35 | 36 | override fun onPause() { 37 | super.onPause() 38 | 39 | CoroutineScope(Dispatchers.IO).launch { 40 | delay(1000) 41 | 42 | withContext(Dispatchers.Main) { 43 | viewItems.value = ViewItemsObserver(ArrayList(models.map { it.viewItem })) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | android { 10 | dataBinding { 11 | enabled = true 12 | } 13 | 14 | compileSdkVersion 28 15 | defaultConfig { 16 | applicationId "com.recyclerviewbuilder.sample" 17 | minSdkVersion 15 18 | targetSdkVersion 28 19 | versionCode 1 20 | versionName "1.0" 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | } 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 34 | implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' 35 | implementation 'androidx.core:core-ktx:1.1.0-alpha05' 36 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 37 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 38 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' 39 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' 40 | implementation 'com.github.bumptech.glide:glide:4.9.0' 41 | implementation "androidx.lifecycle:lifecycle-livedata:2.0.0" 42 | 43 | implementation project(path:':recyclerviewbuilder') 44 | 45 | annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' 46 | 47 | testImplementation 'junit:junit:4.12' 48 | androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' 49 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' 50 | } 51 | -------------------------------------------------------------------------------- /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/layout/item_image_and_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | 42 | 49 | 50 | 59 | 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | class Adapter : RecyclerView.Adapter(), BaseAdapterInterface { 9 | override var viewItemsArrayList = ArrayList() 10 | 11 | private var onItemClickBlock: ((View, ViewItemRepresentable?, Int) -> Unit)? = null 12 | private var onItemLongClickBlock: ((View, ViewItemRepresentable?, Int) -> Unit)? = null 13 | 14 | init { 15 | setHasStableIds(true) 16 | } 17 | 18 | override fun getItemId(position: Int): Long { 19 | return viewItemsArrayList[position].hashCode().toLong() 20 | } 21 | 22 | override fun setOnItemClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) { 23 | onItemClickBlock = block 24 | } 25 | 26 | override fun setOnItemLongClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) { 27 | onItemLongClickBlock = block 28 | } 29 | 30 | override fun getItemViewType(position: Int): Int { 31 | if (viewItemsArrayList[position] !is ViewItem) { 32 | throw Throwable("ViewItem type mismatch, item should be of type ViewItem") 33 | } 34 | 35 | return viewItemsArrayList[position].layoutResourceId 36 | } 37 | 38 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 39 | return ViewHolder( 40 | LayoutInflater.from(parent.context).inflate(viewType, parent, false) 41 | ) 42 | } 43 | 44 | override fun getItemCount(): Int { 45 | return viewItemsArrayList.size 46 | } 47 | 48 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 49 | (viewItemsArrayList[holder.adapterPosition] as ViewItem).bind(holder.itemView, holder.adapterPosition) 50 | 51 | holder.itemView.setOnClickListener { 52 | onItemClickBlock?.invoke( 53 | holder.itemView, 54 | viewItemsArrayList[holder.adapterPosition].dataModel, 55 | holder.adapterPosition 56 | ) 57 | } 58 | 59 | holder.itemView.setOnLongClickListener { 60 | onItemLongClickBlock?.invoke( 61 | holder.itemView, 62 | viewItemsArrayList[holder.adapterPosition].dataModel, 63 | holder.adapterPosition 64 | ) 65 | true 66 | } 67 | } 68 | 69 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 70 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_change_profile_picture.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 30 | 31 | 43 | 44 | 53 | 54 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 26 | 27 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /changelogs/Changelog-v1.2.0.md: -------------------------------------------------------------------------------- 1 | ## What's new in v1.2.0 2 | 3 | ### New functions has been added 4 | 5 | 6 | #### isAdapterEmpty 7 | * A function returns whether the adapter is empty or not 8 | 9 | 10 | #### scrollTo 11 | * A function that accepts viewItemIndex, smoothScroll (true by default) and scrolls to the specific item smoothly 12 | ```kotlin 13 | recyclerViewBuilder.scrollTo(viewItemIndex = 7, smoothScroll = false) 14 | ``` 15 | 16 | 17 | #### notifyViewItemChanged 18 | * A function that notifies the builder that a certain item was changed and should be reloaded 19 | ```kotlin 20 | recyclerViewBuilder.notifyViewItemChanged(atIndex = 8) 21 | ``` 22 | 23 | 24 | 25 | #### modifyViewItem<`T`> 26 | * Where T is one of your models (ViewItemRepresentable), It accepts the index, a lambda function (which has the item as a parameter) whatever changes are done to the items takes effect immediately 27 | ```kotlin 28 | recyclerViewBuilder.modifyViewItem(atIndex = 5) { 29 | (it as Product).date = "22/4/2019" 30 | } 31 | ``` 32 | or 33 | ```kotlin 34 | recyclerViewBuilder.modifyViewItem(atIndex = 5) { 35 | it.date = "22/4/2019" 36 | } 37 | ``` 38 | 39 | 40 | 41 | #### modifyViewItems<`T`> 42 | * Where T is one of your models (ViewItemRepresentable), It accepts the indices, a lambda function (which has the item list as a parameter) whatever changes are done to the items takes effect immediately 43 | ```kotlin 44 | recyclerViewBuilder.modifyViewItems(atIndices = 4, 13, 20) { 45 | it.forEach { 46 | when(it) { 47 | is Product -> it.title = "New product" 48 | is SomeViewItemRepresentableClass -> it.isChecked = true //for example 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | 55 | 56 | #### insertViewItem 57 | * A function that accepts atIndex, viewItem and inserts the item at specified index 58 | ```kotlin 59 | recyclerViewBuilder.insertViewItem(atIndex = 4, viewItem = Product().viewItem) 60 | ``` 61 | * Note that, if you already have a header or a footer set to the builder and you decide to insert a new view item at their index; the builder by default will insert the new view item after the header or before the footer 62 | 63 | 64 | 65 | #### switchViewItem 66 | * A function that accepts ofIndex, withIndex and replaces the two items 67 | ```kotlin 68 | recyclerViewBuilder.switchViewItem(ofIndex = 12, withIndex = 20) 69 | ``` 70 | * Note that, the builder by default prevents you from switching the header or the footer if you decide to switch some view item with them 71 | 72 | 73 | 74 | #### removeViewItem 75 | * A function that accepts atIndex and removes it from the recylcerview 76 | ```kotlin 77 | recyclerViewBuilder.removeViewItem(atIndex = 30) 78 | ``` 79 | * Note that, you can also use this function to remove the header or the footer (same as setHeader(null) and setFooter(null)) 80 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/BindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.databinding.ViewDataBinding 8 | import androidx.recyclerview.widget.RecyclerView 9 | 10 | class BindingAdapter : RecyclerView.Adapter(), BaseAdapterInterface { 11 | override var viewItemsArrayList = ArrayList() 12 | 13 | private var onItemClickBlock: ((View, ViewItemRepresentable?, Int) -> Unit)? = null 14 | private var onItemLongClickBlock: ((View, ViewItemRepresentable?, Int) -> Unit)? = null 15 | 16 | init { 17 | setHasStableIds(true) 18 | } 19 | 20 | override fun getItemId(position: Int): Long { 21 | return viewItemsArrayList[position].hashCode().toLong() 22 | } 23 | 24 | override fun setOnItemClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) { 25 | onItemClickBlock = block 26 | } 27 | 28 | override fun setOnItemLongClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit) { 29 | onItemLongClickBlock = block 30 | } 31 | 32 | override fun getItemViewType(position: Int): Int { 33 | if (viewItemsArrayList[position] !is BindingViewItem<*>) { 34 | throw Throwable("ViewItem type mismatch, item should be of type BindingViewItem") 35 | } 36 | 37 | return viewItemsArrayList[position].layoutResourceId 38 | } 39 | 40 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { 41 | return BindingViewHolder( 42 | DataBindingUtil.inflate(LayoutInflater.from(parent.context), viewType, parent, false) 43 | ) 44 | } 45 | 46 | override fun getItemCount(): Int { 47 | return viewItemsArrayList.size 48 | } 49 | 50 | @Suppress("UNCHECKED_CAST") 51 | override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { 52 | 53 | (viewItemsArrayList[holder.adapterPosition] as BindingViewItem) 54 | .bind(holder.binding, holder.adapterPosition) 55 | 56 | holder.itemView.setOnClickListener { 57 | onItemClickBlock?.invoke( 58 | holder.itemView, 59 | viewItemsArrayList[holder.adapterPosition].dataModel, 60 | holder.adapterPosition 61 | ) 62 | } 63 | 64 | holder.itemView.setOnLongClickListener { 65 | onItemLongClickBlock?.invoke( 66 | holder.itemView, 67 | viewItemsArrayList[holder.adapterPosition].dataModel, 68 | holder.adapterPosition 69 | ) 70 | true 71 | } 72 | } 73 | 74 | inner class BindingViewHolder( 75 | val binding: ViewDataBinding 76 | ) : RecyclerView.ViewHolder(binding.root) 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/recyclerviewbuilder/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.sample 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.widget.Toast 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.lifecycle.MutableLiveData 8 | import com.recyclerviewbuilder.library.RecyclerViewBuilder 9 | import com.recyclerviewbuilder.library.RecyclerViewBuilderFactory 10 | import com.recyclerviewbuilder.library.ViewItemRepresentable 11 | import com.recyclerviewbuilder.library.ViewItemsObserver 12 | import com.recyclerviewbuilder.sample.models.ImageAndContent 13 | import com.recyclerviewbuilder.sample.models.Images 14 | import com.recyclerviewbuilder.sample.models.ProfilePicture 15 | import com.recyclerviewbuilder.sample.viewitems.FooterViewItem 16 | import com.recyclerviewbuilder.sample.viewitems.HeaderViewItem 17 | import kotlinx.android.synthetic.main.activity_main.* 18 | import kotlinx.coroutines.* 19 | 20 | class MainActivity : AppCompatActivity() { 21 | 22 | private lateinit var recyclerViewBuilder: RecyclerViewBuilder 23 | 24 | private val viewItems = MutableLiveData() 25 | 26 | private lateinit var models: ArrayList 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | setContentView(R.layout.activity_main) 31 | 32 | models = arrayListOf() 33 | models.add(ImageAndContent()) 34 | models.add(Images()) 35 | models.add(ProfilePicture()) 36 | 37 | recyclerViewBuilder = RecyclerViewBuilderFactory(recyclerView) 38 | .buildWithLinearLayout() 39 | .setLoadingView(loading) 40 | .setEmptyView(noData) 41 | .setHeader(HeaderViewItem()) 42 | .setFooter(FooterViewItem()) 43 | .bindViewItems(this, viewItems) 44 | .setPaginationEnabled(true) 45 | .onUpdatingAdapterFinished { 46 | Toast.makeText(this, "Job done!", Toast.LENGTH_SHORT).show() 47 | }.onPaginate { 48 | CoroutineScope(Dispatchers.IO).launch { 49 | delay(2000) 50 | 51 | withContext(Dispatchers.Main) { 52 | // recyclerViewBuilderFactory.setViewItems(viewItems = ArrayList(models.map { it.viewItem })) 53 | viewItems.value = ViewItemsObserver(ArrayList(models.map { it.viewItem })) 54 | } 55 | } 56 | }.startLoading() 57 | .setOnItemClick { itemView, model, position -> 58 | when(model) { 59 | is ImageAndContent -> { 60 | Toast.makeText(this, "Post content: ${model.content}", Toast.LENGTH_SHORT).show() 61 | } 62 | 63 | is Images -> { 64 | 65 | } 66 | 67 | is ProfilePicture -> { 68 | 69 | } 70 | } 71 | } 72 | 73 | CoroutineScope(Dispatchers.IO).launch { 74 | delay(2000) 75 | 76 | withContext(Dispatchers.Main) { 77 | // recyclerViewBuilderFactory.setViewItems(viewItems = ArrayList(models.map { it.viewItem })) 78 | viewItems.value = ViewItemsObserver(ArrayList(models.map { it.viewItem })) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_binding_change_profile_picture.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 15 | 16 | 29 | 30 | 38 | 39 | 51 | 52 | 62 | 63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/AbstractRecyclerViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.View 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.Observer 7 | 8 | abstract class AbstractRecyclerViewBuilder { 9 | 10 | protected var emptyView: View? = null 11 | protected var loadingView: View? = null 12 | 13 | protected var headerViewItem: AbstractViewItem? = null 14 | protected var footerViewItem: AbstractViewItem? = null 15 | 16 | protected lateinit var recyclerViewAdapter: BaseAdapterInterface 17 | 18 | protected var onUpdatingAdapterFinishedBlock: (() -> Unit)? = null 19 | protected var onPaginateBlock: (() -> Unit)? = null 20 | 21 | protected var isRecyclerViewLoading = false 22 | protected var isPaginationEnabled = false 23 | 24 | protected var lifecycleOwner: LifecycleOwner? = null 25 | 26 | protected var viewItems: MutableLiveData? = null 27 | 28 | protected val observer = Observer { 29 | setAdapterViewItems(it.viewItemsArrayList, it.clearsOnSet, it.appendToEnd) 30 | notifyDataSetChanged() 31 | toggleLoading(false) 32 | onUpdatingAdapterFinishedBlock?.invoke() 33 | } 34 | 35 | abstract fun setEmptyView(emptyView: View): RecyclerViewBuilder 36 | 37 | abstract fun setLoadingView(loadingView: View): RecyclerViewBuilder 38 | 39 | abstract fun startLoading(): RecyclerViewBuilder 40 | 41 | abstract fun finishLoading(): RecyclerViewBuilder 42 | 43 | abstract fun isLoading(): Boolean 44 | 45 | abstract fun isAdapterEmpty(): Boolean 46 | 47 | abstract fun setHeader(headerViewItem: AbstractViewItem?): RecyclerViewBuilder 48 | 49 | abstract fun setFooter(footerViewItem: AbstractViewItem?): RecyclerViewBuilder 50 | 51 | abstract fun scrollTo(viewItemIndex: Int, smoothScroll: Boolean = true): RecyclerViewBuilder 52 | 53 | abstract fun setPaginationEnabled(enable: Boolean): RecyclerViewBuilder 54 | 55 | abstract fun notifyDataSetChanged() 56 | 57 | abstract fun notifyViewItemChanged(atIndex: Int) 58 | 59 | abstract fun modifyViewItem(atIndex: Int, block: (model: T?) -> Unit) 60 | 61 | abstract fun modifyViewItems( 62 | vararg atIndices: Int, 63 | block: (models: List) -> Unit 64 | ) 65 | 66 | abstract fun insertViewItem(atIndex: Int, viewItem: AbstractViewItem) 67 | 68 | abstract fun switchViewItem(ofIndex: Int, withIndex: Int) 69 | 70 | abstract fun removeViewItem(atIndex: Int) 71 | 72 | abstract fun indexOf(viewItemRepresentable: ViewItemRepresentable): Int 73 | 74 | abstract fun setEmptyAdapter(): RecyclerViewBuilder 75 | 76 | abstract fun setViewItems( 77 | viewItemsArrayList: ArrayList, 78 | clearsOnSet: Boolean = false, 79 | appendToEnd: Boolean = true 80 | ): RecyclerViewBuilder 81 | 82 | abstract fun bindViewItems( 83 | lifecycleOwner: LifecycleOwner, 84 | viewItems: MutableLiveData 85 | ): RecyclerViewBuilder 86 | 87 | abstract fun onPaginate(block: () -> Unit): RecyclerViewBuilder 88 | 89 | abstract fun onUpdatingAdapterFinished(block: () -> Unit): RecyclerViewBuilder 90 | 91 | abstract fun setOnItemClick( 92 | block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit 93 | ): RecyclerViewBuilder 94 | 95 | abstract fun setOnItemLongClick( 96 | block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit 97 | ): RecyclerViewBuilder 98 | 99 | protected abstract fun setAdapterViewItems( 100 | viewItemsArrayList: ArrayList, 101 | clearsOnSet: Boolean = false, 102 | appendToEnd: Boolean = true 103 | ) 104 | 105 | protected abstract fun toggleLoading(isLoading: Boolean) 106 | 107 | protected abstract fun setViewsVisibility() 108 | 109 | protected abstract fun setFullWidthHeaderForGridLayout(enabled: Boolean) 110 | 111 | protected abstract fun setFullWidthFooterForGridLayout(enabled: Boolean) 112 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_images.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 30 | 31 | 43 | 44 | 50 | 51 | 63 | 64 | 76 | 77 | 91 | 92 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RecyclerViewBuilder 2 | 3 | RecyclerViewBuilder is an elegant drop-in generic recyclerview that removes all boilerplate code to build recycler view. 4 | 5 | RecyclerViewBuilder is built over: 6 | 7 | * Factory design pattern 8 | * Builder design pattern 9 | * Live data (or not) 10 | * DataBinding (or not) 11 | * And lots of abstraction 12 | 13 | In short, you simply implement some interfaces and your recycler view will be up on the screen with zero effort. 14 | 15 | If you end up using RecyclerViewBuilder in production, I'd love to hear from you. You can reach me through [email](mailto:amr.reda151@gmail.com) 16 | 17 | ## Changelogs 18 | [v1.1.1](https://github.com/amrreda1995/recyclerview-builder/blob/master/changelogs/Changelog-v1.1.1.md) 19 | [v1.2.0](https://github.com/amrreda1995/recyclerview-builder/blob/master/changelogs/Changelog-v1.2.0.md) 20 | [v1.2.1](https://github.com/amrreda1995/recyclerview-builder/blob/master/changelogs/Changelog-v1.2.1.md) 21 | [v2.0.0](https://github.com/amrreda1995/recyclerview-builder/blob/master/changelogs/Changelog-v2.0.0.md) 22 | [v2.0.1](https://github.com/amrreda1995/recyclerview-builder/blob/master/changelogs/Changelog-v2.0.1.md) 23 | 24 | ## Preview 25 | 26 | | Final Result | Header | Footer | 27 | | ---- | ---- | ---- | 28 | | | | | 29 | 30 | ## Installation 31 | 32 | ### With Jitpack 33 | 34 | #### Step 1 35 | Add the **JitPack** repository in your root `build.gradle` at the end of repositories: 36 | ```groovy 37 | allprojects { 38 | repositories { 39 | //Other repositories... 40 | maven { url 'https://jitpack.io' } 41 | } 42 | } 43 | ``` 44 | #### Step 2 45 | Add the **dependency**: 46 | ```groovy 47 | android { 48 | dataBinding { 49 | enabled = true 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation 'com.github.amrreda1995:recyclerview-builder:2.0.1' 55 | } 56 | ``` 57 | ### Without Jitpack 58 | 59 | Clone the repo and copy the recycler view builder files into your project. 60 | 61 | ## Features 62 | * Multiple view item types 63 | * Pagination 64 | * Item click listener 65 | * Item long click listener 66 | * Different states for recycler view (empty views, loading views) 67 | * Execute a block of code when updating adapter is finished 68 | * If you decide to use live data; the builder is by default life cycle aware 69 | * Header / Footer items 70 | * DataBinding 71 | 72 | ## Usage 73 | 74 | * Create a recycler view 75 | * Create one or more layouts for your recycler view item 76 | * Create your model (Which is your normal class that represents an entity I.E user, post...etc) 77 | * Implement ViewItemRepresentable interface 78 | 79 | ```kotlin 80 | class Product(val id: Int, val title: String, val date: String): ViewItemRepresentable { 81 | 82 | override val viewItem: AbstractViewItem 83 | get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. 84 | } 85 | ``` 86 | 87 | ### This leads us to the next step which is view items 88 | 89 | * Now we have two routes to take, either use normal ViewItems or BindingViewItems 90 | 91 | **Using ViewItem** 92 | 93 | * Create a class which implements ViewItem and pass the model which we created in the previous step, also pass the layout id, the model from the constructor to the view item constructor and finally implement the bind function 94 | 95 | ```kotlin 96 | class ProductViewItem(private val model: Product) : ViewItem(R.layout.item_product, model) { 97 | 98 | //recommended to have stable ids for your view items 99 | override fun hashCode(): Int { 100 | return model.id 101 | } 102 | 103 | override fun bind(itemView: View, viewItemPosition: Int) { 104 | itemView.titleTextView.text = model.title 105 | itemView.dateTextView.text = model.date 106 | } 107 | } 108 | ``` 109 | 110 | **Using BindingViewItem** 111 | * Make sure your databinding is enabled (it should really be at this point or the library won't be even installed correctly, but just make sure) 112 | ```groovy 113 | android { 114 | dataBinding { 115 | enabled = true 116 | } 117 | } 118 | ``` 119 | 120 | * Then, create a class which implements BindingViewItem and pass the model which we created in the previous step, also pass the layout id, the model from the constructor to the data binding view item constructor. The generic parameter is of type ViewDataBinding, which is the class that is automtically generated when you enable dataBinding and create an XML built over DataBinding. 121 | Given that you have created an XML named "item_product" the generated class name will be "ItemProductBinding" 122 | 123 | ```kotlin 124 | class ProductViewItem(private val model: Product) : BindingViewItem(R.layout.item_product, model) { 125 | 126 | //recommended to have stable ids for your view items 127 | override fun hashCode(): Int { 128 | return model.id 129 | } 130 | 131 | override fun bind(binding: ItemProductBinding, viewItemPosition: Int) { 132 | binding.model = model 133 | } 134 | } 135 | ``` 136 | 137 | **Or create a ViewItem (for header or footer) which has no implementation and to be used directly without the next step** 138 | 139 | ```kotlin 140 | class FooterViewItem : ViewItem(R.layout.item_footer) 141 | ``` 142 | 143 | ```kotlin 144 | class FooterViewItem : BindingViewItem(R.layout.item_footer) 145 | ``` 146 | 147 | * Now, go back to your original model and return this view item on the overriden viewItem variable 148 | 149 | ```kotlin 150 | class Product(val id: Int, val title: String, val date: String): ViewItemRepresentable { 151 | 152 | override val viewItem: AbstractViewItem 153 | get() = ProductViewItem(this) 154 | 155 | //to take the advantage of indexOf function in RecyclerViewBuilder 156 | override fun equals(other: Any?): Boolean { 157 | if (other is Product) { 158 | return other.id == this.id 159 | } 160 | return false 161 | } 162 | } 163 | ``` 164 | 165 | * Now you are ready to use the builder and you have two routes either use LiveData or normal arrays. 166 | 167 | ## Using LiveData (with / without DataBinding) 168 | 169 | ```kotlin 170 | class MainActivity : AppCompatActivity() { 171 | 172 | private lateinit var recyclerViewBuilder: RecyclerViewBuilder 173 | 174 | private val viewItems = MutableLiveData() 175 | 176 | private var models = arrayListOf( 177 | Product(1, "Product one", "2/4/2019"), 178 | Product(2, "Product two", "2/4/2019"), 179 | Product(3, "Product three", "2/4/2019"), 180 | Product(4, "Product four", "2/4/2019") 181 | ) 182 | 183 | override fun onCreate(savedInstanceState: Bundle?) { 184 | super.onCreate(savedInstanceState) 185 | setContentView(R.layout.activity_main) 186 | 187 | recyclerViewBuilder = RecyclerViewBuilderFactory(recyclerView) 188 | .buildWithLinearLayout(isDataBindingEnabled = true) 189 | .bindViewItems(this, viewItems) 190 | 191 | viewItems.value = ViewItemsObserver(ArrayList(models.map { it.viewItem }), false) 192 | } 193 | } 194 | ``` 195 | 196 | The Live Data is of type "ViewItemsObserver" class which accepts two parameters; viewItemsArrayList (ArrayList) and clearsOnSet (Boolean). If you'd like to clear the items each time you set a value for the live data; you set clearsOnSet to true, otherwise just leave it to default value which is false and it appends the added value 197 | 198 | ## Using Normal Arrays (with / without DataBinding) 199 | 200 | ```kotlin 201 | class MainActivity : AppCompatActivity() { 202 | 203 | private lateinit var recyclerViewBuilder: RecyclerViewBuilder 204 | 205 | private var models = arrayListOf( 206 | Product(1, "Product one", "2/4/2019"), 207 | Product(2, "Product two", "2/4/2019"), 208 | Product(3, "Product three", "2/4/2019"), 209 | Product(4, "Product four", "2/4/2019") 210 | ) 211 | 212 | override fun onCreate(savedInstanceState: Bundle?) { 213 | super.onCreate(savedInstanceState) 214 | setContentView(R.layout.activity_main) 215 | 216 | recyclerViewBuilder = RecyclerViewBuilderFactory(recyclerView) 217 | .buildWithLinearLayout() 218 | 219 | recyclerViewBuilder.setViewItems(ArrayList(models.map { it.viewItem }), false) 220 | } 221 | } 222 | ``` 223 | 224 | ### Supported functionality 225 | 226 | | Function | Description | 227 | | ------------- |-------------| 228 | | buildWithLinearLayout | A function that accepts isDataBindingEnabled, orientation, reverselayout, canScrollHorizontally and canScrollVertically parameters and builds the linear layout recycler view | 229 | | buildWithGridLayout | A function that accepts isDataBindingEnabled, columnCount, orientation, reverselayout, canScrollHorizontally and canScrollVertically parameters and builds the grid layout recycler view | 230 | | setEmptyView | A function that accepts a view which is used as a placeholder when the recycler view is empty | 231 | | setLoadingView | A function that accepts a view which is used as a placeholder when the recycler view is in loading state | 232 | | startLoading | A function that manually triggers loading state on | 233 | | finishLoading | A function that manually triggers loading state off | 234 | | isLoading | A function which returns whether the recycler view is in loading state | 235 | | setViewItems | A function that accepts viewItemsArrayList and clearsOnSet parameters and either replaces the items or appends them | 236 | | bindViewItems | A function that accepts lifecycleOwner and viewItems of type MutableLiveData as explained previously | 237 | | notifyDataSetChanged | A function that reloads the recycler view manually | 238 | | setEmptyAdapter | A function that manually clears the recycler view | 239 | | setOnItemClick | A function that accepts a lambda function which has three paramaters (itemView, model, position) | 240 | | setOnItemLongClick | A function that accepts a lambda function which has three paramaters (itemView, model, position) | 241 | | onUpdatingAdapterFinished | A function that acceps a lambda and is invoked once the adapter has been updated (filled with view items or was cleared from them) | 242 | | setPaginationEnabled | A function that either enables or disables pagination feature | 243 | | onPaginate | A function that accepts a lambda which is triggered once the recylcer view has reached its end to trigger pagination (given that pagination feature is enabled) | 244 | | setHeader | A function that accepts a view item and always sets it as the first item no matter how many items are added or removed | 245 | | setFooter | A function that accepts a view item and always sets it as the last item no matter how many items are added or removed | 246 | | isAdapterEmpty | A function returns whether the adapter is empty or not | 247 | | scrollTo | A function that accepts viewItemIndex, smoothScroll (true by default) and scrolls to the specific item smoothly | 248 | | notifyViewItemChanged | A function that notifies the builder that a certain item was changed and should be reloaded | 249 | | modifyViewItem<`T`> | Where T is one of your models (ViewItemRepresentable), It accepts the index, a lambda function (which has the item as a parameter) whatever changes are done to the items takes effect immediately | 250 | | insertViewItem | A function that accepts atIndex, viewItem and inserts the item at specified index | 251 | | switchViewItem | A function that accepts ofIndex, withIndex and replaces the two items | 252 | | removeViewItem | A function that accepts atIndex and removes it from the recylcerview | 253 | | indexOf | A function that accepts a viewItemRepresentable and returns its index in the recyclerView | 254 | 255 | ## **Example project** 256 | 257 | * Clone the repo using `git clone https://github.com/amrreda1995/recyclerview-builder.git` 258 | * Navigate to the cloned folder 259 | * Open it in the android studio 260 | * Build and run to see it in action (It also includes multiple view items if you want to expirement around with it!) 261 | 262 | 263 | ## About RecyclerViewBuilder 264 | 265 | Initially, I just wanted to use a library that encapuslates all of this logic and of course I've found more than one. But I asked myself why not make a library for myself ? And also deliver it for everyone to use (Because boilerplate code is boring, am I right?). 266 | 267 | Also RecyclerViewBuilder is built completely in code, no external libraries. It's highly flexible and can be customized easily with minimum effort. 268 | 269 | 270 | ## Support 271 | 272 | Please, don't hesitate to [file an issue](https://github.com/amrreda1995/RecyclerviewBuilder/issues/new) if you have questions. 273 | 274 | ## License 275 | 276 | RecyclerViewBuilder is released under the MIT license. [See LICENSE](https://github.com/amrreda1995/RecyclerviewBuilder/blob/master/LICENSE) for details. 277 | -------------------------------------------------------------------------------- /recyclerviewbuilder/src/main/java/com/recyclerviewbuilder/library/RecyclerViewBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.recyclerviewbuilder.library 2 | 3 | import android.view.View 4 | import androidx.lifecycle.* 5 | import androidx.recyclerview.widget.GridLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | open class RecyclerViewBuilder( 9 | private val recyclerView: RecyclerView, 10 | private var isDataBindingEnabled: Boolean, 11 | private var orientation: Int? = null, 12 | private var reverseLayout: Boolean? = null, 13 | private val columnCount: Int? = null 14 | ) : LifecycleObserver, AbstractRecyclerViewBuilder() { 15 | 16 | init { 17 | unbindViewItemsObserver() 18 | 19 | if (isDataBindingEnabled) { 20 | recyclerView.adapter = BindingAdapter() 21 | recyclerViewAdapter = recyclerView.adapter as BindingAdapter 22 | } else { 23 | recyclerView.adapter = Adapter() 24 | recyclerViewAdapter = recyclerView.adapter as Adapter 25 | } 26 | 27 | recyclerView.addScrollListenerForPagination(orientation, reverseLayout) { 28 | if (!isRecyclerViewLoading && isPaginationEnabled && !isAdapterEmpty()) { 29 | isRecyclerViewLoading = true 30 | onPaginateBlock?.invoke() 31 | } 32 | } 33 | } 34 | 35 | override fun setEmptyView(emptyView: View): RecyclerViewBuilder { 36 | this.emptyView = emptyView 37 | return this 38 | } 39 | 40 | override fun setLoadingView(loadingView: View): RecyclerViewBuilder { 41 | this.loadingView = loadingView 42 | return this 43 | } 44 | 45 | override fun startLoading(): RecyclerViewBuilder { 46 | toggleLoading(true) 47 | return this 48 | } 49 | 50 | override fun finishLoading(): RecyclerViewBuilder { 51 | toggleLoading(false) 52 | return this 53 | } 54 | 55 | override fun isLoading(): Boolean { 56 | return isRecyclerViewLoading 57 | } 58 | 59 | override fun isAdapterEmpty(): Boolean { 60 | return recyclerViewAdapter.viewItemsArrayList.isEmpty() 61 | } 62 | 63 | override fun setHeader(headerViewItem: AbstractViewItem?): RecyclerViewBuilder { 64 | if (this.headerViewItem == null && headerViewItem != null) { 65 | recyclerViewAdapter.viewItemsArrayList.add(0, headerViewItem) 66 | 67 | setFullWidthHeaderForGridLayout(true) 68 | } else if (this.headerViewItem != null && headerViewItem == null) { 69 | recyclerViewAdapter.viewItemsArrayList.removeAt(0) 70 | 71 | setFullWidthHeaderForGridLayout(false) 72 | } else if (this.headerViewItem != null && headerViewItem != null) { 73 | recyclerViewAdapter.viewItemsArrayList.removeAt(0) 74 | 75 | recyclerViewAdapter.viewItemsArrayList.add(0, headerViewItem) 76 | 77 | setFullWidthHeaderForGridLayout(true) 78 | } 79 | 80 | notifyDataSetChanged() 81 | this.headerViewItem = headerViewItem 82 | return this 83 | } 84 | 85 | override fun setFooter( 86 | footerViewItem: AbstractViewItem? 87 | ): RecyclerViewBuilder { 88 | var indexToBeAdded = 0 89 | var indexToBeRemoved = 0 90 | 91 | if (recyclerViewAdapter.viewItemsArrayList.size > 0) { 92 | indexToBeAdded = recyclerViewAdapter.viewItemsArrayList.size 93 | indexToBeRemoved = recyclerViewAdapter.viewItemsArrayList.size - 1 94 | } 95 | 96 | if (this.footerViewItem == null && footerViewItem != null) { 97 | recyclerViewAdapter.viewItemsArrayList.add(indexToBeAdded, footerViewItem) 98 | } else if (this.footerViewItem != null && footerViewItem == null) { 99 | recyclerViewAdapter.viewItemsArrayList.removeAt(indexToBeRemoved) 100 | } else if (this.footerViewItem != null && footerViewItem != null) { 101 | recyclerViewAdapter.viewItemsArrayList.removeAt(indexToBeRemoved) 102 | 103 | if (indexToBeRemoved == 0) { 104 | recyclerViewAdapter.viewItemsArrayList.add(footerViewItem) 105 | } else { 106 | recyclerViewAdapter.viewItemsArrayList.add( 107 | recyclerViewAdapter.viewItemsArrayList.size, 108 | footerViewItem 109 | ) 110 | } 111 | } 112 | 113 | if (footerViewItem != null) { 114 | setFullWidthFooterForGridLayout(true) 115 | } else { 116 | setFullWidthFooterForGridLayout(false) 117 | } 118 | notifyDataSetChanged() 119 | this.footerViewItem = footerViewItem 120 | return this 121 | } 122 | 123 | override fun scrollTo(viewItemIndex: Int, smoothScroll: Boolean): RecyclerViewBuilder { 124 | if (smoothScroll) { 125 | recyclerView.smoothScrollToPosition(viewItemIndex) 126 | } else { 127 | recyclerView.scrollToPosition(viewItemIndex) 128 | } 129 | 130 | return this 131 | } 132 | 133 | override fun setPaginationEnabled(enable: Boolean): RecyclerViewBuilder { 134 | this.isPaginationEnabled = enable 135 | return this 136 | } 137 | 138 | override fun notifyDataSetChanged() { 139 | recyclerViewAdapter.notifyDataSetChanged() 140 | setViewsVisibility() 141 | onUpdatingAdapterFinishedBlock?.invoke() 142 | } 143 | 144 | override fun notifyViewItemChanged(atIndex: Int) { 145 | recyclerViewAdapter.notifyItemChanged(atIndex) 146 | onUpdatingAdapterFinishedBlock?.invoke() 147 | } 148 | 149 | @Suppress("UNCHECKED_CAST") 150 | override fun modifyViewItem(atIndex: Int, block: (model: T?) -> Unit) { 151 | block(recyclerViewAdapter.viewItemsArrayList[atIndex].dataModel as T) 152 | recyclerViewAdapter.notifyItemChanged(atIndex) 153 | onUpdatingAdapterFinishedBlock?.invoke() 154 | } 155 | 156 | @Suppress("UNCHECKED_CAST") 157 | override fun modifyViewItems( 158 | vararg atIndices: Int, 159 | block: (models: List) -> Unit 160 | ) { 161 | val models = ArrayList() 162 | atIndices.forEach { models.add(recyclerViewAdapter.viewItemsArrayList[it].dataModel as T) } 163 | block(models) 164 | atIndices.forEach { recyclerViewAdapter.notifyItemChanged(it) } 165 | onUpdatingAdapterFinishedBlock?.invoke() 166 | } 167 | 168 | override fun insertViewItem( 169 | atIndex: Int, 170 | viewItem: AbstractViewItem 171 | ) { 172 | if (headerViewItem == null && footerViewItem == null) { 173 | recyclerViewAdapter.viewItemsArrayList.add(atIndex, viewItem) 174 | recyclerViewAdapter.notifyItemInserted(atIndex) 175 | } else if (headerViewItem != null && footerViewItem == null) { 176 | if (atIndex == 0) { 177 | recyclerViewAdapter.viewItemsArrayList.add(1, viewItem) 178 | recyclerViewAdapter.notifyItemInserted(1) 179 | } else { 180 | recyclerViewAdapter.viewItemsArrayList.add(atIndex, viewItem) 181 | recyclerViewAdapter.notifyItemInserted(atIndex) 182 | } 183 | } else if (headerViewItem == null && footerViewItem != null) { 184 | if (atIndex == recyclerViewAdapter.viewItemsArrayList.size - 1) { 185 | recyclerViewAdapter.viewItemsArrayList.add(recyclerViewAdapter.viewItemsArrayList.size - 2, viewItem) 186 | recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.viewItemsArrayList.size - 2) 187 | } else { 188 | recyclerViewAdapter.viewItemsArrayList.add(atIndex, viewItem) 189 | recyclerViewAdapter.notifyItemInserted(atIndex) 190 | } 191 | } else if (headerViewItem != null && footerViewItem != null) { 192 | when (atIndex) { 193 | 0 -> { 194 | recyclerViewAdapter.viewItemsArrayList.add(1, viewItem) 195 | recyclerViewAdapter.notifyItemInserted(1) 196 | } 197 | recyclerViewAdapter.viewItemsArrayList.size - 1 -> { 198 | recyclerViewAdapter.viewItemsArrayList.add( 199 | recyclerViewAdapter.viewItemsArrayList.size - 2, 200 | viewItem 201 | ) 202 | recyclerViewAdapter.notifyItemInserted(recyclerViewAdapter.viewItemsArrayList.size - 2) 203 | } 204 | else -> { 205 | recyclerViewAdapter.viewItemsArrayList.add(atIndex, viewItem) 206 | recyclerViewAdapter.notifyItemInserted(atIndex) 207 | } 208 | } 209 | } 210 | 211 | setViewsVisibility() 212 | onUpdatingAdapterFinishedBlock?.invoke() 213 | } 214 | 215 | override fun switchViewItem(ofIndex: Int, withIndex: Int) { 216 | if (headerViewItem != null && (ofIndex == 0 || withIndex == 0)) { 217 | throw Throwable("Can not switch header view item with another index") 218 | } else if (footerViewItem != null && (ofIndex == recyclerViewAdapter.viewItemsArrayList.size - 1 219 | || withIndex == recyclerViewAdapter.viewItemsArrayList.size - 1) 220 | ) { 221 | throw Throwable("Can not switch footer view item with another index") 222 | } else { 223 | val tmpViewItem = recyclerViewAdapter.viewItemsArrayList[ofIndex] 224 | recyclerViewAdapter.viewItemsArrayList[ofIndex] = recyclerViewAdapter.viewItemsArrayList[withIndex] 225 | recyclerViewAdapter.viewItemsArrayList[withIndex] = tmpViewItem 226 | recyclerViewAdapter.notifyItemMoved(ofIndex, withIndex) 227 | } 228 | 229 | onUpdatingAdapterFinishedBlock?.invoke() 230 | } 231 | 232 | override fun removeViewItem(atIndex: Int) { 233 | recyclerViewAdapter.viewItemsArrayList.removeAt(atIndex) 234 | recyclerViewAdapter.notifyItemRemoved(atIndex) 235 | setViewsVisibility() 236 | 237 | if (headerViewItem != null && atIndex == 0) { 238 | headerViewItem = null 239 | } else if (footerViewItem != null && atIndex == recyclerViewAdapter.viewItemsArrayList.size - 1) { 240 | headerViewItem = null 241 | } 242 | 243 | onUpdatingAdapterFinishedBlock?.invoke() 244 | } 245 | 246 | override fun indexOf(viewItemRepresentable: ViewItemRepresentable): Int { 247 | return recyclerViewAdapter.viewItemsArrayList.indexOfFirst { 248 | it.dataModel?.equals(viewItemRepresentable) == true 249 | } 250 | } 251 | 252 | override fun setEmptyAdapter(): RecyclerViewBuilder { 253 | val size = recyclerViewAdapter.viewItemsArrayList.size 254 | recyclerViewAdapter.viewItemsArrayList.clear() 255 | recyclerViewAdapter.notifyItemRangeRemoved(0, size) 256 | setViewsVisibility() 257 | onUpdatingAdapterFinishedBlock?.invoke() 258 | return this 259 | } 260 | 261 | override fun setViewItems( 262 | viewItemsArrayList: ArrayList, 263 | clearsOnSet: Boolean, 264 | appendToEnd: Boolean 265 | ): RecyclerViewBuilder { 266 | setAdapterViewItems(viewItemsArrayList, clearsOnSet, appendToEnd) 267 | 268 | notifyDataSetChanged() 269 | toggleLoading(false) 270 | onUpdatingAdapterFinishedBlock?.invoke() 271 | return this 272 | } 273 | 274 | override fun bindViewItems( 275 | lifecycleOwner: LifecycleOwner, 276 | viewItems: MutableLiveData 277 | ): RecyclerViewBuilder { 278 | this.lifecycleOwner = lifecycleOwner 279 | this.viewItems = viewItems 280 | lifecycleOwner.lifecycle.addObserver(this) 281 | return this 282 | } 283 | 284 | override fun setOnItemClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit): RecyclerViewBuilder { 285 | recyclerViewAdapter.setOnItemClick(block) 286 | return this 287 | } 288 | 289 | override fun setOnItemLongClick(block: (itemView: View, model: ViewItemRepresentable?, position: Int) -> Unit): RecyclerViewBuilder { 290 | recyclerViewAdapter.setOnItemLongClick(block) 291 | return this 292 | } 293 | 294 | override fun onPaginate(block: () -> Unit): RecyclerViewBuilder { 295 | onPaginateBlock = block 296 | return this 297 | } 298 | 299 | override fun onUpdatingAdapterFinished(block: () -> Unit): RecyclerViewBuilder { 300 | onUpdatingAdapterFinishedBlock = block 301 | return this 302 | } 303 | 304 | override fun setAdapterViewItems( 305 | viewItemsArrayList: ArrayList, 306 | clearsOnSet: Boolean, 307 | appendToEnd: Boolean 308 | ) { 309 | if (clearsOnSet) { 310 | recyclerViewAdapter.viewItemsArrayList.clear() 311 | 312 | if (headerViewItem == null && footerViewItem == null) { 313 | recyclerViewAdapter.viewItemsArrayList = viewItemsArrayList 314 | } else if (headerViewItem != null && footerViewItem == null) { 315 | recyclerViewAdapter.viewItemsArrayList.add(headerViewItem!!) 316 | recyclerViewAdapter.viewItemsArrayList.addAll(viewItemsArrayList) 317 | } else if (headerViewItem == null && footerViewItem != null) { 318 | recyclerViewAdapter.viewItemsArrayList.addAll(viewItemsArrayList) 319 | recyclerViewAdapter.viewItemsArrayList.add(footerViewItem!!) 320 | } else if (headerViewItem != null && footerViewItem != null) { 321 | recyclerViewAdapter.viewItemsArrayList.add(headerViewItem!!) 322 | recyclerViewAdapter.viewItemsArrayList.addAll(viewItemsArrayList) 323 | recyclerViewAdapter.viewItemsArrayList.add(footerViewItem!!) 324 | } 325 | 326 | } else { 327 | if (appendToEnd) { 328 | if ((headerViewItem == null && footerViewItem == null) || (headerViewItem != null && footerViewItem == null)) { 329 | recyclerViewAdapter.viewItemsArrayList.addAll(viewItemsArrayList) 330 | } else if ((headerViewItem == null && footerViewItem != null) || (headerViewItem != null && footerViewItem != null)) { 331 | recyclerViewAdapter.viewItemsArrayList.removeAt(recyclerViewAdapter.viewItemsArrayList.size - 1) 332 | recyclerViewAdapter.viewItemsArrayList.addAll(viewItemsArrayList) 333 | recyclerViewAdapter.viewItemsArrayList.add(footerViewItem!!) 334 | } 335 | } else { 336 | if ((headerViewItem == null && footerViewItem == null) || (headerViewItem == null && footerViewItem != null)) { 337 | recyclerViewAdapter.viewItemsArrayList = 338 | ArrayList((viewItemsArrayList + recyclerViewAdapter.viewItemsArrayList)) 339 | } else if ((headerViewItem != null && footerViewItem == null) || (headerViewItem != null && footerViewItem != null)) { 340 | recyclerViewAdapter.viewItemsArrayList = 341 | ArrayList(arrayListOf(headerViewItem!!) + viewItemsArrayList + recyclerViewAdapter.viewItemsArrayList) 342 | } 343 | } 344 | } 345 | 346 | if (headerViewItem != null) { 347 | setFullWidthHeaderForGridLayout(true) 348 | } else { 349 | setFullWidthHeaderForGridLayout(false) 350 | } 351 | 352 | if (footerViewItem != null) { 353 | setFullWidthFooterForGridLayout(true) 354 | } else { 355 | setFullWidthFooterForGridLayout(false) 356 | } 357 | } 358 | 359 | override fun toggleLoading(isLoading: Boolean) { 360 | this.isRecyclerViewLoading = isLoading 361 | 362 | if (isRecyclerViewLoading) { 363 | recyclerView.visibility = View.GONE 364 | emptyView?.visibility = View.GONE 365 | loadingView?.visibility = View.VISIBLE 366 | } else { 367 | setViewsVisibility() 368 | } 369 | } 370 | 371 | override fun setViewsVisibility() { 372 | loadingView?.visibility = View.GONE 373 | 374 | if (recyclerViewAdapter.viewItemsArrayList.isNotEmpty()) { 375 | recyclerView.visibility = View.VISIBLE 376 | emptyView?.visibility = View.GONE 377 | } else { 378 | recyclerView.visibility = View.GONE 379 | emptyView?.visibility = View.VISIBLE 380 | } 381 | } 382 | 383 | override fun setFullWidthHeaderForGridLayout(enabled: Boolean) { 384 | columnCount?.let { 385 | (recyclerView.layoutManager as GridLayoutManager).spanSizeLookup = 386 | object : GridLayoutManager.SpanSizeLookup() { 387 | override fun getSpanSize(position: Int): Int { 388 | if (enabled && headerViewItem != null && position == 0) { 389 | return columnCount 390 | } 391 | 392 | return 1 393 | } 394 | } 395 | } 396 | } 397 | 398 | override fun setFullWidthFooterForGridLayout(enabled: Boolean) { 399 | columnCount?.let { 400 | (recyclerView.layoutManager as GridLayoutManager).spanSizeLookup = 401 | object : GridLayoutManager.SpanSizeLookup() { 402 | override fun getSpanSize(position: Int): Int { 403 | if (enabled 404 | && footerViewItem != null 405 | && position == recyclerViewAdapter.viewItemsArrayList.size - 1 406 | ) { 407 | return columnCount 408 | } 409 | 410 | return 1 411 | } 412 | } 413 | } 414 | } 415 | 416 | @Suppress("Unused") 417 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 418 | fun bindViewItemsObserver() { 419 | lifecycleOwner?.let { 420 | viewItems?.observe(it, observer) 421 | } 422 | } 423 | 424 | @Suppress("Unused") 425 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 426 | fun unbindViewItemsObserver() { 427 | lifecycleOwner?.let { 428 | viewItems?.removeObservers(it) 429 | lifecycleOwner?.lifecycle?.removeObserver(this) 430 | } 431 | } 432 | } --------------------------------------------------------------------------------