Android Image Viewer With ViewPager2 and Fresco (Kotlin)
April 26, 2020This 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
@Parcelize
class 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 = 0
images.add(ImageArgs.Item(id = item.id, uri = item.uri, date = item.date, content = item.content))
// refer to jetpack navigation
val args = ImageArgs(items = images, position = position)
val action = BatchFragmentDirections.actionBatchNavToImageNav(args)
findNavController().navigate(action)
- algolia
- analytics
- android
- android-ktx
- android-permission
- android-studio
- apps-script
- bash
- bootstrap
- bootstrapvue
- chartjs
- chrome
- cloud-functions
- coding-interview
- coroutines
- crashlytics
- css
- dagger2
- datastore
- datetime
- docker
- eslint
- firebase
- firebase-auth
- firebase-hosting
- firestore
- firestore-security-rules
- flask
- fontawesome
- fresco
- git
- github
- glide
- google-app-engine
- google-cloud-storage
- google-colab
- google-drive
- google-maps
- google-places
- google-play
- google-sheets
- gradle
- html
- hugo
- inkscape
- java
- java-time
- javascript
- jetson-nano
- kotlin
- layout
- lets-encrypt
- lifecycle
- linux
- logging
- lubuntu
- markdown
- mate
- material-design
- matplotlib
- md5
- mongodb
- moshi
- mplfinance
- mysql
- navigation
- nginx
- nodejs
- npm
- nuxtjs
- nvm
- payment
- pip
- pwa
- pyenv
- python
- recylerview
- regex
- room
- rxjava
- scoped-storage
- selenium
- social-media
- ssh
- ssl
- static-site-generator
- static-website-hosting
- sublime-text
- ubuntu
- unit-test
- uwsgi
- viewmodel
- viewpager2
- virtualbox
- vue-cli
- vue-router
- vuejs
- vuelidate
- vuepress
- web-development
- web-hosting
- webpack
- windows
- workmanager
- wsl
- yarn