【自定义控件】ZoomImage-支持缩放,拖拽

入坑的路上,你能从本文中接触到以下知识点,共勉:

1、自定义Component实现步骤
2、属性动画
3、矩阵变化
4、Touch事件处理

实现思路:使用matrix变化,应用在canvas上进行pixmap绘制。

具体实现如下:

了解矩阵含义

先看看matrix矩阵是什么样子的:

这里写图片描述

这里通过他们的名字可以看出,scale是缩放,skew是错切,trans是平移,persp代表透视。
实现代码中,只需改变scale,以及trans参数即可。

矩阵更多详情请参考CSDN博客

一、继承Component

构造方法初始化:
实现接口:
Component.DoubleClickedListener:双击事件
Component.DrawTask:绘制流程
Component.TouchEventListener:触摸事件

public ZoomImage(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(context);
    }

private void init(Context ct) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPrePoint = new Point();
        mPreCtrlPoint = new Point();
        mEndPoint = new Point();
        mid = new Point();
      //  增加touch,双击,绘制事件监听
        setTouchEventListener(this);
        setDoubleClickedListener(this);
        addDrawTask(this);
}

二、双击缩放实现

注意:使用handler更新scale,否则动画监听回调值会超出预期(我理解是:绘制时间包含在动画执行时间内,假如动画执行时间为500ms,绘制一次耗时500ms,则onUpdate()只回调一次或者不回调)。

    private synchronized void scaleExecute() {
        if (defScale > maxScaleLimit) { //小图默认缩放已经超过3倍,无需继续放大
            return;
        }
        float defS = scale;
        if (scale < defScale || scale >= maxScaleLimit) {
            scale = defScale;
        } else {
            if (maxScaleLimit - defScale > 1) {
                scale += new BigDecimal((maxScaleLimit - defScale) / 2f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
            } else {
                scale += defScale;
            }
        }
        mCurrentMatrix.setMatrix(mBaseMatrix);
        final long st = System.currentTimeMillis();
        if (av == null) {
            av = new AnimatorValue();
            av.setDuration(500);
            av.setLoopedCount(0);
            av.setCurveType(Animator.CurveType.LINEAR);
        } else {
            if (av.isRunning()) {
                av.stop();
            }
        }
        av.setValueUpdateListener((animatorValue, v) -> {
            curScl = defS + (scale - defS) * v;
//            System.out.println(curScl+ "插值器:" + v + "==" + (System.currentTimeMillis() - st));
//            mDrawMatrix.setScale(curScl, curScl);
//            updateImageSize(curScl);
//            float[] lt = getLeftTopPoint();
//            mDrawMatrix.postTranslate(-lt[0], -lt[1]);
//            matrix.setMatrix(mDrawMatrix);
//            invalidate();
            /** 直接invalidate会阻塞ui,导致回调值v异常,或不回调**/
            InnerEvent in = InnerEvent.get();
            in.object = curScl;
            handler.sendEvent(in);

        });
        av.start();
    }

  private EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()) {
        @Override
        protected void processEvent(InnerEvent event) {
            curScl = (float) event.object;
            mCurrentMatrix.setScale(curScl, curScl);
            updateImageSize(curScl);
            float[] lt = getLeftTopPoint();
            mCurrentMatrix.postTranslate(-lt[0], -lt[1]);
            setImageMatrix(mCurrentMatrix);
        }
    };

// 根据缩放比例计算图片尺寸
  private void updateImageSize(float scl) {
        imageW = Float.valueOf(imageDefW * scl).intValue();
        imageH = Float.valueOf(imageDefH * scl).intValue();
    }

// 更新图片矩阵,触发onDraw方法
  public void setImageMatrix(Matrix mMatrix) {
        if (mMatrix != null && mMatrix.isIdentity()) {
            mMatrix = null;
        }
        if (mMatrix == null && !matrix.isIdentity() ||
                mMatrix != null && !mMatrix.equals(matrix)) {
            this.matrix.setMatrix(mMatrix);
            invalidate();
        }
    }

三、绘制

    @Override
    public void onDraw(Component component, Canvas canvas) {
        canvas.save();
        updateBaseMatrix();
        canvas.setMatrix(matrix);
        PixelMapHolder holder = new PixelMapHolder(drawable);
        canvas.drawPixelMapHolder(holder, 0, 0, mPaint);
        holder = null;
        canvas.restore();
    }

    /** 
    *计算默认参数
    *
    */
    private void updateBaseMatrix() {
        if (drawable == null) {
            return;
        }
        if (!displayRect.isEmpty()) {
//            System.out.println("已初始化");
            return;
        }
        initDefaultSize();

        int viewWidth = getWidth();
        int viewHeight = getHeight();
        while (viewWidth == 0 || viewHeight == 0) {
            viewWidth = getWidth();
            viewHeight = getHeight();
        }
        final int drawableWidth = imageDefW - getPaddingLeft() - getPaddingRight();
        final int drawableHeight = imageDefH - getPaddingTop() - getPaddingBottom();

        displayRect.clear();
        displayRect.fuse(0, 0, viewWidth, viewHeight);

        mBaseMatrix.reset();
        final float widthScale = new BigDecimal(1f * viewWidth / (drawableWidth)).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
        final float heightScale = new BigDecimal(1f * viewHeight / (drawableHeight)).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
        defScale = Math.min(widthScale, heightScale);
        maxScaleLimit = Math.max(widthScale, heightScale);
        scale = defScale;
        updateImageSize(defScale);
        mBaseMatrix.postScale(defScale, defScale);
        mBaseMatrix.postTranslate((viewWidth - drawableWidth * defScale) / 2.0F,
                (viewHeight - drawableHeight * defScale) / 2.0F);
        setImageMatrix(mBaseMatrix);
    }

    private void initDefaultSize() {
        imageDefW = drawable.getImageInfo().size.width;
        imageDefH = drawable.getImageInfo().size.height;
        imageW = imageDefW;
        imageH = imageDefH;

    }

对外暴露方法:
设置图片资源。

    public void setPixelMap(PixelMap pixelMap) {
        this.drawable = pixelMap;
        invalidate();
    }

四、Touch事件处理

1、默认展示时,未缩放,不支持拖拽;
2、down:判断触摸位置是否在图片上,否则mode不处理;
坐标系中,利用向量判断是否超过边界:


image.png

理解上图,请移步,复习高中向量

【无真机,多指触摸缩放未完善,有需要请自行实现】

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN: {
                //获取点信息
                MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
                mPrePoint.modify(point.getX(), point.getY());
                if (isInMatrix() != null) {
                    mode = DRAG;
                    //PRIMARY_POINT_DOWN 一定要返回true
                    System.out.println("down:::" + mPrePoint.toString() + "===" + mPreCtrlPoint.toString());
                } else {
                    System.out.println("触摸位置超出图片边界");
                }
                return true;
            }
            case TouchEvent.OTHER_POINT_DOWN:
                mode = ZOOM;
                oldDist = spacing(touchEvent);
                oldRotation = rotation(touchEvent);
                midPoint(mid, touchEvent);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                MmiPoint point_up = touchEvent.getPointerPosition(touchEvent.getIndex());
                mEndPoint.modify(point_up.getX(), point_up.getY());
                mode = NONE;
                return false;
            case TouchEvent.POINT_MOVE: {
                if (mode == ZOOM) {
                    float rotation = rotation(touchEvent) - oldRotation;
                    float newDist = spacing(touchEvent);
                    float scale = newDist / oldDist;
                    mCurrentMatrix.postScale(scale, scale, mid.getPointX(), mid.getPointY());// 縮放
                    mCurrentMatrix.postRotate(rotation, mid.getPointX(), mid.getPointY());// 旋轉
                    setImageMatrix(mCurrentMatrix);
                } else if (mode == DRAG) {
                    if (scale <= defScale) {
                        System.out.println("未缩放无法拖动");
                        return false;
                    }
                    mCurrentMatrix.setMatrix(matrix);
                    MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
                    mPreCtrlPoint.position[0] = (point.getX() - mPrePoint.position[0]);
                    mPreCtrlPoint.position[1] = (point.getY() - mPrePoint.position[1]);
                    if (Math.sqrt(mPreCtrlPoint.getPointX() * mPreCtrlPoint.getPointX() + mPreCtrlPoint.getPointY() * mPreCtrlPoint.getPointY()) < 8) {
                        return false;
                    }
                    handleDrag();
                }
                break;
            }
            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.CANCEL:
                mode = NONE;
                break;
            default:
                break;
        }
        return true;
    }

// 判断点击位置,是否超出图片边界
    private float[] isInMatrix() {
        float[] f = matrix.getData();
        int mw = imageDefW;
        int mh = imageDefH;
        // 图片4个顶点的坐标:左上,右上,左下,右下
        float x1 = f[0] * 0 + f[1] * 0 + f[2];
        float y1 = f[3] * 0 + f[4] * 0 + f[5];
        float x2 = f[0] * mw + f[1] * 0 + f[2];
        float y2 = f[3] * mw + f[4] * 0 + f[5];
        float x3 = f[0] * 0 + f[1] * mh + f[2];
        float y3 = f[3] * 0 + f[4] * mh + f[5];
        float x4 = f[0] * mw + f[1] * mh + f[2];
        float y4 = f[3] * mw + f[4] * mh + f[5];
     
        float x = mPrePoint.getPointX();
        float y = mPrePoint.getPointY();

        float[] v1 = new float[]{x1 - x, y1 - y};
        float[] v2 = new float[]{x2 - x, y2 - y};
        float[] v3 = new float[]{x3 - x, y3 - y};
        float[] v4 = new float[]{x4 - x, y4 - y};
        if ((v1[0] * v2[1] - v2[0] * v1[1]) > 0
                && (v2[0] * v4[1] - v4[0] * v2[1]) > 0
                && (v4[0] * v3[1] - v3[0] * v4[1]) > 0
                && (v3[0] * v1[1] - v1[0] * v3[1]) > 0) {
            return new float[]{x1, y1, x2, y2, x3, y3, x4, y4};
        }
        return null;
    }


    // 处理拖动边界
    private synchronized void handleDrag(DragInfo dragInfo) {
        float x = matrix.getTranslateX() + mPreCtrlPoint.getPointX();
        float y = matrix.getTranslateY() + mPreCtrlPoint.getPointY();
        float trans_x, trans_y;
      // 计算x方向拖动距离
        if (imageW <= getWidth()) {
            trans_x = 0;
        } else if (x > 0) {
            trans_x = -matrix.getTranslateX();
        } else if (x < -(imageW - getWidth())) {
            trans_x = -(imageW - getWidth()) - matrix.getTranslateX();
        } else {
            trans_x = mPreCtrlPoint.getPointX();
        }
      //  计算y方向拖动距离
        if (imageH <= getHeight()) {
            trans_y = 0;
        } else if (y > 0) {
            trans_y = -matrix.getTranslateY();
        } else if (y < -(imageH - getHeight())) {
            trans_y = -(imageH - getHeight()) - matrix.getTranslateY();
        } else {
            trans_y = mPreCtrlPoint.getPointY();
        }
        mCurrentMatrix.postTranslate(trans_x, trans_y);
        setImageMatrix(mCurrentMatrix);

    }

五、使用方式

xml中:
不知道如何自定义属性,暂时只能在java中调用setPixmap设置图片。

    

源码:刚入坑,建议自行编码尝试,有问题请留言一起讨论。加油,干就完事!

参考代码:

链接: https://pan.baidu.com/s/1CQYi3Bqa0NGMgK0mTRm6bQ
提取码: yxjb

效果图:

链接: https://pan.baidu.com/s/1Sm6vRvlW2mluwzB2F5Kw3g
提取码: kpg7

你可能感兴趣的:(【自定义控件】ZoomImage-支持缩放,拖拽)