可以设置宽度


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;@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawPath(mPath, mPaint);}@Overridepublic 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) {//最粗30fNewWidth = fNewWidth + changeWidth;}if (!isMoving) {isPressed = true;}
//                    if (alpha > 255) {//                        alpha = alpha + 5;
//                    }} else {if (fNewWidth > minWidth) {//最细10fNewWidth = 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 == 0mPath.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或者/2changeWidth = fOldWidth / 10;maxWidth = fOldWidth * 2;minWidth = fOldWidth / 2;}
}

仿毛笔字的自定义绘图View相关推荐

  1. android自定义View学习(二)----自定义绘图

    自定义绘图 自定义视图中最重要的部分是它的外观.根据您的应用需求,自定义绘图可以很容易或复杂.本篇涵盖了一些最常见的操作 onDraw() 绘制自定义视图中最重要的步骤是重写该onDraw()方法.参 ...

  2. Android自定义控件学习(五)-------自定义绘图

    自定义视图中最重要的部分是它的外观.根据您的应用需求,自定义绘图可以很容易或复杂. 本课包含一些最常见的操作 覆盖onDraw() 绘制自定义视图中最重要的步骤是重写该onDraw()方法.参数to ...

  3. 仿 IOS 桌面图标下载 view

    DownloadLoadingView 项目地址:xiaweizi/DownloadLoadingView  简介:仿 IOS 桌面图标下载 view 更多:作者   提 Bug 标签: 在工作中难免 ...

  4. android人脸识显示头像自定义,Android 仿QQ头像自定义截取功能

    看了Android版QQ的自定义头像功能,决定自己实现,随便熟悉下android绘制和图片处理这一块的知识. 先看看效果: 思路分析: 这个效果可以用两个View来完成,上层View是一个遮盖物,绘制 ...

  5. 自定义的View实现跟随手指的小兔子

    自定义的View实现跟随手指的小兔子 按前面的例子新创建一个project,再在project中新创建一个module 将需要的背景图和兔子图片放入mipmap中 将布局管理器改为帧布局管理器 < ...

  6. Android中自定义视图View

    标签: 前言 好长时间没写blog了,心里感觉有点空荡荡的,今天有时间就来写一个关于自定义视图的的blog吧.关于这篇blog,网上已经有很多案例了,其实没什么难度的.但是我们在开发的过程中有时候会用 ...

  7. Android中自定义视图View之---前奏篇

    前言 好长时间没写blog了,心里感觉有点空荡荡的,今天有时间就来写一个关于自定义视图的的blog吧.关于这篇blog,网上已经有很多案例了,其实没什么难度的.但是我们在开发的过程中有时候会用到一些自 ...

  8. cufflinks基于dataframe数据自定义绘图基于df.iplot功能

    cufflinks基于dataframe数据自定义绘图基于df.iplot功能 # cufflinks绘制自定义图像 # 基于df.iplot参数控制: from chart_studio impor ...

  9. 谷歌地图控件,通过扩展实现GMAP的自定义绘图系统

    这两天搞网络规划和优化系统,需要开发一套地图,并在地图上放置自定义图标,和放置复杂贴图,或画矩形多边形等随着地图移动和放缩.这件事情一开始搞得很痛苦,通过GMAP.NET开发,并使用了CodeProj ...

最新文章

  1. 幂等性 第三方交易编号_java幂等性的解决方案
  2. Nginx PHP支持
  3. 图形处理(六)拖拽式网格融合-Siggraph 2010
  4. python中执行sql语句_Python执行sql语句
  5. 霸榜Github第一!谷歌重磅开源的“海啸”,我服了
  6. Docker的基本使用(部署python项目)+两个奇技淫巧,将 Docker 镜像体积减小 99%
  7. 社会计算机比赛,哈尔滨工业大学社会计算与信息检索研究中心 – 理解语言,认知社会 » IR-Lab参加计算机学院“光熙杯”篮球赛...
  8. Python小白的数学建模课-17.条件最短路径算法
  9. 使用常识 | 如何在word中添加勾选框
  10. 陆奇最新投资方向:机器人、生物科技、远程工作、云计算技术、新材料、新消费娱乐等,奇绩创坛春季创业营线上开营
  11. 对“流形”最好的讲解在维基
  12. MyEclipse开启服务器时总是进入Debug模式
  13. 在Dynamics 365 Fo/AX2012中获取不同类型的时间
  14. QPSK调制及MATLAB实现
  15. Android Intent 机制解析 - Intent 是什么?作用是什么?
  16. php slim 教程,php框架slim架构上存在XXE漏洞(XXE的典型存在形式)
  17. 到底什么是CE、C++、C+L波段?
  18. Docker安装mysql:Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use
  19. redis解除(删除)主从关系
  20. [CSS]常见布局技巧

热门文章

  1. 计算机毕业设计之Android的地铁查询系统app(源码+系统+mysql数据库+Lw文档)
  2. F12 开发人员工具入门
  3. 自然几何之分形(1)
  4. STemWin专题-控件设计
  5. SEVERE: Error configuring application listener of class org.springframework.web.context.ContextLoade
  6. CRM开发要点(五)
  7. SQL语句(数据库、基本表、视图、索引)
  8. 为什么纳斯达克和雅加达期货等传统交易所在转向区块链?
  9. Ubuntu 14.04 Nvidia显卡驱动安装及设置
  10. java获取当前目录