当前位置: 首页 > news >正文

【安卓】自定义View实现画板涂鸦等功能

一、实现效果

画笔测试
开始设计矩形
实现矩形的随笔画
圆形
三角形,还有bug
等腰三角形和塔型
等边三角形
等边三角形计算

二、代码

1、MainActivity.class

package com.lsl.mydrawingboarddemo;import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageButton;import com.lsl.mydrawingboarddemo.view.DrawingBoardView;import java.io.IOException;/*** author lishilin* date 2023/8/9* desc 负责画板UI交互*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private ImageButton paintBtn,fillPaintBtn,rubberBtn,rectBtn,circleBtn,ellipticBtn,triangleBtn,triangleTowerBtn,selectedBtn,clearBtn,equilateralBtn,rightAngleBtn;private ImageButton blackBtn,whiteBtn,redBtn,blueBtn,grayBtn,cyanBtn,currentColorBtn;private DrawingBoardView drawingBoardView;private View selectColorBoxView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){paintBtn = findViewById(R.id.paint);fillPaintBtn = findViewById(R.id.fillPaint);//rubberBtn = findViewById(R.id.rubber);rectBtn = findViewById(R.id.rect);circleBtn = findViewById(R.id.circle);ellipticBtn = findViewById(R.id.elliptic);triangleBtn = findViewById(R.id.triangle);triangleTowerBtn = findViewById(R.id.triangleTower);clearBtn = findViewById(R.id.clear);equilateralBtn = findViewById(R.id.equilateralTriangle);drawingBoardView = findViewById(R.id.drawView);selectColorBoxView = findViewById(R.id.selectColorBox);rightAngleBtn = findViewById(R.id.rightAngle);blackBtn = findViewById(R.id.blackBtn);whiteBtn = findViewById(R.id.whiteBtn);redBtn = findViewById(R.id.redBtn);blueBtn = findViewById(R.id.blueBtn);grayBtn = findViewById(R.id.grayBtn);cyanBtn = findViewById(R.id.cyanBtn);paintBtn.setSelected(true);selectedBtn = paintBtn;blackBtn.setSelected(true);currentColorBtn = blackBtn;paintBtn.setOnClickListener(this);fillPaintBtn.setOnClickListener(this);//rubberBtn.setOnClickListener(this);rectBtn.setOnClickListener(this);circleBtn.setOnClickListener(this);ellipticBtn.setOnClickListener(this);triangleBtn.setOnClickListener(this);triangleTowerBtn.setOnClickListener(this);clearBtn.setOnClickListener(this);blackBtn.setOnClickListener(this);whiteBtn.setOnClickListener(this);redBtn.setOnClickListener(this);blueBtn.setOnClickListener(this);grayBtn.setOnClickListener(this);equilateralBtn.setOnClickListener(this);rightAngleBtn.setOnClickListener(this);cyanBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.paint:setSelectBtn(paintBtn);drawingBoardView.setPaint("paint");selectColorBoxView.setVisibility(View.VISIBLE);break;case R.id.fillPaint:setSelectBtn(fillPaintBtn);drawingBoardView.setPaint("fillPaint");selectColorBoxView.setVisibility(View.VISIBLE);break;
//            case R.id.rubber:
//                setSelectBtn(rubberBtn);
//                drawingBoardView.setEraseMode();
//                selectColorBoxView.setVisibility(View.INVISIBLE);
//                break;case R.id.rect:setSelectBtn(rectBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("rect");break;case R.id.circle:setSelectBtn(circleBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawCircle");break;case R.id.elliptic:setSelectBtn(ellipticBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawOval");break;case R.id.triangle:setSelectBtn(triangleBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawTriangle");break;case R.id.equilateralTriangle:setSelectBtn(equilateralBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawEquilateral");break;case R.id.triangleTower:setSelectBtn(triangleTowerBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawTriangleTower");break;case R.id.rightAngle:setSelectBtn(rightAngleBtn);selectColorBoxView.setVisibility(View.VISIBLE);drawingBoardView.setPaint("drawRightAngle");break;case R.id.clear:clearStyle();//设置点击清除图标时的背景闪烁selectColorBoxView.setVisibility(View.VISIBLE);if (drawingBoardView!=null){drawingBoardView.clearBoard();}break;case R.id.blackBtn:int blackColor = ContextCompat.getColor(this,R.color.black);drawingBoardView.setPaintColor(blackColor);setCurrentColorBtn(blackBtn);break;case R.id.blueBtn:int blueColor = ContextCompat.getColor(this,R.color.blue);drawingBoardView.setPaintColor(blueColor);setCurrentColorBtn(blueBtn);break;case R.id.whiteBtn:int whiteColor = ContextCompat.getColor(this,R.color.white);drawingBoardView.setPaintColor(whiteColor);setCurrentColorBtn(whiteBtn);break;case R.id.cyanBtn:int cyanColor = ContextCompat.getColor(this,R.color.cyan);drawingBoardView.setPaintColor(cyanColor);setCurrentColorBtn(cyanBtn);break;case R.id.grayBtn:int grayColor = ContextCompat.getColor(this,R.color.gray);drawingBoardView.setPaintColor(grayColor);setCurrentColorBtn(grayBtn);break;case R.id.redBtn:int redColor = ContextCompat.getColor(this,R.color.red);drawingBoardView.setPaintColor(redColor);setCurrentColorBtn(redBtn);break;default:break;}}/*** 设置选中的图形button* @param selectBtn*/private void setSelectBtn(ImageButton selectBtn){if (this.selectedBtn!=null){this.selectedBtn.setSelected(false);}selectBtn.setSelected(true);this.selectedBtn = selectBtn;}private void setCurrentColorBtn(ImageButton currentColorBtn){if (this.currentColorBtn!=null){this.currentColorBtn.setSelected(false);}currentColorBtn.setSelected(true);this.currentColorBtn = currentColorBtn;}/*** 设置点击清除图标时的样式变化,让其背景变色500毫秒*/private void clearStyle(){clearBtn.setSelected(true);new Handler().postDelayed(new Runnable() {@Overridepublic void run() {clearBtn.setSelected(false);}}, 200);}}

2、布局代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/bord_color"tools:context=".MainActivity"><com.lsl.mydrawingboarddemo.view.DrawingBoardViewandroid:id="@+id/drawView"android:layout_width="match_parent"android:layout_height="650dp"android:layout_alignParentTop="true"android:background="@color/bord_color" /><LinearLayoutandroid:layout_width="600dp"android:layout_height="80dp"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:background="@drawable/bottom_bg_corner"android:gravity="center"android:orientation="horizontal"><LinearLayoutandroid:layout_width="100dp"android:layout_height="match_parent"android:gravity="center"><TableLayoutandroid:id="@+id/selectColorBox"android:layout_width="40dp"android:layout_height="100dp"android:gravity="center"><TableRow><ImageButtonandroid:id="@+id/blackBtn"android:background="@drawable/color_select"android:src="@drawable/black" /><ImageButtonandroid:id="@+id/whiteBtn"android:background="@drawable/color_select"android:src="@drawable/white" /></TableRow><TableRow><ImageButtonandroid:id="@+id/redBtn"android:background="@drawable/color_select"android:src="@drawable/red_circle" /><ImageButtonandroid:id="@+id/blueBtn"android:background="@drawable/color_select"android:src="@drawable/blue" /></TableRow><TableRow><ImageButtonandroid:id="@+id/grayBtn"android:background="@drawable/color_select"android:src="@drawable/gray" /><ImageButtonandroid:id="@+id/cyanBtn"android:background="@drawable/color_select"android:src="@drawable/cyan" /></TableRow></TableLayout></LinearLayout><TextViewandroid:layout_width="1dp"android:layout_height="match_parent"android:background="@color/gray" /><LinearLayoutandroid:layout_width="500dp"android:layout_height="match_parent"android:gravity="center"><ImageButtonandroid:id="@+id/paint"android:layout_width="28dp"android:layout_height="28dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/paint" /><ImageButtonandroid:id="@+id/fillPaint"android:layout_width="32dp"android:layout_height="32dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/brush" />
<!--            <ImageButton-->
<!--                android:id="@+id/rubber"-->
<!--                android:layout_width="32dp"-->
<!--                android:layout_height="32dp"-->
<!--                android:scaleType="centerInside"-->
<!--                android:layout_marginLeft="8dp"-->
<!--                android:background="@drawable/shape_selector"-->
<!--                android:src="@drawable/rubber" />--><ImageButtonandroid:id="@+id/rect"android:layout_width="32dp"android:layout_height="32dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:focusable="true"android:src="@drawable/rect" /><ImageButtonandroid:id="@+id/circle"android:layout_width="32dp"android:layout_height="32dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/circle" /><ImageButtonandroid:id="@+id/elliptic"android:layout_width="35dp"android:layout_height="35dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/elliptic" /><ImageButtonandroid:id="@+id/triangle"android:layout_width="38dp"android:layout_height="40dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/triangle" /><ImageButtonandroid:id="@+id/equilateralTriangle"android:layout_width="32dp"android:layout_height="32dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/equilateral_triangle" /><ImageButtonandroid:id="@+id/rightAngle"android:layout_width="37dp"android:layout_height="37dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/plan" /><ImageButtonandroid:id="@+id/triangleTower"android:layout_width="28dp"android:layout_height="28dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/tower" /><ImageButtonandroid:id="@+id/clear"android:layout_width="32dp"android:layout_height="32dp"android:scaleType="centerInside"android:layout_marginLeft="8dp"android:background="@drawable/shape_selector"android:src="@drawable/clear" /></LinearLayout></LinearLayout></RelativeLayout>

3、自定义View代码

package com.lsl.mydrawingboarddemo.view;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.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;import androidx.annotation.Nullable;import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;/*** author lishilin* date 2023/8/9* desc 自定义View,实现画板功能*/
public class DrawingBoardView extends SurfaceView {private Paint erasePaint, rectPaint, circlePaint;private Path erasePath, selectPath, triangleTowerPath, trianglePath, equilateralPath, anglePath;private List<Path> paintPaths, fillPaths, triangleTowerPaths, trianglePaths, equilateralPaths, anglePaths;private List<Paint> paints, fillPaints, rectPaints, circlePaints, ovalPaints, triangleTowerPaints, trianglePaints, equilateralPaints, anglePaints;private String pathType = "paintPath";private boolean eraseMode = false;private List<Rect> rects;private List<Circle> circleList;private List<RectF> ovals;private RectF rectF;private float startX, startY, currentX, currentY;private boolean isDrawing, drawPath, drawRect, drawCircle, isDrawingCircle, isDrawingOval, drawOval, drawTriangleTower, isDrawingTriangleTower, drawTriangle, isDrawingTriangle, isDrawingEquilateral, drawEquilateral, isDrawingRightAngle, drawRightAngle;private Rect rect;private Circle circle;private int pointX, pointY, radius;private float tipX, tipY, leftX, leftY, rightX, rightY, equilateralX;public DrawingBoardView(Context context) {super(context);init();}public DrawingBoardView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public DrawingBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}public DrawingBoardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}private void init() {erasePaint = new Paint();circle = new Circle();rect = new Rect();triangleTowerPaints = new ArrayList<>();triangleTowerPaths = new ArrayList<>();ovals = new ArrayList<>();trianglePaints = new ArrayList<>();trianglePaths = new ArrayList<>();rectPaints = new ArrayList<>();ovalPaints = new ArrayList<>();circlePaints = new ArrayList<>();circleList = new ArrayList<>();anglePaints = new ArrayList<>();anglePath = new Path();anglePaths = new ArrayList<>();equilateralPaths = new ArrayList<>();equilateralPaints = new ArrayList<>();circlePaint = new Paint();triangleTowerPath = new Path();circlePaint.setColor(Color.BLACK);circlePaint.setStyle(Paint.Style.STROKE);circlePaint.setStrokeWidth(5);erasePath = new Path();trianglePath = new Path();equilateralPath = new Path();triangleTowerPath = new Path();paints = new ArrayList<>();fillPaints = new ArrayList<>();fillPaths = new ArrayList<>();paintPaths = new ArrayList<>();setPaintColor(Color.BLACK);rects = new ArrayList<>();rectPaint = new Paint();rectPaint.setColor(Color.BLACK);rectPaint.setStrokeWidth(5);rectPaint.setStyle(Paint.Style.STROKE);drawPath = true;isDrawing = false;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Paint paint = paints.get(paints.size() - 1);if (isDrawing) {int left = (int) Math.min(startX, currentX);int right = (int) Math.max(startX, currentX);int top = (int) Math.min(startY, currentY);int bottom = (int) Math.max(startY, currentY);rect.set(left, top, right, bottom);canvas.drawRect(rect, paint);isDrawing = false;}if (!isDrawing) {for (int i = 0; i < rects.size(); i++) {canvas.drawRect(rects.get(i), rectPaints.get(i));}}for (int i = 0; i < paintPaths.size(); i++) {canvas.drawPath(paintPaths.get(i), paints.get(i));}for (int i = 0; i < trianglePaths.size(); i++) {canvas.drawPath(trianglePaths.get(i), trianglePaints.get(i));}if (isDrawingCircle) {circle.setPointX(pointX);circle.setPointY(pointY);circle.setRadius(radius);canvas.drawCircle(circle.getPointX(), circle.getPointY(), circle.getRadius(), paint);isDrawingCircle = false;}if (!isDrawingCircle) {for (int i = 0; i < circleList.size(); i++) {canvas.drawCircle(circleList.get(i).getPointX(), circleList.get(i).getPointY(), circleList.get(i).getRadius(), circlePaints.get(i));}}if (isDrawingOval) {int left = (int) Math.min(startX, currentX);int right = (int) Math.max(startX, currentX);int top = (int) Math.min(startY, currentY);int bottom = (int) Math.max(startY, currentY);rectF.set(left, top, right, bottom);canvas.drawOval(rectF, paint);isDrawingOval = false;}if (!isDrawingOval) {for (int i = 0; i < ovals.size(); i++) {canvas.drawOval(ovals.get(i), ovalPaints.get(i));}}if (isDrawingTriangleTower) {float leftX = currentX - (currentX - startX) * 2;float leftY = currentY;triangleTowerPath.lineTo(leftX, leftY);triangleTowerPath.lineTo(currentX, currentY);triangleTowerPath.close();canvas.drawPath(triangleTowerPath, paint);isDrawingTriangleTower = false;}if (!isDrawingTriangleTower){for (int i = 0; i < triangleTowerPaths.size(); i++) {canvas.drawPath(triangleTowerPaths.get(i), triangleTowerPaints.get(i));}}if (isDrawingTriangle) {trianglePath.reset();trianglePath.moveTo(tipX, tipY);trianglePath.lineTo(leftX, leftY);trianglePath.lineTo(rightX, rightY);trianglePath.close();canvas.drawPath(trianglePath, paint);isDrawingTriangle = false;}if (isDrawingEquilateral) {equilateralPath.reset();equilateralPath.moveTo(equilateralX, startY);int x = (int) ((currentY - startY) / Math.sqrt(3));int rightX = (int) (equilateralX + x);int leftX = (int) (equilateralX - x);equilateralPath.lineTo(leftX, currentY);equilateralPath.lineTo(rightX, currentY);equilateralPath.close();canvas.drawPath(equilateralPath, paint);isDrawingEquilateral = false;}if (!isDrawingEquilateral) {for (int i = 0; i < equilateralPaths.size(); i++) {canvas.drawPath(equilateralPaths.get(i), equilateralPaints.get(i));}}if (isDrawingRightAngle) {anglePath.reset();anglePath.moveTo(startX, startY);anglePath.lineTo(startX, currentY);anglePath.lineTo(currentX, currentY);anglePath.close();canvas.drawPath(anglePath, paint);isDrawingRightAngle = false;}if (!isDrawingRightAngle) {for (int i = 0; i < anglePaths.size(); i++) {canvas.drawPath(anglePaths.get(i), anglePaints.get(i));}}for (int i = 0; i < fillPaths.size(); i++) {canvas.drawPath(fillPaths.get(i), fillPaints.get(i));}}@Overridepublic boolean onTouchEvent(MotionEvent event) {float x = event.getX();float y = event.getY();Paint paint = paints.get(paints.size() - 1);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:startX = event.getX();startY = event.getY();if (drawPath && selectPath != null) {selectPath.moveTo(x, y);}if (drawRect) {rect = new Rect();rectPaints.add(paint);}if (drawCircle) {circle = new Circle();circlePaints.add(paint);}if (drawOval) {rectF = new RectF();ovalPaints.add(paint);}if (drawTriangleTower) {triangleTowerPaints.add(paint);triangleTowerPath.moveTo(event.getX(), event.getY());}if (drawTriangle) {trianglePath = new Path();tipY = startY;leftX = startX;trianglePaints.add(paint);}if (drawEquilateral) {equilateralPath = new Path();equilateralPaints.add(paint);}if (drawRightAngle) {anglePath = new Path();anglePaints.add(paint);}break;case MotionEvent.ACTION_MOVE:currentX = event.getX();currentY = event.getY();if (drawPath) {selectPath.lineTo(x, y);}if (drawRect) {isDrawing = true;invalidate();}if (drawOval) {isDrawingOval = true;invalidate();}if (drawCircle) {pointX = (int) (startX + (currentX - startX) / 2);pointY = (int) (startY + (currentY - startY) / 2);radius = (int) Math.abs(currentY - startY);isDrawingCircle = true;invalidate();}if (drawTriangleTower) {isDrawingTriangleTower = true;invalidate();}if (drawTriangle) {isDrawingTriangle = true;leftY = currentY;tipX = (currentX - leftX) / 2 + leftX;rightX = currentX;rightY = currentY;invalidate();}if (drawEquilateral) {equilateralX = startX + (currentX - startX) / 2;isDrawingEquilateral = true;invalidate();}if (drawRightAngle) {isDrawingRightAngle = true;invalidate();}break;case MotionEvent.ACTION_UP:if (drawRect) {rects.add(rect);isDrawing = false;}if (drawCircle) {circleList.add(circle);isDrawingCircle = false;}if (drawOval) {ovals.add(rectF);isDrawingOval = false;}if (drawTriangleTower) {triangleTowerPaths.add(triangleTowerPath);isDrawingTriangleTower = false;}if (drawTriangle) {trianglePaths.add(trianglePath);isDrawingTriangle = false;}if (drawEquilateral) {equilateralPaths.add(equilateralPath);isDrawingEquilateral = false;}if (drawRightAngle) {anglePaths.add(anglePath);isDrawingRightAngle = false;}break;}invalidate();return true;}public void clearBoard() {for (int i = 0; i < paintPaths.size(); i++) {paintPaths.get(i).reset();}for (int i = 0; i < fillPaths.size(); i++) {fillPaths.get(i).reset();}for (int i = 0; i < triangleTowerPaths.size(); i++) {triangleTowerPaths.get(i).reset();}for (int i = 0; i < trianglePaths.size(); i++) {trianglePaths.get(i).reset();}for (int i = 0; i < equilateralPaths.size(); i++) {equilateralPaths.get(i).reset();}for (Path path : anglePaths) {path.reset();}rects.clear();ovals.clear();circleList.clear();erasePath.reset();eraseMode = false;invalidate();}public void setPaint(String type) {switch (type) {case "paint":selectPath = paintPaths.get(paintPaths.size() - 1);this.pathType = "paintPath";drawPath = true;drawRect = false;drawCircle = false;drawOval = false;drawTriangle = false;drawTriangleTower = false;drawEquilateral = false;drawRightAngle = false;break;case "fillPaint":selectPath = fillPaths.get(fillPaths.size() - 1);this.pathType = "fillPath";drawPath = true;drawRect = false;drawCircle = false;drawOval = false;drawTriangle = false;drawTriangleTower = false;drawEquilateral = false;drawRightAngle = false;break;case "rect":drawPath = false;drawRect = true;drawCircle = false;drawOval = false;drawTriangle = false;drawTriangleTower = false;drawRightAngle = false;drawEquilateral = false;break;case "drawCircle":drawRect = false;drawPath = false;drawOval = false;drawCircle = true;drawTriangle = false;drawTriangleTower = false;drawEquilateral = false;drawRightAngle = false;break;case "drawOval":drawPath = false;drawRect = false;drawCircle = false;drawOval = true;drawTriangle = false;drawTriangleTower = false;drawEquilateral = false;drawRightAngle = false;break;case "drawTriangleTower":drawPath = false;drawRect = false;drawCircle = false;drawOval = false;drawTriangle = false;drawTriangleTower = true;drawEquilateral = false;drawRightAngle = false;break;case "drawTriangle":drawPath = false;drawRect = false;drawCircle = false;drawOval = false;drawTriangleTower = false;drawTriangle = true;drawRightAngle = false;drawEquilateral = false;break;case "drawEquilateral":drawPath = false;drawRect = false;drawCircle = false;drawOval = false;drawTriangleTower = false;drawTriangle = false;drawRightAngle = false;drawEquilateral = true;break;case "drawRightAngle":drawPath = false;drawRect = false;drawCircle = false;drawOval = false;drawTriangleTower = false;drawTriangle = false;drawEquilateral = false;drawRightAngle = true;break;}}public void setPaintColor(int color) {Paint paint = new Paint();paint.setColor(color);paint.setAntiAlias(true);paint.setStyle(Paint.Style.STROKE);paint.setStrokeCap(Paint.Cap.ROUND);paint.setStrokeJoin(Paint.Join.ROUND);paint.setStrokeWidth(3);paints.add(paint);Paint fillPaint = new Paint();fillPaint.setColor(color);fillPaint.setStyle(Paint.Style.STROKE);fillPaint.setStrokeWidth(20);fillPaint.setAntiAlias(true);fillPaint.setDither(true);fillPaint.setAlpha(250);fillPaint.setStrokeJoin(Paint.Join.ROUND);fillPaint.setStrokeCap(Paint.Cap.ROUND);fillPaints.add(fillPaint);Path path = new Path();Path fillPath = new Path();Path triangleTowerPath = new Path();this.triangleTowerPath = triangleTowerPath;this.triangleTowerPaints.add(paint);triangleTowerPaths.add(triangleTowerPath);Path trianglePath = new Path();trianglePaths.add(trianglePath);trianglePaints.add(paint);fillPaths.add(fillPath);paintPaths.add(path);Path anglePath = new Path();anglePaths.add(anglePath);this.anglePath = anglePath;this.anglePaints.add(paint);Path equilateralPath = new Path();this.equilateralPath = equilateralPath;equilateralPaths.add(equilateralPath);equilateralPaints.add(paint);if (pathType.equals("paintPath")) {selectPath = path;} else {selectPath = fillPath;}}public void setEraseMode() {eraseMode = true;drawPath = true;drawRect = false;drawTriangleTower = false;drawTriangle = false;drawCircle = false;drawEquilateral = false;drawRightAngle = false;erasePath = new Path();erasePaint.setAntiAlias(true);erasePaint.setDither(true);erasePaint.setStrokeWidth(30);erasePaint.setStyle(Paint.Style.STROKE);erasePaint.setStrokeJoin(Paint.Join.ROUND);erasePaint.setStrokeCap(Paint.Cap.SQUARE);erasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));selectPath = erasePath;}private class Circle {int pointX;int pointY;int radius;public int getPointX() {return pointX;}public void setPointX(int pointX) {this.pointX = pointX;}public int getPointY() {return pointY;}public void setPointY(int pointY) {this.pointY = pointY;}public int getRadius() {return radius;}public void setRadius(int radius) {this.radius = radius;}}
}

三、总结

绘制主要用到Paint和Path,大体思路都是监听OnTouchEvent事件,绘制路径或者其他图案,开发这个项目用时一周。

相关文章:

【安卓】自定义View实现画板涂鸦等功能

一、实现效果 二、代码 1、MainActivity.class package com.lsl.mydrawingboarddemo;import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat;import android.os.Bundle; import android.os.Handler; import android.view.View; impo…...

面试题. 搜索旋转数组

搜索旋转数组。给定一个排序后的数组&#xff0c;包含n个整数&#xff0c;但这个数组已被旋转过很多次了&#xff0c;次数不详。请编写代码找出数组中的某个元素&#xff0c;假设数组元素原先是按升序排列的。若有多个相同元素&#xff0c;返回索引值最小的一个。 示例1: 输入…...

前端需要理解的数据治理与异常监控知识

1 数据治理 前端数据治理的重要指标是准确性和数据&#xff0c;一个数据对象包括数据值和其他元数据。 2 数据上报方式 2.1 Image 通过将采集的数据拼接在图片请求的后面&#xff0c;向服务端请求一个 1*1 px 大小的图片&#xff08;gif&#xff09;实现的&#xff0c;设置…...

ip_vs 原理解析 (四)hook 后的开始 一

文章目录 ip_vs hook 后NF_INET_LOCAL_IN 本章重点&#xff1a; k8s 如何利用 ip_vs 实现源 IP 会话亲和性。 ip_vs hook 后 NF_INET_LOCAL_IN 根据优先级依次是 ip_vs_reply4&#xff0c;ip_vs_remote_request4 ip_vs_reply4| -- ip_vs_out| -- skb_to_full_sk(skb&#xf…...

【论文解读】基于图的自监督学习联合嵌入预测架构

一、简要介绍 本文演示了一种学习高度语义的图像表示的方法&#xff0c;而不依赖于手工制作的数据增强。论文介绍了基于图像的联合嵌入预测架构&#xff08;I-JEPA&#xff09;&#xff0c;这是一种用于从图像中进行自监督学习的非生成性方法。I-JEPA背后的idea很简单&#xff…...

C++ 容器

string 1. 构造 string s1(); // 1 string s2("hello"); // hello string s3(3, k); // kkk string s4("hellohello", 2, 4); // lloh2. 赋值 string s1 "hellohello"; // hellohello string s2.assign(s1); // he…...

【PHP】PHP文件操作详解

PHP是一种广泛使用的服务器端脚本语言&#xff0c;用于开发Web应用程序。在PHP中&#xff0c;文件操作是一项重要的功能&#xff0c;包括文件的读取、写入、删除和其他操作。本文将详细介绍PHP文件操作的各个方面&#xff0c;并通过示例代码进行说明。 一、文件读取 要读取一…...

硬核旗舰南卡OE CC开放式耳机发布,重新定义百元开放式耳机新标杆!

​随着现在健康意识的不断提高&#xff0c;人们对于日常用品的要求越来越高&#xff0c;在耳机市场中&#xff0c;健康因素也逐渐成为消费者所考虑的因素之一&#xff0c;入耳式耳机因为会引发中耳炎、耳膜炎等疾病&#xff0c;正在逐渐被开放式蓝牙耳机所取代&#xff0c;南卡…...

785. 判断二分图

785. 判断二分图 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 785. 判断二分图 https://leetcode.cn/problems/is-graph-bipartite/description/ 完成情况&#xff1a; 解题思路&#xff1a; 题目解释&#x…...

限时 180 天,微软为 RHEL 9 和 Ubuntu 22.04 推出 SQL Server 2022 预览评估版

导读近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布面向 Red Hat Enterprise Linux&#xff08;RHEL&#xff09;9 和 Ubuntu 22.04 两大发行版&#xff0c;以预览模式推出 SQL Server 2022 评估版。 近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布…...

一款ccm的功率因素校正控制器ncp1654

产品概述&#xff1a; NCP1654是用于连续传导模式的控制器&#xff08;CCM&#xff09;功率因数校正升压预转换器。它控制固定频率模式下的电源开关导通时间&#xff08;PWM&#xff09;并且取决于瞬时线圈电流。 该电路封装在SO8封装中&#xff0c;最大限度地减少了外部组件&a…...

4.若依框架上传文件

1.服务端代码-控制器 /*** 上传文件*/PostMapping("/upload")Operation(summary "上传")public AjaxResult uploadFile(MultipartFile file) throws Exception {try {// 上传文件路径String filePath RuoYiConfig.getUploadPath();// 上传并返回新文件名…...

Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required

项目场景&#xff1a; 最近因为公司业务需要在搭一个新架构&#xff0c;用的springboot3和jdk17,在整合mybatis多数据源的时候报错 &#xff08;引用的mybatisplus 和 mybatisplusjion的是最新的包-2023-08-26&#xff09; Error creating bean with name ‘XXXServiceImpl’:…...

idea的debug断点的使用

添加断点&#xff08;目前不知道如何添加断点&#xff0c;就给AutoConfigurationImportSelector的每个方法都加上断点&#xff09;&#xff1a; 然后将StockApplication启动类以debug方式运行&#xff0c;然后程序就会停在119行 点击上边的step over让程序往下运行一行&#x…...

【UE】蓝图通信——事件分发器

目标 比如我现在希望点击控件蓝图A中的按钮后&#xff0c;蓝图B能够马上做出响应 实现步骤 1. 这里控件蓝图A叫“UI_按钮”&#xff0c;我在该蓝图中创建了一个名为“btnIsClicked”的事件分发器 当按钮被点击时&#xff0c;就会调用“btnIsClicked” 2. 蓝图B这里叫做“BP_…...

Python爬虫分布式架构问题汇总

在使用Python爬虫分布式架构中可能出现以下的问题&#xff0c;我们针对这些问题&#xff0c;列出相应解决方案&#xff1a; 1、任务重复执行 在分布式环境下&#xff0c;多个爬虫节点同时从消息队列中获取任务&#xff0c;可能导致任务重复执行的问题。 解决方案&#xff1a;…...

AIGC人工智能涉及三十六职业,看看有没有你的职业(一)

文章目录 一只弹吉他的熊猫 神奇的企鹅 功夫熊猫 视觉光影下的女子 闪光灯效 局部柔光 生物光 LOGO设计 制作儿童绘本故事 换脸艺术 打造专属动漫头像 包装设计之美 建筑设计 如何转高清图 生成3D质感图标 生成微信表情包 探索美食摄影的奇妙之旅 蛋糕创意设…...

万界星空科技/免费MES系统/免费质量检测系统

质量管理也是万界星空科技免费MES中的一个重要组成部分&#xff0c;旨在帮助制造企业实现全面的质量管理。该系统涵盖了供应商来料、生产过程、质量检验、数据分析等各个环节&#xff0c;为企业提供了一站式的质量管理解决方案。 1. 实时质量监控 质量管理能够实时监控生产过程…...

解决IndexError: index 0 is out of bounds for axis 1 with size 0

标题 引言问题背景解决思路如何防止总结参考资料 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 &#x1f369;惟余辈才疏学浅&#xff0c;临摹之作或有不妥之处&#xff0c;还请读者海涵指正。☕&#x1f36d;…...

Java中hashTable的基本介绍,细节讨论,使用注意事项,常用方法和底层的扩容机制

Hashtable 是 Java 标准库中提供的一个古老的散列表&#xff08;Hash Table&#xff09;实现&#xff0c;用于存储键值对。它是线程安全的&#xff0c;基于哈希表的数据结构。然而&#xff0c;由于其线程安全性引入的同步机制&#xff0c;使得在多线程环境下性能相对较低。在现…...

redis -实战记录

redis -实战记录 一、安装二、使用 一、安装 centos - docker安装redisWindows10安装redis(图文教程&#xff09; 二、使用 node-red进行读写redis...

Mysql知识梳理

Mysql知识梳理 索引索引分类索引未命中的原因性能调优命令Explain回表 mysql性能优化事务四大特性事务隔离级别设置事务隔离级别 存储引擎聚簇索引和非聚簇索引聚簇索引非聚簇索引 最左前缀结合原则全文索引 索引 索引分类 mysql有普通索引、空间索引、主键索引、唯一索引、组…...

文生图模型之Stable Diffusion

原始文章地址 autoencoder CLIP text encoder tokenizer最大长度为77&#xff08;CLIP训练时所采用的设置&#xff09;&#xff0c;当输入text的tokens数量超过77后&#xff0c;将进行截断&#xff0c;如果不足则进行paddings&#xff0c;这样将保证无论输入任何长度的文本&…...

Java List循环安全删除元素

Java List循环安全删除元素的几种方式如下&#xff1a; 使用迭代器&#xff08;Iterator&#xff09;&#xff1a;通过调用List的iterator()方法获取List的迭代器&#xff0c;然后使用迭代器的remove()方法删除元素。这种方式可以避免在遍历过程中修改List导致的并发修改异常&…...

2023年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:和数 给定一个正整数序列,判断其中有多少个数,等于数列中其他两个数的和。 比如,对于数列1 2 3 4, 这个问题的答案就是2, 因为3 = 2 + 1, 4 = 1 + 3。 时间限制:10000 内存限制:65536 输入 共两行,第一行是数列中数的个数n ( 1 <= n <= 100),第二行是由n个…...

bert-base-chinese 判断上下句

利用BERT等模型来实现语义分割。BERT等模型在预训练的时候采用了NSP&#xff08;next sentence prediction&#xff09;的训练任务&#xff0c;因此BERT完全可以判断两个句子&#xff08;段落&#xff09;是否具有语义衔接关系。这里我们可以设置相似度阈值 MERGE_RATIO &#…...

vue3+vue-cli使用mockjs

1.下载mockjs包 npm i mockjs -D 2.main.js中全局引入 // mock模拟后端数据 import /mock/index.js 3.axios下baseUrl注释掉&#xff0c;让其不走本地代理 // 使用mock数据的话&#xff0c;将这一项注释即可 // axios.defaults.baseURL process.env.VUE_APP_BASE_API; 4.s…...

Android 全局监听软键盘弹起隐藏 动态修改布局并适配无限循环的问题

思路&#xff1a; 要在 Android 应用中全局检测软键盘的弹起&#xff0c;您可以使用 ViewTreeObserver.OnGlobalLayoutListener 监听器来监听布局树的变化。当软键盘弹起或隐藏时&#xff0c;布局树会发生变化&#xff0c;因此您可以在监听器中捕获这些变化。 以下是一个示例…...

第 k 小整数

题目描述 现有 n 个正整数&#xff0c;要求出这 n 个正整数中的第 k 个最小整数&#xff08;相同大小的整数只计算一次&#xff09;。 输入格式 第一行为 n 和 k; 第二行开始为 n 个正整数的值&#xff0c;整数间用空格隔开。 输出格式 第kk个最小整数的值&#xff1b;若无…...

LeetCode 1448. 统计二叉树中好节点的数目:DFS

【LetMeFly】1448.统计二叉树中好节点的数目 力扣题目链接&#xff1a;https://leetcode.cn/problems/count-good-nodes-in-binary-tree/ 给你一棵根为 root 的二叉树&#xff0c;请你返回二叉树中好节点的数目。 「好节点」X 定义为&#xff1a;从根到该节点 X 所经过的节点…...

AR室内导航技术之技术说明与效果展示

随着科技的飞速发展&#xff0c;我们周围的环境正在经历着一场数字化的革命。其中&#xff0c;AR室内导航技术以其独特的魅力&#xff0c;为我们打开了一扇通往全新数字化世界的大门。本文将为您详细介绍这一技术的实现原理、工具应用以及成品展示&#xff0c;带您领略AR室内导…...

06-Numpy基础-线性代数

线性代数&#xff08;如矩阵乘法、矩阵分解、行列式以及其他方阵数学等&#xff09;是任何数组库的重要组成部分。 NumPy提供了一个用于矩阵乘法的dot函数&#xff08;既是一个数组方法也是numpy命名空间中的一个函数&#xff09; x.dot(y)等价于np.dot(x, y) 符&#xff08;…...

SpringBootWeb 登录认证

登录认证&#xff0c;那什么是认证呢&#xff1f; 所谓认证指的就是根据用户名和密码校验用户身份的这个过程&#xff0c;认证成功之后&#xff0c;我们才可以访问系统当中的信息&#xff0c;否则就拒绝访问。 在前面的案例中&#xff0c;我们已经实现了部门管理、员工管理的…...

【JVM 内存结构丨栈】

栈 -- 虚拟机栈 简介定义压栈出栈局部变量表操作数栈方法调用特点作用 本地方法栈&#xff08;C栈&#xff09;定义栈帧变化作用对比 主页传送门&#xff1a;&#x1f4c0; 传送 简介 栈是用于执行线程的内存区域&#xff0c;它包括局部变量和操作数栈。 Java 虚拟机栈会为每…...

LeetCode 138.复制带随机指针的链表

文章目录 &#x1f4a1;题目分析&#x1f4a1;解题思路&#x1f6a9;步骤一&#xff1a;拷贝节点插入到原节点的后面&#x1f369;步骤一代码 &#x1f6a9;步骤二&#xff1a;控制拷贝节点的random进行连接&#x1f369;步骤二代码 &#x1f6a9;步骤三&#xff1a;拷贝节点解…...

基于SSM的小说网站的设计与实现(论文+源码)_kaic

目 录 1 绪论................................................................................................... 1 1.1 项目背景................................................................................................................ 1 1.2 发展历程..…...

【Python】代理池针对ip拦截破解

代理池是一种常见的反反爬虫技术&#xff0c;通过维护一组可用的代理服务器&#xff0c;来在被反爬虫限制的情况下&#xff0c;实现数据的爬取。但是&#xff0c;代理池本身也面临着被目标网站针对ip进行拦截的风险。 本文将详细介绍代理池针对ip拦截破解的方法&#xff0c;包含…...

P1065 [NOIP2006 提高组] 作业调度方案

[NOIP2006 提高组] 作业调度方案 题目描述 我们现在要利用 m m m 台机器加工 n n n 个工件&#xff0c;每个工件都有 m m m 道工序&#xff0c;每道工序都在不同的指定的机器上完成。每个工件的每道工序都有指定的加工时间。 每个工件的每个工序称为一个操作&#xff0c;…...

设计模式三原则

1.1单一职责原则 C 面向对象三大特性之一的封装指的就是将单一事物抽象出来组合成一个类&#xff0c;所以我们在设计类的时候每个类中处理的是单一事物而不是某些事物的集合。 设计模式中所谓的单一职责原则&#xff0c;就是对一个类而言&#xff0c;应该仅有一个引起它变化的原…...

dll载入时发生的事情

dll是什么 DLL 是一个包含可由多个程序同时使用的代码和数据的库。 对于 Windows 操作系统&#xff0c;操作系统的大部分功能都由 DLL 提供。 另外&#xff0c;当您在这些 Windows 操作系统之一上运行某一程序时&#xff0c;该程序的很多功能可能是由 DLL 提供的。 例如&…...

k8s-ingress-context deadline exceeded

报错&#xff1a; rancher-rke-01:~/rke # helm install rancher rancher-latest/rancher --namespace cattle-system --set hostnamewww.rancher.local Error: INSTALLATION FAILED: Internal error occurred: failed calling webhook "validate.nginx.ingress.kube…...

css盒模型

盒模型的组成&#xff1a; content&#xff0c;padding&#xff0c;border&#xff0c;margin 盒模型的分类&#xff1a; 内容盒模型(标准盒模型) — 盒子的宽widthpaddingborder 边框盒模型 — 盒子的宽width 参考 盒模型【CSS面试题】_哔哩哔哩_bilibili...

cuda11.1和cuDNN v8.8.1的安装目录问题

cuda的不同版本文件路径是不一致的&#xff0c;在cuda10.1中&#xff0c;配置cudnn的文件路径是&#xff1a; sudo cp cuda/include/cudnn.h /usr/local/cuda-10.1/include/ sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda-10.1/lib64/但是在cuda11.1中&#xff0c;文件路径…...

微信小程序scroll-view的触发机制

一、scroll-view 可滚动视图区域。使用竖向滚动时&#xff0c;需要给scroll-view一个固定高度&#xff0c;通过 WXSS 设置 height。组件属性的长度单位默认为px&#xff0c;2.4.0起支持传入单位(rpx/px)。 两个属性是作为上拉加载下拉刷新触发事件 scroll-view属性bindrefresh…...

为本地文件创建URL

1.搭建Nginx流媒体服务器 2.nginx.conf中添加 server {#listen 80 default_server;#listen [::]:80 default_server;location /var/www/html/Dir {autoindex on;}root /var/www/html; # 设置默认网页的根目录index index.html; # 设置默认网页的文件名}在/var/www/html中加…...

UI位置与布局

UI位置与布局 引言 发现UGUI的RectTransform定位还是很复杂的&#xff0c;感觉有必要详细了解一下 RectTransform 继承自Transform。他的local position由其他几个变量控制。建议不要直接设置position 目的是为了实现UI自动布局。这套方法将绝对定位&#xff0c;相对定位&a…...

《存储IO路径》专题:DDIO对系统性能的影响

DDIO对系统性的影响 想象一下,有一天,你在网上冲浪,突然,一个巨大的数据包从天而降,直接砸在了你的电脑上。你一看,哇,是全新的《英雄联盟》版本!你迫不及待地打开了游戏,发现加载速度简直快如闪电。 那么,这个神奇的事情是怎么发生的呢? 其实,这都要归功于DDIO技…...

ModaHub魔搭社区:WinPlan经营大脑数据采集

目录 WinPlan经营大脑数据采集介绍 WinPlan经营大脑数据采集模版 WinPlan经营大脑数据采集介绍 基于指标、维度来创建业务表单,通过业务表单的形式来采集实际数据,最终生成企业统一的经营数据库。由于需要客户创建数据采集模版(业务流程),然后可以基于各个业务模版作为…...

缓存最佳实践

目录 前言 一、Cache Aside&#xff08;旁路缓存&#xff09;策略 二、不一致解决场景及解决方案 一、数据库主从不一致 二、缓存与数据库不一致 三、问题分析 三、缓存误用 一、多服务共用缓存实例 二、调用方缓存数据 三、缓存作为服务与服务之间传递数据的媒介 四…...

Linux 终端命令之文件目录操作,对比Dos相关命令

目录 前言 基础命令&#xff08;文件目录相关的&#xff09; cd命令 【英文帮助】 【对应Dos命令】 pwd命令 【英文帮助】 【对应Dos命令】 ls命令 【英文帮助】 【对应Dos命令】 tree命令 【英文帮助】 【对应Dos命令】 mkdir命令 【英文帮助】 【对应Dos命令…...