Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/afollestad/recyclical
🚀 An easy-to-use, extensible Kotlin DSL for setting up and manipulating RecyclerViews.
https://github.com/afollestad/recyclical
List: recyclical
android androidx dsl kotlin list-selection lists recyclerview user-interface
Last synced: about 2 months ago
JSON representation
🚀 An easy-to-use, extensible Kotlin DSL for setting up and manipulating RecyclerViews.
- Host: GitHub
- URL: https://github.com/afollestad/recyclical
- Owner: afollestad
- License: apache-2.0
- Archived: true
- Created: 2019-03-10T01:58:18.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2022-12-24T21:51:22.000Z (almost 2 years ago)
- Last Synced: 2024-05-22T02:22:50.857Z (7 months ago)
- Topics: android, androidx, dsl, kotlin, list-selection, lists, recyclerview, user-interface
- Language: Kotlin
- Homepage: https://af.codes
- Size: 2.1 MB
- Stars: 716
- Watchers: 20
- Forks: 62
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
- awesome-kotlin - recyclical - 🚀 An easy-to-use, extensible Kotlin DSL for setting up and manipulating RecyclerViews. (Libraries)
README
# Recyclical
*recyclical*: an easy-to-use, extensible Kotlin DSL for setting up and manipulating RecyclerViews.
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/bdc552fb3832423986a296a47b9ddef0)](https://www.codacy.com/app/drummeraidan_50/recyclical?utm_source=github.com&utm_medium=referral&utm_content=afollestad/recyclical&utm_campaign=Badge_Grade)
[![Android CI](https://github.com/afollestad/recyclical/workflows/Android%20CI/badge.svg)](https://github.com/afollestad/recyclical/actions?query=workflow%3A%22Android+CI%22)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html)---
This repo is archived as this library is deprecated. I do not wish to maintain a View-based library now that Jetpack Compose is a thing. Prefer `LazyColumn`/`LazyRow`.
# Table of Contents
## Core
1. [Gradle Dependency](#gradle-dependency)
2. [The Basics](#the-basics)
3. [More Options](#more-options)
4. [Child View Clicks](#child-view-clicks)
5. [Multiple Item Types](#multiple-item-types)
6. [DataSource](#datasource)
1. [Construction](#construction)
2. [Manipulation](#manipulation)
4. [Diffing](#diffing)
7. [SelectableDataSource](#selectabledatasource)
1. [Construction](#construction-1)
2. [Manipulation](#manipulation-1)
3. [Use in Binding](#use-in-binding)
8. [Stable IDs](#stable-ids)## Swipe
1. [Gradle Dependency](#gradle-dependency-1)
2. [The Basics](#the-basics-1)
3. [Long Swipes](#long-swipes)
4. [Customization](#customization)---
# Core
[ ![Core](https://img.shields.io/maven-central/v/com.afollestad/recyclical?style=flat&label=Core) ](https://repo1.maven.org/maven2/com/afollestad/recyclical)
## Gradle Dependency
Add this to your module's `build.gradle` file:
```gradle
dependencies {implementation 'com.afollestad:recyclical:1.1.1'
}
```---
## The Basics
**First**, declare an Item class:
```kotlin
data class Person(
var name: String,
var arg: Int
)
```**Second**, a layout and a View Holder:
```xml
```
```kotlin
class PersonViewHolder(itemView: View) : ViewHolder(itemView) {
val name: TextView = itemView.findViewById(R.id.text_name)
val age: TextView = itemView.findViewById(R.id.text_age)
}
```**Finally**, you can begin using the DSL API:
```kotlin
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// dataSourceTypedOf(...) here creates a DataSource
val dataSource = dataSourceTypedOf(
Person("Aidan", 24),
Person("Nina", 24)
)
// setup{} is an extension method on RecyclerView
recyclerView.setup {
withDataSource(dataSource)
withItem(R.layout.person_item_layout) {
onBind(::PersonViewHolder) { index, item ->
// PersonViewHolder is `this` here
name.text = item.name
age.text = "${item.age}"
}
onClick { index ->
// item is a `val` in `this` here
toast("Clicked $index: ${item.name}")
}
onLongClick { index ->
// item is a `val` in `this` here
toast("Long clicked $index: ${item.name}")
}
}
}
}
}
```---
## More Options
There are other things you can give to the setup extension:
```kotlin
recyclerView.setup {
// Custom layout manager, rather than the default which is a vertical LinearLayoutManager
withLayoutManager(GridLayoutManager(context, 2))
// Assigns a view that is made visible when the data source has no content, else is hidden (gone)
withEmptyView(view)
// Global click listener for any item type. Individual item click listeners are called first.
withClickListener { index, item -> }
// Global long click listener for any item type. Individual item long click listeners are called first.
withLongClickListener { index, item -> }
// Add an animation used to animate the group's children after the first layout.
withLayoutAnimation(R.anim.your_anim, durationRes = android.R.integer.config_animShortTime)
}
```---
## Child View Clicks
There are many cases in which you'd want to get callbacks for a child view in your list items
getting clicked, such as the sender icon in a list of emails.```kotlin
class EmailViewHolder(itemView: View) : ViewHolder(itemView) {
val icon = itemView.findViewById(R.id.icon)
}recyclerView.setup {
withItem(R.layout.email_item_layout) {
...
onChildViewClick(EmailViewHolder::icon) { index, view ->
// `this` includes `item` along with selection-related methods discussed below in SelectableDataSource
// `view` argument here is automatically an `ImageView`
}
}
}
```---
## Multiple Item Types
You can mix different types of items - but you need to specify view holders and layouts for them too:
```kotlin
// dataSourceOf(...) without "typed" creates a DataSource
val dataSource = dataSourceOf(
Car(2012, "Volkswagen GTI"),
Motorcycle(2018, "Triumph", "Thruxton R"),
Person("Aidan", 24)
)recyclerView.setup {
withDataSource(dataSource)
withItem(R.layout.person_item_layout) {
onBind(::PersonViewHolder) { index, item ->
name.text = item.name
age.text = "${item.age}"
}
}
withItem(R.layout.motorcycle_item_layout) {
onBind(::MotorcycleViewHolder) { index, item ->
year.text = "${item.year}"
make.text = item.make
model.text = item.model
}
}
withItem(R.layout.car_item_layout) {
onBind(::CarViewHolder) { index, item ->
year.text = "${item.year}"
name.text = item.name
}
}
}
```---
## DataSource
`DataSource` is an interface which provides data and allows manipulation of the data, to display in a RecyclerView.
Being an interface means you make your own implementations of it, you can mock it in tests, you could even provide it
via Dagger to a presenter and manipulate the RecyclerView outside of your UI layer.### Construction
The included implementation of data source operates on a List of objects (of any type).
```kotlin
// Empty by default, but can still add, insert, etc.
val dataSource: DataSource = emptyDataSource()
val dataSourceTyped: DataSource = emptyDataSourceTyped()
// Initial data set of items from a vararg list
val dataSource: DataSource = dataSourceOf(item1, item2)
val dataSourceTyped: DataSource = dataSourceTypedOf(item1, item2)// Initial data set of items from an existing list
// Could also use dataSourceTypedOf(...)
val items = listOf(item1, item2)
val dataSource: DataSource = dataSourceOf(items)
val dataSourceTyped: DataSource = dataSourceTypedOf(items)```
### Manipulation
```kotlin
val dataSource: DataSource = // ...// getters
val item: ItemType = dataSource[5]
val contains: Boolean = dataSource.contains(item)
val size: Int = dataSource.size()
val isEmpty: Boolean = dataSource.isEmpty()
val isNotEmpty: Boolean = dataSource.isNotEmpty()
val firstIndex: Int = dataSource.indexOfFirst { }
val lastIndex: Int = dataSource.indexOfLast { }// mutation
val person = Person("Aidan", 24)
dataSource.add(person)
dataSource.set(listOf(person))
dataSource.insert(1, person)
dataSource.removeAt(1)
dataSource.remove(person)
dataSource.swap(1, 4)
dataSource.move(1, 4)
dataSource.clear()// iteration
for (item in dataSource) { }
dataSource.forEach { } // emits all items
dataSource.forEachOf { } // only emits items that are a Person// operators
val item: Any = dataSource[5] // get(5)
val contains: Boolean = item in dataSource // contains(item)
dataSource += person // add(person)
dataSource -= person // remove(person)
```### Diffing
When performing a `set` on the data set, you can opt to use diff utils:
```kotlin
dataSource.set(
newItems = newItems,
areTheSame = ::areItemsTheSame,
areContentsTheSame = ::areItemContentsTheSame
)// Return true if items represent the same entity, e.g. by ID or name
private fun areItemsTheSame(left: Any, right: Any): Boolean {
return when (left) {
is Person -> {
right is Person && right.name == left.name
}
else -> false
}
}// Return true if all contents in the items are equal
private fun areItemContentsTheSame(left: Any, right: Any): Boolean {
return when (left) {
is Person -> {
right is Person &&
right.name == left.name &&
right.age == left.age
}
else -> false
}
}
```This will automatically coordinate notifying of adds, moves, and insertions so that
update of the data set is pretty and animated by the RecyclerView.---
## SelectableDataSource
A `SelectableDataSource` is built on top of a regular [DataSource]. It provides additional APIs
to manage the selection state of items in your list.### Construction
Construction methods for `SelectableDataSource` are the same as the `DataSource` ones, they just
include `selectable` in their names.```kotlin
// Empty by default, but can still add, insert, etc.
// Could also use emptySelectableDataSourceTyped()
val dataSource: SelectableDataSource = emptySelectableDataSource()
val dataSourceTyped: SelectableDataSource = emptySelectableDataSourceTyped()// Initial data set of items from a vararg list
// Could also use selectableDataSourceTypedOf(...)
val dataSource: SelectableDataSource = selectableDataSourceOf(item1, item2)
val dataSourceTyped: SelectableDataSource = selectableDataSourceTypedOf(item1, item2)// Initial data set of items from an existing list
// Could also use selectableDataSourceTypedOf(...)
val items = listOf(item1, item2)
val dataSource: SelectableDataSource = selectableDataSourceOf(items)
val dataSourceTyped: SelectableDataSource = selectableDataSourceTypedOf(items)
```### Manipulation
There are some additional methods added on top of the `DataSource` methods:
```kotlin
val dataSource: SelectableDataSource = // ...// Index operations
dataSource.selectAt(1)
dataSource.deselectAt(1)
dataSource.toggleSelectionAt(1)
val selected: Boolean = dataSource.isSelectedAt(1)// Item operations, uses index operations under the hood
val item: Any = // ...
dataSource.select(item)
dataSource.deselect(item)
dataSource.toggleSelection(item)
val selected: Boolean = dataSource.isSelected(item)// Mass operations
dataSource.selectAll()
dataSource.deselectAll()// Misc operations
val count: Int = dataSource.getSelectionCount()
val hasSelection: Boolean = dataSource.hasSelection()// Set a callback invoked when something is selected or deselected
dataSource.onSelectionChange { dataSource -> }
```### Use in Binding
During binding of your items, you can access selection states *even if you don't have a direct
reference to your `DataSource`.*In `onBind` blocks, this is done with extensions in `ViewHolder` which provide functions to check
selection state and select/deselect the current item that is being bound.In `onClick` and `onLongClick` blocks, this is done using a type that is passed as `this` which provides the same set
of functions.```kotlin
recyclerView.setup {
withEmptyView(emptyView)
withDataSource(dataSource)
withItem(R.layout.my_list_item) {
onBind(::MyViewHolder) { index, item ->
// Selection-related methods that can be used here:
isSelected()
select()
deselect()
toggleSelection()
hasSelection()
}
onClick { index ->
// Selection-related methods that can be used here:
isSelected()
select()
deselect()
toggleSelection()
hasSelection()
}
onChildViewClick(MyViewHolder::someView) { index, view ->
// The same methods used in onClick can be used here as well
}
onLongClick { index ->
// The same methods used in onClick can be used here as well
}
}
}
```---
## Stable IDs
Stable IDs are an optimization hint for `RecyclerView`. When using stable IDs, you're telling
the view that each ViewHolder ID is unique and will not change. In Recyclical, to can use stable IDs
by having *all* of your items provide a unique ID for themselves.```kotlin
data class AnItemWithAnId(
val id: Int,
val name: String
)recyclerView.setup {
withDataSource(dataSource)
withItem(R.layout.my_item_layout) {
onBind(::MyViewHolder) { index, item -> ... }
// The key is this, which says the `id` field of your item represents a unique ID.
hasStableIds { it.id }
}
}
```If you have more than one item that your RecyclerView can hold, *all* need to define `hasStableIds`.
---
# Swipe
The swipe module provides extensions to setup swipe actions, like swipe to delete.
[ ![Swipe](https://img.shields.io/maven-central/v/com.afollestad/recyclical-swipe?style=flat&label=Swipe) ](https://repo1.maven.org/maven2/com/afollestad/recyclical-swipe)
## Gradle Dependency
Add this to your module's `build.gradle` file:
```gradle
dependencies {implementation 'com.afollestad:recyclical-swipe:1.0.1'
}
```## The Basics
This example below sets up swipe to delete, so that it works if you swipe either right or left.
A delete icon and delete text would be shown over a red gutter. *The callback returning true means
that the item should be removed from the `DataSource` when the action triggers.*```kotlin
list.setup {
...
withSwipeAction(LEFT, RIGHT) {
icon(R.drawable.ic_delete)
text(R.string.delete)
color(R.color.md_red)
callback { index, item -> true }
}
}
```You can target specific item types with `withSwipeActionOn`, too:
```kotlin
withSwipeActionOn(LEFT, RIGHT) {
icon(R.drawable.ic_delete)
text(R.string.delete)
color(R.color.md_red)
callback { index, item -> true }
}
```With `withSwipeActionOn`, `item` in the callback is a `MyItem` instead of `Any` as well.
## Customization
As you saw above, you can use icons, text, and background colors easily. There are more details
you can customize about your swipe actions, mainly around text:```kotlin
list.setup {
...
withSwipeAction(LEFT, RIGHT) {
text(
res = R.string.delete,
color = R.color.black,
size = R.dimen.small_text_size,
typefaceRes = R.font.roboto_mono
)
}
}
```