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