├── 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 | }
--------------------------------------------------------------------------------