美文网首页
Paint详解(二)绘制路径以及贝塞尔曲线

Paint详解(二)绘制路径以及贝塞尔曲线

作者: GoodWen | 来源:发表于2019-10-23 13:46 被阅读0次
    10.png

    1,下面自定义View展示了Paint的一些api的用法

    public class PathView extends View {
    
        private Path mPath = new Path();
        private Paint mPaint = new Paint();
    
        public PathView(Context context) {
            super(context);
            mPaint.setColor(Color.RED);
    
            mPaint.setStrokeWidth(4);
            mPaint.setStyle(Paint.Style.STROKE);//描边模式
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //        mPaint.setStyle(Paint.Style.FILL);//填充内部区域  填充模式
    
    
            /**一阶贝塞尔曲线,表示的是一条直线*/
    //        mPath.moveTo(100, 70); //移动
    ////        mPath.lineTo(140, 800);//连线
    //        //等同于上一行代码效果
    //        mPath.rLineTo(40, 730);//相对于当前点的位置移动
    //
    //        mPath.lineTo(250, 600);
    //        mPath.close();//设置曲线是否闭合   不关闭就不会设置曲线为闭合状态
    
    
            /**添加子图形   addXXX   方法*/
    //        //添加弧形  前面四个参数表示弧形所在的矩形区域   后面两个表示  开始绘制的角度   和
    ////        弧形扫过的角度   这个弧形的中心是 矩形的中心  顺时针方向  0度在X轴上
    //        mPath.addArc(200, 200, 400, 400, -225, 225);
    //        canvas.drawRect(200, 200, 400, 400, mPaint);
    //        //Path.Direction.CW表示顺时针方向绘制,CCW表示逆时针方向
    //        mPath.addRect(500, 500, 900, 900, Path.Direction.CW);
    //        //添加一个圆
    //        mPath.addCircle(700, 700, 200, Path.Direction.CW);
    //        //添加一个椭圆
    //        mPath.addOval(0, 0, 500, 300, Path.Direction.CCW);
    
    
            /**追加图形*/
    //        //xxxTo画线
    //        mPath.addArc(200, 200, 400, 400, -225, 225);
    ////        canvas.drawRect(200, 200, 400, 400, mPaint);//辅助理解
    //        /**低五个参数表示起点相对于X轴 上  0度 的位置   最后一个参数表示是否将 这次的起点 和上次的 终点连线
    //         * 强制移动的意思
    //         * */
    //        mPath.arcTo(400, 200, 600, 400, -180, 225, true);
    ////        canvas.drawRect(400, 200, 600, 400, mPaint);//辅助理解
    
    
    //        //forceMoveTo,true,绘制时移动起点,false,绘制时连接最后一个点与圆弧起点
    //        mPath.moveTo(0, 0);
    //        mPath.lineTo(100, 100);
    //        mPath.arcTo(400, 200, 600, 400, 0, 270, true);
    
    
            /**添加一个路径*/
    //        mPath.moveTo(100, 70);
    //        mPath.lineTo(140, 180);
    //        mPath.lineTo(250, 330);
    //        mPath.lineTo(400, 630);
    //        mPath.lineTo(100, 830);
    //
    //        Path newPath = new Path();
    //        newPath.moveTo(100, 1000);
    //        newPath.lineTo(600, 1300);
    //        newPath.lineTo(400, 1700);
    //        mPath.addPath(newPath);
    
    
    //        /**添加圆角矩形, CW顺时针,CCW逆时针*/
    //        RectF rectF5 = new RectF(200, 800, 700, 1200);
    //        mPath.addRoundRect(rectF5, 20, 20, Path.Direction.CCW);//三四参数 表示X方向和 Y方向的  圆角半径
    //
    
    
            /**画二阶贝塞尔曲线*/
    //        //二阶贝塞尔曲线会有两个数据点  分别是起点和终点  还有一个控制点  控制点的位置不同 呈现的二阶贝塞尔曲线也不同
    //        mPath.moveTo(300, 500);//绘制起点
    //        mPath.quadTo(500, 100, 800, 500);//一二参数是 控制点的坐标  三四参数是   终点的坐标
    //        Paint paint = new Paint();//辅助理解控制点的位置 就是圆心的位置
    //        paint.setColor(Color.GREEN);//辅助理解控制点的位置 就是圆心的位置
    //        canvas.drawCircle(500, 100, 100, paint);//辅助理解控制点的位置 就是圆心的位置
    ////        参数表示相对位置,等同于上面一行代码
    ////        mPath.rQuadTo(200, -400, 500, 0);//第一二参数 相对于  起点得位置  做加减
    
    
            /**画三阶贝塞尔曲线*/
            mPath.moveTo(300, 500);
            //前面四个参数是两个控制点的坐标,最后一个点是终点坐标
            mPath.cubicTo(500, 100, 600, 1200, 800, 500);
            Paint paint = new Paint();//辅助理解控制点的位置 就是圆心的位置
            paint.setColor(Color.GREEN);//辅助理解控制点的位置 就是圆心的位置
            canvas.drawCircle(500, 100, 5, paint);//辅助理解控制点的位置 就是圆心的位置
            canvas.drawCircle(600, 1200, 5, paint);//辅助理解控制点的位置 就是圆心的位置
    //        参数表示相对位置,等同于上面一行代码
    //        mPath.rCubicTo(200, -400, 300, 700, 500, 0);//;两个控制点分别跟起点做比较获得各自的坐标
    
    
            canvas.drawPath(mPath, mPaint);
    
        }
    }
    

    2,贝塞尔曲线

    11.png

    多介贝塞尔曲线

    public class BezierView extends View {
    
        private Paint mPaint, mLinePointPaint;
        private Path mPath;
    
        //多控制点
        private ArrayList<PointF> mControlPoints;    //控制点集,没有分数据点还是控制点
    
        public BezierView(Context context) {
            this(context, null);
        }
    
        public BezierView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
    
        }
    
        public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setStrokeWidth(4);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.RED);
    
            mLinePointPaint = new Paint();
            mLinePointPaint.setAntiAlias(true);
            mLinePointPaint.setStrokeWidth(4);
            mLinePointPaint.setStyle(Paint.Style.STROKE);
            mLinePointPaint.setColor(Color.GRAY);
    
            mPath = new Path();
            mControlPoints = new ArrayList<>();
            init();
        }
    
        private void init() {
            mControlPoints.clear();
            Random random = new Random();
            for (int i = 0; i < 9; i++) {
                int x = random.nextInt(800) + 200;
                int y = random.nextInt(800) + 200;
                PointF pointF = new PointF(x, y);
                mControlPoints.add(pointF);
            }
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // 控制点和控制点连线
            int size = mControlPoints.size();
            PointF point;
            for (int i = 0; i < size; i++) {
                point = mControlPoints.get(i);
                if (i > 0) {
                    // 控制点连线
                    canvas.drawLine(mControlPoints.get(i - 1).x, mControlPoints.get(i - 1).y, point.x, point.y, mLinePointPaint);
                }
                // 控制点
                canvas.drawCircle(point.x, point.y, 12, mLinePointPaint);
            }
    
            buildBezierPoints();
            canvas.drawPath(mPath, mPaint);
        }
    
    
        /**
         * deCasteljau算法
         * p(i,j) =  (1-t) * p(i-1,j)  +  t * p(i-1,j+1);
         *
         * @param i 阶数   4
         * @param j 控制点 3
         * @param t 时间
         * @return
         */
        private float deCasteljauX(int i, int j, float t) {
            if (i == 1) {
                return (1 - t) * mControlPoints.get(j).x + t * mControlPoints.get(j + 1).x;
            }
            return (1 - t) * deCasteljauX(i - 1, j, t) + t * deCasteljauX(i - 1, j + 1, t);
        }
    
        /**
         * deCasteljau算法
         *
         * @param i 阶数
         * @param j 第几个点
         * @param t 时间
         * @return
         */
        private float deCasteljauY(int i, int j, float t) {
            if (i == 1) {
                return (1 - t) * mControlPoints.get(j).y + t * mControlPoints.get(j + 1).y;
            }
            return (1 - t) * deCasteljauY(i - 1, j, t) + t * deCasteljauY(i - 1, j + 1, t);
        }
    
    
        private ArrayList<PointF> buildBezierPoints() {
            mPath.reset();
    
            ArrayList<PointF> points = new ArrayList<>();
            int order = mControlPoints.size() - 1; //阶数
            //画的密集度,帧
            float delta = 1.0f / 1000;
            for (float t = 0; t <= 1; t += delta) {
                // Bezier点集
                PointF pointF = new PointF(deCasteljauX(order, 0, t), deCasteljauY(order, 0, t));
                points.add(pointF);
                if (points.size() == 1) {
                    mPath.moveTo(points.get(0).x, points.get(0).y);
                } else {
                    mPath.lineTo(pointF.x, pointF.y);
                }
    
            }
            return points;
        }
    
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                init();
                invalidate();
            }
            return true;
        }
    }
    
    

    3,类似QQ消息提醒的气泡效果

    public class DragBubbleView extends View {
    
        /**
         * 气泡默认状态--静止
         */
        private final int BUBBLE_STATE_DEFAULT = 0;
        /**
         * 气泡相连
         */
        private final int BUBBLE_STATE_CONNECT = 1;
        /**
         * 气泡分离
         */
        private final int BUBBLE_STATE_APART = 2;
        /**
         * 气泡消失
         */
        private final int BUBBLE_STATE_DISMISS = 3;
    
        /**
         * 气泡半径
         */
        private float mBubbleRadius;
        /**
         * 气泡颜色
         */
        private int mBubbleColor;
        /**
         * 气泡消息文字
         */
        private String mTextStr;
        /**
         * 气泡消息文字颜色
         */
        private int mTextColor;
        /**
         * 气泡消息文字大小
         */
        private float mTextSize;
        /**
         * 不动气泡的半径
         */
        private float mBubFixedRadius;
        /**
         * 可动气泡的半径
         */
        private float mBubMovableRadius;
        /**
         * 不动气泡的圆心
         */
        private PointF mBubFixedCenter;
        /**
         * 可动气泡的圆心
         */
        private PointF mBubMovableCenter;
        /**
         * 气泡的画笔
         */
        private Paint mBubblePaint;
        /**
         * 贝塞尔曲线path
         */
        private Path mBezierPath;
    
        private Paint mTextPaint;
    
        //文本绘制区域
        private Rect mTextRect;
    
        private Paint mBurstPaint;
    
        //爆炸绘制区域
        private Rect mBurstRect;
    
        /**
         * 气泡状态标志
         */
        private int mBubbleState = BUBBLE_STATE_DEFAULT;
        /**
         * 两气泡圆心距离
         */
        private float mDist;
        /**
         * 气泡相连状态最大圆心距离
         */
        private float mMaxDist;
        /**
         * 手指触摸偏移量
         */
        private final float MOVE_OFFSET;
    
        /**
         * 气泡爆炸的bitmap数组
         */
        private Bitmap[] mBurstBitmapsArray;
        /**
         * 是否在执行气泡爆炸动画
         */
        private boolean mIsBurstAnimStart = false;
    
        /**
         * 当前气泡爆炸图片index
         */
        private int mCurDrawableIndex;
    
        /**
         * 气泡爆炸的图片id数组
         */
        private int[] mBurstDrawablesArray = {R.mipmap.burst_1, R.mipmap.burst_2, R.mipmap.burst_3, R.mipmap.burst_4, R.mipmap.burst_5};
    
        public DragBubbleView(Context context) {
            this(context, null);
        }
    
        public DragBubbleView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public DragBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DragBubbleView, defStyleAttr, 0);
            mBubbleRadius = array.getDimension(R.styleable.DragBubbleView_bubble_radius, mBubbleRadius);
            mBubbleColor = array.getColor(R.styleable.DragBubbleView_bubble_color, Color.RED);
            mTextStr = array.getString(R.styleable.DragBubbleView_bubble_text);
            mTextSize = array.getDimension(R.styleable.DragBubbleView_bubble_textSize, mTextSize);
            mTextColor = array.getColor(R.styleable.DragBubbleView_bubble_textColor, Color.WHITE);
            array.recycle();
    
            //两个圆半径大小一致
            mBubFixedRadius = mBubbleRadius;
            mBubMovableRadius = mBubFixedRadius;
            mMaxDist = 8 * mBubbleRadius;
    
            MOVE_OFFSET = mMaxDist / 4;
    
            //抗锯齿
            mBubblePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBubblePaint.setColor(mBubbleColor);
            mBubblePaint.setStyle(Paint.Style.FILL);
            mBezierPath = new Path();
    
            //文本画笔
            mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mTextPaint.setColor(mTextColor);
            mTextPaint.setTextSize(mTextSize);
            mTextRect = new Rect();
    
            //爆炸画笔
            mBurstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBurstPaint.setFilterBitmap(true);
            mBurstRect = new Rect();
            mBurstBitmapsArray = new Bitmap[mBurstDrawablesArray.length];
            for (int i = 0; i < mBurstDrawablesArray.length; i++) {
                //将气泡爆炸的drawable转为bitmap
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mBurstDrawablesArray[i]);
                mBurstBitmapsArray[i] = bitmap;
            }
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            //不动气泡圆心
            if (mBubFixedCenter == null){
                mBubFixedCenter = new PointF(w/2,h/2);
            }else{
                mBubFixedCenter.set(w/2,h/2);
            }
    
            //可动气泡圆心
            if (mBubMovableCenter == null){
                mBubMovableCenter = new PointF(w/2,h/2);
            }else{
                mBubMovableCenter.set(w/2,h/2);
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            if (mBubbleState == BUBBLE_STATE_CONNECT){
                //绘制不动气泡
                canvas.drawCircle(mBubFixedCenter.x, mBubFixedCenter.y, mBubFixedRadius, mBubblePaint);
                //绘制贝塞尔曲线
                //控制点坐标
                int iAnchorX = (int) ((mBubFixedCenter.x + mBubMovableCenter.x) / 2);
                int iAnchorY = (int) ((mBubFixedCenter.y + mBubMovableCenter.y) / 2);
    
                float sinTheta = (mBubMovableCenter.y - mBubFixedCenter.y) / mDist;
                float cosTheta = (mBubMovableCenter.x - mBubFixedCenter.x) / mDist;
    
                //B
                float iBubMovableStartX = mBubMovableCenter.x + sinTheta * mBubMovableRadius;
                float iBubMovableStartY = mBubMovableCenter.y - cosTheta * mBubMovableRadius;
    
                //A
                float iBubFixedEndX = mBubFixedCenter.x + mBubFixedRadius * sinTheta;
                float iBubFixedEndY = mBubFixedCenter.y - mBubFixedRadius * cosTheta;
    
                //D
                float iBubFixedStartX = mBubFixedCenter.x - mBubFixedRadius * sinTheta;
                float iBubFixedStartY = mBubFixedCenter.y + mBubFixedRadius * cosTheta;
                //C
                float iBubMovableEndX = mBubMovableCenter.x - mBubMovableRadius * sinTheta;
                float iBubMovableEndY = mBubMovableCenter.y + mBubMovableRadius * cosTheta;
    
                mBezierPath.reset();
                mBezierPath.moveTo(iBubFixedStartX, iBubFixedStartY);
                mBezierPath.quadTo(iAnchorX, iAnchorY, iBubMovableEndX, iBubMovableEndY);
                //移动到B点
                mBezierPath.lineTo(iBubMovableStartX, iBubMovableStartY);
                mBezierPath.quadTo(iAnchorX, iAnchorY,iBubFixedEndX,iBubFixedEndY);
                mBezierPath.close();
                canvas.drawPath(mBezierPath, mBubblePaint);
    
            }
    
            if (mBubbleState != BUBBLE_STATE_DISMISS){
                //绘制一个气泡加消息数据
                canvas.drawCircle(mBubMovableCenter.x, mBubMovableCenter.y, mBubMovableRadius, mBubblePaint);
                mTextPaint.getTextBounds(mTextStr, 0, mTextStr.length(), mTextRect);
                canvas.drawText(mTextStr, mBubMovableCenter.x-mTextRect.width() / 2, mBubMovableCenter.y + mTextRect.height()/2, mTextPaint);
            }
    
            if (mBubbleState == BUBBLE_STATE_DISMISS &&mCurDrawableIndex < mBurstBitmapsArray.length){
                mBurstRect.set(
                        (int)(mBubMovableCenter.x - mBubMovableRadius),
                        (int)(mBubMovableCenter.y - mBubMovableRadius),
                        (int)(mBubMovableCenter.x + mBubMovableRadius),
                        (int)(mBubMovableCenter.y + mBubMovableRadius)
                );
                canvas.drawBitmap(mBurstBitmapsArray[mCurDrawableIndex], null, mBurstRect, mBubblePaint);
            }
    
            //1,静止状态,一个气泡加消息数据
    
            //2, 连接状态,一个气泡加消息数据,贝塞尔曲线,本身位置上气泡,大小可变化
    
            //3,分离状态,一个气泡加消息数据
    
            //4,消失状态,爆炸效果
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    if (mBubbleState !=BUBBLE_STATE_DISMISS){
                        mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY()-mBubFixedCenter.y);
                        if (mDist < mBubbleRadius + MOVE_OFFSET){ //加上MOVE_OFFSET是为了方便拖拽
                            mBubbleState = BUBBLE_STATE_CONNECT;
                        }else{
                            mBubbleState = BUBBLE_STATE_DEFAULT;
                        }
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mBubbleState != BUBBLE_STATE_DEFAULT){
                        mDist = (float) Math.hypot(event.getX() - mBubFixedCenter.x, event.getY()-mBubFixedCenter.y);
                        mBubMovableCenter.x = event.getX();
                        mBubMovableCenter.y = event.getY();
                        if (mBubbleState == BUBBLE_STATE_CONNECT){
                            if (mDist < mMaxDist - MOVE_OFFSET){//当拖拽的距离在指定范围内,那么调整不动气泡的半径
                                mBubFixedRadius = mBubbleRadius- mDist / 8;
                            }else{
                                mBubbleState = BUBBLE_STATE_APART;//当拖拽的距离超过指定范围,那么改成分离状态
                            }
                        }
                        invalidate();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (mBubbleState == BUBBLE_STATE_CONNECT){
                        //橡皮筋动画效果
                        startBubbleRestAnim();
                    }else if (mBubbleState == BUBBLE_STATE_APART){
                        if (mDist < 2* mBubbleRadius){
                            startBubbleRestAnim();
                        }else{
                            //爆炸效果
                            startBubbleBurstAnim();
                        }
    
                    }
                    break;
            }
    
            return true;
        }
    
        private void startBubbleBurstAnim() {
            mBubbleState = BUBBLE_STATE_DISMISS;
            ValueAnimator anim = ValueAnimator.ofInt(0,mBurstBitmapsArray.length);
            anim.setDuration(500);
            anim.setInterpolator(new LinearInterpolator());
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurDrawableIndex = (int) animation.getAnimatedValue();
                    invalidate();
                }
            });
            anim.start();
        }
    
        private void startBubbleRestAnim() {
            ValueAnimator anim = ValueAnimator.ofObject(new PointFEvaluator(),
                    new PointF(mBubMovableCenter.x, mBubMovableCenter.y),
                    new PointF(mBubFixedCenter.x, mBubFixedCenter.y));
            anim.setDuration(200);
            anim.setInterpolator(new OvershootInterpolator(5f));
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mBubMovableCenter = (PointF) animation.getAnimatedValue();
                    invalidate();
                }
            });
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mBubbleState = BUBBLE_STATE_DEFAULT;
                }
            });
            anim.start();
        }
    }
    

    配上布局

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="重置"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    
        <com.cdzp.zrwtest.curvedemo.DragBubbleView
            android:id="@+id/bubbleView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:bubble_color="#ff0000"
            app:bubble_radius="12dp"
            app:bubble_text="12"
            app:bubble_textColor="#ffffff"
            app:bubble_textSize="12dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    相关文章

      网友评论

          本文标题:Paint详解(二)绘制路径以及贝塞尔曲线

          本文链接:https://www.haomeiwen.com/subject/luphvctx.html