RecyclerView Navigation transitions in Android Navigation Architecture Component
Navigational transitions occur when users move between screens, such as from a home screen to a detail screen.
Navigation transitions use motion to guide users between two screens in your app. They help users orient themselves by expressing your app's hierarchy, using movement to indicate how elements are related to one another.
For example, when an element expands to fill the entire screen, the act of expansion expresses that the new screen is a child element. The screen from which it expanded is its parent element.
To implement transition element from RecyclerView item to Fragment with Android Navigation Component, follow this code
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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:id="@+id/cardsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionGroup="true"
android:background="#e9ecef"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/recycler_view_design">
</androidx.recyclerview.widget.RecyclerView>
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
exitTransition = MaterialElevationScale(false).apply {
duration = 100
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 100
}
bindingDesign.cardView.apply {
transitionName = "Title $position"
}
// Fragment
class RecyclerViewFragment : BaseFragment<RecyclerViewBinding>() {
private var recyclerItemModels = ArrayList<MyModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
arrayList()
binding.cardsRecyclerView.apply {
layoutManager = LinearLayoutManager(context)
hasFixedSize()
binding.cardsRecyclerView.adapter = DashboardAdapter(event = { rowEvenBinding ->
if (findNavController().currentDestination!!.id == R.id.nav_recycler_view) {
val extras: FragmentNavigator.Extras = FragmentNavigatorExtras(
rowEvenBinding.cardView to rowEvenBinding.cardView.transitionName
)
findNavController().navigate(
RecyclerViewFragmentDirections.actionNavRecyclerViewToNavDetailView(rowEvenBinding.cardView.transitionName,""+rowEvenBinding.titleText.text),
extras
)
exitTransition = MaterialElevationScale(false).apply {
duration = 100
}
reenterTransition = MaterialElevationScale(true).apply {
duration = 100
}
}
},recyclerItemModels)
}
}
private fun arrayList() {
recyclerItemModels.clear()
recyclerItemModels.add(MyModel("App bars: bottom"))
recyclerItemModels.add(MyModel("App bars: top"))
recyclerItemModels.add(MyModel("Bottom navigation"))
recyclerItemModels.add(MyModel("Buttons"))
recyclerItemModels.add(MyModel("Buttons: floating action button"))
recyclerItemModels.add(MyModel("Cards"))
recyclerItemModels.add(MyModel("Chips"))
recyclerItemModels.add(MyModel("Dialogs"))
recyclerItemModels.add(MyModel("Menus"))
recyclerItemModels.add(MyModel("Navigation drawer"))
recyclerItemModels.add(MyModel("Date Pickers"))
recyclerItemModels.add(MyModel("Time Pickers"))
recyclerItemModels.add(MyModel("Progress indicators"))
recyclerItemModels.add(MyModel("Radio buttons"))
recyclerItemModels.add(MyModel("Checkboxes"))
recyclerItemModels.add(MyModel("Switches"))
recyclerItemModels.add(MyModel("Sheets: bottom"))
recyclerItemModels.add(MyModel("Sliders"))
recyclerItemModels.add(MyModel("Snackbars"))
recyclerItemModels.add(MyModel("Tabs"))
recyclerItemModels.add(MyModel("Text fields"))
recyclerItemModels.add(MyModel("Shapeable Image View"))
recyclerItemModels.add(MyModel("Font"))
recyclerItemModels.add(MyModel("Elevation & Shadow"))
recyclerItemModels.add(MyModel("Shape Theming"))
recyclerItemModels.add(MyModel("Transition"))
recyclerItemModels.add(MyModel("Color"))
recyclerItemModels.add(MyModel("Data tables"))
recyclerItemModels.add(MyModel("Dividers"))
recyclerItemModels.add(MyModel("Image lists"))
recyclerItemModels.add(MyModel("Navigation rail"))
recyclerItemModels.add(MyModel("Sheets: side"))
recyclerItemModels.add(MyModel("Lists"))
recyclerItemModels.add(MyModel("Backdrop"))
recyclerItemModels.add(MyModel("Banners"))
recyclerItemModels.add(MyModel("Tooltips"))
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?, bundle: Bundle?) =
RecyclerViewBinding.inflate(inflater, container, false)
}
// Adapter
class DashboardAdapterViewHolder(val bindingDesign: RecyclerViewDesignBinding) : RecyclerView.ViewHolder(bindingDesign.root)
class DashboardAdapter(
private val event: (RecyclerViewDesignBinding) -> Unit,
private val recyclerViewViewBindModelList: List<MyModel>) :
RecyclerView.Adapter<DashboardAdapterViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DashboardAdapterViewHolder {
return DashboardAdapterViewHolder(
RecyclerViewDesignBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: DashboardAdapterViewHolder, position: Int) {
val currentItem = recyclerViewViewBindModelList[position]
with(holder) {
bindingDesign.cardView.apply {
transitionName = "Title $position"
}
//bindingDesign.titleText.text = currentItem.label
bindingDesign.titleText.text = "Title $position"
}
holder.itemView.setOnClickListener {
event(holder.bindingDesign)
}
}
// Return the size of your data set (invoked by the layout manager)
override fun getItemCount(): Int {
return recyclerViewViewBindModelList.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
}
// Data class
data class MyModel(val label: String)
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
app:strokeWidth="2dp"
android:id="@+id/cardView"
app:cardElevation="10dp"
app:cardCornerRadius="10dp"
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- app:strokeColor="@color/stroke_color"
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Media -->
<ImageView
android:visibility="gone"
android:padding="30dp"
android:id="@+id/heroImage"
android:layout_width="match_parent"
android:layout_height="160dp"
app:srcCompat="@drawable/donut"
android:contentDescription="Image Description here"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Title, secondary and supporting text -->
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textStyle="bold"
android:textAppearance="?attr/textAppearanceHeadline6"
/>
<TextView
android:visibility="gone"
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Secondary text"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Material is a design system – backed by open-source code "
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
</LinearLayout>
<!-- Buttons -->
<!-- <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="Action 1"
style="?attr/borderlessButtonStyle"
/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action 2"
style="?attr/borderlessButtonStyle"
/>
</LinearLayout>-->
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
app:startDestination="@id/nav_recycler_view">
<!--this is our start destination-->
<fragment
android:id="@+id/nav_recycler_view"
android:name="com.androidx.androidjetpack.RecyclerViewFragment"
android:label="Home Fragment"
tools:layout="@layout/recycler_view">
<!--Navigate home to detail page-->
<action
android:id="@+id/action_nav_recycler_view_to_nav_detail_view"
app:destination="@id/nav_detail_view" />
</fragment>
<fragment
android:id="@+id/nav_detail_view"
android:name="com.androidx.androidjetpack.DetailFragment"
android:label="Detail Fragment"
tools:layout="@layout/detail_fragment"
>
<!--Get this argument data from home fragment-->
<argument
android:name="transition"
app:argType="string" />
<argument
android:defaultValue="Title"
android:name="title"
app:argType="string" />
</fragment>
</navigation>
class DetailFragment : BaseFragment<DetailFragmentBinding>() {
private val args: DetailFragmentArgs by navArgs()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?, bundle: Bundle?) =
DetailFragmentBinding.inflate(inflater, container, false)
@SuppressLint("RestrictedApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transformation = MaterialContainerTransform()
transformation.interpolator = AnimationUtils.LINEAR_INTERPOLATOR
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.nav_host_fragment_content_main
duration = 400
scrimColor = Color.TRANSPARENT
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val key = args.transition // get from home fragment
binding.titleText.text = args.title
with(binding.cardView) {
transitionName = key // name of the View to uniquely identify it for Transitions.
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e9ecef">
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardView"
app:cardCornerRadius="10dp"
app:cardElevation="10dp"
android:layout_margin="15dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Media -->
<ImageView
android:padding="40dp"
android:id="@+id/heroImage"
android:layout_width="match_parent"
android:layout_height="300dp"
app:srcCompat="@drawable/donut"
android:contentDescription="Image Description here"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Title, secondary and supporting text -->
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textStyle="bold"
android:textAppearance="?attr/textAppearanceHeadline6"
/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Secondary text"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Material is a design system – backed by open-source code – that helps teams build high-quality digital experiences.
\nIn this Session, you learn about the latest design improvements to help you build personal dynamic experiences with Material Design.
"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
protected val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = getBinding(inflater, container, savedInstanceState)
return binding.root
}
protected abstract fun getBinding(
inflater: LayoutInflater,
container: ViewGroup?, bundle: Bundle?
): VB
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Comments
Post a Comment