Convert Figma logo to code with AI

iielse logoimageviewer

A simple and customizable Android full-screen image viewer 一个简单且可自定义的Android全屏图像浏览器

2,302
311
2,302
0

Top Related Projects

A simple and customizable Android full-screen image viewer with shared image transition support, "pinch to zoom" and "swipe to dismiss" gestures

图片浏览缩放控件

Android library (AAR). Highly configurable, easily extendable deep zoom view for displaying huge images without loss of detail. Perfect for photo galleries, maps, building plans etc.

Big image viewer supporting pan and zoom, with very little memory usage and full featured image loading choices. Powered by Subsampling Scale Image View, Fresco, Glide, and Picasso. Even with gif and webp support! 🍻

Quick Overview

ImageViewer is an Android library for displaying and interacting with images. It provides a customizable image viewer with features like zooming, panning, and swiping between multiple images. The library is designed to be easy to integrate into existing Android applications.

Pros

  • Smooth and responsive image viewing experience
  • Supports zooming, panning, and swiping gestures
  • Customizable UI elements and transitions
  • Easy integration with existing Android projects

Cons

  • Limited documentation and examples
  • May require additional setup for advanced customization
  • Not actively maintained (last update was over a year ago)
  • Potential performance issues with very large images

Code Examples

  1. Basic implementation of ImageViewer:
val imageUrls = listOf("https://example.com/image1.jpg", "https://example.com/image2.jpg")
ImageViewer.Builder(this, imageUrls)
    .setStartPosition(0)
    .show()
  1. Customizing the overlay view:
ImageViewer.Builder(this, imageUrls)
    .setOverlayView(customOverlayView)
    .setImageChangeListener { position ->
        customOverlayView.updatePosition(position)
    }
    .show()
  1. Implementing a custom transition:
ImageViewer.Builder(this, imageUrls)
    .setCustomImageRequestBuilder { requestBuilder ->
        requestBuilder.transition(DrawableTransitionOptions.withCrossFade())
    }
    .show()

Getting Started

To use ImageViewer in your Android project, follow these steps:

  1. Add the JitPack repository to your project's build.gradle file:
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  1. Add the dependency to your app's build.gradle file:
dependencies {
    implementation 'com.github.iielse:imageviewer:VERSION'
}
  1. Initialize and show the ImageViewer in your activity or fragment:
val imageUrls = listOf("https://example.com/image1.jpg", "https://example.com/image2.jpg")
ImageViewer.Builder(this, imageUrls)
    .setStartPosition(0)
    .show()

Replace VERSION with the latest version of the library. Make sure to handle necessary permissions for internet access and image loading in your Android manifest file.

Competitor Comparisons

A simple and customizable Android full-screen image viewer with shared image transition support, "pinch to zoom" and "swipe to dismiss" gestures

Pros of StfalconImageViewer

  • More customizable UI elements, including custom overlays and transitions
  • Built-in support for zooming and panning gestures
  • Easier integration with various image loading libraries

Cons of StfalconImageViewer

  • Less frequent updates and maintenance compared to ImageViewer
  • Slightly more complex implementation due to additional features
  • Limited documentation and examples available

Code Comparison

StfalconImageViewer:

StfalconImageViewer.Builder<MyImage>(context, images) { imageView, image ->
    Glide.with(context).load(image.url).into(imageView)
}.show()

ImageViewer:

ImageViewer.Builder(fragments, ImageViewerAdapter())
    .setStartPosition(startPosition)
    .show()

Both libraries offer simple implementation, but StfalconImageViewer provides more flexibility in image loading, while ImageViewer focuses on a streamlined approach with fragment support.

StfalconImageViewer excels in customization and gesture handling, making it suitable for complex image viewing scenarios. ImageViewer, on the other hand, offers a more straightforward implementation with regular updates, making it ideal for simpler use cases and long-term maintenance.

图片浏览缩放控件

Pros of PhotoView

  • More comprehensive feature set, including support for video playback and GIF animations
  • Better performance optimization, especially for large images and smooth zooming
  • More active development and community support

Cons of PhotoView

  • Larger library size, which may impact app size and load times
  • Steeper learning curve due to more complex API and configuration options
  • Less customizable UI compared to ImageViewer's simpler approach

Code Comparison

ImageViewer:

val imageViewer = ImageViewer.Builder(fragments = images, startPosition = startPosition)
    .setBackgroundColorRes(R.color.black)
    .setOverlayView(overlayView)
    .build()
imageViewer.show()

PhotoView:

PhotoView photoView = findViewById(R.id.photo_view);
photoView.setImageResource(R.drawable.image);
photoView.setScaleType(ImageView.ScaleType.CENTER_CROP);
photoView.setZoomable(true);
photoView.setOnPhotoTapListener(new OnPhotoTapListener() { ... });

Both libraries offer easy-to-use APIs for displaying and interacting with images. PhotoView provides more granular control over zoom behavior and touch events, while ImageViewer focuses on simplicity and quick implementation. PhotoView's code is more verbose but offers greater flexibility, whereas ImageViewer's builder pattern allows for concise setup with fewer lines of code.

Android library (AAR). Highly configurable, easily extendable deep zoom view for displaying huge images without loss of detail. Perfect for photo galleries, maps, building plans etc.

Pros of subsampling-scale-image-view

  • Efficient memory usage through subsampling and tiling
  • Supports very large images without OutOfMemoryErrors
  • Smooth pan and zoom with inertia scrolling

Cons of subsampling-scale-image-view

  • Less feature-rich compared to imageviewer
  • Limited customization options for UI elements
  • Lacks built-in support for image transitions or animations

Code Comparison

subsampling-scale-image-view:

SubsamplingScaleImageView imageView = new SubsamplingScaleImageView(context);
imageView.setImage(ImageSource.uri("/sdcard/image.jpg"));
imageView.setMaxScale(10f);

imageviewer:

val imageViewer = ImageViewer.Builder(fragments, images)
    .setStartPosition(startPosition)
    .setImageChangeListener(onImageChangeListener)
    .setOnItemClickListener(onItemClickListener)
    .build()
imageViewer.show()

subsampling-scale-image-view focuses on efficient handling of large images with smooth zooming and panning. It's ideal for applications dealing with high-resolution images or maps. However, it lacks some of the advanced features and customization options offered by imageviewer.

imageviewer provides a more comprehensive solution for image viewing, including transitions, customizable UI elements, and support for various image sources. It's better suited for applications requiring a full-featured image gallery or viewer with enhanced user interaction.

The code comparison shows that subsampling-scale-image-view is more focused on individual image display and manipulation, while imageviewer offers a more complete gallery-like experience with multiple images and customizable behaviors.

Big image viewer supporting pan and zoom, with very little memory usage and full featured image loading choices. Powered by Subsampling Scale Image View, Fresco, Glide, and Picasso. Even with gif and webp support! 🍻

Pros of BigImageViewer

  • Supports multiple image loading libraries (Fresco, Glide, and custom image loaders)
  • Handles very large images efficiently with tiling and subsampling
  • Provides smooth transitions between images in a gallery view

Cons of BigImageViewer

  • More complex setup and configuration compared to ImageViewer
  • Larger library size due to additional features and dependencies
  • May have a steeper learning curve for basic use cases

Code Comparison

ImageViewer:

val imageViewer = ImageViewer.Builder(context, images)
    .setStartPosition(startPosition)
    .show()

BigImageViewer:

BigImageViewer.initialize(FrescoImageLoader.with(context))

val bigImageView = findViewById<BigImageView>(R.id.big_image_view)
bigImageView.showImage(Uri.parse("https://example.com/image.jpg"))

Both libraries offer easy-to-use APIs for displaying images, but BigImageViewer requires initialization of an image loader and uses a custom view, while ImageViewer provides a more straightforward builder pattern for quick implementation.

ImageViewer is generally simpler to use for basic image viewing needs, while BigImageViewer offers more advanced features and flexibility for handling large images and complex scenarios. The choice between the two depends on the specific requirements of your project and the level of customization needed.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

ImageViewer

一个面向 Android 的图片/视频预览库,支持从缩略图到原图的平滑过渡动画,并兼容普通图、长图、GIF、视频和分页数据源。

特性

  • 缩略图到大图的无缝过渡动画
  • 普通图缩放、拖拽浏览
  • 长图分块加载
  • 视频预览与播放控制
  • 下拉/上滑拖拽关闭
  • 分页数据加载
  • 预览数据删除
  • 预览页 UI 自定义扩展
  • RTL 适配

效果图

ImageViewer Demo

引入

JitPack:

implementation("com.github.iielse:imageviewer:x.y.z")

运行环境

  • minSdk 23
  • compileSdk 36
  • Java 17

快速开始

最简单的调用方式:

fun show(view: View) {
    val dataList: List<Photo> = TODO()
    val clickedData: Photo = TODO()

    val builder = ImageViewerBuilder(
        context = view.context,
        dataProvider = SimpleDataProvider(clickedData, dataList),
        imageLoader = SimpleImageLoader(),
        transformer = SimpleTransformer(),
    )

    builder.show()
}

ImageLoader 示例

普通图使用 Glide,长图先下载到本地文件再交给 SubsamplingScaleImageView:

class SimpleImageLoader : ImageLoader {
    override fun load(view: ImageView, data: Photo, viewHolder: RecyclerView.ViewHolder) {
        val url = (data as? MyData)?.url ?: return
        Glide.with(view)
            .load(url)
            .placeholder(view.drawable)
            .into(view)
    }

    override fun load(
        subsamplingView: SubsamplingScaleImageView,
        data: Photo,
        viewHolder: RecyclerView.ViewHolder,
    ) {
        val url = (data as? MyData)?.url ?: return
        val cache = subsamplingCache.get(url)

        if (cache != null) {
            subsamplingView.setImage(cache)
            return
        }

        subsamplingView.lifecycleOwner.lifecycleScope.launch {
            try {
                findLoadingView(viewHolder)?.visibility = View.VISIBLE
                val file = subsamplingDownloadRequest(url)
                subsamplingView.setImage(
                    ImageSource.uri(Uri.fromFile(file)).also { source ->
                        subsamplingCache.put(url, source)
                    }
                )
            } catch (e: Throwable) {
                toast(e.message)
            } finally {
                findLoadingView(viewHolder)?.visibility = View.GONE
            }
        }
    }

    private suspend fun subsamplingDownloadRequest(url: String): File =
        withContext(Dispatchers.IO) {
            Glide.with(appContext).downloadOnly().load(url).submit().get()
        }
}

说明:

  • subsamplingDownloadRequest() 现在是协程挂起函数,不再依赖 RxJava。
  • 长图下载逻辑建议绑定生命周期,避免页面销毁后继续回调 UI。

Transformer 示例

Transformer 用于把缩略图和预览页建立“配对关系”,决定过渡动画的起点和终点。

class SimpleTransformer : Transformer {
    override fun getView(key: Long): ImageView? = provide(key)

    companion object {
        private val transition = HashMap<ImageView, Long>()

        fun put(photoId: Long, imageView: ImageView) {
            require(isMainThread())
            if (!imageView.isAttachedToWindow) return

            imageView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
                override fun onViewAttachedToWindow(v: View) = Unit

                override fun onViewDetachedFromWindow(v: View) {
                    transition.remove(imageView)
                    imageView.removeOnAttachStateChangeListener(this)
                }
            })

            transition[imageView] = photoId
        }

        private fun provide(photoId: Long): ImageView? {
            transition.keys.forEach {
                if (transition[it] == photoId) return it
            }
            return null
        }
    }
}

到这里,基础接入已经完成。

进阶使用

你可以在 builder 上追加 3 类自定义能力:

  • 自定义每一页上的 UI
    例如:更多信息、保存、分享、操作按钮
    builder.setVHCustomizer(MyCustomViewHolderUI())

  • 自定义覆盖层 UI
    例如:指示器、顶部栏、底部工具栏
    builder.setOverlayCustomizer(MyCustomIndicatorUI())

  • 监听预览状态变化
    例如:翻页、拖拽、过渡动画、视频播放状态
    builder.setViewerCallback(MyViewerStateChangedListener())

ViewerCallback

interface ViewerCallback : ImageViewerAdapterListener {
    override fun onInit(viewHolder: RecyclerView.ViewHolder) {}
    override fun onDrag(viewHolder: RecyclerView.ViewHolder, view: View, fraction: Float) {}
    override fun onRestore(viewHolder: RecyclerView.ViewHolder, view: View, fraction: Float) {}
    override fun onRelease(viewHolder: RecyclerView.ViewHolder, view: View) {}

    fun onPageScrollStateChanged(state: Int) {}
    fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
    fun onPageSelected(position: Int, viewHolder: RecyclerView.ViewHolder) {}
}

参数配置

一般情况下不需要调整,但以下参数可按业务需要覆盖:

属性说明
OFFSCREEN_PAGE_LIMITViewer 预加载页数
VIEWER_ORIENTATIONViewer 滑动方向
VIEWER_BACKGROUND_COLOR预览背景色
DURATION_TRANSITION过渡动画时长
DURATION_BG背景动画时长
SWIPE_DISMISS是否支持拖拽关闭
SWIPE_TOUCH_SLOP拖拽触摸阈值
DISMISS_FRACTION拖拽关闭判定阈值
TRANSITION_OFFSET_Y透明状态栏场景下的过渡位置修正

数据源定义

interface Photo {
    fun id(): Long
    fun itemType(): @ItemType.Type Int
}

说明:

  • id() 必须唯一,用于分页定位和过渡动画映射。
  • itemType() 决定当前数据是普通图、长图还是视频。

FAQ

如何手动关闭预览页?

通过:

ViewModelProvider(activity).get(ImageViewerActionViewModel::class.java)

获取 viewer 的动作控制器后,可以调用:

  • setCurrentItem(pos: Int) 切换到指定位置
  • dismiss() 关闭预览
  • remove(item: List<Photo>) 删除指定元素

如何展示视频?

可直接参考 demo 中的实现:

  • SimpleViewerCustomizer
  • SimpleImageLoader
  • ExoVideoView

为什么没有过渡动画?

通常是 Transformer 配置不正确。要保证:

  • getView() 能返回当前缩略图对应的 ImageView
  • 缩略图与预览页使用相同的 photoId

为什么过渡动画和原图位置有偏差?

注意状态栏/沉浸式布局的影响,必要时调整:

Config.TRANSITION_OFFSET_Y

Demo 说明

仓库内自带 demo,包含以下能力示例:

  • 普通图/长图/视频
  • 分页预览
  • 自定义覆盖层
  • 自定义每页 UI
  • 沉浸式 DialogFragment

备注

  • 当前 demo 已迁移到 Paging 3
  • Viewer 动作事件已改为 SharedFlow
  • 视频能力已迁移到 AndroidX Media3