仿毛笔字的自定义绘图View

可以设置宽度


public class CanvasViewPlus extends View {
    private static String TAG = "TAG";
    float fOldPointX = -1f;
    float fOldPointY = -1f;
    float fNewPointX = -1f;
    float fNewPointY = -1f;
    float fOldWidth = 30, maxWidth = 50, minWidth = 10, changeWidth = 1, finalWidth = 30;
    float fNewWidth;
    float fSpecialPointX;
    float fSpecialPointY;
    long lastDownTime, thisEventTime;
    //    int alpha = 255;改变透明度会整条改变
    Point[] oldPoints;
    //Point[] orderedPoints;   //对应索引分别为最上,最右,最下,最左
    boolean isMoving = false, isPressed = false;
    Path mPath = new Path();
    Paint mPaint = new Paint();
    Date date;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & ACTION_MASK) {
            case ACTION_DOWN:
                fOldPointX = event.getX();
                fOldPointY = event.getY();
                lastDownTime = getNowDate();
                break;
            case ACTION_MOVE:
                fNewPointX = event.getX();
                fNewPointY = event.getY();
                thisEventTime = getNowDate();
                fNewWidth = fOldWidth;
                if (fOldPointX == fNewPointX && fNewPointY == fNewPointY) {
                    break;
                }
                float k = 0;    //斜率
                boolean isVaidedByK = true;
                if (fOldPointX == fNewPointX) {
                    isVaidedByK = false;
                } else {
                    k = (fOldPointY - fNewPointY) / (fOldPointX - fNewPointX);
                }
                mPath.moveTo(fOldPointX, fOldPointY);
                //判断长按
                if (isLongPressed(fOldPointX, fOldPointY, fNewPointX, fNewPointY, lastDownTime, thisEventTime, 200)) {//200毫秒判断
                    if (fNewWidth < maxWidth) {//最粗30
                        fNewWidth = fNewWidth + changeWidth;
                    }
                    if (!isMoving) {
                        isPressed = true;
                    }
//                    if (alpha > 255) {
//                        alpha = alpha + 5;
//                    }
                } else {
                    if (fNewWidth > minWidth) {//最细10
                        fNewWidth = fNewWidth - changeWidth;
                    }
//                    if (alpha <= 255) {
//                        alpha = alpha - 5;
//                    }
                    //move获取到第一个后画出开头的笔锋
                    if (!isMoving) {
                        isMoving = true;    //标记为正在触摸中
                        oldPoints = calculatePoint(fOldPointX, fOldPointY, fOldWidth, k, isVaidedByK);
                        calculateStroke(fNewPointX, fNewPointY, fOldPointX, fOldPointY, fOldWidth, 4);
                    }
                    isPressed = false;
                }
//                mPaint.setAlpha(alpha);
                if (isPressed) {
                    mPath.addCircle(fOldPointX, fOldPointY, fOldWidth, CW);
                } else {
                    isPressed = false;
                    Point[] newPoints = calculatePoint(fNewPointX, fNewPointY, fNewWidth, k, isVaidedByK);
                    calculateDrawRect(oldPoints, newPoints);
                    oldPoints = newPoints;
                }
                //更新数值
                fSpecialPointX = fOldPointX;
                fSpecialPointY = fOldPointY;
                fOldPointX = fNewPointX;
                fOldPointY = fNewPointY;
                fOldWidth = fNewWidth;
                invalidate();
                break;
            case ACTION_UP:
                if (isMoving == true) {
                    isMoving = false;
                    calculateStroke(fSpecialPointX, fSpecialPointY, fOldPointX, fOldPointY, fOldWidth, 4);
                }
                isPressed = false;
                invalidate();
                fOldWidth = finalWidth;
                break;
        }
        return true;
    }

    //获取当前时间
    public long getNowDate() {
        date = new Date();
        return date.getTime();
    }

    /**
     * 判断是否有长按动作发生
     *
     * @param lastX         按下时X坐标
     * @param lastY         按下时Y坐标
     * @param thisX         移动时X坐标
     * @param thisY         移动时Y坐标
     * @param lastDownTime  按下时间
     * @param thisEventTime 移动时间
     * @param longPressTime 判断长按时间的阀值
     */
    private boolean isLongPressed(float lastX, float lastY,
                                  float thisX, float thisY,
                                  long lastDownTime, long thisEventTime,
                                  long longPressTime) {
        float offsetX = Math.abs(thisX - lastX);
        float offsetY = Math.abs(thisY - lastY);
        long intervalTime = thisEventTime - lastDownTime;
        if (offsetX <= 10 && offsetY <= 10 && intervalTime >= longPressTime) {
            return true;
        }
        return false;
    }

    //    过圆心的直线必有两点与圆相交
    Point[] calculatePoint(double x0, double y0, double r, double k, boolean isValidByK) {
        double b, ar, br, cr, x1, y1, x2, y2;
        if (isValidByK) {

            if (k == 0) {

                ar = 1;
                br = -2 * y0;
                cr = Math.pow(y0, 2) - Math.pow(r, 2);

                x2 = x0;
                //y2 = ((-br - Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
                y2 = y0 + r;
                x1 = x0;
                //y1 = ((-br + Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
                y1 = y0 - r;

            } else {
                b = y0 + x0 / k;
                ar = 1 + 1 / Math.pow(k, 2);
                br = -(2 * (b - y0) / k + 2 * x0);
                cr = (Math.pow(x0, 2) + Math.pow(b - y0, 2) - Math.pow(r, 2));

                x1 = ((-br - Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
                y1 = -x1 / k + b;

                x2 = ((-br + Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
                y2 = -x2 / k + b;
            }

        } else {
            //K是不存在的
            ar = 1;
            br = -2 * x0;
            cr = Math.pow(x0, 2) - Math.pow(r, 2);

            x1 = ((-br - Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
            y1 = y0;
            x2 = ((-br + Math.sqrt(Math.pow(br, 2) - 4 * ar * cr)) / 2 / ar);
            y2 = y0;
        }
        return new Point[]{
                new Point(x1, y1),
                new Point(x2, y2)};

    }

    /*
     * 连成四边形,保证四个点不交叉
     * */
    void calculateDrawRect(Point[] prevPoints, Point[] nextPoints) {
        //连成三角形
        lineToTriangle(prevPoints[0], prevPoints[1], nextPoints[0]);
        lineToTriangle(prevPoints[0], prevPoints[1], nextPoints[1]);
        lineToTriangle(prevPoints[0], nextPoints[0], nextPoints[1]);
        lineToTriangle(prevPoints[1], nextPoints[0], nextPoints[1]);
    }

    /*
     * 把三个点连成三角形,以顺时针的顺序
     * */
    void lineToTriangle(Point p0, Point p1, Point p2) {
        //找出最高点,放在第一个,如果有一样高的选靠左的
        Point[] points = new Point[]{p0, p1, p2};
        for (int i = 0; i < points.length; i++) {
            if (points[0].y > points[i].y || (points[0].y == points[i].y && points[0].x > points[i].x)) {
                Point tmp = points[i];
                points[i] = points[0];
                points[0] = tmp;
            }
        }
        mPath.moveTo((float) points[0].x, (float) points[0].y);
        //判断斜率是否存在,存在,小的先连接,不存在,大于0的先连接
        Double k1 = points[0].x == points[1].x ? NaN : (points[0].y - points[1].y) / (points[0].x - points[1].x);
        Double k2 = points[0].x == points[2].x ? NaN : (points[0].y - points[2].y) / (points[0].x - points[2].x);
        //不可能都不存在,10种情况
        if (k1.equals(NaN)) {
            if (k2 >= 0) {
                mPath.lineTo((float) points[2].x, (float) points[2].y);
                mPath.lineTo((float) points[1].x, (float) points[1].y);
            } else {
                mPath.lineTo((float) points[1].x, (float) points[1].y);
                mPath.lineTo((float) points[2].x, (float) points[2].y);
            }
        } else if (k2.equals(NaN)) {
            if (k1 >= 0) {
                mPath.lineTo((float) points[1].x, (float) points[1].y);
                mPath.lineTo((float) points[2].x, (float) points[2].y);
            } else {
                mPath.lineTo((float) points[2].x, (float) points[2].y);
                mPath.lineTo((float) points[1].x, (float) points[1].y);
            }
        } else if (k1 >= 0 && k2 >= 0 && k1 > k2) {
            mPath.lineTo((float) points[2].x, (float) points[2].y);
            mPath.lineTo((float) points[1].x, (float) points[1].y);
        } else if (k1 >= 0 && k2 >= 0 && k1 < k2) {
            mPath.lineTo((float) points[1].x, (float) points[1].y);
            mPath.lineTo((float) points[2].x, (float) points[2].y);
        } else if (k1 >= 0 && k2 < 0) {
            mPath.lineTo((float) points[1].x, (float) points[1].y);
            mPath.lineTo((float) points[2].x, (float) points[2].y);
        } else if (k1 < 0 && k2 >= 0) {
            mPath.lineTo((float) points[2].x, (float) points[2].y);
            mPath.lineTo((float) points[1].x, (float) points[1].y);
        } else if (k1 < 0 && k2 < 0 && k1 < k2) {
            mPath.lineTo((float) points[1].x, (float) points[1].y);
            mPath.lineTo((float) points[2].x, (float) points[2].y);
        } else if (k1 < 0 && k2 < 0 && k1 > k2) {
            mPath.lineTo((float) points[2].x, (float) points[2].y);
            mPath.lineTo((float) points[1].x, (float) points[1].y);
        }
        //最后连回去
        mPath.lineTo((float) points[0].x, (float) points[0].y);
    }

    /*
     *direction: 1-正序连线,0-反序连线
     * */
    void lineToByDirection(Point[] prevBothPoints, Point[] nextBothPoints, int direction) {
        if (direction == 1) {
            mPath.moveTo((float) prevBothPoints[0].x, (float) prevBothPoints[0].y);
            mPath.lineTo((float) prevBothPoints[1].x, (float) prevBothPoints[1].y);
            mPath.lineTo((float) nextBothPoints[1].x, (float) nextBothPoints[1].y);
            mPath.lineTo((float) nextBothPoints[0].x, (float) nextBothPoints[0].y);
            mPath.lineTo((float) prevBothPoints[0].x, (float) prevBothPoints[0].y);
        } else {
            mPath.moveTo((float) prevBothPoints[0].x, (float) prevBothPoints[0].y);
            mPath.lineTo((float) prevBothPoints[1].x, (float) prevBothPoints[1].y);
            mPath.lineTo((float) nextBothPoints[0].x, (float) nextBothPoints[0].y);
            mPath.lineTo((float) nextBothPoints[1].x, (float) nextBothPoints[1].y);
            mPath.lineTo((float) prevBothPoints[0].x, (float) prevBothPoints[0].y);
        }
    }

    /*
     * 修整笔画的开头和结尾(使两端突出),方向是(x0,y0)->(x1,y1),
     * 突出的程度取决于两点之间的距离和第一个点的压力值或接触面积
     * (与两点间距离等长)version 1
     * */
    void calculateStroke(float x0, float y0, float x1, float y1, float width0, int multiple) {
        //两点的斜率
        Float k;
        if (oldPoints[0].x == oldPoints[1].x) {
            k = Float.NaN;
        } else {
            k = (float) ((oldPoints[0].y - oldPoints[1].y) / (oldPoints[0].x - oldPoints[1].x));
        }
        if (k.equals(Float.NaN)) {
            width0 *= multiple;
            mPath.moveTo((float) oldPoints[0].x, (float) oldPoints[0].y);
//            mPath.addCircle((float) oldPoints[0].x, (float) oldPoints[0].y, 10, CW);
//            mPath.addCircle((float) oldPoints[1].x, (float) oldPoints[1].y, 15, CW);
            if (x0 > x1) {
                //向左的
                //先线
                mPath.lineTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                mPath.quadTo((float) (oldPoints[0].x - width0), (float) oldPoints[0].y,
                        (float) oldPoints[0].x, (float) oldPoints[0].y);
                //mPath.addCircle((float) (oldPoints[0].x - width0), (float) oldPoints[0].y, 10, CW);
            } else {
                //向右的
                //先圆弧
                mPath.quadTo((float) (oldPoints[1].x + width0), (float) oldPoints[1].y,
                        (float) oldPoints[1].x, (float) oldPoints[1].y);
                mPath.lineTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                //mPath.addCircle((float) (oldPoints[1].x + width0), (float) oldPoints[1].y, 10, CW);
            }
        } else if (k == 0) {
            width0 *= multiple;
            //k == 0
            mPath.moveTo((float) oldPoints[0].x, (float) oldPoints[0].y);
            if (y0 > y1) {
                //向上的
                //先圆弧
                mPath.quadTo((float) oldPoints[0].x, (float) (oldPoints[0].y - width0),
                        (float) oldPoints[1].x, (float) oldPoints[1].y);
                mPath.lineTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                //mPath.addCircle((float) oldPoints[0].x, (float) (oldPoints[0].y - width0), 15, CW);

            } else {
                //向下的
                //先线
                mPath.lineTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                mPath.quadTo((float) oldPoints[1].x, (float) (oldPoints[1].y + width0),
                        (float) oldPoints[0].x, (float) oldPoints[0].y);
                //mPath.addCircle((float) oldPoints[1].x, (float) (oldPoints[1].y + width0), 15, CW);
            }
        } else {
            //直线一般式求系数
            double a = y1 - y0;
            double b = x0 - x1;
            double c = x1 * y0 - x0 * y1;
            double distanceY = Math.abs(c / b);     //纵坐标截距
            double distanceX = Math.abs(c / a);     //横坐标截距
            //两点间距离
            //double distancePoint = Math.sqrt(Math.pow(oldPoints[0].x - oldPoints[1].x, 2) +
//                    Math.pow(oldPoints[0].y - oldPoints[1].y, 2));
            //偏移
            double offsetX = (width0 * distanceX / Math.sqrt(distanceX * distanceX + distanceY * distanceY));
            double offsetY = (width0 * distanceY / Math.sqrt(distanceX * distanceX + distanceY * distanceY));
//        distancePoint *= 2;
            offsetX *= multiple;
            offsetY *= multiple;
            if (x0 > x1) {
                //先划线
                if (oldPoints[0].y < oldPoints[1].y) {
                    //已x0为原点,左下
                    mPath.moveTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                    mPath.lineTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                    mPath.quadTo((float) (oldPoints[0].x - offsetX), (float) (oldPoints[0].y + offsetY),
                            (float) oldPoints[0].x, (float) oldPoints[0].y);
                    //mPath.addCircle((float) (oldPoints[0].x - offsetX), (float) (oldPoints[0].y + offsetY), 10, CW);
                } else {
                    //已x0为原点,左上
                    mPath.moveTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                    mPath.lineTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                    mPath.quadTo((float) (oldPoints[0].x - offsetX), (float) (oldPoints[0].y - offsetY),
                            (float) oldPoints[1].x, (float) oldPoints[1].y);
                    //mPath.addCircle((float) (oldPoints[0].x - offsetX), (float) (oldPoints[0].y - offsetY), 10, CW);
                }
            } else {
                //先圆弧
                if (oldPoints[0].y < oldPoints[1].y) {
                    //已x0为原点,右上
                    mPath.moveTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                    mPath.quadTo((float) (oldPoints[1].x + offsetX), (float) (oldPoints[1].y - offsetY),
                            (float) oldPoints[1].x, (float) oldPoints[1].y);
                    mPath.lineTo((float) oldPoints[0].x, (float) oldPoints[0].y);
                    //mPath.addCircle((float) (oldPoints[1].x + offsetX), (float) (oldPoints[1].y - offsetY), 10, CW);

                } else {
                    //已x0为原点,右下
                    mPath.moveTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                    mPath.quadTo((float) (oldPoints[1].x + offsetX), (float) (oldPoints[1].y + offsetY),
                            (float) oldPoints[0].x, (float) oldPoints[0].y);
                    mPath.lineTo((float) oldPoints[1].x, (float) oldPoints[1].y);
                    //mPath.addCircle((float) (oldPoints[1].x + offsetX), (float) (oldPoints[1].y + offsetY), 10, CW);
                }
            }
        }
    }

    public void reset() {
        mPath.reset();
    }


    /*
     * 通过f 计算出点的“宽度”(此刻需要渲染的面积的大小)
     */
    float calculateWidth(float f) {
//        f = 1.0f;
        f *= 20;
        if (isMoving) {
            f = speed(f);
            f -= (f - fOldWidth) / 2;
        }
        return f;
    }

    float speed(float f) {
        float distance = (float) Math.sqrt(Math.pow(fOldPointX - fNewPointX, 2) + Math.pow(fOldPointY - fNewPointY, 2));
        float limit = 150;
//        Log.d("suqin:distance", "" + distance);
        //以一半的f为缩减量,
        if (distance <= limit) {
            return f - f * distance / 2 / limit;
        } else {
            return f / 2;
        }
    }

    public CanvasViewPlus(Context context) {
        super(context);
    }

    public CanvasViewPlus(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint.setColor(Color.BLACK);
        mPaint.setAntiAlias(true);  //抗锯齿
//        mPath.setFillType(Path.FillType.WINDING);   //填充模式
//        mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setStrokeWidth(1);
    }

    public CanvasViewPlus(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CanvasViewPlus(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    class Point {
        double x;
        double y;

        Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    public void setfOldWidth(float fOldWidth) {
        this.fOldWidth = fOldWidth;
        finalWidth = fOldWidth;
        //设置±10档粗细变化,默认最大最小为设置大小的*2或者/2
        changeWidth = fOldWidth / 10;
        maxWidth = fOldWidth * 2;
        minWidth = fOldWidth / 2;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值