Translation animation, moving in from top, anim/move_in_from_top.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="@android:anim/decelerate_interpolator"> <translate android:fromXDelta="0%" android:toXDelta="0%" android:fromYDelta="-100%" android:toYDelta="0%" /> </set>
Translation animation, moving in from bottom, anim/move_in_from_bottom.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="@android:anim/decelerate_interpolator"> <translate android:fromXDelta="0%" android:toXDelta="0%" android:fromYDelta="100%" android:toYDelta="0%" /> </set>
Translation animation, moving out to the bottom, anim/move_out_down.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="@android:anim/decelerate_interpolator"> <translate android:fromXDelta="0%" android:toXDelta="0%" android:fromYDelta="0%" android:toYDelta="100%" /> </set>
Translation animation, moving out to the top, anim/move_out_top.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="@android:anim/decelerate_interpolator"> <translate android:fromXDelta="0%" android:toXDelta="0%" android:fromYDelta="0%" android:toYDelta="-100%" /> </set>
The layout file for the list item, list_item.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="60dp" android:background="@color/lightblue" android:focusable="true"> <View android:id="@+id/view_bg" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" android:visibility="invisible"/> <TextView android:id="@+id/topic" android:layout_width="match_parent" android:layout_height="match_parent" android:textColor="#ffffff" android:gravity="center" tools:text="Education"/> </RelativeLayout>
The layout file for the main activity with the RecyclerView, activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="200dp" android:layout_height="match_parent" android:scrollbars="none" android:descendantFocusability="afterDescendants" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </android.support.constraint.ConstraintLayout>
The RecylerView adapter, this class does all the animation jobs. ItemListAdapter.kt
import android.support.v7.widget.RecyclerView import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AnimationUtils import android.widget.TextView import android.widget.Toast import kotlinx.android.synthetic.main.list_item.view.* class ItemListAdapter(private val itemList: List<String>): RecyclerView.Adapter<ItemListAdapter.ViewHolder>() { private var previousFocusedViewHolder: ViewHolder? = null private var previouslyFocusedPos = 0 private var currentlyFocusedPos = 0 private lateinit var recyclerView: RecyclerView override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutView = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false) return ViewHolder(layoutView) } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) Log.d("ItemListAdapter", "onAttachedToRecyclerView ") this.recyclerView = recyclerView } override fun onBindViewHolder(holder: ViewHolder, position: Int) { Log.d("ItemListAdapter", "onBindViewHolder position: $position") holder.itemView.setOnFocusChangeListener { focusedView, hasFocus -> Log.d("ItemListAdapter", "onBindViewHolder OnFocusChangeListener position: $position") updateFocusPositions(holder, hasFocus, position) startFocusAnimation(holder, hasFocus) } holder.tvTopic.text = itemList[position] } override fun getItemCount() = itemList.size private fun startFocusAnimation(holder: ViewHolder, hasFocus: Boolean) { Log.d("ItemListAdapter", "startFocusAnimation hasFocus: $hasFocus, currentlyFocusedPos: $currentlyFocusedPos, previouslyFocusedPos: $previouslyFocusedPos") if (hasFocus) { previousFocusedViewHolder?.let { val moveOutAnimSet = if (currentlyFocusedPos > previouslyFocusedPos) R.anim.move_out_down else R.anim.move_out_up val moveOutAnim = AnimationUtils.loadAnimation(it.itemBg.context, moveOutAnimSet) moveOutAnim.fillAfter = true moveOutAnim.duration = 250 it.itemBg.startAnimation(moveOutAnim) Log.d("ItemListAdapter", "startFocusAnimation, moving out from previousViewHolder: $it") } val moveInAnimSet = if (currentlyFocusedPos > previouslyFocusedPos) R.anim.move_in_from_top else R.anim.move_in_from_bottom val moveInAnim = AnimationUtils.loadAnimation(holder.itemBg.context, moveInAnimSet) moveInAnim.fillAfter = true moveInAnim.duration = 400 holder.itemBg.startAnimation(moveInAnim) Log.d("ItemListAdapter", "startFocusAnimation, moving into the currentViewHolder: $holder") } } private fun updateFocusPositions(viewHolder: ViewHolder, hasFocus: Boolean, position: Int) { if (hasFocus) { previouslyFocusedPos = currentlyFocusedPos currentlyFocusedPos = position } else { previousFocusedViewHolder = viewHolder } } inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { var itemBg: View var tvTopic: TextView init { itemView.setOnClickListener(this) tvTopic = itemView.topic itemBg = itemView.view_bg } override fun onClick(view: View) { Toast.makeText(view.context, "Clicked Position = " + adapterPosition, Toast.LENGTH_SHORT).show() } } }
The MainActivity.kt
import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var listAdapter: ItemListAdapter val topics = listOf("Education","Finance","Government","Entertainment","Technology","Math","Biology","Physics","Chemistry","Space","Sports","Music","Animal","Countries","Weather","Politics","Traffic","Poverty","Social Media","Internet","Housing") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) linearLayoutManager = LinearLayoutManager(this) listAdapter = ItemListAdapter(topics) recycler_view.setHasFixedSize(true) recycler_view.layoutManager = linearLayoutManager recycler_view.adapter = listAdapter } }