理解这篇文章还需要矩阵的知识Matrix详解
PhotoView的使用-全方向滑动浏览
一、分析
需求:实现界面全方向滑动浏览功能。
实现方式:使用第三方库PhotoView
PhotoView简介
https://github.com/chrisbanes/PhotoView
PhotoView是一个大佬贡献的图片预览控件,主要的功能有:
- 图片手势缩放;
- 旋转;
- 全方向平滑滚动;
- 在滑动父控件下能够运行良好;(例如:ViewPager)
其实可以实现这些功能的轮子github上还有很多,比如还有
https://github.com/bm-x/PhotoView
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);
}