Android Image Viewer With ViewPager2 and Fresco (Kotlin)

This is an image viewer which support multiple images, where only one image is shown at any time, where you can swipe to navigate.

ImageFragment

NOTE: I am using Jetpack Navigation, thus I am using a Fragment instead of Activity.

class ImageFragment : Fragment() {    // private val viewModel: ImageViewModel by viewModels()    private lateinit var adapter: LocalAdapter    private val args: ImageFragmentArgs by navArgs()    override fun onCreateView(        inflater: LayoutInflater, container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        return inflater.inflate(R.layout.image, container, false)    }    override fun onActivityCreated(savedInstanceState: Bundle?) {        super.onActivityCreated(savedInstanceState)        init()    }    private fun init() {        adapter = LocalAdapter(viewModel)        viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL        viewPager.adapter = adapter        val items = args.default.items?.map {            ViewItem.ImageItem(id = it.id, date = it.date, uri = it.uri, content = it.content)        }        adapter.submitList(items)        args.default.position?.also { position ->            viewPager.doOnLayout {                viewPager.currentItem = position            }        }    }    sealed class ViewItem(open val id: String, val resource: Int) {        data class ImageItem(override val id: String, val date: LocalDateTime, val uri: Uri, val content: String?) : ViewItem(id, R.layout.image_item)    }    class LocalAdapter(val viewModel: ImageViewModel): ListAdapter<ViewItem, LocalAdapter.ViewHolder>(DiffCallback()) {        override fun getItemViewType(position: Int): Int {            return getItem(position).resource        }        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {            val view = LayoutInflater.from(parent.context)                .inflate(viewType, parent, false)            return ViewHolder(view)        }        override fun onBindViewHolder(holder: ViewHolder, position: Int) {            val item = getItem(position)            when(item) {                is ViewItem.ImageItem -> {                    bindImage(holder, item)                }            }        }        private fun bindImage(holder: ViewHolder, item: ViewItem.ImageItem) {            holder.apply {                imageTextView.text = item.date.toString()                imageDraweeView.setImageURI(item.uri)                val lines = listOf(                    item.id,                    item.date.toString(),                    item.content                ).filter {                    it?.isEmpty() == false                }                imageTextView.text = lines.joinToString("\n")            }        }        private class DiffCallback : DiffUtil.ItemCallback<ViewItem>() {            override fun areItemsTheSame(oldItem: ViewItem, newItem: ViewItem): Boolean {                if (oldItem.resource != newItem.resource) return false                // check if id is the same                return oldItem.id == newItem.id            }            @SuppressLint("DiffUtilEquals")            override fun areContentsTheSame(oldItem: ViewItem, newItem: ViewItem): Boolean {                // check if content is the same                // equals using data class                return oldItem == newItem            }        }        inner class ViewHolder(override val containerView: View) :            RecyclerView.ViewHolder(containerView), LayoutContainer {        }    }}

NOTE: For zoomable image, refer to Zoomable PhotoView With Glide or ZoomableDraweeView

ImageArgs

@Parcelizeclass ImageArgs(val items: List<Item>? = null, val position: Int): Parcelable {    @Parcelize    class Item(val id: String, val date: LocalDateTime, val uri: Uri, val content: String?): Parcelable}

NOTE: Refer to Jetpack Navigation

image.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".view.support.ImageFragment">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

image_item.xml

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

    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/imageDraweeView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        fresco:actualImageScaleType="fitCenter"
        fresco:placeholderImage="@drawable/placeholder_100"
        />

    <TextView
        android:id="@+id/imageTextView"
        tools:text="Hello"
        android:layout_gravity="bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#fff"
        android:shadowColor="#000"
        android:shadowDx="2"
        android:shadowDy="2"
        android:shadowRadius="1.5"
        />

</FrameLayout>

Usage

val images = mutableListOf<ImageArgs.Item>()// get image// item = ...var position = 0images.add(ImageArgs.Item(id = item.id, uri = item.uri, date = item.date, content = item.content))// refer to jetpack navigationval args = ImageArgs(items = images, position = position)val action = BatchFragmentDirections.actionBatchNavToImageNav(args)findNavController().navigate(action)

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.