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

PhotoView的使用

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

一、分析

需求:实现界面全方向滑动浏览功能。
实现方式:使用第三方库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);
}
PhotoView PhotoView aims to help produce an easily usable implementation of a zooming Android ImageView. Branch Develop: Build Status Branch Master: Build Status PhotoView Features Out of the box zooming, using multi-touch and double-tap. Scrolling, with smooth scrolling fling. Works perfectly when used in a scrolling parent (such as ViewPager). Allows the application to be notified when the displayed Matrix has changed. Useful for when you need to update your UI based on the current zoom/scroll position. Allows the application to be notified when the user taps on the Photo. Sample Application The sample application (the source is in the repository) has been published onto Google Play for easy access: Get it on Google Play Gradle Dependency Add this in your root build.gradle file (not your module build.gradle file): allprojects { repositories { ... maven { url "https://jitpack.io" } } } Then, add the library to your project build.gradle dependencies { compile 'com.github.chrisbanes:PhotoView:1.2.6' } Sample Usage There is a sample provided which shows how to use the library in a more advanced way, but for completeness here is all that is required to get PhotoView working: ImageView mImageView; PhotoViewAttacher mAttacher; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Any implementation of ImageView can be used! mImageView = (ImageView) findViewById(R.id.iv_photo); // Set the Drawable displayed Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper); mImageView.setImageDrawable(bitmap); // Attach a PhotoViewAttacher, which takes care of all of the zooming functionality. // (not needed unless you are going to change the drawable later) mAttacher = new PhotoViewAttacher(mImageView); } // If you later call mImageView.setImageDrawable/setImageBitmap/setImageResource/etc then you just need to call mAttacher.update(); Issues With ViewGroups There are some ViewGroups (ones that utilize onInterceptTouchEvent) that throw exceptions when a PhotoView is placed within them, most notably ViewPager and DrawerLayout. This is a framework issue that has not been resolved. In order to prevent this exception (which typically occurs when you zoom out), take a look at HackyDrawerLayout and you can see the solution is to simply catch the exception. Any ViewGroup which uses onInterceptTouchEvent will also need to be extended and exceptions caught. Use the HackyDrawerLayout as a template of how to do so. The basic implementation is: public class HackyProblematicViewGroup extends ProblematicViewGroup { public HackyProblematicViewGroup(Context context) { super(context); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { return super.onInterceptTouchEvent(ev); } catch (IllegalArgumentException e) { //uncomment if you really want to see these errors //e.printStackTrace(); return false; } } } Usage with Fresco Due to the complex nature of Fresco, this library does not currently support Fresco. See this project as an alternative solution. Subsampling Support This library aims to keep the zooming implementation simple. If you are looking for an implementation that supports subsampling, check out this project Pull Requests / Contribution Development happens in develop branch of this repository, and Pull Requests should be filled against that branch. Any Pull Request against master will be rejected License Copyright 2011, 2012 Chris Banes Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
要实现这个功能,你可以使用以下步骤: 1. 在你的项目中添加PhotoView库,可以使用以下Gradle依赖: ```gradle implementation 'com.github.chrisbanes:PhotoView:2.3.0' ``` 2. 创建一个自定义的PhotoView类,并继承自PhotoView类,并实现视频播放的功能。你可以使用Android系统自带的VideoView或者其他第三方库来实现视频播放。 ```java public class CustomPhotoView extends PhotoView { private VideoView mVideoView; public CustomPhotoView(Context context) { super(context); init(); } public CustomPhotoView(Context context, AttributeSet attr) { super(context, attr); init(); } public CustomPhotoView(Context context, AttributeSet attr, int defStyle) { super(context, attr, defStyle); init(); } private void init() { mVideoView = new VideoView(getContext()); mVideoView.setMediaController(new MediaController(getContext())); mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mediaPlayer.setLooping(true); } }); } public void setVideoURI(Uri uri) { mVideoView.setVideoURI(uri); } public void startVideo() { mVideoView.start(); } public void stopVideo() { mVideoView.stopPlayback(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopVideo(); } } ``` 3. 在布局文件中使用自定义的PhotoView,并设置相应的属性和视频URI。 ```xml <com.example.CustomPhotoView android:id="@+id/photo_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitCenter" app:src="@drawable/photo" app:video_uri="@raw/video" /> ``` 4. 在自定义的PhotoView类中处理触摸事件,实现上下滑动的效果。 ```java public class CustomPhotoView extends PhotoView { private static final int INVALID_POINTER_ID = -1; private float mLastTouchX; private float mLastTouchY; private int mActivePointerId = INVALID_POINTER_ID; public CustomPhotoView(Context context) { super(context); init(); } public CustomPhotoView(Context context, AttributeSet attr) { super(context, attr); init(); } public CustomPhotoView(Context context, AttributeSet attr, int defStyle) { super(context, attr, defStyle); init(); } private void init() { setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { final int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final float x = event.getX(); final float y = event.getY(); mLastTouchX = x; mLastTouchY = y; mActivePointerId = event.getPointerId(0); break; } case MotionEvent.ACTION_MOVE: { final int pointerIndex = event.findPointerIndex(mActivePointerId); final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); final float dx = x - mLastTouchX; final float dy = y - mLastTouchY; mLastTouchX = x; mLastTouchY = y; // 上下滑动的阈值 final int SCROLL_THRESHOLD = 10; if (Math.abs(dy) > SCROLL_THRESHOLD) { getParent().requestDisallowInterceptTouchEvent(true); } break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { mActivePointerId = INVALID_POINTER_ID; break; } case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = event.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastTouchX = event.getX(newPointerIndex); mLastTouchY = event.getY(newPointerIndex); mActivePointerId = event.getPointerId(newPointerIndex); } break; } } return false; } }); } } ``` 这样,你就可以使用自定义的PhotoView来支持上下滑动和视频播放了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值