在画图白板应用中,橡皮擦是一个必不可少的功能。它让用户能够修正错误,精确调整他们的创作。本文将详细介绍如何在Android画图白板应用中实现一个高效且用户友好的橡皮擦功能。主要分为以下部分:
- 橡皮擦:使用透明笔迹覆盖
- 一键清屏:清空画布所有内容
- 按笔迹清除:清除选中的笔迹
- 橡皮擦(修改原本 Path 结构):需要更改原笔迹,分割成新笔迹
- 电子笔笔帽擦除:电子笔笔帽当橡皮擦使用
部分功能演示:
橡皮檫演示效果
一、橡皮擦
1. 使用PorterDuff模式
PorterDuffXfermode是Android中处理图形混合的强大工具。对于橡皮擦,我们可以使用**PorterDuff.Mode.CLEAR
**模式,它会将绘制区域的像素完全清除。
2. 代码
java
//设置橡皮的属性
paint_eraser.setStrokeWidth(50f);
paint_eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint_eraser.setStyle(Paint.Style.STROKE);
paint_eraser.setStrokeCap(Paint.Cap.ROUND);
paint_eraser.setStrokeJoin(Paint.Join.ROUND);
paint_eraser.setAntiAlias(true);
paint_eraser.setDither(true);
paint_eraser.setFilterBitmap(true);
paint_eraser.setStrokeMiter(1.0f);
注意:此操作不会修改 原始的笔迹数据层 (StrokeManager
)。它只是在视觉上覆盖。
3. 效果图
擦除前:

擦除后:

二、一键清屏
1. 代码
java
//清空画布
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
invalidate();
清空作为Canvas绘制目标的那块Bitmap ,PorterDuff.Mode.CLEAR
: 这是核心所在。它是一种混合模式(Blending Mode),其规则是:清除目标图像中的所有像素,忽略源图像。无论原本画布上有什么,这个模式都会将其变为完全透明。
三、按笔迹擦除
1.思路
画一条曲线,清除相交的可视化笔画(橡皮擦笔画除外),下面配置笔迹擦除橡皮。
java
//设置笔画删除的属性
paint_eraser_sliding.setStrokeWidth(3f);//3
paint_eraser_sliding.setColor(Color.parseColor("#E94F4F"));//设置为红色
paint_eraser_sliding.setStyle(Paint.Style.STROKE);
paint_eraser_sliding.setStrokeCap(Paint.Cap.ROUND);
paint_eraser_sliding.setStrokeJoin(Paint.Join.ROUND);
paint_eraser_sliding.setAntiAlias(true);
paint_eraser_sliding.setDither(true);
paint_eraser_sliding.setFilterBitmap(true);
paint_eraser_sliding.setStrokeMiter(1.0f);
//设置画出来的为虚线
paint_eraser_sliding.setPathEffect( new DashPathEffect(new float[]{20f,10f,5f,10f},0f));
2. 代码
当手指在屏幕上移动,显示笔迹擦除橡皮的同时,记录最新的点和上一个点,判断是否跟可视化笔画相交。
java
private void actionMove_Sliding(MotionEvent event){
//来判断应该删除哪些线条,利用高光显示
float cx = (event.getX(0)+ mStartXs[0]) / 2f;
float cy = (event.getY(0) + mStartYs[0]) / 2f;
mDottedLine.mXY.add(new DottedLineDates.XAndY(event.getX(0),event.getY(0)));
mDottedLine.path.quadTo(mStartXs[0], mStartYs[0], cx, cy);
//resetDirtyRect(mDottedLine.mRectF,event.getX(0),event.getY(0));
//判断是否相交(虚线只用提供两个点即可) isDelete为true时变成高光
isCross(mStartXs[0],mStartYs[0],event.getX(0),event.getY(0));
mStartXs[0] = event.getX(0);
mStartYs[0] = event.getY(0);
paths[0].moveTo(cx, cy);
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR); //这个很关键
invalidateReason = REASON_DRAW_MOVE;
invalidate();
}
可视化笔迹的类型:线和点,删除笔迹的标准有所不同
- 线:判断是否相交
- 点:判断是否相切
为了更严谨些,线除了判断相交之外;还需要判断起点和终点是否相切。下面的代码我只写了起点的判断,还有另外原因,是因为用电子笔按下的点,很大可能性是经历了多个move点,这些move点是重叠的。判断起点是为了排除类似的点。
java
//判断每个false的是否相交
private void isCross(float x1, float y1, float x2, float y2) {
for (int i = 0; i < mPaintedList.size(); i++) {
//假如为橡皮擦就continue
if(mPaintedList.get(i).mPaint.getXfermode()==paint_eraser.getXfermode()){
//如果为橡皮,就pass
continue;
}
boolean isEnding = false;
while (!mPaintedList.get(i).isDelete()&&!isEnding){
//判断DOTTED_LINE是否为点(在压力发生改变的时候他就会运行到move)所以这个点很难捕捉
//假如没有准备删除就遍历(有两种类型的处理:点和线)
if(mPaintedList.get(i).getLineModel()==LINE||mPaintedList.get(i).getLineModel()==DOTTED_LINE){
//目前是对线的处理
//System.out.println("AAAAAAAAAAAAAAAA 进入了while循环:线 id "+i);
boolean b1; //对首点的判断
float pointWidth = pointToLine(x1,y1,x2,y2,mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix);
b1 = pointWidth<=mPaintedList.get(i).mWidth;
for (int j = 0; j < mPaintedList.get(i).mOnePaths.size() ; j++) {
boolean b ;
if(j==0){
//使用最开始保存的点
b = intersection(x1,y1,x2,y2,mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}else {
b = intersection(x1,y1,x2,y2
,mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix,mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}
if(b||b1){
//如果判断出相交 == 成为待删除
//System.out.println("AAAAAAAAAAAAAAAA 查找到了一条可擦线段 id为:"+ i);
mPaintedList.get(i).setDelete(true);
//同时保存到删除备选
// if(mDeleteList == null){
// mDeleteList = new ArrayList<>();
// }
// mDeleteList.add(new PaintDates(mPaintedList.get(i).mPaint,mPaintedList.get(i).mOnePaths
// ,mPaintedList.get(i).mx,mPaintedList.get(i).my,mPaintedList.get(i).mWidth)); //这里的isDelete默认为false
break;
}
//当结束的时候
if(j == (mPaintedList.get(i).mOnePaths.size()- 1)){
//结束while循环
isEnding=true;
}
}
}else {
//对点的处理
float pointWidth = pointToLine(x1,y1,x2,y2,mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix);
if(pointWidth<=mPaintedList.get(i).mWidth){
//System.out.println("AAAAAAAAAAAAAAAA 查找到了一条可擦点点 id为:"+ i);
mPaintedList.get(i).setDelete(true);
//同时保存到删除备选
// if(mDeleteList == null){
// mDeleteList = new ArrayList<>();
// }
// mDeleteList.add(new PaintDates(mPaintedList.get(i).mPaint,mPaintedList.get(i).mOnePaths
// ,mPaintedList.get(i).mx,mPaintedList.get(i).my,mPaintedList.get(i).mWidth)); //这里的isDelete默认为false
break;
}
//当结束的时候
isEnding=true;
}
}
}
}
// 点到直线的最短距离的判断 点(x0,y0) 到由两点组成的线段(x1,y1) ,( x2,y2 )
private float pointToLine(float x1, float y1, float x2, float y2, float x0,
float y0) {
float space = 0;
float a, b, c;
a = distanceTo(x1, y1, x2, y2);// 线段的长度
b = distanceTo(x1, y1, x0, y0);// (x1,y1)到点的距离
c = distanceTo(x2, y2, x0, y0);// (x2,y2)到点的距离
if (c <= 0.000001 || b <= 0.000001) {
space = 0;
return space;
}
if (a <= 0.000001) {
space = b;
return space;
}
if (c * c >= a * a + b * b) {
space = b;
return space;
}
if (b * b >= a * a + c * c) {
space = c;
return space;
}
float p = (a + b + c) / 2;// 半周长
float s = (float) Math.sqrt(p * (p - a) * (p - b) * (p - c));// 海伦公式求面积
space = 2 * s / a;// 返回点到线的距离(利用三角形面积公式求高)
return space;
}
//判断两条直线是否相交
public static boolean intersection(double l1x1, double l1y1, double l1x2, double l1y2,
double l2x1, double l2y1, double l2x2, double l2y2)
{
// 快速排斥实验 首先判断两条线段在 x 以及 y 坐标的投影是否有重合。 有一个为真,则代表两线段必不可交。
if (Math.max(l1x1,l1x2) < Math.min(l2x1 ,l2x2)
|| Math.max(l1y1,l1y2) < Math.min(l2y1,l2y2)
|| Math.max(l2x1,l2x2) < Math.min(l1x1,l1x2)
|| Math.max(l2y1,l2y2) < Math.min(l1y1,l1y2))
{
return false;
}
// 跨立实验 如果相交则矢量叉积异号或为零,大于零则不相交
return !((((l1x1 - l2x1) * (l2y2 - l2y1) - (l1y1 - l2y1) * (l2x2 - l2x1))
* ((l1x2 - l2x1) * (l2y2 - l2y1) - (l1y2 - l2y1) * (l2x2 - l2x1))) > 0)
&& !((((l2x1 - l1x1) * (l1y2 - l1y1) - (l2y1 - l1y1) * (l1x2 - l1x1))
* ((l2x2 - l1x1) * (l1y2 - l1y1) - (l2y2 - l1y1) * (l1x2 - l1x1))) > 0);
}
流程:
- 遍历所有可视化线段(排除橡皮擦)
- 遍历过程中,先判断线段起点是否跟传入的线段相切。
- 接着判断可视化线段每个点组成的线段,是否跟传入的线段相交。
- 判断相切或相交了,就将其设置为待删除,同时设置该曲线为高光加粗效果。
- 点的逻辑就只需判断相切即可。
注意:xToMatrix 和 yToMatrix 在这里是偏移和放大缩小后的线段点的位置,这个在后续讲解放大缩小时会讲解,各个功能之间相互配合,让程序更出彩。
当手指松开之后,就执行下面的代码。
java
private void actionUp_Sliding(){
//UP开始的时候就开始删除(可能有多个)
//1.查找并删除(依据撤销,没必要删除)
for (int i = mPaintedList.size()-1 ; i>=0 ; i--) {
//反着删除
if(mPaintedList.get(i).isDelete()){
if(mCancelList.get(mCancelList.size()-1).MassageType==SLIDING_MULTI_STROKE_UN_HAVE){
//设置成有效操作
mCancelList.get(mCancelList.size()-1).MassageType=SLIDING_MULTI_STROKE_HAVE;
mCancelList.get(mCancelList.size()-1).paintStrokes = new ArrayList<>();
}
mCancelList.get(mCancelList.size()-1).paintStrokes.add(new MessageStrokes.IdAndStrokes(i,new PaintDates(mPaintedList.get(i))));
mPaintedList.remove(i);
}
}
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
}
这里简单点理解就是将 mPaintedList.get(i) 的笔迹移除到了mCancelList.get(mCancelList.size()-1).paintStrokes 中,这里的mCancelList是撤销恢复使用的,在这里无需理会。
**注意:**这里反向删除,是为了避免以下问题。
当从一个
List
中正向遍历 (从0到size-1)并删除元素时,每删除一个元素,后续元素的索引都会前移(即索引值减1),这会导致:
如果删除第i个元素,那么原本第i+1个元素会变成第i个,但循环索引i会继续增加,从而跳过这个元素。
还可能因为索引越界而引发异常(比如删除后列表大小变化,但循环仍试图访问原索引)。
反向遍历(从size-1到0)删除可以避免这个问题:
删除一个元素后,前面元素的索引不会改变 (因为删除的是当前索引i,而i是递减的,下一个要检查的是i-1,不受删除影响)。
这样确保每个元素都被正确遍历,且不会出现索引越界。
3. 效果图

选中后的状态,其中红色虚线为擦除橡皮的轨迹,高光的笔迹就为待删除的笔迹
删除结果如下:

四、橡皮擦(修改原本 Path 结构)
上面的效果,就是根据橡皮擦将原本 Path 分割的结果。
1. 思路
当橡皮擦经过 Path 的时候,除了可以遮挡笔迹之外,还希望将 Path 在遮挡的位置断开,形成新的 Path 。下面代码是橡皮擦在Move的时候执行的部分代码,需要留意的是isCross_eraser。
java
}else if(mode == ERASER){
mEraser.setLayoutParams(new AbsoluteLayout.LayoutParams((int)maxDistance,(int)maxDistance
,(int) (event.getX(0)+relativeOffsetX-maxDistance/2), (int) (event.getY(0)+relativeOffsetY-maxDistance/2)));
Paints[0].setLineModel(DOTTED_LINE);
resetDirtyRect(mRectFs[0],mStartXs[0],mStartYs[0],event.getX(0)+relativeOffsetX,event.getY(0)+relativeOffsetY);
float cx = (event.getX(0)+relativeOffsetX+mStartXs[0])/2f;
float cy = (event.getY(0)+relativeOffsetY+mStartYs[0])/2f;
//用来判断是否相交,如果相交就在的位置断开即可
isCross_eraser(mStartXs[0],mStartYs[0],event.getX(0)+relativeOffsetX,event.getY(0)+relativeOffsetY);
if(Paints[0].mPath == null){
Paints[0].mPath = new Path(paths[0]);
}
Paints[0].mOnePaths.add(new PaintDates.PathAndWidth(event.getX(0)+relativeOffsetX,event.getY(0)+relativeOffsetY));
Paints[0].mPath.quadTo(mStartXs[0],mStartYs[0],cx,cy);
mStartXs[0] = event.getX(0)+relativeOffsetX;
mStartYs[0] = event.getY(0)+relativeOffsetY;
}
2. 代码
java
//用来判断是否相交,如果相交就在的位置断开即可
private void isCross_eraser(float x1, float y1, float x2, float y2) {
PointF pointF;
for (int i = 0; i < mPaintedList.size(); i++) {
boolean isEnding = false;
while (!isEnding){
//判断DOTTED_LINE是否为点(在压力发生改变的时候他就会运行到move)所以这个点很难捕捉
//假如没有准备删除就遍历(有两种类型的处理:点和线)
if(mPaintedList.get(i).mPaint.getXfermode()==paint_eraser.getXfermode()){
//如果为橡皮,就pass
break;
}
if(mPaintedList.get(i).getLineModel()==POINT){
break;
}
if(mPaintedList.get(i).getLineModel()==LINE||mPaintedList.get(i).getLineModel()==DOTTED_LINE){
//目前是对线的处理(除开对橡皮的处理)
//其实可以两个点之间就设置一个点(并且实时更新就可)
for (int j = 0; j < mPaintedList.get(i).mOnePaths.size() ; j++) {
boolean b ;
if(j==0){
b = intersection(x1,y1,x2,y2,mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}else {
b = intersection(x1,y1,x2,y2
,mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix,mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}
if(b){
//如果判断出相交 == 成为待删除
//System.out.println("AAAAAAAAAAAAAAAA 查找到了一条可擦线段 id为:"+ i);
//mPaintedList.get(i).setDelete(true);
//设置状态,求比例,保存在两点之间(因为有漫游效果,所以求点是不现实的)
//遍历一笔,看到底有几个交点:决定分成多少段
if(!mPaintedList.get(i).isCut()){
mPaintedList.get(i).setCut(true);
}
mPaintedList.get(i).mOnePaths.get(j).isCut = true;
if(j==0){
pointF = intersectionXY(x1,y1,x2,y2,mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
//assert pointF != null; //他为空了,中断程序
if(pointF!=null){
mPaintedList.get(i).mOnePaths.get(j).BL = distanceTo(mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix,pointF.x,pointF.y)
/distanceTo(mPaintedList.get(i).mXToMatrix,mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}else {
mPaintedList.get(i).mOnePaths.get(j).isCut = false; //没有值就设置为不割
}
}else {
pointF = intersectionXY(x1,y1,x2,y2
,mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix,mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
//assert pointF != null;
if(pointF!=null){
mPaintedList.get(i).mOnePaths.get(j).BL = distanceTo(mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix,mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix,pointF.x,pointF.y)
/distanceTo(mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix,mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}else {
mPaintedList.get(i).mOnePaths.get(j).isCut = false; //没有值就设置为不割
}
//System.out.println("AAAAAAAAAAAAAAAAAAAAAAAA"+pointF.x+","+pointF.y+",j="+j+",bl="+mPaintedList.get(i).mOnePaths.get(j).BL);
}
}
if(j == (mPaintedList.get(i).mOnePaths.size()- 1)){
//结束while循环(必须遍历所有,知道结尾)
isEnding=true;
}
}
}
}
}
}
//判断交点
public static PointF intersectionXY(float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4)
{
float x;
float y;
float k1=Float.MAX_VALUE;
float k2=Float.MAX_VALUE;
boolean flag1=false;
boolean flag2=false;
if((x1-x2)==0)
flag1=true;
if((x3-x4)==0)
flag2=true;
if(!flag1)
k1=(y1-y2)/(x1-x2);
if(!flag2)
k2=(y3-y4)/(x3-x4);
if(flag1){
x=x1;
if(k2==0){
y=y3;
}else{
y=k2*(x-x4)+y4;
}
}else if(flag2){
x=x3;
if(k1==0){
y=y1;
}else{
y=k1*(x-x2)+y2;
}
}else{
if(k1==0){
y=y1;
x=(y-y4)/k2+x4;
}else if(k2==0){
y=y3;
x=(y-y2)/k1+x2;
}else{
x=(k1*x2-k2*x4+y4-y2)/(k1-k2);
y=k1*(x-x2)+y2;
}
}
if(between(x1,x2,x)&&between(y1,y2,y)&&between(x3,x4,x)&&between(y3,y4,y)){
PointF point=new PointF();
point.x = x;
point.y = y;
return point;
}
return null;
}
逻辑:
- 流程跟按笔迹擦除差不多,判断相交之后,保存交点和区域段(需要切割的区域)
- move的时候,只需要记录数据。等到手指松开执行 up 时,再去切割所有线段
接下来是松手后执行的代码
java
private void actionUp_Eraser(){
mEraser.setVisibility(GONE);
int size =mPaintedList.size();
//这里将其分段(理论上分段的操作应该是不会显示出不同的)for (int i = 0 ; i< size ; i++)
for (int i = 0 ; i< size ; i++){
if(mPaintedList.get(i).isCut()){ //当此线是准备是剪切的
//这里是不透明笔锋
if(mPaintedList.get(i).getLineModel()==LINE){
mCutList.add(new PaintDatesAndID());//首先要添加一个笔画
mCutList.get(mCutList.size()-1).id = i;
mCutList.get(mCutList.size()-1).mCutPaintList.add(new PaintDates(mPaintedList.get(i).mPaint,new ArrayList<>()
,mPaintedList.get(i).mx,mPaintedList.get(i).my,mPaintedList.get(i).mWidth));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1)
.mXToMatrix = mPaintedList.get(i).mXToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1)
.mYToMatrix = mPaintedList.get(i).mYToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1)
.setLineModel(mPaintedList.get(i).getLineModel());
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1)
.mPath = mPaintedList.get(i).mPath;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1)
.mMatrixS.addAll(mPaintedList.get(i).mMatrixS) ; //这里存疑
for (int j = 0; j <mPaintedList.get(i).mOnePaths.size() ; j++) {
float cx;
float cy;
if(mPaintedList.get(i).mOnePaths.get(j).isCut){
//根据比例求点
PointF pointF ;
PointF pointF1; //偏移点
if(j==0){
pointF= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mx, mPaintedList.get(i).my
,mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y);
pointF1= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mXToMatrix, mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}else {
pointF= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mOnePaths.get(j-1).x, mPaintedList.get(i).mOnePaths.get(j-1).y
,mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y);
pointF1= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix, mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
}
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
new Path(mPaintedList.get(i).mOnePaths.get(j).path), mPaintedList.get(i).mOnePaths.get(j).width
, pointF.x, pointF.y));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = pointF1.x;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = pointF1.y;
if(j==0){
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.moveTo( mPaintedList.get(i).mx, mPaintedList.get(i).my);
cx = (mPaintedList.get(i).mx+ pointF.x)/2f;
cy = (mPaintedList.get(i).my+ pointF.y)/2f;
}else {
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.moveTo( mPaintedList.get(i).mOnePaths.get(j-1).x, mPaintedList.get(i).mOnePaths.get(j-1).y);
cx = (mPaintedList.get(i).mOnePaths.get(j-1).x+ pointF.x)/2f;
cy = (mPaintedList.get(i).mOnePaths.get(j-1).y+ pointF.y)/2f;
}
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.lineTo(cx, cy);
//new下一个
mCutList.get(mCutList.size()-1).mCutPaintList.add(new PaintDates(mPaintedList.get(i).mPaint,new ArrayList<>()
,pointF.x,pointF.y,mPaintedList.get(i).mWidth));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mXToMatrix = pointF1.x;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mYToMatrix = pointF1.y;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).setLineModel(mPaintedList.get(i).getLineModel());
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath = mPaintedList.get(i).mPath;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mMatrixS.addAll(mPaintedList.get(i).mMatrixS); //这里存疑
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
new Path(mPaintedList.get(i).mOnePaths.get(j).path), mPaintedList.get(i).mOnePaths.get(j).width
, mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.reset();
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.moveTo( pointF.x, pointF.y);
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).path.lineTo(mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y);
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = mPaintedList.get(i).mOnePaths.get(j).xToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = mPaintedList.get(i).mOnePaths.get(j).yToMatrix;
}else {
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
new Path(mPaintedList.get(i).mOnePaths.get(j).path), mPaintedList.get(i).mOnePaths.get(j).width
, mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y));
if(mPaintedList.get(i).mOnePaths.get(j).addPaths!=null){
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1)
.addPaths = new ArrayList<>(mPaintedList.get(i).mOnePaths.get(j).addPaths) ;
}
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = mPaintedList.get(i).mOnePaths.get(j).xToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = mPaintedList.get(i).mOnePaths.get(j).yToMatrix;
}
}
}else {//这里是透明笔画(用path的)
mCutList.add(new PaintDatesAndID());//首先要添加一个笔画
mCutList.get(mCutList.size()-1).mCutPaintList.add(new PaintDates(mPaintedList.get(i).mPaint,new ArrayList<>()
,mPaintedList.get(i).mx,mPaintedList.get(i).my,mPaintedList.get(i).mWidth));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mXToMatrix = mPaintedList.get(i).mXToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mYToMatrix = mPaintedList.get(i).mYToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).setLineModel(mPaintedList.get(i).getLineModel());
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath = new Path();
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.moveTo(mPaintedList.get(i).mx,mPaintedList.get(i).my);
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mMatrixS.addAll(mPaintedList.get(i).mMatrixS) ; //这里存疑
for (int j = 0; j <mPaintedList.get(i).mOnePaths.size() ; j++){
float cx;
float cy;
if(mPaintedList.get(i).mOnePaths.get(j).isCut){
PointF pointF ;
PointF pointF1; //偏移点
if(j==0){
pointF= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mx, mPaintedList.get(i).my
,mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y);
pointF1= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mXToMatrix, mPaintedList.get(i).mYToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
cx = (mPaintedList.get(i).mx+pointF.x)/2f;
cy = (mPaintedList.get(i).my+pointF.y)/2f;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.quadTo(mPaintedList.get(i).mx,mPaintedList.get(i).my,cx,cy);
}else {
pointF= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mOnePaths.get(j-1).x, mPaintedList.get(i).mOnePaths.get(j-1).y
,mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y);
pointF1= pointToBL(mPaintedList.get(i).mOnePaths.get(j).BL,mPaintedList.get(i).mOnePaths.get(j-1).xToMatrix, mPaintedList.get(i).mOnePaths.get(j-1).yToMatrix
,mPaintedList.get(i).mOnePaths.get(j).xToMatrix, mPaintedList.get(i).mOnePaths.get(j).yToMatrix);
cx = (mPaintedList.get(i).mOnePaths.get(j-1).x+pointF.x)/2f;
cy = (mPaintedList.get(i).mOnePaths.get(j-1).y+pointF.y)/2f;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.quadTo(mPaintedList.get(i).mOnePaths.get(j-1).x,mPaintedList.get(i).mOnePaths.get(j-1).y,cx,cy);
}
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
pointF.x, pointF.y));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = pointF1.x;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = pointF1.y;
//该new一个了
mCutList.get(mCutList.size()-1).mCutPaintList.add(new PaintDates(mPaintedList.get(i).mPaint,new ArrayList<>()
,pointF.x, pointF.y,mPaintedList.get(i).mWidth));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mXToMatrix = pointF1.x;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mYToMatrix = pointF1.y;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).setLineModel(mPaintedList.get(i).getLineModel());
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath = new Path();
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.moveTo(pointF.x, pointF.y);
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mMatrixS.addAll(mPaintedList.get(i).mMatrixS) ; //这里存疑
cx = (pointF.x+mPaintedList.get(i).mOnePaths.get(j).x)/2f;
cy = (pointF.y+mPaintedList.get(i).mOnePaths.get(j).y)/2f;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.quadTo(pointF.x,pointF.y,cx,cy);
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = mPaintedList.get(i).mOnePaths.get(j).xToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = mPaintedList.get(i).mOnePaths.get(j).yToMatrix;
}else {
if(j==0){
cx = (mPaintedList.get(i).mx+mPaintedList.get(i).mOnePaths.get(j).x)/2f;
cy = (mPaintedList.get(i).my+mPaintedList.get(i).mOnePaths.get(j).y)/2f;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.quadTo(mPaintedList.get(i).mx,mPaintedList.get(i).my,cx,cy);
}else {
cx = (mPaintedList.get(i).mOnePaths.get(j-1).x+mPaintedList.get(i).mOnePaths.get(j).x)/2f;
cy = (mPaintedList.get(i).mOnePaths.get(j-1).y+mPaintedList.get(i).mOnePaths.get(j).y)/2f;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mPath.quadTo(mPaintedList.get(i).mOnePaths.get(j-1).x,mPaintedList.get(i).mOnePaths.get(j-1).y,cx,cy);
}
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.add(new PaintDates.PathAndWidth(
mPaintedList.get(i).mOnePaths.get(j).x, mPaintedList.get(i).mOnePaths.get(j).y));
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).xToMatrix = mPaintedList.get(i).mOnePaths.get(j).xToMatrix;
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.get(
mCutList.get(mCutList.size()-1).mCutPaintList.get(mCutList.get(mCutList.size()-1).mCutPaintList.size()-1).mOnePaths.size()-1).yToMatrix = mPaintedList.get(i).mOnePaths.get(j).yToMatrix;
}
}
}
//在这里保存数字
//mNumList.add(mCutList.size());
}
}
if(mCutList.size()!=0){
for (int i = mCutList.size()-1; i >= 0; i--) {
//从最后一个分割的开始添加,此时包括之前的线+分割线
mPaintedList.addAll(mCutList.get(i).id,mCutList.get(i).mCutPaintList);
for (int j = 0; j < mCutList.get(i).mCutPaintList.size() ; j++) {
mCancelList.get(mCancelList.size()-1).paintStrokes.add(new MessageStrokes.IdAndStrokes(mCutList.get(i).id,null));
//一定要倒着来
}
}
mCutList.clear();
}
//将他们delete +mCutList.size()
for (int j = mPaintedList.size()-1; j>=0 ; j--) {
if(mPaintedList.get(j).isCut()){
mCancelList.get(mCancelList.size()-1).paintStrokes.add(new MessageStrokes.IdAndStrokes(j,new PaintDates(mPaintedList.get(j))));
mPaintedList.remove(j);
}
}
if(Paints[0]!=null){
mPaintedList.add(new PaintDates(Paints[0].mPaint,Paints[0].mOnePaths
,Paints[0].mx,Paints[0].my,Paints[0].mWidth));
mPaintedList.get(mPaintedList.size()-1).setLineModel(Paints[0].getLineModel());
mPaintedList.get(mPaintedList.size()-1).mPath = Paints[0].mPath;
mPaintedList.get(mPaintedList.size() - 1).draw(cacheCanvas);
}
paths[0].reset();
Paints[0] = null;
}
代码有点多,我简单说说流程:整体分为四个阶
阶段一:遍历并处理被切割的笔画
核心逻辑 :正向遍历所有笔画,找到被标记为**"切割"(
isCut()
)**的笔画进行处理。处理分为两种模式:
1. 不透明笔锋模式 (
LINE
)
创建新的切割记录 :在
mCutList
中添加一个PaintDatesAndID
对象。复制原笔画属性 :创建一个新的
PaintDates
对象,复制原笔画的 paint、坐标、宽度等基本属性。遍历笔画的每个线段 (
mOnePaths
):
如果线段被切割 (
isCut
):
计算切割点坐标(
pointF
)和对应的矩阵变换点(pointF1
)。将当前线段在切割点处分割:
当前子笔画结束于切割点。
创建一个新的子笔画从切割点开始。
如果线段未被切割:
- 直接将整个线段添加到当前子笔画中。
2. 透明笔画模式(非
LINE
,使用Path)
逻辑与不透明模式类似,但使用**Path和二次贝塞尔曲线(
quadTo
)**来构建平滑的笔迹。同样在切割点处分割Path,并创建新的子笔画。
阶段二:将分割后的笔画重新插入原列表
反向遍历
mCutList
(从最后处理的笔画开始)。将分割后产生的子笔画列表(
mCutPaintList
)插入回原笔画列表(mPaintedList
)的原始位置 (mCutList.get(i).id
)。为撤销功能记录这些操作(添加到
mCancelList
)。清空临时切割列表
mCutList
。
阶段三:清理被标记为切割的原始笔画
反向遍历当前所有笔画。
找到所有仍被标记为"切割"的原始笔画(这些是刚刚被分割处理的原始笔画)。
将它们从
mPaintedList
中移除,并添加到撤销列表中。
阶段四:处理当前正在绘制的笔画
如果存在正在绘制的笔画(
Paints[0]
),将其完成并添加到笔画列表中。绘制到缓存画布上。
重置路径和笔画变量,为下一次绘制做准备。
五、电子笔笔帽擦除
通过判断是否为MotionEvent.TOOL_TYPE_ERASER,不同的的厂商电子笔的数据有所不同,但是使用方式大差不差。当识别到时,更改为上面的介绍的几种橡皮擦即可。
java
int toolType = event.getToolType(0);
if(toolType == MotionEvent.TOOL_TYPE_ERASER){
//实现逻辑
}