自定义View实现图片的拖动和缩放

来源:互联网 发布:windows defender在哪 编辑:程序博客网 时间:2024/05/21 10:34

转载自http://blog.csdn.net/kevinscsdn/article/details/52448691

整体思路: 
1. 实现缩放功能: 
(1) 创建ScaleGestureDetector对象,实现ScaleGestureDetector.OnScaleGestureListener接口; 
(2) 在onScale方法中实现缩放逻辑 , 相关逻辑包括获取缩放比例的初始值、定义放大的上限比例; 
(3) setOnTouchListener(this),实现OnTouchListener接口,接收触摸事件。 
2. 实现拖动功能: 
(1) 计算、修正拖动距离 
(2) 实现拖动

更多细节呈现在代码中。

代码实现

public class ScalableImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener, 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);//      下面一步必须有        setOnTouchListener(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;        }//        以下是HongYang大神的思路//        在INIT_VALUE到MAX_SCALE范围内缩放(放大不超过MAX_SCALE,缩小不小于INIT_SCALE)//        if ((scale < MAX_SCALE && factor >= 1.0f) || (scale > initScale && factor <= 1.0f)) {//            if (scale * factor > MAX_SCALE) {//放大超过MAX_SCALE的处理//                factor = MAX_SCALE / scale;//            } else if (scale * factor < initScale) {//缩小超过INIT_SCALE的处理//                factor = initScale / scale;//            }//            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 onTouch(View v, 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;                        }                        float dx = x - mLastX;                        float dy = y - mLastY;                        // 对偏移值做适当修正,避免图片与视图边间产生空白区域                        PointF dragDelta = amendDelta(dx, dy);                        mMatrix.postTranslate(dragDelta.x, dragDelta.y);                        setImageMatrix(mMatrix);                        break;                    case GESTURE_ZOOM:                        mScaleGestureDetector.onTouchEvent(event);                        break;                }                break;            case MotionEvent.ACTION_UP:                // 消除缩放造成的图片距视图边的空白                PointF zoomDelta = amendDelta(0, 0);                mMatrix.postTranslate(zoomDelta.x, zoomDelta.y);                setImageMatrix(mMatrix);                break;        }        mLastX = x;        mLastY = y;        return true;    }    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);        }    }//  当View附加到Window上时调用,这时候它的绘画面板已存在,即将开始绘制。注意,该方法能保证发生在onDraw之前,但可能发生在onMeasure之前或之后。//  HongYang大神写在此方法中,考虑到上述原因,这里写在onMeasure中,见仁见智。//    @Override//    protected void onAttachedToWindow() {//        super.onAttachedToWindow();//        getViewTreeObserver().addOnGlobalLayoutListener(this);//    }    @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);        }    }}

关于偏移量修正的示意图如下,以宽度方向为例,当图片宽度小于View的宽度时,无论如何,图片都会与View都某一边产生距离的,这时候要求伸出View的图片部分缩回来;当图片宽度大于View的宽度时,如果与View边缘产生距离,要求将该空白填充满。总之一个原则,充分利用View的区域,尽可能显示更多的图片内容。

这里写图片描述


0 0
原创粉丝点击