自定义ImageView实现图片的拖动、缩放和边界回弹

来源:互联网 发布:护肤品使用手法 知乎 编辑:程序博客网 时间:2024/05/02 02:52

图片的缩放和拖动在上自定义View实现图片的拖动和缩放中已经提到,这里是紧承上文新添加边界回弹功能。
所谓边界回弹指的是类似微信修改头像截取图片时,如果将图片的边缘拉开了屏幕(View)边界,松手后有个回弹动作。
小小的边界回弹功能还让我折腾了好几天,起初用的是Scroller,但是使用过程中发现,如果在边界频繁向一个方向拖动,scrollX(Y)会朝着一个方向不断增大,这时候换个方向,只有不停拖动让这个scrollX(Y)归0之后才能正常拖动图片。
后来经过查询研究得知:使用Matrix实现图片的拖动和Scroller原理是不一样的!
使用Scroller辅助类最终还是得依靠View的scrollTo方法,scrollTo方法改变View的mScrollX和mScrollY值,然后经过invalidate(),在View的onDraw过程中通过这两个参数平移画布实现图片的移动。
使用Matrix平移图片不会涉及到mScrollX和mScrollY。

所以夹杂着Matrix的平移,使用scroller实现回弹很容易出问题,所以我最后换一种思路:使用Handler结合Matrix实现图片的边界回弹。

实现边界回弹的核心代码:

1.用于回弹的Handler

private static final int SPRING_BACK = 0;private static final int COUNTS = 25;private static final int DELAY_MILLIS = 2;private int mCount;private Handler mHandler = new Handler() {    public void handleMessage(Message msg) {        switch (msg.what) {            case SPRING_BACK: {                mCount++;                if (mCount <= COUNTS) {//                      使用浮点数,避免回弹后出现间隙                    mMatrix.postTranslate(msg.arg1*1.0f/COUNTS, msg.arg2*1.0f/COUNTS);                    setImageMatrix(mMatrix);                    Message next = Message.obtain(mHandler,SPRING_BACK,msg.arg1,msg.arg2);                    mHandler.sendMessageDelayed(next, DELAY_MILLIS);                } else {                    mCount = 0;                }                break;            }            default:                break;        }    }};

2.检查是否超出边界,得到回弹的dx、dy

//  超出边界时修正dx、dy,计算回弹距离private PointF amendDelta(float dx, float dy) {        RectF rectF = getRectF();        if (rectF.width() > viewWidth) {// 图片宽度超过视图宽度            if (rectF.left + dx > 0) {// 拖动会引起图片左边出现空白                dx = -rectF.left;            } else if (rectF.right + dx < viewWidth) {// 拖动会引起图片右边出现空白                dx = viewWidth - rectF.right;            }        } else {// 图片宽度不及视图宽度,不允许图片宽度方向可视区域离开边界            if (rectF.left + dx < 0) {                dx = -rectF.left;            } else if (rectF.right + dx > viewWidth) {                dx = viewWidth - rectF.right;            }        }        if (rectF.height() > viewHeight) {            if (rectF.top + dy > 0) {                dy = -rectF.top;            } else if (rectF.bottom + dy < viewHeight) {                dy = viewHeight - rectF.bottom;            }        } else {            if (rectF.top + dy < 0) {                dy = -rectF.top;            } else if (rectF.bottom + dy > viewHeight) {                dy = viewHeight - rectF.bottom;            }        }        return new PointF(dx, dy);    }

3.监听触摸事件,实现回弹效果

@Overridepublic boolean onTouchEvent(View v, MotionEvent event) {    float x = event.getX();    float y = event.getY();    switch (event.getActionMasked()) {        ...        case MotionEvent.ACTION_UP:            // 消除缩放造成的图片距视图边的空白            final PointF delta = amendDelta(0, 0);            int springBackX = Math.round(delta.x);            int springBackY = Math.round(delta.y);            if (springBackX != 0 || springBackY != 0) {                Message msg = Message.obtain(mHandler,SPRING_BACK,springBackX,springBackY);                mHandler.sendMessage(msg);            }            break;    }   ...}

完整代码:

import android.content.Context;import android.graphics.Matrix;import android.graphics.PointF;import android.graphics.RectF;import android.graphics.drawable.Drawable;import android.os.Build;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.ScaleGestureDetector;import android.view.ViewTreeObserver;import android.widget.ImageView;/** * Created by Kevin on 2016/9/5. * */public class ScalableImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, ViewTreeObserver.OnGlobalLayoutListener {    private static final String TAG = "ScalableImageView";    private int gesture;    private static final int GESTURE_DRAG = 1;    private static final int GESTURE_ZOOM = 2;    //  最大的缩放比例    private static final float MAX_SCALE = 4.0f;    //  初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于1(HongYang大神的博客上有笔误)    private float initScale = 1.0f;    private Matrix mMatrix = new Matrix();    private ScaleGestureDetector mScaleGestureDetector;    private int viewWidth;    private int viewHeight;    private Drawable mDrawable;    public ScalableImageView(Context context) {        this(context, null);    }    public ScalableImageView(Context context, AttributeSet attrs) {        super(context, attrs);//      这一步很关键        super.setScaleType(ScaleType.MATRIX);        mScaleGestureDetector = new ScaleGestureDetector(context, this);    }    @Override    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {//      全局缩放比例        float scale = getScale();//      上一次缩放事件到当前事件的缩放比例(微分缩放比例)        float factor = scaleGestureDetector.getScaleFactor();//      第一次获取的factor偏小,会引起缩放手势触屏的一瞬间图片缩小        if (resetFactor) {            factor = 1.0f;            resetFactor = false;        }//      drawable为空或者缩放比例超出范围,拒执行        if (getDrawable() == null || scale * factor > MAX_SCALE || scale * factor < initScale) {            return false;        } else {            mMatrix.postScale(factor, factor, getWidth() / 2, getHeight() / 2);            setImageMatrix(mMatrix);            return true;        }    }    private final float[] matrixValues = new float[9];    //  获取全局缩放比例(相对于未缩放时的缩放比例),和直接用getScaleX()有区别!    private float getScale() {        mMatrix.getValues(matrixValues);        return matrixValues[Matrix.MSCALE_X];    }    //  缩放开始时:可用于过滤一些手势,比如从有效区以外的区域划进来的手势    @Override    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {        return true;    }    //  缩放结束时    @Override    public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {    }    private float mLastX;    private float mLastY;    private boolean resetFactor;    private boolean onZoomFinished;    @Override    public boolean onTouchEvent(MotionEvent event) {        float x = event.getX();        float y = event.getY();        switch (event.getActionMasked()) {            case MotionEvent.ACTION_DOWN:                gesture = GESTURE_DRAG;                break;            case MotionEvent.ACTION_POINTER_DOWN:                gesture = GESTURE_ZOOM;                resetFactor = true;                break;            case MotionEvent.ACTION_POINTER_UP:                gesture = GESTURE_DRAG;                onZoomFinished = true;                break;            case MotionEvent.ACTION_MOVE:                switch (gesture) {                    case GESTURE_DRAG:                        // 屏蔽缩放转换为拖动时的跳动,此时dx、dy偏大。                        if (onZoomFinished) {                            onZoomFinished = false;                            break;                        }                        int dx = (int) (x - mLastX);                        int dy = (int) (y - mLastY);                        mMatrix.postTranslate(dx, dy);                        setImageMatrix(mMatrix);                        break;                    case GESTURE_ZOOM:                        mScaleGestureDetector.onTouchEvent(event);                        break;                }//                不同于scrollTo或scrollBy移动,这两个是通过改变View的mScrollX和mScrollY,然后在onDraw时平移画布的,//                而通过矩阵是对图片的操作,//                这里不曾调用过scrollTo或scrollBy(从View的源码中可以看到这两个方法是用来改变mScrollX和mScrollY的),//                所以打印日志可以看到getX()和getScrollX()一直不变。//                Log.e(TAG,"getX() = "+getX()+", getScrollX() = "+getScrollX());                break;            case MotionEvent.ACTION_UP:                // 消除缩放造成的图片距视图边的空白                final PointF delta = amendDelta(0, 0);                int springBackX = Math.round(delta.x);                int springBackY = Math.round(delta.y);                if (springBackX != 0 || springBackY != 0) {                    Message msg = Message.obtain(mHandler,SPRING_BACK,springBackX,springBackY);                    mHandler.sendMessage(msg);                }                break;        }        mLastX = x;        mLastY = y;        return true;    }    private static final int SPRING_BACK = 0;    private static final int COUNTS = 25;    private static final int DELAY_MILLIS = 2;    private int mCount;    private Handler mHandler = new Handler() {        public void handleMessage(Message msg) {            switch (msg.what) {                case SPRING_BACK: {                    mCount++;                    if (mCount <= COUNTS) {//                      使用浮点数,避免回弹后出现间隙                        mMatrix.postTranslate(msg.arg1*1.0f/COUNTS, msg.arg2*1.0f/COUNTS);                        setImageMatrix(mMatrix);                        Message next = Message.obtain(mHandler,SPRING_BACK,msg.arg1,msg.arg2);                        mHandler.sendMessageDelayed(next, DELAY_MILLIS);                    } else {                        mCount = 0;                    }                    break;                }                default:                    break;            }        }    };    @Override    public void computeScroll() {        super.computeScroll();    }    private PointF amendDelta(float dx, float dy) {        RectF rectF = getRectF();        if (rectF.width() > viewWidth) {// 图片宽度超过视图宽度            if (rectF.left + dx > 0) {// 拖动会引起图片左边出现空白                dx = -rectF.left;            } else if (rectF.right + dx < viewWidth) {// 拖动会引起图片右边出现空白                dx = viewWidth - rectF.right;            }        } else {// 图片宽度不及视图宽度,不允许图片宽度方向可视区域离开边界            if (rectF.left + dx < 0) {                dx = -rectF.left;            } else if (rectF.right + dx > viewWidth) {                dx = viewWidth - rectF.right;            }        }        if (rectF.height() > viewHeight) {            if (rectF.top + dy > 0) {                dy = -rectF.top;            } else if (rectF.bottom + dy < viewHeight) {                dy = viewHeight - rectF.bottom;            }        } else {            if (rectF.top + dy < 0) {                dy = -rectF.top;            } else if (rectF.bottom + dy > viewHeight) {                dy = viewHeight - rectF.bottom;            }        }        return new PointF(dx, dy);    }    private RectF mRectF;    public RectF getRectF() {        if (mDrawable == null) {            mDrawable = getDrawable();        }        if (mRectF == null) {            mRectF = new RectF();        }        if (mDrawable != null) {            mRectF.set(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());            mMatrix.mapRect(mRectF);        }        return mRectF;    }    //  onGlobalLayoutListener可能会多次触发    private boolean isFirstTime = true;    // 观察布局变化,目的是获取View的尺寸,在测量之前执行onMeasure getWidth()和getHeight()可能为0    @Override    public void onGlobalLayout() {        if (isFirstTime) {            Drawable drawable = getDrawable();            if (drawable == null) {                return;            }            isFirstTime = false;            int drawableWidth = drawable.getIntrinsicWidth();            int drawableHeight = drawable.getIntrinsicHeight();            viewWidth = getWidth();            viewHeight = getHeight();//          图片长宽超出View的可见范围的处理            if (drawableWidth > viewWidth || drawableHeight > viewHeight) {                initScale = Math.min(viewWidth * 1.0f / drawableWidth, viewHeight * 1.0f / drawableHeight);            }//          将图片偏移到中心位置            mMatrix.postTranslate((viewWidth - drawableWidth) / 2, (viewHeight - drawableHeight) / 2);//          初始化缩放            mMatrix.postScale(initScale, initScale, viewWidth / 2, viewHeight / 2);            setImageMatrix(mMatrix);        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        getViewTreeObserver().addOnGlobalLayoutListener(this);        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {            getViewTreeObserver().removeOnGlobalLayoutListener(this);        }    }}
0 0
原创粉丝点击