我是靠谱客的博主 聪慧大象,最近开发中收集的这篇文章主要介绍仿毛笔字的自定义绘图View,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

可以设置宽度


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

最后

以上就是聪慧大象为你收集整理的仿毛笔字的自定义绘图View的全部内容,希望文章能够帮你解决仿毛笔字的自定义绘图View所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部