Android 利用Canvas实现双指拖动和双指缩放图片

背景

  关于Android中使用Canvas绘制图片对于大家来说已经很熟悉了.关于图片的缩放和拖动,一般使用到的技能是:单指拖动图片和双指缩放图片.最近由于工作的要求,需要实现:
  1.双指拖动和在拖动过程中控制图片缩放.
  2.绘制的线条与背景图片实现正片叠底的混合效果.

先上效果图:
这里写图片描述

原理:

使用的原理:
  1.我采用的是自定义View的方式来控制图片的缩放,坐标系起始点(0,0)在整个自定义View的左上角;
  2.使用Canvas.scale(float sx, float sy)来实现画布的缩放进而实画布上现图片的缩放;
  3.以两个手指触点的中心点作为整张图片缩放的中心点;通过函数canvas.drawBitmap(mSrcBitmap,x,y,mPdxPaint)中的参数x,y的指来实现图片位置的放置;
  4.通过自定义View中onTouchEvent 函数来实现画笔线条的绘制;
  5.把画笔绘制的线条单独绘制在一张空白的Bitmap图片上,底色必须为白色(正片叠底时使用),采用Paint中的混合模式 PorterDuff.Mode.MULTIPLY 来实现当前画笔绘制的Bitmap和背景Bitmap的混合;
  6.由于在绘制过程中采用的两张图片的混合模式,所以在整个图片的移动过程中,整个画面会很卡,用户体验不好.为了提高拖动的效率,提高用户体验,我采用的方式:当双指同时触屏时,把当前背景图和绘制图直接混合成一张图片,在拖动过程中实际拖动的是混合后的一张图片.当拖动结束后,释放这张图片.
  7.为了实现绘制过程中,可以让客户实时的看到绘制的效果,又重新定义了一张图片,这张图片就是指显示正在绘制的这条线,当绘制完成后,触电抬起ACTION_UP时,把这条线绘制到画笔的Bitmap上,在和背景图片实现正片叠底的混合.

这里写图片描述

主要代码:

MainActivity.java

package com.test.wb.doublemoveview3;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Author: aaa
 * Date: 2016/12/5 17:31.
 */
public class DoubleMoveView3 extends View {
    private Context mContext;
    private Bitmap mSrcBitmap;
    private Bitmap mMultiplyBitmap = null;//混合之后的图片,双指缩放移动的时候,单独移动这张混合后图片,提高用户体验
    public boolean mIsMove;//是否双指拖动图片中ing
    private int mBitmapWidth, mBitmapHeight;//图片的长度和高度

    private float mCenterLeft, mCenterTop;//图片居中时左上角的坐标
    private int mCenterHeight, mCenterWidth; // 图片适应屏幕时的大小
    private float mCenterScale;//画布居中时的比例
    private int mViewWidth, mViewHeight;//当前View的长度和宽度

    private float mTransX = 0, mTransY = 0; // 偏移量,图片真实偏移量为 mCentreTranX + mTransX
    private float mScale = 1.0f; // 缩放倍数, 图片真实的缩放倍数为 mPrivateScale * mScale

    private boolean mIsSaveArg = false;//保存参数使用

    private Paint mPaint;
    private Bitmap mGraffitiBitmap; // 用绘制涂鸦的图片
    private Canvas mBitmapCanvas; // 用于绘制涂鸦的画布

    private Bitmap mCurrentBitmap; // 绘制当前线时用到的图片
    private Canvas mCurrentCanvas; // 当前绘制线的画布

    private int[] mWhiteBuffer;//保存白色图片内存,刷新时重新刷新图片
    // 保存涂鸦操作,便于撤销
    private CopyOnWriteArrayList<MyDrawSinglePath> mPathStack = new CopyOnWriteArrayList<MyDrawSinglePath>();
    private CopyOnWriteArrayList<MyDrawSinglePath> pathStackBackup = new CopyOnWriteArrayList<MyDrawSinglePath>();
    private int mTouchMode; // 触摸模式,触点数量
    private float mTouchDownX, mTouchDownY, mLastTouchX, mLastTouchY, mTouchX, mTouchY;
    private MyDrawSinglePath mCurrPath; // 当前手写的路径

    private PorterDuffXfermode mPdXfermode; // 定义PorterDuffXfermode变量
    private Paint mPdXfPaint;// 绘图的混合模式
    private Paint mCurrentPaint;
    private Path mCanvasPath; //仅用于当前Path的绘制

    public DoubleMoveView3(Context context, Bitmap bitmap) {
        super(context);
        mContext = context;
        init(bitmap);
    }

    public DoubleMoveView3(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private void init(Bitmap bitmap) {
        mSrcBitmap = bitmap;
        mBitmapWidth = mSrcBitmap.getWidth();
        mBitmapHeight = mSrcBitmap.getHeight();
        mMultiplyBitmap = Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);

        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(20);
        mGraffitiBitmap = getTransparentBitmap(mSrcBitmap);

        mCurrentPaint = new Paint();
        mCurrentPaint.setStyle(Paint.Style.STROKE);
        mCurrentPaint.setStrokeWidth(50);
        mCurrentPaint.setColor(Color.RED);
        mCurrentPaint.setAlpha(100);
        mCurrentPaint.setAntiAlias(true);
        mCurrentPaint.setStrokeJoin(Paint.Join.ROUND);
        mCurrentPaint.setStrokeCap(Paint.Cap.ROUND);
        mCurrentPaint.setXfermode(null);

        mCanvasPath = new Path();
        mCurrentBitmap = getTransparentBitmap(mSrcBitmap);

        //设置混合模式   (正片叠底)
        mPdXfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);

        mPdXfPaint = new Paint();
        mPdXfPaint.setAntiAlias(true);
        mPdXfPaint.setFilterBitmap(true);

        mIsMove = false;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mViewWidth = w;
        mViewHeight = h;

        float nw = mBitmapWidth * 1f / mViewWidth;
        float nh = mBitmapHeight * 1f / mViewHeight;
        if (nw > nh) {
            mCenterScale = 1 / nw;
            mCenterWidth = mViewWidth;
            mCenterHeight = (int) (mBitmapHeight * mCenterScale);
        } else {
            mCenterScale = 1 / nh;
            mCenterWidth = (int) (mBitmapWidth * mCenterScale);
            mCenterHeight = mViewHeight;
        }

        // 使图片居中
        mCenterLeft = (mViewWidth - mCenterWidth) / 2f;
        mCenterTop = (mViewHeight - mCenterHeight) / 2f;

        initCanvas();
        initCurrentCanvas();

        mIsMove = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float scale = mCenterScale * mScale;
        float x = (mCenterLeft + mTransX) / scale;
        float y = (mCenterTop + mTransY) / scale;

        canvas.scale(scale, scale);

        if (!mIsMove) {
            //正片叠底混合模式
            initCurrentCanvas();
            mCurrentCanvas.drawPath(mCanvasPath, mCurrentPaint);

            canvas.drawBitmap(mSrcBitmap, x, y, mPdXfPaint);
            mPdXfPaint.setXfermode(mPdXfermode);
            canvas.drawBitmap(mCurrentBitmap, x, y, mPdXfPaint);
            // 绘制涂鸦图片
            canvas.drawBitmap(mGraffitiBitmap, x, y, mPdXfPaint);
            mPdXfPaint.setXfermode(null);
        } else {
            //只显示原始图片
            canvas.drawBitmap(mMultiplyBitmap, x, y, null);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN:
                Log.i("aaa", "ACTION_DOWN");
                mTouchMode = 1;
                penTouchDown(event.getX(), event.getY());
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                Log.i("aaa", "ACTION_UP");
                mTouchMode = 0;
                mCanvasPath.reset();
                initCanvas();//添上这句防止重复绘制
                draw(mBitmapCanvas, mPathStack); // 保存到图片中
                invalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.i("aaa", "ACTION_MOVE");
                if (mTouchMode == 1 && !mIsMove) {
                    mLastTouchX = mTouchX;
                    mLastTouchY = mTouchY;
                    mTouchX = event.getX();
                    mTouchY = event.getY();

                    mCurrPath.getPath().quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                            screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));

                    mCanvasPath.quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                            screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));
                    invalidate();
                }
                return true;
        }
        return true;
    }

    public void setTransScale(float scale, float dx, float dy) {
        mScale = scale;
        mTransX = dx;
        mTransY = dy;
        if (!mIsSaveArg) {
            invalidate();
        }
        mIsSaveArg = false;
    }

    public void saveCurrentScale() {
        mCenterScale = mCenterScale * mScale;

        mCenterLeft = (mCenterLeft + mTransX) / mCenterScale;
        mCenterTop = (mCenterTop + mTransY) / mCenterScale;

        mIsSaveArg = true;

        saveMultiplyBitmap();
    }

    //双指移动的时候,生成混合之后的图片
    private void saveMultiplyBitmap() {
        mIsMove = true;

        Canvas canvas = new Canvas(mMultiplyBitmap);
        canvas.drawBitmap(mSrcBitmap, 0, 0, mPdXfPaint);
        mPdXfPaint.setXfermode(mPdXfermode);
        // 绘制涂鸦图片
        canvas.drawBitmap(mGraffitiBitmap, 0, 0, mPdXfPaint);
        mPdXfPaint.setXfermode(null);
    }

    /**
     * 在画笔的状态下第一个触点按下的情况
     */
    private void penTouchDown(float x, float y) {
        mIsMove = false;
        mTouchDownX = mTouchX = mLastTouchX = x;
        mTouchDownY = mTouchY = mLastTouchY = y;

        // 为了仅点击时也能出现绘图,模拟滑动一个像素点
        mTouchX++;
        mTouchY++;

        mCurrPath = new MyDrawSinglePath(Color.RED, 50, 100, true);
        mCurrPath.getPath().moveTo(screenToBitmapX(mTouchDownX), screenToBitmapY(mTouchDownY));
        mPathStack.add(mCurrPath);

        mCanvasPath.reset();
        mCanvasPath.moveTo(screenToBitmapX(mTouchDownX), screenToBitmapY(mTouchDownY));
        // 为了仅点击时也能出现绘图,必须移动path
        mCanvasPath.quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));
    }

    /**
     * 初始化当前画线的绘图
     */
    private void initCurrentCanvas() {
        mCurrentBitmap.setPixels(mWhiteBuffer, 0, mSrcBitmap.getWidth(), 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight());
        mCurrentCanvas = new Canvas(mCurrentBitmap);
    }

    /**
     * 初始化涂鸦的绘图
     */
    private void initCanvas() {
        mGraffitiBitmap.setPixels(mWhiteBuffer, 0, mSrcBitmap.getWidth(), 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight());
        mBitmapCanvas = new Canvas(mGraffitiBitmap);
    }

    /**
     * 创建一个图片,透明度为255(不透明), 底色为白色 ,目的是为了使用正片叠底
     *
     * @param sourceImg
     * @return
     */
    public Bitmap getTransparentBitmap(Bitmap sourceImg) {
        mWhiteBuffer = new int[sourceImg.getWidth() * sourceImg.getHeight()];
        Arrays.fill(mWhiteBuffer, 0xFFFFFFFF);
        sourceImg = Bitmap.createBitmap(mWhiteBuffer, sourceImg.getWidth(), sourceImg.getHeight(), Bitmap.Config.ARGB_8888).copy(Bitmap.Config.ARGB_8888, true);
        return sourceImg;
    }

    private void draw(Canvas canvas, CopyOnWriteArrayList<MyDrawSinglePath> pathStack) {
        // 还原堆栈中的记录的操作
        for (MyDrawSinglePath path : pathStack) {
            canvas.drawPath(path.getPath(), path.getMyPen().getPenPaint());
        }
    }

    //双指抬起时
    public void PointertUp() {

        if (!mCanvasPath.isEmpty()) {//单指画线过程中,出现双触点则停止画线
            mCanvasPath.reset();
            if (!mPathStack.isEmpty()) {
                mPathStack.remove(mPathStack.size() - 1);
            }
        }
    }

    /**
     * 将触摸的屏幕坐标转换成实际图片中的坐标
     */
    public float screenToBitmapX(float touchX) {
        return (touchX - mCenterLeft - mTransX) / (mCenterScale * mScale);
    }

    public float screenToBitmapY(float touchY) {
        return (touchY - mCenterTop - mTransY) / (mCenterScale * mScale);
    }
    //通过触点的坐标和实际图片中的坐标,得到当前图片的起始点坐标
    public final float toTransX(float touchX, float graffitiX) {
        return -graffitiX * (mCenterScale * mScale) + touchX - mCenterLeft;
    }

    public final float toTransY(float touchY, float graffitiY) {
        return -graffitiY * (mCenterScale * mScale) + touchY - mCenterTop;
    }
}

自定义View DoubleMoveView3.java

package com.test.wb.doublemoveview3;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Author: aaa
 * Date: 2016/12/5 17:31.
 */
public class DoubleMoveView3 extends View {
    private Context mContext;
    private Bitmap mSrcBitmap;
    private Bitmap mMultiplyBitmap = null;//混合之后的图片,双指缩放移动的时候,单独移动这张混合后图片,提高用户体验
    public boolean mIsMove;//是否双指拖动图片中ing
    private int mBitmapWidth, mBitmapHeight;//图片的长度和高度

    private float mCenterLeft, mCenterTop;//图片居中时左上角的坐标
    private int mCenterHeight, mCenterWidth; // 图片适应屏幕时的大小
    private float mCenterScale;//画布居中时的比例
    private int mViewWidth, mViewHeight;//当前View的长度和宽度

    private float mTransX = 0, mTransY = 0; // 偏移量,图片真实偏移量为 mCentreTranX + mTransX
    private float mScale = 1.0f; // 缩放倍数, 图片真实的缩放倍数为 mPrivateScale * mScale

    private boolean mIsSaveArg = false;//保存参数使用

    private Paint mPaint;
    private Bitmap mGraffitiBitmap; // 用绘制涂鸦的图片
    private Canvas mBitmapCanvas; // 用于绘制涂鸦的画布

    private Bitmap mCurrentBitmap; // 绘制当前线时用到的图片
    private Canvas mCurrentCanvas; // 当前绘制线的画布

    private int[] mWhiteBuffer;//保存白色图片内存,刷新时重新刷新图片
    // 保存涂鸦操作,便于撤销
    private CopyOnWriteArrayList<MyDrawSinglePath> mPathStack = new CopyOnWriteArrayList<MyDrawSinglePath>();
    private CopyOnWriteArrayList<MyDrawSinglePath> pathStackBackup = new CopyOnWriteArrayList<MyDrawSinglePath>();
    private int mTouchMode; // 触摸模式,触点数量
    private float mTouchDownX, mTouchDownY, mLastTouchX, mLastTouchY, mTouchX, mTouchY;
    private MyDrawSinglePath mCurrPath; // 当前手写的路径

    private PorterDuffXfermode mPdXfermode; // 定义PorterDuffXfermode变量
    private Paint mPdXfPaint;// 绘图的混合模式
    private Paint mCurrentPaint;
    private Path mCanvasPath; //仅用于当前Path的绘制

    public DoubleMoveView3(Context context, Bitmap bitmap) {
        super(context);
        mContext = context;
        init(bitmap);
    }

    public DoubleMoveView3(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private void init(Bitmap bitmap) {
        mSrcBitmap = bitmap;
        mBitmapWidth = mSrcBitmap.getWidth();
        mBitmapHeight = mSrcBitmap.getHeight();
        mMultiplyBitmap = Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);

        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(20);
        mGraffitiBitmap = getTransparentBitmap(mSrcBitmap);

        mCurrentPaint = new Paint();
        mCurrentPaint.setStyle(Paint.Style.STROKE);
        mCurrentPaint.setStrokeWidth(50);
        mCurrentPaint.setColor(Color.RED);
        mCurrentPaint.setAlpha(100);
        mCurrentPaint.setAntiAlias(true);
        mCurrentPaint.setStrokeJoin(Paint.Join.ROUND);
        mCurrentPaint.setStrokeCap(Paint.Cap.ROUND);
        mCurrentPaint.setXfermode(null);

        mCanvasPath = new Path();
        mCurrentBitmap = getTransparentBitmap(mSrcBitmap);

        //设置混合模式   (正片叠底)
        mPdXfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);

        mPdXfPaint = new Paint();
        mPdXfPaint.setAntiAlias(true);
        mPdXfPaint.setFilterBitmap(true);

        mIsMove = false;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mViewWidth = w;
        mViewHeight = h;

        float nw = mBitmapWidth * 1f / mViewWidth;
        float nh = mBitmapHeight * 1f / mViewHeight;
        if (nw > nh) {
            mCenterScale = 1 / nw;
            mCenterWidth = mViewWidth;
            mCenterHeight = (int) (mBitmapHeight * mCenterScale);
        } else {
            mCenterScale = 1 / nh;
            mCenterWidth = (int) (mBitmapWidth * mCenterScale);
            mCenterHeight = mViewHeight;
        }

        // 使图片居中
        mCenterLeft = (mViewWidth - mCenterWidth) / 2f;
        mCenterTop = (mViewHeight - mCenterHeight) / 2f;

        initCanvas();
        initCurrentCanvas();

        mIsMove = false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float scale = mCenterScale * mScale;
        float x = (mCenterLeft + mTransX) / scale;
        float y = (mCenterTop + mTransY) / scale;

        canvas.scale(scale, scale);

        if (!mIsMove) {
            //正片叠底混合模式
            initCurrentCanvas();
            mCurrentCanvas.drawPath(mCanvasPath, mCurrentPaint);

            canvas.drawBitmap(mSrcBitmap, x, y, mPdXfPaint);
            mPdXfPaint.setXfermode(mPdXfermode);
            canvas.drawBitmap(mCurrentBitmap, x, y, mPdXfPaint);
            // 绘制涂鸦图片
            canvas.drawBitmap(mGraffitiBitmap, x, y, mPdXfPaint);
            mPdXfPaint.setXfermode(null);
        } else {
            //只显示原始图片
            canvas.drawBitmap(mMultiplyBitmap, x, y, null);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN:
                mTouchMode = 1;
                penTouchDown(event.getX(), event.getY());
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mTouchMode = 0;
                mCanvasPath.reset();
                initCanvas();//添上这句防止重复绘制
                draw(mBitmapCanvas, mPathStack); // 保存到图片中
                invalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                if (mTouchMode == 1 && !mIsMove) {
                    mLastTouchX = mTouchX;
                    mLastTouchY = mTouchY;
                    mTouchX = event.getX();
                    mTouchY = event.getY();

                    mCurrPath.getPath().quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                            screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));

                    mCanvasPath.quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                            screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));
                    invalidate();
                }
                return true;
        }
        return true;
    }

    public void setTransScale(float scale, float dx, float dy) {
        mScale = scale;
        mTransX = dx;
        mTransY = dy;
        if (!mIsSaveArg) {
            invalidate();
        }
        mIsSaveArg = false;
    }

    public void saveCurrentScale() {
        mCenterScale = mCenterScale * mScale;

        mCenterLeft = (mCenterLeft + mTransX) / mCenterScale;
        mCenterTop = (mCenterTop + mTransY) / mCenterScale;

        mIsSaveArg = true;

        saveMultiplyBitmap();
    }

    //双指移动的时候,生成混合之后的图片
    private void saveMultiplyBitmap() {
        mIsMove = true;

        Canvas canvas = new Canvas(mMultiplyBitmap);
        canvas.drawBitmap(mSrcBitmap, 0, 0, mPdXfPaint);
        mPdXfPaint.setXfermode(mPdXfermode);
        // 绘制涂鸦图片
        canvas.drawBitmap(mGraffitiBitmap, 0, 0, mPdXfPaint);
        mPdXfPaint.setXfermode(null);
    }

    /**
     * 在画笔的状态下第一个触点按下的情况
     */
    private void penTouchDown(float x, float y) {
        mIsMove = false;
        mTouchDownX = mTouchX = mLastTouchX = x;
        mTouchDownY = mTouchY = mLastTouchY = y;

        // 为了仅点击时也能出现绘图,模拟滑动一个像素点
        mTouchX++;
        mTouchY++;

        mCurrPath = new MyDrawSinglePath(Color.RED, 50, 100, true);
        mCurrPath.getPath().moveTo(screenToBitmapX(mTouchDownX), screenToBitmapY(mTouchDownY));
        mPathStack.add(mCurrPath);

        mCanvasPath.reset();
        mCanvasPath.moveTo(screenToBitmapX(mTouchDownX), screenToBitmapY(mTouchDownY));
        // 为了仅点击时也能出现绘图,必须移动path
        mCanvasPath.quadTo(screenToBitmapX(mLastTouchX), screenToBitmapY(mLastTouchY),
                screenToBitmapX((mTouchX + mLastTouchX) / 2), screenToBitmapY((mTouchY + mLastTouchY) / 2));
    }

    /**
     * 初始化当前画线的绘图
     */
    private void initCurrentCanvas() {
        mCurrentBitmap.setPixels(mWhiteBuffer, 0, mSrcBitmap.getWidth(), 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight());
        mCurrentCanvas = new Canvas(mCurrentBitmap);
    }

    /**
     * 初始化涂鸦的绘图
     */
    private void initCanvas() {
        mGraffitiBitmap.setPixels(mWhiteBuffer, 0, mSrcBitmap.getWidth(), 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight());
        mBitmapCanvas = new Canvas(mGraffitiBitmap);
    }

    /**
     * 创建一个图片,透明度为255(不透明), 底色为白色 ,目的是为了使用正片叠底
     *
     * @param sourceImg
     * @return
     */
    public Bitmap getTransparentBitmap(Bitmap sourceImg) {
        mWhiteBuffer = new int[sourceImg.getWidth() * sourceImg.getHeight()];
        Arrays.fill(mWhiteBuffer, 0xFFFFFFFF);
        sourceImg = Bitmap.createBitmap(mWhiteBuffer, sourceImg.getWidth(), sourceImg.getHeight(), Bitmap.Config.ARGB_8888).copy(Bitmap.Config.ARGB_8888, true);
        return sourceImg;
    }

    private void draw(Canvas canvas, CopyOnWriteArrayList<MyDrawSinglePath> pathStack) {
        // 还原堆栈中的记录的操作
        for (MyDrawSinglePath path : pathStack) {
            canvas.drawPath(path.getPath(), path.getMyPen().getPenPaint());
        }
    }

    //双指抬起时
    public void PointertUp() {

        if (!mCanvasPath.isEmpty()) {//单指画线过程中,出现双触点则停止画线
            mCanvasPath.reset();
            if (!mPathStack.isEmpty()) {
                mPathStack.remove(mPathStack.size() - 1);
            }
        }
    }

    /**
     * 将触摸的屏幕坐标转换成实际图片中的坐标
     */
    public float screenToBitmapX(float touchX) {
        return (touchX - mCenterLeft - mTransX) / (mCenterScale * mScale);
    }

    public float screenToBitmapY(float touchY) {
        return (touchY - mCenterTop - mTransY) / (mCenterScale * mScale);
    }

    //通过触点的坐标和实际图片中的坐标,得到当前图片的起始点坐标
    public final float toTransX(float touchX, float graffitiX) {
        return -graffitiX * (mCenterScale * mScale) + touchX - mCenterLeft;
    }

    public final float toTransY(float touchY, float graffitiY) {
        return -graffitiY * (mCenterScale * mScale) + touchY - mCenterTop;
    }
}

使用到的另外的两个类:
MyPen.java

package com.test.wb.doublemoveview3;

import android.graphics.Color;
import android.graphics.Paint;

/**
 * Author: aaa
 * Date: 2016/10/13 09:54.
 * 涂鸦时所用的画笔
 */
public class MyPen {
    boolean mBooleanPen;//是否是画笔 true - 画笔  false - 橡皮差
    private int mPenColor;
    private int mPenSize;
    private int mPenAlpha;//0 - 100
    private Paint mPaint, mEraserPaint;

    public MyPen(int penColor, int penSize, int penAlpha, boolean bPen) {
        mPenAlpha = penAlpha;
        mPenColor = penColor;
        mPenSize = penSize;
        mBooleanPen = bPen;

        if (mBooleanPen){//画笔
            mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(mPenSize);
            mPaint.setColor(mPenColor);
            mPaint.setAlpha(mPenAlpha);

            mPaint.setAntiAlias(true);
            mPaint.setStrokeJoin(Paint.Join.ROUND);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
        }else {//橡皮擦
            mEraserPaint = new Paint();
            mEraserPaint.setStyle(Paint.Style.STROKE);
            mEraserPaint.setAlpha(255);
            mEraserPaint.setColor(Color.WHITE);
            mEraserPaint.setStrokeWidth(mPenSize);
            mEraserPaint.setAntiAlias(true);
            mEraserPaint.setStrokeJoin(Paint.Join.ROUND);
            mEraserPaint.setStrokeCap(Paint.Cap.ROUND);

        }
    }

    public int getPenAlpha() {
        return mPenAlpha;
    }

    public void setPenAlpha(int penAlpha) {
        mPenAlpha = penAlpha;
    }

    public int getPenColor() {
        return mPenColor;
    }

    public void setPenColor(int penColor) {
        mPenColor = penColor;
    }

    public int getPenSize() {
        return mPenSize;
    }

    public void setPenSize(int penSize) {
        mPenSize = penSize;
    }

    public boolean isBooleanPen() {
        return mBooleanPen;
    }

    public void setBooleanPen(boolean booleanBen) {
        mBooleanPen = booleanBen;
    }

    public Paint getPenPaint(){
        if (mBooleanPen) {
            return mPaint;
        }else{
            return mEraserPaint;
        }
    }
}

MyDrawSinglePath.java

package com.test.wb.doublemoveview3;

import android.graphics.Path;

/**
 * Author: aaa
 * Date: 2016/10/15 11:24.
 * 当前涂鸦的路径
 */
public class MyDrawSinglePath {
    private MyPen mMyPen;
    private Path mPath;

    public MyDrawSinglePath(int color, int size, int alpha, boolean b){
        mMyPen = new MyPen(color, size, alpha, b);
        mPath = new Path();
    }

    public MyPen getMyPen() {
        return mMyPen;
    }

    public Path getPath() {
        return mPath;
    }
}

下载链接:Demo下载

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
要在小程序的 canvas实现双指缩放图片的功能,你可以按照以下步骤进行操作: 1. 创建一个 canvas 组件,并设置好宽度和高度。 ``` <canvas id="myCanvas" style="width: 300px; height: 300px;"></canvas> ``` 2. 在小程序的 js 文件中获取到 canvas 组件的上下文对象。 ``` const ctx = wx.createCanvasContext('myCanvas'); ``` 3. 加载并绘制图片canvas 上。 ``` const imgPath = '图片路径'; // 替换为你的图片路径 wx.getImageInfo({ src: imgPath, success(res) { const imgWidth = res.width; // 图片原始宽度 const imgHeight = res.height; // 图片原始高度 // 计算图片缩放后的初始宽度和高度 const initialWidth = 300; const initialHeight = (imgHeight / imgWidth) * initialWidth; ctx.drawImage(imgPath, 0, 0, initialWidth, initialHeight); ctx.draw(); } }); ``` 4. 监听 canvas 组件的触摸事件,实现双指缩放功能。 ``` let touchStartDistance = 0; // 记录触摸开始时两指之间的距离 let scale = 1; // 记录当前缩放比例 wx.canvasTouchStart((e) => { if (e.touches.length >= 2) { const x1 = e.touches[0].x; const y1 = e.touches[0].y; const x2 = e.touches[1].x; const y2 = e.touches[1].y; // 计算两指之间的距离 touchStartDistance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); } }); wx.canvasTouchMove((e) => { if (e.touches.length >= 2) { const x1 = e.touches[0].x; const y1 = e.touches[0].y; const x2 = e.touches[1].x; const y2 = e.touches[1].y; // 计算两指之间的距离 const touchMoveDistance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); // 计算缩放比例 const newScale = touchMoveDistance / touchStartDistance; // 更新缩放比例 scale = newScale; // 清空 canvas ctx.clearRect(0, 0, 300, 300); // 根据缩放比例绘制图片 const newWidth = initialWidth * scale; const newHeight = initialHeight * scale; ctx.drawImage(imgPath, 0, 0, newWidth, newHeight); ctx.draw(); } }); ``` 请根据你的实际需求,将上述代码片段进行适当修改和组合。希望对你有所帮助!
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wb175208

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值