一种基于自定义View的贴纸控件Demo

其实就是自定义一套触摸事件规则,加上对Matrix的使用即可。

首先定义基类,首先不同类型的图元,例如Bitmap或者文本,需要的缩放、移动、测量、绘制方式可能都不一致,所以做成抽象函数顶个接口规范,等待子类自己实现:

package com.chenjiezhu.waterMarkShape;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.view.MotionEvent;

import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

public abstract class BaseShape {
    private PointF mCurrentCenter = new PointF();
    private PointF mPrevCurrentCenter = null;
    private float mPrevDistance = Float.MIN_VALUE;
    private float mAvergeX = 0, mAvergeY = 0;
    private int mPrevPointCount = 0;

    /**是否已经缩放过**/
    private boolean mIsScaled = false;
    private Queue<Float> mTouchDistanceQueue = new LinkedBlockingQueue<>();
    private PointF mPrevLocation;

    /**水印类型**/
    public enum ShapeType {
        BITMAP,
        TEXT
    }
    private ShapeType mShapeType = null;

    public void setmShapeType(ShapeType mShapeType) {
        this.mShapeType = mShapeType;
    }

    public ShapeType getmShapeType() {
        Paint paint;
        return mShapeType;
    }

    /**移动代码实现**/
    public abstract void translate(float dx, float dy);

    /**缩放代码实现**/
    public abstract void scale(float scaleXRatio, float scaleYRatio, float scaleCenterX, float scaleCenterY);

    /**绘制代码实现**/
    public abstract void draw(Canvas canvas);

    /**了解显示范围**/
    public abstract RectF getRange();

    /**设置显示效果**/
    public abstract void setPaint(Paint paint, Shader shader);

    /**了解缩放量**/
    public abstract PointF getScale();

    /**了解位置**/
    public abstract PointF getXY();

    /**触摸处理**/
    public boolean touchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mPrevDistance = 0;
                mPrevPointCount = event.getPointerCount();
                //算出移动中心坐标、点间距离
                for (int i = 0; i < event.getPointerCount(); i++) {
                    mAvergeX += event.getX(i);
                    mAvergeY += event.getY(i);
                    if (i + 1 < event.getPointerCount()) {
                        mPrevDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
                    }
                }
                mAvergeX /= event.getPointerCount();
                mAvergeY /= event.getPointerCount();
                mPrevLocation = getXY();
                if (mPrevCurrentCenter == null) {
                    mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
                } else {
                    mPrevCurrentCenter.set(mAvergeX, mAvergeY);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                mAvergeX = 0;
                mAvergeY = 0;
                float nowDistance = 0;
                //算出移动中心坐标、点间距离
                for (int i = 0; i < event.getPointerCount(); i++) {
                    mAvergeX += event.getX(i);
                    mAvergeY += event.getY(i);
                    if (i + 1 < event.getPointerCount()) {
                        nowDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2));
                    }
                }
                //现在的点间距离 除以 上次点间距离 这次得到缩放比例
                mAvergeX /= event.getPointerCount();
                mAvergeY /= event.getPointerCount();
                if ((mPrevPointCount != event.getPointerCount()) || event.getPointerCount() <= 1 || mPrevPointCount <= 1) { //触摸点数突然改变 或者 触摸点不超过2,不允许缩放
                    mPrevDistance = nowDistance = 0;
                }
                //如果缩放数据有效,则进行平均平滑化并且进行缩放
                if (mPrevDistance > 0 && nowDistance > 0) {
                    mTouchDistanceQueue.add(nowDistance / mPrevDistance);
                    if (mTouchDistanceQueue.size() >= 6) {
                        Float point[] = new Float[mTouchDistanceQueue.size()];
                        mTouchDistanceQueue.toArray(point);
                        float avergDistance = 0;
                        for (int i = 0; i < point.length; i++) {
                            avergDistance += point[i];
                        }
                        avergDistance /= point.length;
//                        scale((float) Math.sqrt(avergDistance), (float) Math.sqrt(avergDistance), mAvergeX, mAvergeY);
                        scale((float) Math.sqrt(avergDistance), (float) Math.sqrt(avergDistance), event.getX(0), event.getY(0));
                        mIsScaled = true;
                        while (mTouchDistanceQueue.size() > 6) {
                            mTouchDistanceQueue.poll();
                        }
                    }
                }
                mPrevPointCount = event.getPointerCount();
                mPrevDistance = nowDistance;
                //当前坐标 - 上次坐标 = 偏移值,然后进行位置偏移
                if (mPrevCurrentCenter == null) {
                    mPrevCurrentCenter = new PointF(mAvergeX, mAvergeY);
                } else {
                    if (!mIsScaled && event.getPointerCount() == 1) {
                        translate(mAvergeX - mPrevCurrentCenter.x, mAvergeY - mPrevCurrentCenter.y);
                    }
                    mPrevCurrentCenter.set(mAvergeX, mAvergeY);
                }
                break;
            case MotionEvent.ACTION_UP:
                //抬起,清理干净数据
                mAvergeX = 0;
                mAvergeY = 0;
                mTouchDistanceQueue.clear();
                mIsScaled = false;
                break;
        }
        return true;
    }
}

Bitmap类型子类,通过matrix控制图片移动:

package com.chenjiezhu.waterMarkShape;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;

public class BitmapShape extends BaseShape {

    private Matrix mMatrix;
    private Bitmap mBitmap;
    private RectF mRange;

    public BitmapShape() {
        setmShapeType(ShapeType.BITMAP);
        mMatrix = new Matrix();
    }

    public void setmBitmap(Bitmap b) {
        this.mBitmap = b;
    }

    @Override
    public void translate(float dx, float dy) {
        mMatrix.postTranslate(dx, dy);
    }

    @Override
    public void scale(float scaleXRatio, float scaleYRatio, float scaleCenterX, float scaleCenterY) {
        mMatrix.postScale(scaleXRatio, scaleYRatio, scaleCenterX, scaleCenterY);
    }

    @Override
    public void draw(Canvas canvas) {
        if (mBitmap != null) {
            canvas.drawBitmap(mBitmap, mMatrix, null);
            //debug code:
//            Paint paint = new Paint();
//            paint.setStrokeWidth(5f);
//            paint.setColor(Color.RED);
//            paint.setStyle(Paint.Style.STROKE);
//            canvas.drawRect(getRange(), paint);
        }

    }

    @Override
    public RectF getRange() {
        //cjz: you can see this to know why I do that:https://www.jianshu.com/p/c83f59613c18
        float matrix[] = new float[9];
        mMatrix.getValues(matrix);
        mRange = new RectF(
                matrix[2],
                matrix[5],
                matrix[2] + mBitmap.getWidth() * matrix[0],
                matrix[5] + mBitmap.getHeight() * matrix[4]);
        return mRange;
    }

    @Override
    public void setPaint(Paint paint, Shader shader) {

    }

    @Override
    public PointF getScale() {
        float matrix[] = new float[9];
        mMatrix.getValues(matrix);
        return new PointF(matrix[0], matrix[4]);
    }

    @Override
    public PointF getXY() {
        float matrix[] = new float[9];
        mMatrix.getValues(matrix);
        return new PointF(matrix[2] + (mBitmap.getWidth() * matrix[0]) / 2f, matrix[5] + (mBitmap.getHeight() * matrix[4]) / 2f);
    }

}

文本子类,较为复杂,因为文本不能直接应用Matrix进行形变,所以通过绘制时对Canvas应用Matrix进行调整来实现该功能。难点在于测量方面,大小的计算是通过字符量×画笔大小×缩放量确定的,这样确定的图元范围实测可以准确根据移动位置和缩放大小进行精确调整。

package com.chenjiezhu.waterMarkShape;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;

public class TextShape extends BaseShape {
    private String mText;
    private Matrix mMatrix;
    private Paint mPaint;
    private RectF mRange;

    public TextShape() {
        mMatrix = new Matrix();
    }

    public void setText(String text) {
        this.mText = text;
    }

    @Override
    public void translate(float dx, float dy) {
        mMatrix.postTranslate(dx, dy);
    }

    @Override
    public void scale(float scaleXRatio, float scaleYRatio, float scaleCenterX, float scaleCenterY) {
        mMatrix.postScale(scaleXRatio, scaleYRatio, scaleCenterX, scaleCenterY);
    }

    @Override
    public void draw(Canvas canvas) {
        if (mText != null && mPaint != null) {
            float matrix[] = new float[9];
            mMatrix.getValues(matrix);
            canvas.save();
            canvas.translate(matrix[2], matrix[5]);
            canvas.scale(matrix[0], matrix[4]);
            canvas.drawText(mText, 0, 0, mPaint);
            canvas.restore();
            //debug code:
//            Paint paint = new Paint();
//            paint.setStrokeWidth(5f);
//            paint.setColor(Color.BLUE);
//            paint.setStyle(Paint.Style.STROKE);
//            canvas.drawRect(getRange(), paint);
        }

    }

    @Override
    public RectF getRange() {
        //cjz: you can see this to know why I do that:https://www.jianshu.com/p/c83f59613c18
        if (mText != null && mPaint != null) {
            float matrix[] = new float[9];
            mMatrix.getValues(matrix);
            mRange = new RectF(
                    matrix[2],
                    matrix[5] - mPaint.getTextSize() * matrix[4],
                    matrix[2] + (mText.length() * mPaint.getTextSize()) * matrix[0],
                    matrix[5] + mPaint.getTextSize() / 2f * matrix[4]);
            return mRange;
        }
        return null;
    }

    @Override
    public void setPaint(Paint paint, Shader shader) {
        this.mPaint = new Paint(paint);
        if (shader != null) {
            this.mPaint.setShader(shader);
        }
    }

    @Override
    public PointF getScale() {
        float matrix[] = new float[9];
        mMatrix.getValues(matrix);
        return new PointF(matrix[0], matrix[4]);
    }

    @Override
    public PointF getXY() {
        float matrix[] = new float[9];
        mMatrix.getValues(matrix);
        return new PointF(matrix[2],
                matrix[5] - mPaint.getTextSize() * matrix[4]);
    }
}

绘制View,通过遍历BaseShape并调用onDraw方法绘制图元,注意事件传递后调整图层层叠关系时,应该从顶(链表尾)到底(链表头)进行遍历,而绘制时则反之,才可以实现点击时,被点击的图元置顶的效果。另外刚刚设计的测量范围的方法在这里就可以派上用场了,可以用于确定是哪个图元被点击,就像UI SDK的设计:

package com.example.android.camera2basic;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.chenjiezhu.waterMarkShape.BaseShape;
import com.chenjiezhu.waterMarkShape.BitmapShape;
import com.chenjiezhu.waterMarkShape.TextShape;

import java.util.LinkedList;
import java.util.List;


public class WaterMarkView extends View {
    private Bitmap mBitmap;
    private List<BaseShape> mShapesList = new LinkedList<>();
    private BitmapShape mBitmapShape;
    private BaseShape mCurrentTouchShape;


    public WaterMarkView(Context context) {
        super(context);
        init();
    }

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

    public WaterMarkView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    @RequiresApi(api = Build.VERSION_CODES.O)
    private void init() {
        mBitmap = BitmapFactory.decodeResource(getResources(), android.R.mipmap.sym_def_app_icon);
        for(int i = 0; i < 3; i++) {
            BitmapShape bs  = new BitmapShape();
            bs.setmBitmap(mBitmap);
            bs.translate(50 * i, 50 * i);
            mShapesList.add(bs);
        }
        TextShape textShape = new TextShape();
        Paint paint = new Paint();
        paint.setStrokeWidth(5f);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setTextSize(150f);
        textShape.setPaint(paint, null);
        textShape.setText("测试测试测试");
        textShape.translate(200, 200);
        mShapesList.add(textShape);

        textShape = new TextShape();
        paint = new Paint();
        paint.setStrokeWidth(5f);
        paint.setColor(Color.argb(0.5f, 0.1f, 0.5f, 0.5f));
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setTextSize(100f);
        textShape.setPaint(paint, null);
        textShape.setText("水印编辑器Demo");
        textShape.translate(400, 500);
        mShapesList.add(textShape);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                for (int i = mShapesList.size() - 1; i >=0; i--) { //从顶传递到底
                    BaseShape shape = mShapesList.get(i);
                    RectF shapeRange = shape.getRange();
                    if (shapeRange != null && shapeRange.contains(event.getX(), event.getY())) {
                        mCurrentTouchShape = shape;
                        //置顶选中图案
                        mShapesList.remove(mCurrentTouchShape);
                        mShapesList.add(mShapesList.size(), mCurrentTouchShape);
                        mCurrentTouchShape.touchEvent(event);
                        break;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (mCurrentTouchShape != null) {
                    mCurrentTouchShape.touchEvent(event);
                    break;
                }
            }
            case MotionEvent.ACTION_UP: {
                if (mCurrentTouchShape != null) {
                    mCurrentTouchShape.touchEvent(event);
                    mCurrentTouchShape = null;
                    break;
                }
            }

        }
        invalidate();
        return true;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (BaseShape view : mShapesList) {
            view.draw(canvas);
        }
    }
}

实际效果如下:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值