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:
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:
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.
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!
when i tried to run the app, the images are not showing
hi, can you open the image url directly on your browser? if it is not loaded, you can freely use your own image
i don’t understand where ‘import com.thesimplycoder.imagegallery.R’ is coming from. This ‘R’ class/file was never created.
In case anyone wonders: AndroidX version of RecyclerView: androidx.recyclerview.widget.RecyclerView, see: https://stackoverflow.com/questions/55677041/how-to-access-recyclerview-in-androidx
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.
Hi, the GlideApp will be generated by the Glide annotation inside MyAppGlideModule.kt. You can just simply type CMD + Space on Mac or Ctrl+space on Windows to auto-import while you typing GlideApp. This post uses the older version of glide that needs this kind of setup. The latest version of Glide should not require module setup and it’s easier.
Hi, thanks for your reply, but I managed to get it working differently. I used Glide.with() with Glide imported via “import com.bumptech.glide.Glide”. I saw this done by someone this way here at the code fragment before step 8 at https://www.c-sharpcorner.com/article/how-to-load-the-imageurl-to-imageview-using-glide-in-kotlin2/
Nice Tutorial
Thanks
Excellent tutorial, thanks a lot!
this projects shows 13 errors in new android studio version . can you or anybody do code in latest android version share it. please.