【高级UI】【010】使用贝塞尔曲线,绘制经过若干固定点的平滑曲线

前言

前篇我们已经见过贝塞尔曲线的功能,原理,公式,和推导过程

这节课我们来讲实际应用:如何通过贝塞尔曲线,绘制经过若干固定点的平滑曲线

首先,我们要明确需求,我们的目标是画一条平滑曲线,不是求一条贝塞尔曲线,经过所有点

可以是通过多条贝塞尔曲线来拼接成一条平滑曲线

然后,经过若干点的贝塞尔曲线,可能是有无数条的,并不是唯一解

所以我们的目标,是找一个简单的方案,能画出平滑曲线就行

并不是由曲线上的点,求一个万能的曲线公式,千万别弄错了

方案

  • 每两个点之间画一条贝塞尔曲线,然后所有曲线连接成一条平滑曲线(之所以这么设计,是因为这个想法最简单)
  • 多条曲线想要连成一条平滑曲线,那么在曲线交点,也就是顶点两侧,曲线的斜率或切线方向,一定得是一致的
  • 我们就用x方向,作为所有顶点处,曲线切线的方向(之所以这么设计,是因为这样最简单)
  • 第一个控制点,一定在起点切线上,最后一个控制点,一定在终点切线上(贝塞尔曲线特性决定的)
  • 选用三阶贝塞尔曲线,在起点和终点切线上,随便选个点作为控制点(之所以这么设计,是因为这样最简单)
  • 让曲线两端,控制点到顶点的距离相等(这样曲线就比较匀称,不会出现很突兀的情况)
  • 画出所有顶点之间的贝塞尔曲线,得到最终的平滑曲线

流程示意图

确定切线和控制点
【高级UI】【010】使用贝塞尔曲线,绘制经过若干固定点的平滑曲线
画三阶贝塞尔曲线
【高级UI】【010】使用贝塞尔曲线,绘制经过若干固定点的平滑曲线
最终结果
【高级UI】【010】使用贝塞尔曲线,绘制经过若干固定点的平滑曲线
方案说明

  • 由于所有切线方向,都是沿x方向,看起来会像是沿x方向的波形图,所以实际应用中,最好是根据数据实际走向来确定切线方向
  • 控制点离顶点越近,曲线越接趋于直线
  • 控制点离顶点越远,曲线越接趋于波形
  • 两端控制点离顶点距离,相差越小,曲线越对称(我们是完全相等,则完全对称)

Android平滑曲线绘制代码


	package com.android.architecture;
	
	import android.content.Context;
	import android.graphics.Canvas;
	import android.graphics.DashPathEffect;
	import android.graphics.Paint;
	import android.graphics.Path;
	import android.util.AttributeSet;
	import android.view.View;
	
	import com.easing.commons.android.value.color.Colors;
	import com.easing.commons.android.value.measure.FPos;
	
	import java.util.ArrayList;
	import java.util.List;
	
	@SuppressWarnings("all")
	public class BezierCurve extends View {
	
	    final List<FPos> posList = new ArrayList();
	
	    final List<FPos> controlPosList = new ArrayList();
	
	    final Paint posPaint = new Paint();
	    final Paint linePaint = new Paint();
	    final Paint tangentPaint = new Paint();
	    final Paint bezierPaint = new Paint();
	
	    public BezierCurve(Context context) {
	        this(context, null);
	    }
	
	    public BezierCurve(Context context, AttributeSet attributeSet) {
	        super(context, attributeSet);
	        init(context, attributeSet);
	    }
	
	    protected void init(Context context, AttributeSet attributeSet) {
	        //创建初始点
	        posList.add(new FPos(100, 100));
	        posList.add(new FPos(300, 200));
	        posList.add(new FPos(560, 300));
	        posList.add(new FPos(720, 350));
	        posList.add(new FPos(900, 600));
	        posList.add(new FPos(1200, 650));
	        posList.add(new FPos(1300, 700));
	        //初始化点画笔
	        posPaint.setColor(Colors.RED);
	        posPaint.setStrokeWidth(10);
	        posPaint.setStyle(Paint.Style.STROKE);
	        posPaint.setStrokeCap(Paint.Cap.ROUND);
	        //初始化折线画笔
	        linePaint.setColor(Colors.LIGHT_BLUE);
	        linePaint.setStrokeWidth(2);
	        linePaint.setStyle(Paint.Style.STROKE);
	        //初始化切线画笔
	        tangentPaint.setColor(Colors.LIGHT_GREY);
	        tangentPaint.setStrokeWidth(4);
	        tangentPaint.setStyle(Paint.Style.STROKE);
	        tangentPaint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0));
	        //初始化曲线画笔
	        bezierPaint.setColor(Colors.ORANGE);
	        bezierPaint.setStrokeWidth(2);
	        bezierPaint.setStyle(Paint.Style.STROKE);
	        //计算控制点
	        final float ratio = 0.3F;
	        for (int i = 0; i < posList.size() - 1; i++) {
	            FPos p1 = posList.get(i);
	            FPos p2 = posList.get(i + 1);
	            FPos cp1 = new FPos();
	            cp1.x = p1.x + ratio * (p2.x - p1.x);
	            cp1.y = p1.y;
	            FPos cp2 = new FPos();
	            cp2.x = p1.x + (1 - ratio) * (p2.x - p1.x);
	            cp2.y = p2.y;
	            controlPosList.add(cp1);
	            controlPosList.add(cp2);
	        }
	    }
	
	    @Override
	    protected void onDraw(Canvas canvas) {
	        //画点
	        for (int i = 0; i < posList.size(); i++) {
	            FPos p = posList.get(i);
	            canvas.drawPoint(p.x, p.y, posPaint);
	        }
	        //画折线
	        {
	            Path path = new Path();
	            FPos p0 = posList.get(0);
	            path.moveTo(p0.x, p0.y);
	            for (int i = 0; i < posList.size(); i++) {
	                FPos p = posList.get(i);
	                path.lineTo(p.x, p.y);
	            }
	            canvas.drawPath(path, linePaint);
	        }
	        //画切线
	        {
	            FPos p1 = posList.get(0);
	            FPos p2 = controlPosList.get(0);
	            canvas.drawLine(p1.x, p1.y, p2.x, p2.y, tangentPaint);
	        }
	        for (int i = 1; i < controlPosList.size() - 1; i += 2) {
	            FPos p1 = controlPosList.get(i);
	            FPos p2 = controlPosList.get(i + 1);
	            canvas.drawLine(p1.x, p1.y, p2.x, p2.y, tangentPaint);
	        }
	        {
	            FPos p1 = controlPosList.get(controlPosList.size() - 1);
	            FPos p2 = posList.get(posList.size() - 1);
	            canvas.drawLine(p1.x, p1.y, p2.x, p2.y, tangentPaint);
	        }
	        //画曲线
	        for (int i = 0; i < posList.size() - 1; i++) {
	            FPos p1 = posList.get(i);
	            FPos p2 = posList.get(i + 1);
	            FPos cp1 = controlPosList.get(i * 2);
	            FPos cp2 = controlPosList.get(i * 2 + 1);
	            Path path = new Path();
	            path.moveTo(p1.x, p1.y);
	            path.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);
	            canvas.drawPath(path, bezierPaint);
	        }
	    }
	}

源码下载

Android过若干点绘制平滑曲线.zip

上一篇:const in cpp


下一篇:Android Log图文详解