自定义View-2Path贝塞尔曲线

Path贝塞尔曲线

贝塞尔曲线用途广泛

QQ消息小红点拖拽效果
炫酷的下拉控件
翻书效果

类型 作用
数据点 确定曲线的起始和结束为止
控制点 确定曲线的弯曲程度

一阶曲线是没有控制点的,只有两个数据点连城一个线段。即lineTo()

二阶曲线对应方法为quadTo()

二阶曲线动效演示

AD/AB = BE/BC

代码:

public class Bazier2View extends View{

    private PointF start,end,control;
    private int centerX,centerY;
    private Paint mPaint = new Paint();
    public Bazier2View(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStyle(Paint.Style.STROKE);
        start = new PointF(0,0);
        end = new PointF(0,0);
        control = new PointF();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;

        start.x = centerX - 100;
        start.y = centerY;
        end.x = centerX + 100;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY - 100;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        return super.onTouchEvent(event);
        control.x = event.getX();
        control.y = event.getY();
        invalidate();
        return true;
    }

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

        mPaint.setStrokeWidth(12);
        mPaint.setColor(Color.BLACK);
        canvas.drawPoint(start.x,start.y,mPaint);
        canvas.drawPoint(end.x,end.y,mPaint);
        canvas.drawPoint(control.x,control.y,mPaint);

        mPaint.setStrokeWidth(2);
        mPaint.setColor(Color.GRAY);
        //绘制辅助线
        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
        canvas.drawLine(end.x,end.y,control.x,control.y,mPaint);
        mPaint.setStrokeWidth(4);
        mPaint.setColor(Color.RED);
        Path path = new Path();
        path.moveTo(start.x,start.y);
        path.quadTo(control.x,control.y,end.x,end.y);
        canvas.drawPath(path,mPaint);
    }
}

结果:


quadTo()

三阶曲线对应的方法为cubicTo()。由两个数据点A和D,两个控制点B和C



代码:

public class Bazier3View extends View {
    private static final String TAG = "Bazier3View";
    private Paint mPaint;
    private boolean mode;
    private PointF start,end,control1,control2;
    public Bazier3View(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        start = new PointF();
        end = new PointF();
        control1 = new PointF();
        control2 = new PointF();
    }

    public void setMode(boolean mode){
        this.mode = mode;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.i(TAG, "onSizeChanged: ");
        int centerX = w/2;
        int centerY = h/2;
        start.x = centerX-100;
        start.y = centerY;
        end.x = centerX+100;
        end.y = centerY;
        control1.x = centerX-110;
        control1.y = centerY-100;
        control2.x = centerX+110;
        control2.y = centerY-100;

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mode){
            control1.x = event.getX();
            control1.y = event.getY();
        }else{
            control2.x = event.getX();
            control2.y = event.getY();
        }
        invalidate();
        return true;
//        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //因为实际布局中,此view被包含在ScrollView中,
        // 调用此方法告知父View,由我本身处理touch事件
        //可对比二阶贝塞尔View(Bazier2View)在ScrollView中的效果
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(12);
        canvas.drawPoint(start.x,start.y,mPaint);
        canvas.drawPoint(end.x,end.y,mPaint);
        canvas.drawPoint(control1.x,control1.y,mPaint);
        canvas.drawPoint(control2.x,control2.y,mPaint);

        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x,start.y,control1.x,control1.y,mPaint);
        canvas.drawLine(control1.x,control1.y,control2.x,control2.y,mPaint);
        canvas.drawLine(control2.x,control2.y,end.x,end.y,mPaint);

        mPaint.setColor(Color.RED);
        Path path = new Path();
        path.moveTo(start.x,start.y);
        path.cubicTo(control1.x,control1.y,control2.x,control2.y,end.x,end.y);
        canvas.drawPath(path,mPaint);
    }
}

结果:


实例

贝塞尔曲线的优点是可是实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化


核心难点:
1.如何得到数据点和控制点的位置?
分析:关于使用绘制圆形的数据点与控制点早就已经有人详细的计算好了,可以参考下图
2.如何达到渐变效果?
分析:渐变其实就是每次对数据点和控制点稍微移动一点,然后重绘界面,在短时间多次的调整数据点与控制点,使其逐渐接近目标值,通过不断的重绘界面达到一种渐变的效果。过程可以参照下图动态效果:


就是所需要的数值c约等于0.551915024494f

图中假设1为圆的半径,那么对应的M值就应该是半径乘以0.551915024494f

代码:

public class BazierCircleView extends View {
    private static final String TAG = "BazierCircleView";
    private static final float C = 0.551915024494f;
    private Paint mPaint = new Paint();
    private PointF p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11;
    private int centerX,centerY;
    private int radius = 100;
    private float M = radius*C;
    private float mCurrent = 0;
    private float mCount = 10;
    public BazierCircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStrokeWidth(6);
        mPaint.setStyle(Paint.Style.STROKE);
        p0 = new PointF(0,radius);
        p1 = new PointF(M,radius);
        p2 = new PointF(radius,M);
        p3 = new PointF(radius,0);
        p4 = new PointF(radius,-M);
        p5 = new PointF(M,-radius);
        p6 = new PointF(0,-radius);
        p7 = new PointF(-M,-radius);
        p8 = new PointF(-radius,-M);
        p9 = new PointF(-radius,0);
        p10 = new PointF(-radius,M);
        p11 = new PointF(-M,radius);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w/2;
        centerY = h/2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);//设置中心点

        drawPoint(canvas);
        drawLine(canvas);
        drawCoordinateSystem(canvas);

        mPaint.setColor(Color.BLUE);
        Path path = new Path();
        path.moveTo(p0.x,p0.y);
        path.cubicTo(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y);
        path.cubicTo(p4.x,p4.y,p5.x,p5.y,p6.x,p6.y);
        path.cubicTo(p7.x,p7.y,p8.x,p8.y,p9.x,p9.y);
        path.cubicTo(p10.x,p10.y,p11.x,p11.y,p0.x,p0.y);
        canvas.drawPath(path,mPaint);

        mCurrent++;
        if (mCurrent < mCount){
            Log.i(TAG, "onDraw: ");
            p6.y += 5;
            p11.y -=5;
            p1.y -=5;
            p10.x += 2;
            p2.x -= 2;
            postInvalidateDelayed(20);
        }
    }

    private void drawPoint(Canvas canvas){
        mPaint.setStrokeWidth(6);
        mPaint.setColor(Color.RED);
        canvas.drawPoint(p0.x,p0.y,mPaint);
        canvas.drawPoint(p1.x,p1.y,mPaint);
        canvas.drawPoint(p2.x,p2.y,mPaint);
        canvas.drawPoint(p3.x,p3.y,mPaint);
        canvas.drawPoint(p4.x,p4.y,mPaint);
        canvas.drawPoint(p5.x,p5.y,mPaint);
        canvas.drawPoint(p6.x,p6.y,mPaint);
        canvas.drawPoint(p7.x,p7.y,mPaint);
        canvas.drawPoint(p8.x,p8.y,mPaint);
        canvas.drawPoint(p9.x,p9.y,mPaint);
        canvas.drawPoint(p10.x,p10.y,mPaint);
        canvas.drawPoint(p11.x,p11.y,mPaint);
    }

    private void drawLine(Canvas canvas){
        mPaint.setStrokeWidth(2);
        mPaint.setColor(Color.CYAN);
        canvas.drawLine(p11.x,p11.y,p1.x,p1.y,mPaint);
        canvas.drawLine(p2.x,p2.y,p4.x,p4.y,mPaint);
        canvas.drawLine(p5.x,p5.y,p7.x,p7.y,mPaint);
        canvas.drawLine(p8.x,p8.y,p10.x,p10.y,mPaint);
    }

    private void drawCoordinateSystem(Canvas canvas){
        mPaint.setColor(Color.RED);
        canvas.drawLine(0,-getHeight(),0,getHeight(),mPaint);
        canvas.drawLine(-getWidth(),0,getWidth(),0,mPaint);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容