Android-PhotoView的使用-全方向滑动浏览

1,641 阅读5分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

理解这篇文章还需要矩阵的知识Matrix详解PhotoView的使用

一、分析

需求:实现界面全方向滑动浏览功能。
实现方式:使用第三方库PhotoView

PhotoView简介

github.com/chrisbanes/…
PhotoView是一个大佬贡献的图片预览控件,主要的功能有:

  • 图片手势缩放;
  • 旋转;
  • 全方向平滑滚动;
  • 在滑动父控件下能够运行良好;(例如:ViewPager)

其实可以实现这些功能的轮子github上还有很多,比如还有
github.com/bm-x/PhotoV…
ImageViewZoom
大家就看心情选择好了,我瞅着我选的这个有几k个小星星。

程序

1、将自定义view转化为图片设置给photoView;
2、监听photoView的滚动事件;

二、PhotoView的使用

1、配置

在我们项目module build.gradle中配置
目前已经到2.3版本了,但是那个版本上做了androidx的迁移,我不想搞,所以后退了几个版本

dependencies {
    implementation 'com.github.chrisbanes:PhotoView:2.1.4'
}

2、在xml布局中引用该控件

    <com.github.chrisbanes.photoview.PhotoView
        android:id="@+id/photo_view"
        android:layout_width="match_parent"
        android:layout_height="822dp"/>

3、在Activity中调用

//full_information_view是自定义的View,这句是把自定义View转化为bitmap
Bitmap bitmap = loadBitmapFromView(full_information_view);
//将图片设置给photoView
photoView.setImageBitmap(bitmap);
//3.1设置图片在photoView中的显示形式,包括是否进行缩放、等比缩放、缩放后展示位置等;
photoView.setScaleType(ImageView.ScaleType.CENTER);
//3.2使用矩阵来控制视图的变换
Matrix matrix = new Matrix();
//获取photoView的矩阵
photoView.getDisplayMatrix(matrix);
float[] floats = new float[9];
matrix.getValues(floats);
//取得photoView矩阵的偏移值
float offsetX = Math.abs(floats[2]);
float offsetY = Math.abs(floats[5]);
//创建操作矩阵
Matrix matrix1 = new Matrix();
//设置偏移
matrix1.preTranslate(offsetX, offsetY);
//当前矩阵在后a=b*a,意味着先平移再缩放
matrix1.postScale(2,2);
//将操作矩阵设置给photoView
photoView.setDisplayMatrix(matrix1);

代码图示,一行行代码干了什么

黑线部分代表photoView,红线部分代表Drawable。
以photoView为参考系

3.1 首先设置图片在photoView中的显示形式

//设置图片在photoView中的显示形式,包括是否进行缩放、等比缩放、缩放后展示位置等;
//轮子大佬提供了七种scaleType的属性值;
//不设置的话,默认属性值为FIT_CENTER。在该模式下,图片会被等比缩放到能够填充控件大小,并居中展示;
//这里设置了Center模式,这个模式下,图片不缩放,photoView会展示图片的中心部分。
//为什么这里不适用FIT_CENTER,是因为我想实现的功能是,将图片放大两倍,然后显示图片的左上角,超过部分通过滚动滑进可视区域(photoView)
//显示左上角这种奇葩需求,使用setScaleType()是无法实现的,所以我这里设置为Center后下面会进行平移处理
photoView.setScaleType(ImageView.ScaleType.CENTER);

在这里插入图片描述
此时drawable的矩阵为,以photoView为参考坐标,左移886,上移539
在这里插入图片描述
这部分模式的定义可以参考Android ImageView 的scaleType 属性图解
大佬是照着这个仿写的,实现效果大差不差。只是不支持MATRIX,即矩阵模式。

3.2、更新矩阵

上述代码之后,我们已经使图片显示在正中。
接下来我们还需要缩放2倍,并且显示左上角。
那么我们该如何更新矩阵呢?
由于photoView是继承自ImageView的,如果我们不进行滚动。完全可以使用ImageView的方法进行处理

//这里注意,不能setTranslate()再setScale这样写
Matrix matrix = new Matrix();
matrix.setTranslate(0,0);
matrix.preScale(2,2);
photoView.setImageMatrix(matrix);

但是我们是需要滚动的,就需要处理滚动时的显示
所以PhotoView提供了setDisplayMatrix方法
我们跟一下这块代码,看这句代码到底做了什么

Activity.java
photoView.setDisplayMatrix(matrix);


photoView.java
public boolean setDisplayMatrix(Matrix finalRectangle) {
    return attacher.setDisplayMatrix(finalRectangle);
}


PhotoViewAttacher.java
public boolean setDisplayMatrix(Matrix finalMatrix) {
    ...
    mSuppMatrix.set(finalMatrix);
    checkAndDisplayMatrix();
    return true;
}


PhotoViewAttacher.java
private void checkAndDisplayMatrix() {
    if (checkMatrixBounds()) {
        setImageViewMatrix(getDrawMatrix());
    }
}

如下,重点在这个地方,它是用了mDrawMatrix矩阵后乘了一下我们刚设置进来的矩阵。那么mDrawMatrix矩阵是什么呢?是我们上一次操作的结果矩阵。也就是说原矩阵后乘操作矩阵。所以我们setDisplayMatrix时只需要将操作矩阵传进来就可以了。
注意:a.postConCat(b),相当于后乘,即 a=b×a;

private Matrix getDrawMatrix() {
    mDrawMatrix.set(mBaseMatrix);
    mDrawMatrix.postConcat(mSuppMatrix);
    return mDrawMatrix;
}

最后获取到的这个矩阵,设置给photoView

mImageView.setImageMatrix(matrix);

3.2、操作矩阵的构成

//3.2使用矩阵来控制视图的变换
Matrix matrix = new Matrix();
//获取photoView的矩阵
photoView.getDisplayMatrix(matrix);
float[] floats = new float[9];
matrix.getValues(floats);
//取得photoView矩阵的偏移值
float offsetX = Math.abs(floats[2]);
float offsetY = Math.abs(floats[5]);
//创建操作矩阵
Matrix matrix1 = new Matrix();
//设置偏移
matrix1.preTranslate(offsetX, offsetY);
//当前矩阵在后a=b*a,意味着先平移再缩放。注意这个地方一定要用后乘
//如果前乘,就是先缩放再平移。此时平移的话,移到坐标原点的位置,将是偏移值*缩放值,而不是仅仅的偏移值了
matrix1.postScale(2,2);
//将操作矩阵设置给photoView
photoView.setDisplayMatrix(matrix1);

先平移
在这里插入图片描述
再缩放
在这里插入图片描述
以photoView为参考坐标,最后传进去的操作矩阵为
在这里插入图片描述

3.3 photoView接收到操作矩阵后的操作

注意:如果有大佬看到了这里,并且看懂了的话,能不能告诉我这个地方为什么是后乘。因为在我的理解里,图像处理中,越靠近右边的矩阵越先执行,这里不应该先平移到中心点,然后再执行平移缩放的操作吗?

//原矩阵后乘操作矩阵
mDrawMatrix.postConcat(mSuppMatrix);

在这里插入图片描述

4、设置监听

        photoView.setOnMatrixChangeListener(new OnMatrixChangedListener() {
            @Override
            public void onMatrixChanged(RectF rect) {
                Matrix matrix = new Matrix();
                photoView.getDisplayMatrix(matrix);
                float[] floats = new float[9];
                matrix.getValues(floats);
                float scaleX = floats[0];
                float scaleY = floats[4];
                //获取当前平移的距离
                float offsetX = Math.abs(floats[2]);
                float offsetY = Math.abs(floats[5]);
                Log.i("rachel", "scaleX: " + scaleX + "scaleY: " + scaleY + " x: " + offsetX + " y: " + offsetY);
                Drawable drawable = photoView.getDrawable();
                final float viewWidth = getImageViewWidth(photoView);
                final float viewHeight = getImageViewHeight(photoView);
                final float drawableWidth = drawable.getIntrinsicWidth() * scaleX;
                final float drawableHeight = drawable.getIntrinsicHeight() * scaleY;
                //可以移动的最大水平距离,为photoView和drawable宽度差值的绝对值
                final float x=Math.abs(viewWidth - drawableWidth);
                final float y=Math.abs(viewHeight - drawableHeight);
                horizontalProgressBar.setProcess(offsetX / (x * 1f));
                verticalProgressBar.setProcess(offsetY / (y * 1f));
            }
        });

如下图所示,红色实线为drawable当前所在位置。photoview的位置保持不变(黑色实线),drawable相对于photoview移动,所能移动的最大水平距离,为photoView和drawable宽度差值的绝对值,即下图所示X部分。垂直移动距离同理。
在这里插入图片描述

相关API整理

事件监听

/**
 * Interface definition for a callback to be invoked when the photo is experiencing a drag event
 */
public interface OnViewDragListener {

    /**
     * Callback for when the photo is experiencing a drag event. This cannot be invoked when the
     * user is scaling.
     *
     * @param dx The change of the coordinates in the x-direction
     * @param dy The change of the coordinates in the y-direction
     */
    void onDrag(float dx, float dy);
}