PhotoView图片缩放控件源码浅析(一)

2024-04-03 05:32

本文主要是介绍PhotoView图片缩放控件源码浅析(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文参考自http://www.tuicool.com/articles/ea2ANjm

简介

PhotoView属性: 
可以用于查看图片,并对图片进行拖动缩放,拖动过程中不会出现边缘空白; 
双击缩小放大,Fling移动,并支持上述过程的渐变; 
在放大情况下也支持viewpager等的拖动切换; 
支持多击事件检测,单机,双击事件; 
支持各种回调给调用者;

具体的调用方法如下

//将imageview和PhotoViewAttacher 这个控制器关联起来
mAttacher = new PhotoViewAttacher(mImageView);
可以看出来 主要的工作都是在这个PhotoViewAttacher里做的。

public PhotoViewAttacher(ImageView imageView) {//使用软引用,防止内存泄露mImageView = new WeakReference<ImageView>(imageView);
  //這個draswingcache 我們做截屏的時候會經常用到,只需要理解成我們可以通過getDrawingCache拿到view里的內容(這個內容被轉成了bitmap)
    imageView.setDrawingCacheEnabled(true);
    imageView.setOnTouchListener(this);
 //这里就是监听imageview的 layout变化用的 imageview发生变化就会调用这个回调接口
    ViewTreeObserver observer = imageView.getViewTreeObserver();
    if (null != observer)observer.addOnGlobalLayoutListener(this);

     // 设置绘制时这个imageview 可以随着matrix矩阵进行变换
    setImageViewScaleTypeMatrix(imageView);
   //这个是让你在可视化界面能看到预览效果的,大家自定义控件时 也可以使用这个技巧
    if (imageView.isInEditMode()) {return;
    } //根据版本不同 取得需要的mScaleDragDetector 主要就是监听pinch手势的
    mScaleDragDetector = VersionedGestureDetector.newInstance(imageView.getContext(), this);
    //这个dectecor 就是用来监听双击和长按事件的 
    mGestureDetector = new GestureDetector(imageView.getContext(),
            new GestureDetector.SimpleOnGestureListener() {// forward long click listener
                @Override
                public void onLongPress(MotionEvent e) {if (null != mLongClickListener) {mLongClickListener.onLongClick(getImageView());
                    }}});
    //监听双击事件
    mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));

    // Finally, update the UI so that we're zoomable
    setZoomable(true);
}

接下来我们来看看 addOnGlobalLayoutListener这个接听里做了什么,接口中有onGlobalLayout方法

public void onGlobalLayout() {ImageView imageView = getImageView();

    if (null != imageView) {if (mZoomEnabled) { //这个地方要注意imageview的 四个坐标点是永远不会变化的。final int top = imageView.getTop();
            final int right = imageView.getRight();
            final int bottom = imageView.getBottom();
            final int left = imageView.getLeft();

            /**
             * We need to check whether the ImageView's bounds have changed.
             * This would be easier if we targeted API 11+ as we could just use
             * View.OnLayoutChangeListener. Instead we have to replicate the
             * work, keeping track of the ImageView's bounds and then checking
             * if the values change.
             */
            if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
                    || right != mIvRight) {// Update our base matrix, as the bounds have changed
                updateBaseMatrix(imageView.getDrawable());

                // Update values as something has changed
                mIvTop = top;
                mIvRight = right;
                mIvBottom = bottom;
                mIvLeft = left;
            }} else {updateBaseMatrix(imageView.getDrawable());
        }}
}
然后我们跟到updateBaseMatrix()里边

private void updateBaseMatrix(Drawable d) {ImageView imageView = getImageView();
    if (null == imageView || null == d) {return;
    }final float viewWidth = getImageViewWidth(imageView);
    final float viewHeight = getImageViewHeight(imageView);
    final int drawableWidth = d.getIntrinsicWidth();//这个是取原始图片大小的 永远不会变化的
    final int drawableHeight = d.getIntrinsicHeight();

    mBaseMatrix.reset();

    final float widthScale = viewWidth / drawableWidth;
    final float heightScale = viewHeight / drawableHeight;

    if (mScaleType == ScaleType.CENTER) {   //根据传进去的scaletype的值来确定 基础的matrix大小mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
                (viewHeight - drawableHeight) / 2F);

    } else if (mScaleType == ScaleType.CENTER_CROP) {float scale = Math.max(widthScale, heightScale);
        mBaseMatrix.postScale(scale, scale);
        mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
                (viewHeight - drawableHeight * scale) / 2F);

    } else if (mScaleType == ScaleType.CENTER_INSIDE) {float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
        mBaseMatrix.postScale(scale, scale);
        mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
                (viewHeight - drawableHeight * scale) / 2F);

    } else {RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
        RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);

        switch (mScaleType) {case FIT_CENTER:mBaseMatrix
                        .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
                break;

            case FIT_START:mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
                break;

            case FIT_END:mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
                break;

            case FIT_XY:mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
                break;

            default:break;
        }}resetMatrix();
}
然后我们跟到resetMatrix()中看看

/**
 * Resets the Matrix back to FIT_CENTER, and then displays it.s
 */
private void resetMatrix() {mSuppMatrix.reset();
    setImageViewMatrix(getDrawMatrix());
    checkMatrixBounds();
}
进入checkMatrixBounds()中看看

private boolean checkMatrixBounds() {//检查当前显示范围是否在边界上  然後對圖片進行平移(垂直或水平方向) 防止出現留白的現象final ImageView imageView = getImageView();
    if (null == imageView) {return false;
    }final RectF rect = getDisplayRect(getDrawMatrix());
    if (null == rect) {return false;
    }final float height = rect.height(), width = rect.width();
    float deltaX = 0, deltaY = 0;

    final int viewHeight = getImageViewHeight(imageView);
    if (height <= viewHeight) {switch (mScaleType) {case FIT_START:deltaY = -rect.top;
                break;
            case FIT_END:deltaY = viewHeight - height - rect.top;
                break;
            default:deltaY = (viewHeight - height) / 2 - rect.top;
                break;
        }} else if (rect.top > 0) {deltaY = -rect.top;
    } else if (rect.bottom < viewHeight) {deltaY = viewHeight - rect.bottom;
    }final int viewWidth = getImageViewWidth(imageView);
    if (width <= viewWidth) {switch (mScaleType) {case FIT_START:deltaX = -rect.left;
                break;
            case FIT_END:deltaX = viewWidth - width - rect.left;
                break;
            default:deltaX = (viewWidth - width) / 2 - rect.left;
                break;
        }mScrollEdge = EDGE_BOTH;
    } else if (rect.left > 0) {mScrollEdge = EDGE_LEFT;
        deltaX = -rect.left;
    } else if (rect.right < viewWidth) {deltaX = viewWidth - rect.right;
        mScrollEdge = EDGE_RIGHT;
    } else {mScrollEdge = EDGE_NONE;
    }// Finally actually translate the matrix
    mSuppMatrix.postTranslate(deltaX, deltaY);
    return true;
}

这个地方有的人可能会对最后那个检测是否在边界的那个函数不太明白,其实还是挺好理解的,对于容器imageview来说 他的范围是固定的。里面的drawable是不断的变化的,

但是这个drawable 可以和 RectF来关联起来,这个rectF 就是描述出一个矩形,这个矩形就恰好是drawable的大小范围。他有四个值 分别是top left right和bottom。

其中2个值表示矩形的左上面ed点的坐标 另外2个表示右下角的坐标。一个矩形由这2个点即可确定位置以及大小。我用下图来表示:


所以那个函数你要想理解的话 就是自己去画个图。就能知道如何判断是否到边缘了!实际上就是drawbl---matrix---rectF的一个转换。
接着回到PhotoViewAttacher的构造方法,我们看到mScaleDragDetector

public final class VersionedGestureDetector {public static GestureDetector newInstance(Context context,
                                              OnGestureListener listener) {final int sdkVersion = Build.VERSION.SDK_INT;
        GestureDetector detector;

        if (sdkVersion < Build.VERSION_CODES.ECLAIR) {detector = new CupcakeGestureDetector(context);
        } else if (sdkVersion < Build.VERSION_CODES.FROYO) {detector = new EclairGestureDetector(context);
        } else {detector = new FroyoGestureDetector(context);
        }detector.setOnGestureListener(listener);

        return detector;
    }
我们发现这个地方是一个单例,实际上这边代码就是根据sdk的版本号不同 提供不一样的功能,接着我们看FroyoGestureDetector()

@TargetApi(8)
public class FroyoGestureDetector extends EclairGestureDetector {//用于检测缩放的手势protected final ScaleGestureDetector mDetector;

    public FroyoGestureDetector(Context context) {super(context);
        ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {@Override
            public boolean onScale(ScaleGestureDetector detector) {float scaleFactor = detector.getScaleFactor();

                if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))return false;

                mListener.onScale(scaleFactor,
                        detector.getFocusX(), detector.getFocusY());
                return true;
            }//这个函数返回true,onScale函数才会被真正调用
@Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {return true;
            }@Override
            public void onScaleEnd(ScaleGestureDetector detector) {// NO-OP
            }};
        mDetector = new ScaleGestureDetector(context, mScaleListener);
    }@Override
    public boolean isScaling() {return mDetector.isInProgress();
    }@Override
    public boolean onTouchEvent(MotionEvent ev) {mDetector.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }}

这篇关于PhotoView图片缩放控件源码浅析(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/872018

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

Spring MVC 图片上传

引入需要的包 <dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显