RecyclerView is a flexible and upgraded version of ListView. In short, RecyclerView works on ViewHolder design pattern. Each row managed by ViewHolder.
Dependencies
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha05"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
}
Model
data class Movie(var title: String, var rating: Float)
Basic kind of Adapter and Holder
Every RecyclerView has a corresponding Adapter. The Adapter is responsible to take our models and map them each to their corresponding View components (ViewHolders).
Creating of Adapter consists of following steps
getItemCount.getItemViewType.onCreateViewHolder.ViewHolder with the data model by overriding onBindViewHolder.ViewHolders and implement the View logic for each use case using the data model provided.
class MovieAdapter(var items: List<Movie>): RecyclerView.Adapter<MainAdapter.MovieHolder>() {
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
MovieHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false))
override fun onBindViewHolder(holder: MovieHolder, position: Int) { holder.bind(items[position]) }
inner class MovieHolder(override val containerView: View):
RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(item: Movie) {
tvTitle.text = item.title
tvRating.text = item.rating
}
}
}
Activity class
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch(Dispatchers.IO) {
val response = Repository.getMovies()
if (response.isSuccessful) {
val adapter = MainAdapter(response.body() ?: listOf())
recycler.adapter = adapter
...
} else {
Toast.makeText(this@MainActivity, "Error ${response.code()}", Toast.LENGTH_SHORT).show()
}
}
}
}
ListAdapter
ListAdapter is a new class bundled in the 27.1.0 support library and simplifies the code required to work with RecyclerViews. The end result is less code for you to write and more recycler view animations happening for free. It automatically stores the previous list of items and utilises DiffUtil under the hood to only update items in the recycler view which have changed. This typically means better performance as you avoid refreshing the whole list, and nicer animations because only items which change need to be redrawn.
To display items on RecyclerView you need to the following:
class MovieAdapter(private val listItemClickListener: ListItemClickListener)
: ListAdapter<Movie, RecyclerView.ViewHolder>(ListItemCallback()) {
interface ListItemClickListener {
fun onItemClick(item: Movie, position: Int)
}
class ListItemCallback : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.title == newItem.title && oldItem.rating == newItem.rating
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
(holder as ListenItemViewHolder).bind(item, position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return ListenItemViewHolder(view)
}
inner class ListenItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvTitle: TextView = itemView.tvTitle
var tvRating: TextView = itemView.tvRating
fun bind(item: Movie, position : Int) {
tvTitle.text = item.title
tvRating.text = "Rating: ${item.rating}"
itemView.setOnClickListener {
listItemClickListener.onItemClick(item, adapterPosition)
}
}
}
}
Activity class
class MainActivity : AppCompatActivity(), MovieAdapter.ListItemClickListener {
private val movies = ArrayList<Movie>()
private lateinit var movieAdapter: MovieAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
movieAdapter = MovieAdapter(this)
rvItems.layoutManager = LinearLayoutManager(this)
rvItems.itemAnimator = DefaultItemAnimator()
rvItems.adapter = movieAdapter
rvItems.layoutAnimation = AnimationUtils.loadLayoutAnimation(this,
R.anim.layout_animation_right_to_left)
(0..99).mapTo(movies) { Movie("Movie $it", it.toFloat()) }
movieAdapter.submitList(movies)
fab.setOnClickListener {
val newList = ArrayList<Movie>()
val end = (0..10).random()
(0..end).mapTo(newList) { Movie("New movie $it", it.toFloat()) }
movieAdapter.submitList(newList)
}
}
override fun onItemClick(item: Movie, position: Int) {
Log.d("TAG", "item: $item");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.activities.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvItems"
android:layout_width="0dp"
android:layout_height="0dp"
android:layoutAnimation="@anim/layout_animation_right_to_left"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/list_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:srcCompat="@android:drawable/ic_menu_add"/>
</androidx.constraintlayout.widget.ConstraintLayout>
list_item.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:id="@+id/tvRating"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Small"/>
</LinearLayout>
LayoutAnimation
LayoutAnimation adds an initial content animation for a RecyclerView. So let’s start of by creating the item animation, in this example we’ll go for the Fall Down animation shown below.
Start of by creating the file item_animation_fall_down.xml in res/anim/ and add the following:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/anim_duration_medium">
<translate
android:fromYDelta="-20%"
android:toYDelta="0"
android:interpolator="@android:anim/decelerate_interpolator"/>
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:interpolator="@android:anim/decelerate_interpolator"/>
<scale
android:fromXScale="105%"
android:fromYScale="105%"
android:toXScale="100%"
android:toYScale="100%"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
With the item animation done it’s time to define the layout animation which will apply the item animation to each child in the layout. Create a new file called layout_animation_fall_down.xml in res/anim/ and add the following:
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/item_animation_fall_down"
android:delay="15%"
android:animationOrder="normal"/>
A LayoutAnimation can be applied programmatically in the following way:
val resId = R.anim.layout_animation_fall_down val animation = AnimationUtils.loadLayoutAnimation(ctx, resId) recyclerview.setLayoutAnimation(animation)
We can set the LayoutAnimation on RecyclerView in XML in the following way:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/layout_animation_fall_down"/>
If you are changing data set or just want to re-run the animation you can do it like this:
fun runLayoutAnimation(recyclerView: RecyclerView) {
val context = recyclerView.getContext()
val animation =
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down)
recyclerView.setLayoutAnimation(animation)
recyclerView.getAdapter().notifyDataSetChanged()
recyclerView.scheduleLayoutAnimation()
}
Useful links