iielse / imageviewer

A simple and customizable Android full-screen image viewer 一个简单且可自定义的Android全屏图像浏览器
MIT License
2.24k stars 311 forks source link
draggable gallery gesture gif image imageviewer photobrowser transferee video viewer viewpager wechat zoom

Imageviewer

提供查看缩略视图到原视图的无缝过渡转变的视觉效果,优雅的浏览普通图、长图、动图.

主要特征

引入

implementation 'com.github.iielse:imageviewer:x.y.z' 

最简单的调用代码

fun show() { //
    val dataList: List<Photo> = // 将要展示的图片集合列表
    val clickedData: Photo = // 被点击的其中的那个图片元素信息
    val builder = ImageViewerBuilder(
        context = view.context,
        dataProvider = SimpleDataProvider(clickedData, dataList), // 一次性全量加载 // 实现DataProvider接口支持分页加载
        imageLoader = SimpleImageLoader(), // 可使用demo固定写法 // 实现对数据源的加载.支持自定义加载数据类型,加载方案
        transformer = SimpleTransformer(), // 可使用demo固定写法 // 以photoId为标示,设置过渡动画的'配对'.
    )
    builder.show()
}
// 基本是固定写法. Glide 可以换成别的. demo代码中有video的写法.
class SimpleImageLoader : ImageLoader {
    /** 根据自身photo数据加载图片.可以使用其它图片加载框架. */
    override fun load(view: ImageView, data: Photo, viewHolder: RecyclerView.ViewHolder) {
        val it = (data as? MyData?)?.url ?: return
        Glide.with(view).load(it)
                .placeholder(view.drawable)
                .into(view)
    }

    /**
     * 根据自身photo数据加载超大图.subsamplingView数据源需要先将内容完整下载到本地.
     */
    override fun load(subsamplingView: SubsamplingScaleImageView, data: Photo, viewHolder: RecyclerView.ViewHolder) {
        val it = (data as? MyData?)?.url ?: return
        subsamplingDownloadRequest(it)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe { findLoadingView(viewHolder)?.visibility = View.VISIBLE }
                .doFinally { findLoadingView(viewHolder)?.visibility = View.GONE }
                .doOnNext { subsamplingView.setImage(ImageSource.uri(Uri.fromFile(it))) }
                .doOnError { toast(it.message) }
                .subscribe().bindLifecycle(subsamplingView)
    }

    private fun subsamplingDownloadRequest(url: String): Observable<File> {
        return Observable.create {
            try {
                it.onNext(Glide.with(appContext).downloadOnly().load(url).submit().get())
                it.onComplete()
            } catch (e: java.lang.Exception) {
                if (!it.isDisposed) it.onError(e)
            }
        }
    }

    private fun findLoadingView(viewHolder: RecyclerView.ViewHolder): View? {
        return viewHolder.itemView.findViewById<ProgressBar>(R.id.loadingView)
    }

    ......
}
// 基本是可以作为固定写法.
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(p0: View?) = Unit
                override fun onViewDetachedFromWindow(p0: 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
        }
    }
} 

到此简单的集成已经完毕.

进阶使用.

(实现以下3个方法.可以追加自定义的展示和功能)

// 一般监听翻页onPageSelected可以控制 video播放的状态
// viewer 各状态监听回调
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_LIMIT viewer预加载条数
VIEWER_ORIENTATION viewer滑动方向
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 // 是否启用SubsamplingScaleImageView实现图片区块加载或ExoVideoView实现Video加载
}

FAQ

通过 ViewModelProvider(activity).get(ImageViewerActionViewModel::class.java)获取viewer 对象引用. 之后可使用 setCurrentItem(pos: Int)切换大图位置到指定位置; dismiss()退出浏览大图; remove(item: List<Photo>)删除其中的元素

其它重要说明

demo可运行. demo可运行. demo可运行 .demo代码已重构.

都看到这里了,不点下Star吗 [旺柴]

Thanks

如果您觉得我的开源库帮你节省了大量的开发时间,可扫描下方的二维码随意打赏。你的鼓励是我维护项目最大的动力