Home

Awesome

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_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 // 是否启用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

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