Android Image Gallery Using Kotlin Tutorial

Today I wanna show you how easy to build a simple Android Image Gallery using Kotlin and Glide library. Building an image gallery is one of a must step for beginner Android developers (also for experienced developers). There are so many similar tutorials like this one but most of them are confusing and hard to be understood. So I don’t wanna confuse you with so many steps building this kind of app, so I will just stick with a few steps just to get you to understand the basic concepts.

What the app will look like?

The app will look like this after you finish this tutorial:

Android Image Gallery Using Kotlin Tutorial

 

What will you learn?

  • Using RecyclerView with a grid layout
  • Displaying images in a list using Glide image library
  • Handling user’s tapping the image to display it in full screen
  • Displaying fullscreen images with a swipe to navigate to next and previous image
  • Building a simple Android image gallery using Kotlin programming language

Yup, it will take only these five things to build a simple Android image gallery for you, and it’s very, very easy. If you still don’t know or maybe don’t understand about RecyclerView and Glide library, you’re free to read my post here about Android RecyclerView Tutorial Using Kotlin.

Note: In this post I used Android Studio 3.2.1, make sure you use the latest Android Studio, or if you already install it, be sure to check the latest update. The Kotlin version that I used is Kotlin 1.3.11.

 

Getting Started

Open your Android Studio and choose to Start a new Android Studio Project.

 

Give the Application Name ImageGallery and don’t forget to check the Include Kotlin Support checkbox. After that give the Activity Name MainActivity and wait until the Android Studio finishes preparing your project.

Setting Up the Gradle Files

Open the build.gradle file inside the root project directory of the app and find the allprojects and add the mavenCentral() like this:

allprojects {
   repositories {
       google()
       jcenter()
       mavenCentral()
   }
}

 

Open your app/build.gradle file, add the code apply plugin: 'kotlin-kapt' on top of the file:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

 

After that find the dependencies and add RecyclerView and Glide library implementation inside it:

dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
   implementation 'com.android.support:appcompat-v7:28.0.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'

   implementation 'com.android.support:recyclerview-v7:28.0.0'

   implementation 'com.github.bumptech.glide:glide:4.8.0'
   kapt 'com.github.bumptech.glide:compiler:4.8.0'

   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

 

When you have done that, sync the Gradle file and wait for it to finish.

Setting Up the Project Files

Create four new packages inside your project directory named activity, adapter, fragment, and helper. Move the MainActivity.kt file inside the activity package like this:

 

Glide Set Up

Add a new Kotlin file called MyAppGlideModule.kt inside helper package and write this code inside it:

import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule

@GlideModule
class MyAppGlideModule : AppGlideModule()

 

Creating Custom View and Layouts

We’re gonna create a list of square images for our Android image gallery app so we will create a custom view called SquareLayout. Create a new Kotlin class file inside the helper package named SquareLayout.kt.

import android.widget.RelativeLayout
import android.os.Build
import android.annotation.TargetApi
import android.content.Context
import android.util.AttributeSet

internal class SquareLayout: RelativeLayout {

   constructor(context: Context) : super(context)

   constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

   constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
   constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) :
           super(context, attrs, defStyleAttr, defStyleRes)

   override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
       super.onMeasure(widthMeasureSpec, widthMeasureSpec)
   }
}

 

The purpose of the code above is to create a square layout which has the same width and height that will be used as a container for our ImageView later. This class is extending from RelativeLayout class.

Next open the activity_main.xml inside res/layout directory, replace the code to this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/recyclerView"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

 

Create a new Layout resource file inside the res/layout directory named item_gallery_image.xml.

<?xml version="1.0" encoding="utf-8"?>
<com.thesimplycoder.imagegallery.helper.SquareLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/container"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:foreground="?selectableItemBackground"
   android:clickable="true">

   <ImageView
       android:id="@+id/ivGalleryImage"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:scaleType="centerCrop"
       android:background="@color/softGray"/>

</com.thesimplycoder.imagegallery.helper.SquareLayout>

 

The package name of SquareLayout depends on what you use for your application, it might be not the same as mine. Next, open the values/colors.xml and write this code to give the modify the color:

<?xml version="1.0" encoding="utf-8"?>
<resources>
   <color name="colorPrimary">#400863</color>
   <color name="colorPrimaryDark">#340650</color>
   <color name="colorAccent">#D81B60</color>
   <color name="white">#ffffff</color>
   <color name="softGray">#e8e8e8</color>
</resources>

 

Creating Adapter

Create a new Kotlin class named Image.kt inside adapter package:

data class Image (
   val imageUrl: String,
   val title: String
)

 

Create a new Kotlin class named GalleryImageClickListener.kt inside adapter package:

interface GalleryImageClickListener {

   fun onClick(position: Int)

}

 

The purpose of the listener code above is to be used by the adapter to handle user clicking the image. Next create another Kotlin file named GalleryImageAdapter.kt inside adapter package as well like this:

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.thesimplycoder.imagegallery.R
import com.thesimplycoder.imagegallery.helper.GlideApp
import kotlinx.android.synthetic.main.item_gallery_image.view.*

class GalleryImageAdapter(private val itemList: List<Image>) : RecyclerView.Adapter<GalleryImageAdapter.ViewHolder>() {

   private var context: Context? = null
   var listener: GalleryImageClickListener? = null

   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryImageAdapter.ViewHolder {
       context = parent.context
       val view = LayoutInflater.from(parent.context).inflate(R.layout.item_gallery_image, parent,
               false)
       return ViewHolder(view)
   }

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

   override fun onBindViewHolder(holder: GalleryImageAdapter.ViewHolder, position: Int) {
       holder.bind()
   }

   inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

       fun bind() {

           val image = itemList.get(adapterPosition)

           // load image
           GlideApp.with(context!!)
               .load(image.imageUrl)
               .centerCrop()
               .diskCacheStrategy(DiskCacheStrategy.ALL)
               .into(itemView.ivGalleryImage)

           // adding click or tap handler for our image layout
           itemView.container.setOnClickListener {
               listener?.onClick(adapterPosition)
           }
       }
   }
}

 

The codes above are for displaying the gallery image on each row inside our list. Let’s proceed to the next step.

Set up the RecyclerView and load the images

Open the MainActivity.kt file and write the code like this:

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.GridLayoutManager
import com.thesimplycoder.imagegallery.R
import com.thesimplycoder.imagegallery.adapter.GalleryImageAdapter
import com.thesimplycoder.imagegallery.adapter.GalleryImageClickListener
import com.thesimplycoder.imagegallery.adapter.Image
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), GalleryImageClickListener {

   // gallery column count
   private val SPAN_COUNT = 3

   private val imageList = ArrayList<Image>()
   lateinit var galleryAdapter: GalleryImageAdapter

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

       // init adapter
       galleryAdapter = GalleryImageAdapter(imageList)
       galleryAdapter.listener = this

       // init recyclerview
       recyclerView.layoutManager = GridLayoutManager(this, SPAN_COUNT)
       recyclerView.adapter = galleryAdapter

       // load images
       loadImages()
   }

   private fun loadImages() {

       imageList.add(Image("https://i.ibb.co/wBYDxLq/beach.jpg", "Beach Houses"))
       imageList.add(Image("https://i.ibb.co/gM5NNJX/butterfly.jpg", "Butterfly"))
       imageList.add(Image("https://i.ibb.co/10fFGkZ/car-race.jpg", "Car Racing"))
       imageList.add(Image("https://i.ibb.co/ygqHsHV/coffee-milk.jpg", "Coffee with Milk"))
       imageList.add(Image("https://i.ibb.co/7XqwsLw/fox.jpg", "Fox"))
       imageList.add(Image("https://i.ibb.co/L1m1NxP/girl.jpg", "Mountain Girl"))
       imageList.add(Image("https://i.ibb.co/wc9rSgw/desserts.jpg", "Desserts Table"))
       imageList.add(Image("https://i.ibb.co/wdrdpKC/kitten.jpg", "Kitten"))
       imageList.add(Image("https://i.ibb.co/dBCHzXQ/paris.jpg", "Paris Eiffel"))
       imageList.add(Image("https://i.ibb.co/JKB0KPk/pizza.jpg", "Pizza Time"))
       imageList.add(Image("https://i.ibb.co/VYYPZGk/salmon.jpg", "Salmon "))
       imageList.add(Image("https://i.ibb.co/JvWpzYC/sunset.jpg", "Sunset in Beach"))

       galleryAdapter.notifyDataSetChanged()
   }

   override fun onClick(position: Int) {
       // handle click of image
   }
}

 

I already give a list of sample images right there so you won’t have to prepare the images on your own. But if you have any, feel free to change the image URLs from yours. It’s up to you! The SPAN_COUNT is where you define how many columns your gallery will display, you can modify it to any count you like.

Open the AndroidManifest.xml file and add the internet permission like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.thesimplycoder.imagegallery">

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

   <application
           android:allowBackup="true"
           android:icon="@mipmap/ic_launcher"
           android:label="@string/app_name"
           android:roundIcon="@mipmap/ic_launcher_round"
           android:supportsRtl="true"
           android:theme="@style/AppTheme">
       <activity android:name=".activity.MainActivity">
           <intent-filter>
               <action android:name="android.intent.action.MAIN"/>

               <category android:name="android.intent.category.LAUNCHER"/>
           </intent-filter>
       </activity>
   </application>

</manifest>

 

Running the app

Run your app and let’s see our progress here:

Android Image Gallery Using Kotlin Tutorial

 

The first image on the left is using span count of 2 and the second is using span count of 3, cool right! But this is still the halfway from done because we haven’t implemented the fullscreen display and swipe the image yet.

Creating Gallery Layout

Create a new layout resource file named fragment_gallery_fullscreen.xml which will become the layout of our Android image gallery slider using ViewPager:

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

   <android.support.v4.view.ViewPager
       android:id="@+id/viewPager"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@android:color/black" />

   <TextView
       android:id="@+id/tvGalleryTitle"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="#7f000000"
       android:textColor="@android:color/white"
       android:textSize="16sp"
       android:textStyle="bold"
       android:padding="10dp"
       android:gravity="center"
       android:layout_alignParentBottom="true"
       tools:text="Gallery Image Title"/>

</RelativeLayout>

 

Create another new layout resource file named image_fullscreen.xml, this layout will become the place to display our fullscreen image inside ViewPager from the previous layout we created.

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/ivFullscreenImage"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@android:color/black"
   android:scaleType="centerCrop" />

 

Building the Image Gallery Slider

We’re gonna use a DialogFragment to show our fullscreen image gallery and I wanna use ViewPager to support the swipe gesture. So first, create a new Kotlin class inside fragment package named GalleryFullscreenFragment.kt which will extend to DialogFragment Android class.

import android.content.Context
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.view.PagerAdapter
import android.support.v4.view.ViewPager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.thesimplycoder.imagegallery.R
import com.thesimplycoder.imagegallery.adapter.Image
import com.thesimplycoder.imagegallery.helper.GlideApp
import com.thesimplycoder.imagegallery.helper.ZoomOutPageTransformer
import kotlinx.android.synthetic.main.image_fullscreen.view.*

class GalleryFullscreenFragment : DialogFragment() {

   private var imageList = ArrayList<Image>()
   private var selectedPosition: Int = 0

   lateinit var tvGalleryTitle: TextView
   lateinit var viewPager: ViewPager

   lateinit var galleryPagerAdapter: GalleryPagerAdapter

   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
       val view = inflater.inflate(R.layout.fragment_gallery_fullscreen, container, false)

       viewPager = view.findViewById(R.id.viewPager)
       tvGalleryTitle = view.findViewById(R.id.tvGalleryTitle)

       galleryPagerAdapter = GalleryPagerAdapter()

       imageList = arguments?.getSerializable("images") as ArrayList<Image>
       selectedPosition = arguments!!.getInt("position")

       viewPager.adapter = galleryPagerAdapter
       viewPager.addOnPageChangeListener(viewPagerPageChangeListener)
       viewPager.setPageTransformer(true, ZoomOutPageTransformer())

       setCurrentItem(selectedPosition)

       return view
   }

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
   }

   private fun setCurrentItem(position: Int) {
       viewPager.setCurrentItem(position, false)
   }

   // viewpager page change listener
   internal var viewPagerPageChangeListener: ViewPager.OnPageChangeListener =
           object : ViewPager.OnPageChangeListener {

       override fun onPageSelected(position: Int) {
           // set gallery title
           tvGalleryTitle.text = imageList.get(position).title
       }

       override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {
       }

       override fun onPageScrollStateChanged(arg0: Int) {
       }
   }

   // gallery adapter
   inner class GalleryPagerAdapter : PagerAdapter() {

       override fun instantiateItem(container: ViewGroup, position: Int): Any {

           val layoutInflater = activity?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
           val view = layoutInflater.inflate(R.layout.image_fullscreen, container, false)

           val image = imageList.get(position)

           // load image
           GlideApp.with(context!!)
                   .load(image.imageUrl)
                   .centerCrop()
                   .diskCacheStrategy(DiskCacheStrategy.ALL)
                   .into(view.ivFullscreenImage)

           container.addView(view)

           return view
       }

       override fun getCount(): Int {
           return imageList.size
       }

       override fun isViewFromObject(view: View, obj: Any): Boolean {
           return view === obj as View
       }

       override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
           container.removeView(obj as View)
       }
   }
}

 

Create another Kotlin file inside helper package named ZoomOutPageTransformer.kt which will give our ViewPager a nice transition animation when user swipes the gallery image.

import android.support.v4.view.ViewPager
import android.view.View

private const val MIN_SCALE = 0.75f

class ZoomOutPageTransformer : ViewPager.PageTransformer {

   override fun transformPage(view: View, position: Float) {
       view.apply {
           val pageWidth = width
           when {
               position < -1 -> { // [-Infinity,-1)
                   // This page is way off-screen to the left.
                   alpha = 0f
               }
               position <= 0 -> { // [-1,0]
                   // Use the default slide transition when moving to the left page
                   alpha = 1f
                   translationX = 0f
                   scaleX = 1f
                   scaleY = 1f
               }
               position <= 1 -> { // (0,1]
                   // Fade the page out.
                   alpha = 1 - position

                   // Counteract the default slide transition
                   translationX = pageWidth * -position

                   // Scale the page down (between MIN_SCALE and 1)
                   val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)))
                   scaleX = scaleFactor
                   scaleY = scaleFactor
               }
               else -> { // (1,+Infinity]
                   // This page is way off-screen to the right.
                   alpha = 0f
               }
           }
       }
   }
}

 

Last Step

Open your MainActivity.kt file and find the onClick() method and modify it like this to handle user’s tapping the image to show our fullscreen gallery screen.

override fun onClick(position: Int) {
   // handle click of image

   val bundle = Bundle()
   bundle.putSerializable("images", imageList)
   bundle.putInt("position", position)

   val fragmentTransaction = supportFragmentManager.beginTransaction()
   val galleryFragment = GalleryFullscreenFragment()
   galleryFragment.setArguments(bundle)
   galleryFragment.show(fragmentTransaction, "gallery")
}

 

It’s done finally! You can now run your app again and try tapping the image. You can also swipe it to show other images. Awesome, you did it! This is your own Android Image Gallery.

Android Image Gallery Using Kotlin Tutorial

 

Where to go next?

You can download this full code from my GitHub repo here. So In the next post, I will show you how to use TabLayout and ViewPager to create a tab application, so stay tuned. Be sure to check my other post here about using RecyclerView for Android and Kotlin. Hope you like my post, comment and share it with love!

You may also like...

10 Responses

  1. AmyDahay says:

    when i tried to run the app, the images are not showing

  2. Roman says:

    i don’t understand where ‘import com.thesimplycoder.imagegallery.R’ is coming from. This ‘R’ class/file was never created.

  3. Philipp Dölker says:

    In case anyone wonders: AndroidX version of RecyclerView: androidx.recyclerview.widget.RecyclerView, see: https://stackoverflow.com/questions/55677041/how-to-access-recyclerview-in-androidx

  4. Leo says:

    Hi, i was trying this tutorial when i got stumped at this line “import com.thesimplycoder.imagegallery.helper.GlideApp” for GalleryImageAdapter.kt. At the Glide setup portion, there is only the MyAppGlideModule.kt file under the helper package. Am I supposed to rename that to GlideApp to make it work? Thanks for any pointers in advance.

  5. Rahees says:

    Nice Tutorial

  1. March 1, 2020

    […] Android Simple Image Gallery Tutorial […]

Join the discussion...

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: