Before the appearance of RecyclerView
, ListView
and GridView
were the common layout used to display lists of items.
Even though RecyclerView
has optimized how to render list of items without having to worry how you want to display them (either list or grid) it comes with a price when implementing such behaviors.
This library comes with two focuses:
- Make your
RecyclerView
basic implementation dead simple. - Intagrating useful features that could be found in either
ListView
andGridView
(such as choice mode) and even more (sections, pagination, gestures).
Single choice | Multiple choice | Sections |
---|---|---|
If you are already using the v1 of this library and considering migrating to the v2, here are a few things worth mentioning
- You need to declare all modules that you require as they are not transitive anymore.
- Several methods were renamed for clarification
- Packages were renamed to apply latest Android Studio guidelines. Some classes were also rearranged in the process.
- A Kotlin sample was added and callback systems were changed in favor of lambdas to match with Kotlin language.
- In order to not disadvantage Java users I kept the previous methods. Both Java and kotlin samples are available in this repository.
- For Java users, Java 8 is now required for this library because of lambdas.
The core module contains the basic logic to easily manipulate a RecyclerView
.
It allows you to define your adapter in a blink of the eye, with a already built-in ViewHolder
pattern so that you just need to define how your items will be laid out in your list.
Create your model :
data class Sample(val id: Int, val rate: Int, val name: String)
Create your view :
class SampleItemView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
init {
LayoutInflater.from(context).inflate(R.layout.view_sample_item, this, true)
}
fun bind(sample: Sample) {
// Update your subviews with the Sample data
}
}
Then define your own adapter that extends from RecyclerAdapter
and template it with your model that will be used to populate your list :
class SampleAdapter : RecyclerAdapter<Sample>() {
override fun onCreateItemView(parent: ViewGroup, viewType: Int): View = SampleItemView(parent.context)
override fun onBindItemView(view: View, position: Int) {
when (view) {
is SampleItemView -> view.bind(items[position])
}
}
}
Finally in your Activity
/Fragment
, add your Sample
items to your SampleAdapter
and bind your adapter to your RecyclerView
:
val adapter = SampleAdapter(this)
adapter.items = mutableListOf()
recyclerView.adapter = adapter
Your RecyclerView
is ready to be displayed. You just have to implement your bind()
method within your view to configure it (or use the databinding pattern if you prefer).
Listening to click events are often required and usually requires to transit such an event from your adapter to your Activity
/Fragment
to maintain a clean architecture.
This library provides to optional lambdas for click and long click events that can be called directly from your adapter :
adapter.onClick = { view, position ->
val sample = items[position]
// Do whatever you want
}
adapter.onLongClick = { view, position ->
val sample = items[position]
// Do whatever you want
}
A useful feature that can be found in either ListView
and GridView
is ChoiceMode.
Although this does not come natively with RecyclerView
, this library provides such a mechanism.
Choice mode can be either NONE (default), SINGLE or MULTIPLE. If a choice mode other than NONE is declared for your adapter, your selected items will be internally stored at each view click. You can also manually select an item :
adapter.toggleItemView(position)
You can obviously retrieve which items are selected :
adapter.selectedItemViewCount // Returns the selected item view count
adapter.getSelectedItemViews() // Returns a list of selected item views
adapter.isItemViewToggled(position) // Returns true if item is selected
If you need to remove all selected items :
adapter.clearSelectedItemViews()
In most cases the core module should handle most of the heavy work.
This section presents more advanced concepts that can help you while using RecyclerView
in your applications.
You may encounter lists that need to be regrouped within sections to present a clear sorting or your items.
This can be easily done natively by overriding getItemViewType()
and manipulate yourself each viewType in your adapter callbacks.
The section module intends to lift this work for you as well as to provide a clear implementation by separating section building callbacks from main items in your list.
Create a section item view to render your sections. Let's call it SampleSectionItemView
.
Extends your adapter class from RecyclerSectionAdapter
(which itself extends from RecyclerAdapter
) and provides two more abstract methods to shape your views that will be displayed as sections :
class SampleSectionAdapter : RecyclerSectionAdapter<Int, Sample>({ it.rate }) {
override fun onCreateItemView(parent: ViewGroup, viewType: Int): View = SampleItemView(parent.context)
override fun onBindItemView(view: View, position: Int) {
when (view) {
is SampleItemView -> view.bind(items[position])
}
}
// Override these two new methods to render your sections
override fun onCreateSectionItemView(parent: ViewGroup, viewType: Int): View =
SampleSectionItemView(parent.context)
override fun onBindSectionItemView(sectionView: View, sectionPosition: Int) {
sectionAt(sectionPosition)?.let {
when (sectionView) {
is SampleSectionItemView -> sectionView.bind(it)
}
}
}
}
Note the Int generic type in the class declaration that indicates the type that will contains the section. In our case, we want to sort them by rate.
The building of the sections will be automatically taken care of by a lambda that you must provide in your constructor (){ sample -> sample.rate }
).
Wraps things up the way you were doing with your RecyclerAdapter
:
val sectionAdapter = SampleSectionAdapter(this)
sectionAdapter.items = mutableListOf()
recyclerView.adapter = sectionAdapter
Setting items to your adapter will take care of the section building and relayout your list. You may want to sort your items before setting them in your adapter to obtain consistent sections.
Pagination is a common pattern especially when interacting with an API to lazy load your items into your list.
The pagination module provides an extension to the RecyclerView
class to easily activate pagination and notify what to do when pagination is triggered.
You should also notify your users that your content is being loaded into your list by providing a loader at the bottom of your list.
Extend your adapter from RecyclerPaginationAdapter
and implement the loader creator callback :
class SamplePaginationAdapter : RecyclerPaginationAdapter<Sample>() {
override fun onCreateItemView(parent: ViewGroup, viewType: Int): View = SampleItemView(parent.context)
override fun onBindItemView(view: View, position: Int) {
when (view) {
is SampleItemView -> view.bind(items[position], isItemViewToggled(position))
}
}
override fun onCreateLoaderView(parent: ViewGroup, viewType: Int): View =
LayoutInflater.from(parent.context).inflate(R.layout.view_progress, parent, false)
}
Activate pagination on your RecyclerView
and populate your list with the appendItems()
extension method :
val paginationAdapter = SamplePaginationAdapter(this)
recyclerView.adapter = paginationAdapter
recyclerView.enablePagination(
isLoading = {
paginationAdapter.isLoading
},
hasAllItems = {
// return true if you have loaded all your items to stop the pagination to try loading more
},
onLoad = {
// Fetch and/or load your items within your list
paginationAdapter.isLoading = true
// Fetch items from API. Indicate loading done once fetch is finished
paginationAdapter.isLoading = false
// Append items into your list
paginationAdapter.appendItems(items)
}
)
RecyclerPaginationAdapter
extends from RecyclerAdapter
as pagination for sectioned content is not (and won't be) supported.
The RecyclerView
component allows you to interact with your items by draging them with the ItemTouchHelper
class.
The gesture module abstracts this behavior to let you easily swipe-to-delete and/or move your items from your RecyclerView
.
Items are manipulated for you for both move on swipe-to-delete gestures.
You can enable gestures by using the RecyclerView
extension method :
recyclerView.enableGestures(
dragDirections = ItemTouchHelper.UP or ItemTouchHelper.DOWN,
swipeDirections = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,
onMove = { fromPosition, toPosition ->
// Do whatever you want
true
},
onSwipe = { position, direction ->
// Do whatever you want
}
)
Note that all lambdas are optionals so that you may enable/configure your desired gestures. Also, the gesture module includes the section module so that all your gestures also works while using your list with sections. The only limitation is that you cannot move an item from one section to another.
The gradle dependency is available via JitPack. Add this in your root build.gradle
file:
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
Then add the dependencies that you need in your project.
def advancedrecyclerview_version = "{latest_version}"
dependencies {
implementation "com.github.StephenVinouze.AdvancedRecyclerView:core:${advancedrecyclerview_version}"
// If you need to display lists with sections
implementation "com.github.StephenVinouze.AdvancedRecyclerView:section:${advancedrecyclerview_version}"
// If you need to paginate your lists
implementation "com.github.StephenVinouze.AdvancedRecyclerView:pagination:${advancedrecyclerview_version}"
// If you need to handle gestures within your lists
implementation "com.github.StephenVinouze.AdvancedRecyclerView:gesture:${advancedrecyclerview_version}"
}
I welcome and encourage all pull requests. I might not be able to respond as fast as I would want to but I endeavor to be as responsive as possible.
All PR must:
- Be written in Kotlin
- Maintain code style
- Indicate whether it is a enhancement, bug fix or anything else
- Provide a clear description of what your PR brings
- Enjoy coding in Kotlin :)