canvas 绘制曲线是一个比较容易实现的逻辑,但是对于签名,或者手写板,大屏会议机等设备原生的Canvas.drawPath(); 要求线条曲率完美,直接使用drawPath显然不能满足需求,这方面的资源网上也不是很多,这里有一点优化心得,记录下来,分享给有需要的伙伴。这里做一个简单的介绍,对于后续细节,需要自己优化。

关于线条优化,需要深入优化线条,可以私信探讨一下

效果图如下

1:蓝色的是原生的drawPath绘制的线条,仔细看的话,里面有很多不圆滑的因素

2:很多转角比较生硬,处理不过好,原因就是这里的点比较密集,抖动较大

3:红色个的线条是优化后的重绘轨迹,明显要圆滑许多。

原理如下

1:直接生成Path,但是不绘制

2:对原有的path进行测量,重新生成等距的point

3:  便利新增点

4:拿到点,进行重新绘制

好了,直接上代码,一目了然,原理是比较简单的,但是自己去琢磨得话,还是挺费时间的。

常规绘制path代码如下

1:在touch_move 得时候调用path.quadTo 方法。绘制二阶贝塞尔曲线,上图,蓝色得线。

2:touch_move 里面有一个getHistroySize 得方法,因为绘制在主线程,线程阻塞,系统touch点无法全部上报,所以我们拿到历史的点,也加入到path里面,这样绘制得线条和手指触摸轨迹贴合比较近

  @Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPaint.setColor(Color.BLUE);canvas.drawPath(mPath, mPaint);}int actionStatues = MotionEvent.ACTION_UP;@Overridepublic boolean onTouchEvent(MotionEvent event) {int touchX = (int) event.getX();int touchY = (int) event.getY();actionStatues = event.getAction();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mPath.reset();mPath.moveTo(touchX, touchY);preXDefault = touchX;preYDefault = touchY;break;case MotionEvent.ACTION_MOVE:int hisSize = event.getHistorySize();  //历史点for (int hisIndex = 0; hisIndex < hisSize; hisIndex++) {float tempx = event.getHistoricalX(0, hisIndex);float tempy = event.getHistoricalY(0, hisIndex);addPointTpPath(tempx, tempy, event);}addPointTpPath(touchX, touchY, event);invalidate();break;case MotionEvent.ACTION_UP:addPointTpPath(touchX, touchY, event);invalidate();break;}return true;}private float preXDefault = 0.0f; //原始轨迹private float preYDefault = 0.0f; //原始轨迹private void addPointTpPath(float touchX, float touchY, MotionEvent event) {mPath.quadTo(preXDefault, preYDefault, (touchX + preXDefault) / 2, (touchY + preYDefault) / 2);preXDefault = touchX;preYDefault = touchY;}

3:接下来就开始处理测量重绘步骤,理论上,mPath 只是我们测量得path,不需要把它绘制出来,但是我们需要比对效果,暂时绘制出来

4:先给一个测量生成新得touch点得工具类方法

package com.wst.pens.ui.util;import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.util.Log;import com.wst.pens.ui.MyLog;
import com.ydw.solution2.PathPoint;import java.io.File;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;public class BuddleUtil {/*** @param vertexPointX -- 角度对应顶点X坐标值* @param vertexPointY -- 角度对应顶点Y坐标值* @param point0X* @param point0Y* @param point1X* @param point1Y* @return*/private static double getDegree(double vertexPointX, double vertexPointY, double point0X, double point0Y, double point1X, double point1Y) {//向量的点乘double vector = (point0X - vertexPointX) * (point1X - vertexPointX) + (point0Y - vertexPointY) * (point1Y - vertexPointY);//向量的模乘double sqrt = Math.sqrt((Math.abs((point0X - vertexPointX) * (point0X - vertexPointX)) + Math.abs((point0Y - vertexPointY) * (point0Y - vertexPointY)))* (Math.abs((point1X - vertexPointX) * (point1X - vertexPointX)) + Math.abs((point1Y - vertexPointY) * (point1Y - vertexPointY))));//反余弦计算弧度double radian = Math.acos(vector / sqrt);Log.i("====角度::", "==>" + radian);//弧度转角度制return (180 * radian / Math.PI);}private static float currentScal = 1.0f;/**** 上传截图排序* @param fileList* @return*/public static File[] getFileListBuddle(File[] fileList) {File[] fileListCache = fileList;if (fileListCache == null || fileListCache.length < 1) {return null;}Arrays.sort(fileList, new Comparator<File>() {@Overridepublic int compare(File f1, File f2) {return f1.compareTo(f2);}});return fileListCache;}private static void logInfoBuble(String s) {
//        MyLog.bifeng(s);}private static DecimalFormat decimalFormat;public static float floatToFloat(float nums) {if (decimalFormat == null) {decimalFormat = new DecimalFormat(".0");}float backNum = 1.0f;try {backNum = Float.valueOf(decimalFormat.format(new BigDecimal(nums)));} catch (Exception e) {e.printStackTrace();}return backNum;}public static double doubleToTwo(double dou) {double backInfo = (double) Math.round(dou * 100) / 100;return backInfo;}public static float floatToFloatTwo(float nums) {if (decimalFormat == null) {decimalFormat = new DecimalFormat(".00");}return Float.valueOf(decimalFormat.format(new BigDecimal(nums)));}/**** 计算两点之间的速度* @param x1* @param y1* @param x2* @param y2* @param transTime* @return*/public static double getLineSpeed(float x1, float y1, float x2, float y2, long transTime) {double distanceCache = getLineDistance(x1, y1, x2, y2);double speed = doubleToTwo(distanceCache * 10.0 / transTime * 1.0);return speed;}/**** 获取两个点的长度*/public static double getLineDistance(float x1, float y1, float x2, float y2) {double distanceCache = doubleToTwo(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)));return distanceCache;}private Path getNewPath(Path pathForm) {Path tempPath = new Path();PathMeasure pathMes = new PathMeasure(pathForm, false);float pointArrPoint[] = {0f, 0f};float spValue = 6.0f;float lenght = pathMes.getLength();int n = (int) (lenght / spValue);float curr = 0f;pathMes.getPosTan(curr, pointArrPoint, null);tempPath.moveTo(pointArrPoint[0], pointArrPoint[1]);float temx = pointArrPoint[0];float temy = pointArrPoint[1];for (int i = 0; i < n; i++) {curr = i * spValue;pathMes.getPosTan(curr, pointArrPoint, null);tempPath.quadTo(temx,temy,(temx + pointArrPoint[0]) / 2,(temy + pointArrPoint[1]) / 2);temx = pointArrPoint[0];temy = pointArrPoint[1];}MyLog.paintView("touchUp   线条处理  lenght2 " + lenght);pathMes.getPosTan(lenght, pointArrPoint, null);tempPath.quadTo(temx,temy,pointArrPoint[0],pointArrPoint[1]);return tempPath;}/****** @param pathForm* @param jujleSize* 缩放渐变参数* @return*/public static List<Point> getNewPathList(Path pathForm, float jujleSize) {List<Point> pointList = new ArrayList<>();PathMeasure pathMes = new PathMeasure(pathForm, false);float pointArrPoint[] = {0f, 0f};float spValue = jujleSize;float lenght = pathMes.getLength();int n = (int) (lenght / spValue);float curr = 0f;
//        if (touchUp) {
//            pathMes.getPosTan(curr, pointArrPoint, null);
//            pointList.add(new Point((int) pointArrPoint[0], (int) pointArrPoint[1]));
//        }for (int i = 0; i < n; i++) {curr = i * spValue;pathMes.getPosTan(curr, pointArrPoint, null);pointList.add(new Point((int) pointArrPoint[0], (int) pointArrPoint[1]));}
//        MyLog.paintView("touchUp   线条处理  lenght2 " + lenght);
//        if (touchUp) {
//            pathMes.getPosTan(lenght, pointArrPoint, null);
//            pointList.add(new Point((int) pointArrPoint[0], (int) pointArrPoint[1]));
//        }return pointList;}}

5:下面事对新产生得点,做追加处理,然后重新绘制操作,

6:设定两个点间距为40个像素,这个参数根据自己屏幕圆滑程度,修改数值

7:里面有一个currentPath ,这个标识当前移动轨迹得一小段效果。longPath 才是我们最终想要得效果。

int listSizeNum = 0;int lastCX = 0;int lastCY = 0;float lastControlX = 0;float lastControlY = 0;List<Point> pointListJujle = new ArrayList<>();  //用来计算最后合成得点private float preXDefault = 0.0f; //原始轨迹private float preYDefault = 0.0f; //原始轨迹private void addPointTpPath(float touchX, float touchY, MotionEvent event) {mPath.quadTo(preXDefault, preYDefault, (touchX + preXDefault) / 2, (touchY + preYDefault) / 2);preXDefault = touchX;preYDefault = touchY;float distanceJujleSize = 40.0f;List<Point> newPath = BuddleUtil.getNewPathList(mPath, distanceJujleSize);if (newPath != null && newPath.size() > 0) {int addNum = newPath.size() - listSizeNum;if (addNum > 0) {MyLog.paintView("===添加点到集合==0000000000000=" + newPath.size() + " / " + addNum);for (int i = newPath.size() - addNum; i < newPath.size(); i++) {Point pointadd = newPath.get(i);pointListJujle.add(pointadd);MyLog.paintView("===添加点到集合===" + pointadd);float nextControlX = (lastCX + pointadd.x) / 2.0f;float nextControlY = (lastCY + pointadd.y) / 2.0f;currentPath.reset();currentPath.moveTo(lastControlX, lastControlY);currentPath.quadTo(lastCX, lastCY, nextControlX, nextControlY);lastCX = pointadd.x;lastCY = pointadd.y;lastControlX = nextControlX;lastControlY = nextControlY;}}listSizeNum = newPath.size();}}

8:抬手得时候,记得把最后一个点和抬手得点做一个计算,不然就会出现绘制少了一段效果,

9:绘制得时候,下面代码只是参考,可以直接绘制出最终效果,这里demo只显示最后效果

绘制代码如下,

  if (actionStatues == MotionEvent.ACTION_UP) {mPaint.setColor(Color.RED);int lastXCanvas = 0;int lastYCanvas = 0;for (int i = 0; i < pointListJujle.size(); i++) {Point point = pointListJujle.get(i);if (i == 0) {currentPathLong.reset();currentPathLong.moveTo(point.x, point.y);} else {currentPathLong.quadTo(lastXCanvas, lastYCanvas, (lastXCanvas + point.x) / 2.0f, (lastYCanvas + point.y) / 2.0f);}lastXCanvas = point.x;lastYCanvas = point.y;}canvas.drawPath(currentPathLong, mPaint);}

这样就可以绘制出非常圆滑得线条了,这只是一种线条优化得思路,下面是整体源码

package com.wst.pens.ui;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;import androidx.annotation.Nullable;import com.wst.pens.ui.util.BuddleUtil;import java.util.ArrayList;
import java.util.List;public class PaintSplitScreenC extends View {public PaintSplitScreenC(Context context) {this(context, null);}public PaintSplitScreenC(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public PaintSplitScreenC(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaint();}Path mPath;Paint mPaint;Path currentPath;Path currentPathLong;private float PEN_WIDTH_SIZE = 5.0f;private void initPaint() {mPath = new Path();currentPath = new Path();currentPathLong = new Path();mPaint = new Paint();mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeCap(Paint.Cap.ROUND);mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘mPaint.setStrokeWidth(PEN_WIDTH_SIZE);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mPaint.setColor(Color.BLUE);canvas.drawPath(mPath, mPaint);mPaint.setColor(Color.GRAY);canvas.drawPath(currentPath, mPaint);if (actionStatues == MotionEvent.ACTION_UP) {mPaint.setColor(Color.RED);int lastXCanvas = 0;int lastYCanvas = 0;for (int i = 0; i < pointListJujle.size(); i++) {Point point = pointListJujle.get(i);if (i == 0) {currentPathLong.reset();currentPathLong.moveTo(point.x, point.y);} else {currentPathLong.quadTo(lastXCanvas, lastYCanvas, (lastXCanvas + point.x) / 2.0f, (lastYCanvas + point.y) / 2.0f);}lastXCanvas = point.x;lastYCanvas = point.y;}canvas.drawPath(currentPathLong, mPaint);}}int actionStatues = MotionEvent.ACTION_UP;@Overridepublic boolean onTouchEvent(MotionEvent event) {int touchX = (int) event.getX();int touchY = (int) event.getY();actionStatues = event.getAction();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pointListJujle.clear();pointListJujle.add(new Point(touchX, touchY));mPath.reset();mPath.moveTo(touchX, touchY);currentPath.reset();currentPath.moveTo(touchX, touchY);listSizeNum = 0;preXDefault = touchX;preYDefault = touchY;lastCX = touchX;lastCY = touchY;lastControlX = touchX;lastControlY = touchY;break;case MotionEvent.ACTION_MOVE:int hisSize = event.getHistorySize();  //历史点int pointerCount = event.getPointerCount();for (int hisIndex = 0; hisIndex < hisSize; hisIndex++) {for (int finger = 0; finger < pointerCount; finger++) {float tempx = event.getHistoricalX(finger, hisIndex);float tempy = event.getHistoricalY(finger, hisIndex);addPointTpPath(tempx, tempy, event);}}addPointTpPath(touchX, touchY, event);invalidate();break;case MotionEvent.ACTION_UP:addPointTpPath(touchX, touchY, event);invalidate();break;}return true;}int listSizeNum = 0;int lastCX = 0;int lastCY = 0;float lastControlX = 0;float lastControlY = 0;List<Point> pointListJujle = new ArrayList<>();  //用来计算最后合成得点private float preXDefault = 0.0f; //原始轨迹private float preYDefault = 0.0f; //原始轨迹private void addPointTpPath(float touchX, float touchY, MotionEvent event) {mPath.quadTo(preXDefault, preYDefault, (touchX + preXDefault) / 2, (touchY + preYDefault) / 2);preXDefault = touchX;preYDefault = touchY;float distanceJujleSize = 40.0f;List<Point> newPath = BuddleUtil.getNewPathList(mPath, distanceJujleSize);if (newPath != null && newPath.size() > 0) {int addNum = newPath.size() - listSizeNum;if (addNum > 0) {MyLog.paintView("===添加点到集合==0000000000000=" + newPath.size() + " / " + addNum);for (int i = newPath.size() - addNum; i < newPath.size(); i++) {Point pointadd = newPath.get(i);pointListJujle.add(pointadd);MyLog.paintView("===添加点到集合===" + pointadd);float nextControlX = (lastCX + pointadd.x) / 2.0f;float nextControlY = (lastCY + pointadd.y) / 2.0f;currentPath.reset();currentPath.moveTo(lastControlX, lastControlY);currentPath.quadTo(lastCX, lastCY, nextControlX, nextControlY);lastCX = pointadd.x;lastCY = pointadd.y;lastControlX = nextControlX;lastControlY = nextControlY;}}listSizeNum = newPath.size();}}
}

Canvas绘制圆滑曲线相关推荐

  1. html5贝塞尔曲线,用canvas绘制一个曲线动画——深入理解贝塞尔曲线

    前言 在前端开发中,贝赛尔曲线无处不在: 它可以用来绘制曲线,在svg和canvas中,原生提供的曲线绘制都是使用贝赛尔曲线 它也可以用来描述一个缓动算法,设置css的transition-timin ...

  2. html5添加随机率,HTML5 canvas  绘制随机曲线 并实现放大功能

    正在开发一个无线监控系统,要显示频率波形图,所以最近一段时间再研究HTML5 canvas 标签,本例实现了根基传经来的数据绘制曲线,并对整个图实行局部放大功能. js代码(canvas_06.js) ...

  3. html贝塞尔曲线在线,使用canvas绘制贝塞尔曲线

    1.二次贝塞尔曲线 quadraticCurveTo(cpx,cpy,x,y) //cpx,cpy表示控制点的坐标,x,y表示终点坐标: 数学公式表示如下: 二次方贝兹曲线的路径由给定点P0.P1.P ...

  4. Canvas学习:封装Canvas绘制基本图形API

    Canvas学习:封装Canvas绘制基本图形API Canvas Canvas学习 从前面的文章中我们了解到,通过Canvas中的CanvasRenderingContext2D对象中的属性和方法, ...

  5. canvas绘制飞线效果

    在我们做的可视化大屏项目中,经常会遇到飞线的效果. 在我们的大屏编辑器中,可以通过拖拽+配置参数的方式很快就能够实现.下面是我们使用大屏编辑器实现的一个项目效果: 中间地图就有飞线的效果. 抛开编辑器 ...

  6. 用html5的canvas画布绘制贝塞尔曲线

    查看效果:http://keleyi.com/keleyi/phtml/html5/7.htm 完整代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHT ...

  7. html 画动画效果,html5 canvas绘制曲线动画特效

    特效描述:html5 canvas绘制 曲线动画特效. 代码结构 1. HTML代码 Balls Size Speed Delay Go! Presets: Atomic Flower Spiro Y ...

  8. HTML5 Canvas中绘制贝塞尔曲线

    绘制贝塞尔曲线 贝塞尔曲线于1959年,由法国物理学家与数学家Paul de Casteljau所发明,于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,并用于汽车的车 ...

  9. 使用canvas绘制水滴(二次贝塞尔曲线、圆弧)

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

最新文章

  1. 计算机视觉常用数据集总结:包括MS COCO、ImageNet、VOC、人脸识别、行人检测等...
  2. SpringBoot解耦的扩展机制 Spring Factories介绍及使用
  3. node配置ssl证书_在Linux服务器上部署node项目(git部署,forever持续运行,配置SSL证书)...
  4. Hive导入和导出数据
  5. linux查看内存参数
  6. 【数据结构-链表】malloc函数头文件
  7. 2011英语二长难句
  8. 统计——假设检验与p值
  9. wps无法连接到计算机,WPS无法连接网络怎么解决-解决wps不能连接网络的方法 - 河东软件园...
  10. vue中的prop验证
  11. 学大伟业:2019年物理竞赛学习方法
  12. Android中自定义悬浮窗
  13. 【实习之路】在广州欢聚时代入职的第一天
  14. 艺术与科技的狂欢,阿那亚2022砂之盒沉浸艺术季
  15. arcgis伪节点检查_兼顾属性信息的矢量伪节点检查及自动消除方法
  16. centos安装nagios
  17. 超简单修改富文本字体样式
  18. Github没有记录Contributions的原因及解决方案
  19. 图论与图学习(一):图的基本概念
  20. 你真的懂请求参数和路径变量?

热门文章

  1. 微软纯净系统刷机六步走
  2. stm学习---FSMC控制TFT屏程序1
  3. 每日一面 - sqrt (2)约等于 1.414,如何求sqrt (2)小数点后 10 位
  4. mysql mof提权原理_mof提权原理及其过程
  5. 如何有效提升英文口语水平?这 15 个方法值得学习!
  6. ARM裸机篇(五)——异常和中断
  7. Android 可长按拖拽的RecyclerView
  8. 什么是数据仓库?数据仓库的特点与架构演进
  9. 字节跳动文件服务器是什么,字节跳动智能化服务端接口测试解决方案(25页)-原创力文档...
  10. 自动驾驶(四十四)---------主动安全功能简介