Simple Image Slider Using ViewPager2 and DialogFragment + Navigation Component

Salah Afaghani
4 min readFeb 28, 2021

--

I did this story to go walkthrough of how to combine these new UI components to make simple “Image Slider” shown in DialogFragment.

Let’s go quickly of the components that I used:

  • ViewPager2 is an improved version of the ViewPager library that offers enhanced functionality and addresses common difficulties with using ViewPager. You can read more about ViewPager2 from Android website https://developer.android.com/training/animation/vp2-migration.
  • Navigation Component is a part of Android Jetpack library that allow users to navigate across, into, and back out from the different pieces of content within your app. You can read more about it from Android website https://developer.android.com/guide/navigation.
  • Glide is a library to load images efficiently.

Let’s start with initializing required libraries:

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
}
.
.
.
/* add this feature if you want to
generate Binding method in Fragment or Activity */
android {
buildFeatures {
viewBinding = true
}
}
// View Pager 2
implementation "androidx.viewpager2:viewpager2:$viewpager2_version"

// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

// Glide
implementation "com.github.bumptech.glide:glide:$glide_version"

We also need to modify project build gradle file and add this:

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"

First of all, let’s create HomeFragment that contains just a text view and a button to show an image slider. Here is fragment_home.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="match_parent"
tools:context=".HomeFragment">

<TextView
android:id="@+id/text_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:padding="16dp"
android:text="Press the below button to show Image Slider"
android:textAppearance="@style/TextAppearance.AppCompat.Headline" />

<Button
android:id="@+id/button_image_slider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text_message"
android:layout_centerHorizontal="true"
android:text="Image Slider" />

</RelativeLayout>

And HomeFragment.kt is as follows:

class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val binding = FragmentHomeBinding.bind(view)
binding.buttonImageSlider.setOnClickListener {
val action =
HomeFragmentDirections.actionHomeFragmentToImageSlideDialogFragment(
arrayOf(
"https://images.freeimages.com/images/small-previews/0ce/pomegranate-1329925.jpg",
"https://images.freeimages.com/images/small-previews/362/peppers-1329008.jpg",
"https://images.freeimages.com/images/small-previews/b36/tomato-1326722.jpg",
"https://images.freeimages.com/images/small-previews/1d3/asparagus-1321200.jpg",
"https://images.freeimages.com/images/small-previews/565/orange-slice-backlighted-1330008.jpg"
)
)
findNavController().navigate(action)
}
}
}

Now, let’s create fragment_image_slider.xml that contains just ViewPager2:

<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ImageSliderDialogFragment" />

This fragment is DialogFragment to display image slider in AlertDialog format.

class ImageSliderDialogFragment : DialogFragment() {

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val pager = LayoutInflater.from(requireContext())
.inflate(R.layout.fragment_image_slider, ViewPager2(requireContext()), false) as ViewPager2

pager.adapter = ImageSliderPagerAdapter(arguments?.getStringArray("imageUrls")!!)
pager.setPageTransformer(ZoomOutPageTransformer())

return AlertDialog.Builder(requireContext())
.setView(pager)
.create()
}
}

If you want to add Zoom effect when moving from one image to another, you can create a new class to define this transformer. You can find it from https://developer.android.com/training/animation/screen-slide.

We need also to create an adapter for a pager. We can create it by extending FragmentStateAdapter or by extending RecyclerView.Adapter to create a custom adapter. I adopted the custom method which it is better in my case. We need to pass an array of image URLs that we want to show and loan them by Glide library.

class ImageSliderPagerAdapter(private val imageUrls: Array<String>) : RecyclerView.Adapter<ImageSliderPagerAdapter.ImageSlidePagerViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageSlidePagerViewHolder {
val binding = FragmentImageSliderPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ImageSlidePagerViewHolder(binding)
}

override fun onBindViewHolder(holder: ImageSlidePagerViewHolder, position: Int) {
holder.bind(imageUrls[position])
}

override fun getItemCount(): Int {
return imageUrls.size
}

inner class ImageSlidePagerViewHolder(private val binding: FragmentImageSliderPageBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(imageUrl: String) {
Glide.with(binding.root)
.load(imageUrl)
.into(binding.image)
}
}
}

The layout for this adapter is like this (It is important to define ImageView inside layout to work correctly).

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:scaleType="fitCenter"/>

</RelativeLayout>

We need now to connect these fragments together by Navigation Component. First we need to create FragmentContainerView that holds the Navigation in the main activity like this:

<?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=".MainActivity">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

Then, we need to setup the navigation in the main activity:

class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.findNavController()
setupActionBarWithNavController(navController)
}
}

After that, we need to create nav_graph.xml that we defined in the FragmentContainerView. We have to create it under Navigation Resource Type.

This graph contains start destination, how fragments interact with each other, and necessary arguments for each fragment. We can manage fragments in Design mode which is easier for this component or in Code mode. The final format of this nav_graph is like this:

<?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/homeFragment">

<fragment
android:id="@+id/homeFragment"
android:name="com.salahafaghani.simpleimageslider.HomeFragment"
android:label="Home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_homeFragment_to_imageSlideDialogFragment"
app:destination="@id/imageSlideDialogFragment" />
</fragment>
<dialog
android:id="@+id/imageSlideDialogFragment"
android:name="com.salahafaghani.simpleimageslider.ImageSliderDialogFragment"
tools:layout="@layout/fragment_image_slider">
<argument
android:name="imageUrls"
app:argType="string[]" />
</dialog>
</navigation>

Final step is to add internet permission to the Manifest file because we get the images from a network.

<uses-permission android:name="android.permission.INTERNET" />

And when we run the app we will get this:

You can find a complete code here

https://github.com/salah-afa/simple-image-slider

--

--

Salah Afaghani

Software Engineer. Experience in Android Programming, C# Programming, and Embedded System Programming.