ImageView 实现缩放,平移,Fling

Imageview自适应显示图片(不管任何图片任何尺寸都自适应View大小显示)
主要使用了一下知识点:
Matrix:矩阵变换(缩放,平移)
ScaleGestureDetector:缩放手势检测
GestureDetector:手势检测

- ImageView 缩放功能
缩放实现重写:

//如果返回true,则会重置detector对放大比例的计算。默认为1.0
//如果返回false,则持续计算放大比例
public boolean onScale(ScaleGestureDetector detector);

实现步骤:

  1. 获取Image本身的缩放比例
//因为是等比缩放,所以X轴的缩放等于y轴缩放
private float getScale(){
		Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MSCALE_X];
    }
  1. 获取当前用户操作的缩放比例的增量
float scaleFactor = detector.getScaleFactor();
scaleFactor = scaleFactor  - 1.0f;
  1. 使用Matrix进行缩放
float now_scale = getScale();
//当前放大倍数+当次放大的比例,放大倍数不能为负数,负数的话则图片上下颠倒了
now_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);

Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);

- ImageView 平移功能
平移重写:

public boolean onTouch(View v, MotionEvent event);

实现步骤:

  1. OnTouch 处理ACTION_MOVE事件,计算Move的偏移量
float dx = event.getX() - mLastX;
float dy = event.getY() - mLastY;
//计算完之后更新最后的X,Y位置
mLastX = event.getX();
mLastY = event.getY();
  1. 获取Img已经发生的平移量
private float getTranclateX(){
        Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MTRANS_X];
    }

private float getTranclateY(){
        Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MTRANS_Y];
    }
  1. 使用Matrix进行平移
Matrix matrix = new Matrix();
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);

- 平移+缩放综合处理
上述只是单独的实现了缩放或者平移某一功能,setImageMatrix的Matrix是New的新对象会重置Scale或Translate因子。但是图片缩放和平移可能是同步进行的,所以我们需要同时处理Scale和Translate。

例如:图片目前平移量是(-150,-200),放大比例是(2.0,2.0);如果我们将图片平移到(-300,-400),那么图片的放大比例依然是(2.0,2.0)。如果我们将图片放大到(3.0,3.0),因为图片放大了,所以尺寸发生变化导致平移量也将发生变化,不在保持为(-300,-400)。具体偏移量的更新算法,可以参考Matrix 变换原理

所以平移的时候我们需要保持缩放因子

Matrix matrix = new Matrix();
matrix.preScale(getScale(),getScale());
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);

缩放的时候需要更新平移量为缩放之后的平移量

//先进行缩放
Matrix matrix = new Matrix();
matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
setImageMatrix(matrix);
//缩放成功之后,获取最新平移量,更新平移量
matrix = new Matrix();
matrix.postScale(getScale(),getScale());
matrix.postTranslate(getTranclateX(),getTranclateY());
setImageMatrix(matrix);

此时就可以对图片进行正常的缩放和平移了。

为什么我们不使用getImageMatrix来获取image的Matrix,然后在通过Matrix来进行变换?

因为矩阵变换的算法很复杂,如果操作不当,那么结果就和我们预期的有差别了。所以为了简单计算,控制变量因子,每次都重置Matrix,保证Matrix是我们想象中的模样。

- 实现图片的Fling功能
基本监听GestureDetector的onFling中的垂直方向投递速度,然后在定时器里面进行变速移动
当前实现最基本的算法。
1.投掷之后再1s中内完成垂直平移。
2.因为投掷之后是变速的先快后慢,所以打算在1s中完成50次的变速移动
3.变速算法使用垂直速度(velocityY)/5000获得单步移动量。然后50次的移动中,第一次移动50个单位,第二次移动49个单位,逐次递减,直至最后一次移动一个单位,完成Fling过程。
4.如果在移动的过程中用户点击了屏幕,此时应该停止移动。
(以上算法仅仅为最基本的模拟算法,用户可以实现自己的更完善的算法)

 mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
                Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);
				
				 //投掷距离过短或者速度很慢不作为Fling处理
                if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
                    return false;
               //启动定时器20ms移动一次
               try {
               	mTimer = new Timer();
                mTimerCount = 0;
                mTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                	Message message = new Message();
                    message.what = 1;
                    message.arg1 = (int) velocityY;
                    mHandler.sendMessage(message);
                    }
               	},0,20);
             }
             catch (Exception e){
             	e.printStackTrace();
             }
             return super.onFling(e1, e2, velocityX, velocityY);
            }
        });

		//处理定时器
		mHandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case 1: {
                        Log.i(TAG, "handleMessage: arg1:"+msg.arg1);

                        float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
                        float fOffset = fStep * (MAX_COUNT - mTimerCount);
                        if(msg.arg1 < 0)
                            fOffset = -fOffset;
                        ++mTimerCount;
                        //如果滑动结束
                        if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
                            mTimer.cancel();
                            mTimer = null;
                        }
                        break;  
                    }
                        
                }
                super.handleMessage(msg);
            }
        };

private boolean scrollByY(float dOffset){
	 float dy = getTranclateY();
     dy += dOffset;
	
	Matrix matrix = new Matrix();
    matrix.preScale(getScale(),getScale());
    matrix.postTranslate(getTranclateX(), dy);
    setImageMatrix(matrix);
	return false;
}

  • 优化
    1.图片如果已经可以完全展示在视图内,则应该不允许在缩小,而且上下居中显示
 Drawable d = getDrawable();
 if (null == d) return;
 //计算最小的缩放比例
mMinScale = getWidth()/d.getIntrinsicWidth();

ow_scale = now_scale + (scaleFactor - 1.0f);
now_scale = Math.abs(now_scale);
now_scale = Math.max(mMinScale,now_scale);

if(now_scale < SCALE_MAX ){
            Matrix matrix = new Matrix();
            matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
            setImageMatrix(matrix);

            //缩放之后TranslX和TranslateY会发生变化,如果缩放之后图片小于视图则居中显示
            RectF rtMatrixf = getMatrixRectF();
            //检查上下左右边界
            float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
            //如果图片小于视图则左右居中显示
            if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
                dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
            }
            //制图片右边界显示在视图内,导致白边
            else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
                dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
            }

            //如果图片小于视图则垂直居中显示
            if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
                dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
            }
            //控制图片下边界显示在视图内,导致白边
            else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
                dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
            }

            matrix = new Matrix();
            matrix.postScale(getScale(),getScale());
            matrix.postTranslate(dx,dy);
            setImageMatrix(matrix);
     }

2.图片移动的过程中,上下左右边框不应该出现在视图范围之内,造成白边

case MotionEvent.ACTION_MOVE:{
                Drawable drawable = getDrawable();
                if(drawable == null)
                    return true;

                RectF rtMatrixf = getMatrixRectF();
                //检查左右边界
                float dx = 0;
                //如果图片小于视图则左右居中显示
                if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
                    dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
                }
                else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
                    //控制图片左边界显示在视图内,导致白边,dx只允许 < 0
                    dx = getTranclateX() + (event.getX() - mLastX);
                    dx = Math.min(0,dx);
                    //制图片右边界显示在视图内,导致白边
                    if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
                        dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
                    }
                }

                //检查上下边界
                float dy = 0;
                //如果图片小于视图则垂直居中显示
                if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
                    dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
                }
                else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
                    //控制图片上边界显示在视图内,导致白边,dy只允许 < 0
                    dy = getTranclateY() + (event.getY() - mLastY);
                    dy = Math.min(0,dy);
                    //控制图片下边界显示在视图内,导致白边
                    if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
                        dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
                    }
                }

                mLastX = event.getX();
                mLastY = event.getY();
                Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
                //计算边界,
                Matrix matrix = new Matrix();
                matrix.preScale(getScale(),getScale());
                matrix.postTranslate(dx, dy);
                setImageMatrix(matrix);
                break;
            }
  1. Fling的时候,如果已经到底或者到顶部则应该结束
private boolean scrollByY(float dOffset){
        boolean bEnd = false;
        RectF rtMatrixf = getMatrixRectF();
        //检查上下边界
        float dy = getTranclateY();
        dy += dOffset;
        if(dy > 0) {
            dy = 0;
            bEnd = true;
        }

        if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
            dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
            bEnd = true;
        }

        //计算边界,
        Matrix matrix = new Matrix();
        matrix.preScale(getScale(),getScale());
        matrix.postTranslate(getTranclateX(), dy);
        setImageMatrix(matrix);
        return bEnd;
    }

Fling:
在这里插入图片描述
缩放和拖拽
在这里插入图片描述

最终源码:

public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
        View.OnTouchListener {
    private static final String TAG = ZoomImageView.class.getName();
    private ScaleGestureDetector mScaleGestureDetector;
    private GestureDetector mGestureDetector;
    private static final float SCALE_MAX = 4.0f;
    private final float[] matrixValues = new float[9];
    //最终点击的位置
    private float mLastX = 0,mLastY = 0;

    //定时器滑动,滑动1s中,即50次
    private Timer mTimer = null;
    private final int MAX_COUNT = 50;
    int mTimerCount = 0;
    private Handler mHandler;

    public ZoomImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        super.setScaleType(ScaleType.MATRIX);
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
                Log.i(TAG, "onFling: y0:"+e1.getRawY()+",y1:"+e2.getRawY()+",velocityY:"+velocityY);

                //投掷距离过短或者速度很慢不作为Fling处理
                if( Math.abs(e1.getY() - e2.getY()) < 30 || Math.abs(velocityY) < 5000)
                    return false;
                //只有当前显示不下才可以进行Fling
                RectF rtF = getMatrixRectF();
                if(rtF.bottom - rtF.top > getHeight())
                {
                    try {
                        mTimer = new Timer();
                        mTimerCount = 0;
                        mTimer.schedule(new TimerTask() {
                            @Override
                            public void run() {
                                Message message = new Message();
                                message.what = 1;
                                message.arg1 = (int) velocityY;
                                mHandler.sendMessage(message);
                            }
                        },0,20);
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }

                }

                return super.onFling(e1, e2, velocityX, velocityY);
            }
        });
        this.setOnTouchListener(this);

        mHandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case 1: {
                        Log.i(TAG, "handleMessage: arg1:"+msg.arg1);

                        float fStep = Math.max(1,Math.abs(msg.arg1)/5000.0f);
                        float fOffset = fStep * (MAX_COUNT - mTimerCount);
                        if(msg.arg1 < 0)
                            fOffset = -fOffset;
                        ++mTimerCount;
                        //如果滑动结束
                        if(scrollByY(fOffset) ||mTimerCount >= MAX_COUNT){
                            mTimer.cancel();
                            mTimer = null;
                        }
                        break;  
                    }
                        
                }
                super.handleMessage(msg);
            }
        };
    }

    //返回true会重新计算getScale的返回值,默认为1
    public boolean onScale(ScaleGestureDetector detector){
        float now_scale = getScale();
        float scaleFactor = detector.getScaleFactor();

        Drawable d = getDrawable();
        if( d == null)
            return true;

        Log.i(TAG, "Scale: "+scaleFactor + ",Scale:"+now_scale + ",TranslateX:"+getTranclateX()+",TranslateY:"+getTranclateY()+
                ",width:"+getWidth()+",height:"+getHeight());

        //当前放大倍数+当次放大的比例,放大倍数不能为负数,负数的话则图片上下颠倒了
        float fMinScale = (getWidth()*1.0f)/d.getIntrinsicWidth();
        now_scale = now_scale + (scaleFactor - 1.0f);
        now_scale = Math.abs(now_scale);
        now_scale = Math.max(fMinScale,now_scale);

        if(now_scale < SCALE_MAX ){
            Matrix matrix = new Matrix();
            matrix.postScale(now_scale,now_scale,detector.getFocusX(),detector.getFocusY());
            setImageMatrix(matrix);

            //缩放之后TranslX和TranslateY会发生变化,如果缩放之后图片小于视图则居中显示
            RectF rtMatrixf = getMatrixRectF();
            //检查上下左右边界
            float dx =Math.min(0, getTranclateX()),dy = Math.min(0,getTranclateY());
            //如果图片小于视图则左右居中显示
            if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
                dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
            }
            //制图片右边界显示在视图内,导致白边
            else if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
                dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
            }

            //如果图片小于视图则垂直居中显示
            if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
                dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
            }
            //控制图片下边界显示在视图内,导致白边
            else if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
                dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
            }

            matrix = new Matrix();
            matrix.postScale(getScale(),getScale());
            matrix.postTranslate(dx,dy);
            setImageMatrix(matrix);
        }

        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector){
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector){
    }

    @Override
    public boolean onTouch(View v, MotionEvent event)
    {
        mGestureDetector.onTouchEvent(event);
        mScaleGestureDetector.onTouchEvent(event);
        int nTouchCount = event.getPointerCount();
        if(nTouchCount > 1)
            return true;

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:{
                mLastX = event.getX();
                mLastY = event.getY();
               if(mTimer != null)
                   mTimer.cancel();
               mTimer = null;
                break;
            }
            case MotionEvent.ACTION_MOVE:{
                Drawable drawable = getDrawable();
                if(drawable == null)
                    return true;

                RectF rtMatrixf = getMatrixRectF();
                //检查左右边界
                float dx = 0;
                //如果图片小于视图则左右居中显示
                if(rtMatrixf.right - rtMatrixf.left <= getWidth()){
                    dx = (getWidth() - (rtMatrixf.right - rtMatrixf.left))/2;
                }
                else if(rtMatrixf.right - rtMatrixf.left > getWidth()){
                    //控制图片左边界显示在视图内,导致白边,dx只允许 < 0
                    dx = getTranclateX() + (event.getX() - mLastX);
                    dx = Math.min(0,dx);
                    //制图片右边界显示在视图内,导致白边
                    if(dx + (rtMatrixf.right - rtMatrixf.left) < getWidth()){
                        dx = getWidth() - (rtMatrixf.right - rtMatrixf.left);
                    }
                }

                //检查上下边界
                float dy = 0;
                //如果图片小于视图则垂直居中显示
                if(rtMatrixf.bottom - rtMatrixf.top <= getHeight()){
                    dy = (getHeight() - (rtMatrixf.bottom - rtMatrixf.top))/2;
                }
                else if(rtMatrixf.bottom - rtMatrixf.top > getHeight()){
                    //控制图片上边界显示在视图内,导致白边,dy只允许 < 0
                    dy = getTranclateY() + (event.getY() - mLastY);
                    dy = Math.min(0,dy);
                    //控制图片下边界显示在视图内,导致白边
                    if(dy + (rtMatrixf.bottom - rtMatrixf.top) < getHeight()){
                        dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
                    }
                }

                mLastX = event.getX();
                mLastY = event.getY();
                Log.i(TAG, "ACTION_MOVE,dx: "+dx + ",dy:"+dy);
                //计算边界,
                Matrix matrix = new Matrix();
                matrix.preScale(getScale(),getScale());
                matrix.postTranslate(dx, dy);
                setImageMatrix(matrix);
                break;
            }
            case MotionEvent.ACTION_UP:{
                mLastX = event.getX();
                mLastY = event.getY();
            }
        }
        return true;
    }

    private float getScale(){
        Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MSCALE_X];
    }

    private float getTranclateX(){
        Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MTRANS_X];
    }

    private float getTranclateY(){
        Matrix matrix = getImageMatrix();
        matrix.getValues(matrixValues);
        return matrixValues[Matrix.MTRANS_Y];
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        if(mTimer != null)
            mTimer.cancel();
        mTimer = null;
    }

    //获取变换之后的图片尺寸
    private RectF getMatrixRectF()
    {
        Matrix matrix = getImageMatrix();
        RectF rect = new RectF();
        Drawable d = getDrawable();
        if (null != d)
        {
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rect);
        }
        return rect;
    }

    private boolean scrollByY(float dOffset){
        boolean bEnd = false;
        RectF rtMatrixf = getMatrixRectF();
        //检查上下边界
        float dy = getTranclateY();
        dy += dOffset;
        if(dy > 0) {
            dy = 0;
            bEnd = true;
        }

        if(rtMatrixf.bottom - rtMatrixf.top + dy < getHeight()){
            dy = getHeight() - (rtMatrixf.bottom - rtMatrixf.top);
            bEnd = true;
        }

        //计算边界,
        Matrix matrix = new Matrix();
        matrix.preScale(getScale(),getScale());
        matrix.postTranslate(getTranclateX(), dy);
        setImageMatrix(matrix);
        return bEnd;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值