##实现功能
用Android studio 举行编译一个步伐。步伐要求:
1、点击空白区域能产生随机图形(简朴图形有:三角形,圆形,矩形:复杂图形:凹字形,凸字形,拱门形,心形)
2、点击图形可以弹出是否删除的会话框
3、长按图形可以对图形举行推拽
4、附加功能:使点击图形的时候可以或许被识别:添加流动的虚线框
##重要的功能弄块
1、对Paint类、Path类、Canvas类的使用,图形绘画和虚线边框绘画部分
2、对点击变乱识别onTouchEvent触摸变乱(ACTION_DOWN、ACTION_MOVE、ACTION_UP)
##功能进程分析
首先是对鼠标点击变乱举行过程分析,统共分三个进程,分别为鼠标点击时(ACTION_DOWN),鼠标移动时(ACTION_MOVE),鼠标抬起时(ACTION_UP)。
1、当为鼠标点击时(ACTION_DOWN):首先要识别空白画面是否有图形,如果没有则天生图形,如果存在图形就举行手势的点击检测,以便举行下一步的判断。
2、对有图形的手势的点击检测:如果点击时间小于500毫秒则为点击变乱,对接鼠标抬起时(ACTION_UP)变乱,弹出是否删除的会话框。
3、对有图形的手势的点击检测:如果点击时间长于500毫秒则为长按变乱,对接鼠标移动时(ACTION_MOVE)变乱,可以举行移动图形
注意:流动边框的添加应陪同于鼠标点击的过程。
#这是我写的类的分类,以下会徐徐解释每个部分
这是我的类文件分类
1、CustomCanvasView类的构建意义和相关代码
构建意义:举行相关属性的定义,如Paint、Path、Canva、Random、ValueAnimator(动画相关类)、坐标等。
举行对全局属性的定义于设置,如对图形的填充方式(FILL),对图形边框的填充方式(STORK
)以及绘画边框的颜色和流动情况
CustomCanvasView:
- import android.animation.ValueAnimator;
- import android.content.Context;
- import android.graphics.*;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
- import androidx.appcompat.app.AlertDialog;
- import java.util.ArrayList;
- import java.util.Random;
- // 自定义绘图视图,继承自Android的View类
- public class CustomCanvasView extends View {
- private ArrayList<Shape> shapes = new ArrayList<>(); // 存储所有图形的列表
- private Paint paint, borderPaint; // 主画笔(填充图形)和边框画笔(绘制选中效果)
- private Random random = new Random(); // 随机数生成器,用于创建随机颜色和图形类型
- private Shape selectedShape;// 当前选中的图形对象
- private float lastX, lastY; // 记录上次触摸点的坐标
- private float phase; // 虚线动画的相位(控制虚线流动)
- private ValueAnimator phaseAnimator; // 相位动画控制器
- // 构造函数,初始化视图
- public CustomCanvasView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(); // 调用初始化方法
- }
- // 初始化方法:设置画笔和动画
- private void init() {
- // 初始化主画笔(填充图形用)
- paint = new Paint();
- paint.setAntiAlias(true); // 开启抗锯齿
- paint.setStyle(Paint.Style.FILL); // 填充模式
- // 初始化边框画笔(选中效果用)
- borderPaint = new Paint();
- borderPaint.setAntiAlias(true);
- borderPaint.setStyle(Paint.Style.STROKE); // 描边模式
- borderPaint.setStrokeWidth(8); // 边框粗细
- borderPaint.setColor(Color.BLUE); // 边框颜色
- // 创建虚线动画(让边框虚线流动起来)
- phaseAnimator = ValueAnimator.ofFloat(0, 100); // 动画数值范围 // 创建浮点数值动画器,数值范围从0到100(用于驱动相位变化)
- phaseAnimator.setDuration(1500); // 设置动画持续时间为1500毫秒(1.5秒)
- phaseAnimator.setRepeatCount(ValueAnimator.INFINITE); // 设置无限循环模式(动画会持续重复播放)
- phaseAnimator.addUpdateListener(animation -> { // 添加动画更新监听器(每帧更新时触发)
- phase = (float) animation.getAnimatedValue(); // 获取当前动画值并更新相位变量(强制转换为float类型)
- invalidate(); // 请求重绘视图(自动调用View的onDraw方法)
- });
- phaseAnimator.start(); // 启动动画(开始执行数值变化)
- }
- // 绘制方法(Android系统自动调用)
- @Override
- protected void onDraw(Canvas canvas) { // 绘制方法(Android系统自动调用)
- super.onDraw(canvas);
- // 增强型for循环:遍历shapes集合中的所有元素
- // Shape shape : shapes → 每次循环将集合元素赋值给shape变量
- for (Shape shape : shapes) {
- // 调用当前图形的draw方法,完成基础绘制
- // paint: 图形填充画笔,canvas: 绘制画布
- shape.draw(paint, canvas);
- // 检查当前图形是否被选中
- // shape.isSelected → 访问图形的选中状态属性(假设为布尔类型)
- if (shape.isSelected) {
- // 如果被选中,调用drawBorder方法绘制特殊边框
- // borderPaint: 边框专用画笔(通常设置虚线样式)
- // phase: 虚线动画相位值(控制虚线流动效果)
- shape.drawBorder(borderPaint, canvas, phase);
- }
- }
- }
- // 处理触摸事件的方法
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- float x = event.getX();// 获取触摸点坐标
- float y = event.getY();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:// 手指按下
- handleTouchDown(x, y);
- return true;
- case MotionEvent.ACTION_MOVE:// 手指移动
- handleTouchMove(x, y);
- return true;
- case MotionEvent.ACTION_UP:// 手指抬起
- handleTouchUp();
- return true;
- }
- return super.onTouchEvent(event);
- }
- // 处理按下事件
- private void handleTouchDown(float x, float y) {
- selectedShape = findShape(x, y); // 1. 查找点击位置是否有图形 findShape(x, y)
- lastX = x;
- lastY = y; // 2. 记录当前触摸点坐标
- // 遍历图形集合中的所有图形对象
- for (Shape shape : shapes) {
- // 设置当前图形的选中状态:只有当该图形是当前选中对象时才标记为选中
- // (通过对象地址比较实现精准匹配)
- shape.setSelected(shape == selectedShape);
- }
- // 判断当前是否存在已选中的图形
- if (selectedShape == null) {
- // 当没有选中任何图形时:
- // 根据点击坐标(x,y)和随机数生成器,通过工厂模式创建随机类型的新图形
- // 并将新图形添加到图形集合中(例如圆形/矩形等)
- shapes.add(ShapeFactory.createRandomShape(x, y, random));
- } else {
- // 当存在选中图形时:
- // 启动长按手势检测(通常通过Handler.postDelayed实现计时逻辑)
- // 0.5秒后若仍未松开手指,则触发拖拽操作(防止误触)
- startDragDetection();
- }
- invalidate();// 刷新界面
- }
- // 处理移动事件
- // 处理触摸移动事件的方法,参数x/y表示当前触摸点坐标
- private void handleTouchMove(float x, float y) {
- // 检查当前是否存在被选中的图形且处于拖动状态
- if (selectedShape != null && selectedShape.isDragging()) {
- // 计算x轴方向移动距离$dx = x - \text{lastX}$
- float dx = x - lastX;
- // 计算y轴方向移动距离$dy = y - \text{lastY}$
- float dy = y - lastY;
- // 调用图形对象的移动方法,传入$x$/$y$轴偏移量
- selectedShape.move(dx, dy);
- // 更新记录的上次触摸点x坐标(用于下次计算偏移量)
- lastX = x;
- // 更新记录的上次触摸点y坐标(用于下次计算偏移量)
- lastY = y;
- // 请求重绘视图(触发onDraw()更新图形位置)
- invalidate();
- }
- }
- // 处理抬起事件
- private void handleTouchUp() {
- // 如果不是拖动状态,显示删除对话框
- if (selectedShape != null && !selectedShape.isDragging()) {
- showDeleteDialog();
- }
- if (selectedShape != null) {// 结束拖动状态
- selectedShape.setDragging(false);
- }
- }
- // 启动长按检测(延迟500毫秒)
- private void startDragDetection() {
- postDelayed(() -> {
- if (selectedShape != null) {
- selectedShape.setDragging(true);
- }
- }, 500);
- }
- // 显示删除确认对话框
- private void showDeleteDialog() {
- new AlertDialog.Builder(getContext())
- .setTitle("删除图形")
- .setMessage("确定要删除此图形吗?")
- .setPositiveButton("是", (dialog, which) -> {
- shapes.remove(selectedShape);
- invalidate();
- })
- .setNegativeButton("否", null)
- .show();
- }
- // 查找包含指定坐标的图形(从后往前找,实现上层优先)
- // 定义私有方法,用于根据坐标查找符合条件的图形
- private Shape findShape(float x, float y) {
- // 逆向遍历图形列表(从最后一个元素开始)
- // 目的:实现"最后添加的图形优先检测"的层叠逻辑
- for (int i = shapes.size()-1; i >= 0; i--) {
- // 获取当前索引对应的图形对象
- Shape shape = shapes.get(i);
- // 检测坐标(x,y)是否在当前图形范围内
- if (shape.contains(x, y)) {
- // 找到符合条件的图形,立即返回
- return shape;
- }
- }
- // 遍历完所有图形仍未找到,返回null表示未命中
- return null;
- }
复制代码 2、Shape基类的构建意义和代码
构建意义:举行所有图形产生的共有功能的抽象如:主题绘画方法、边框绘画方法、界限检测方法、移动方法、被选中和拖拽状态
Shape:
- abstract class Shape {
- protected float x, y;// 图形中心坐标
- protected int color; // 图形颜色
- private boolean isDragging = false;// 是否正在拖动
- public boolean isSelected = false;// 是否被选中
- // 构造函数
- public Shape(float x, float y, int color) {
- this.x = x;
- this.y = y;
- this.color = color;
- }
- // 抽象方法:由子类实现具体绘制逻辑
- public abstract void draw(Paint paint, Canvas canvas);
- // 抽象方法:绘制选中边框
- public abstract void drawBorder(Paint borderPaint, Canvas canvas, float phase);
- // 抽象方法:检测坐标是否在图形内
- public abstract boolean contains(float touchX, float touchY);
- // 公共方法 // 移动图形的方法
- public void move(float dx, float dy) {
- x += dx;
- y += dy;
- }
- // Getter和Setter方法
- // 设置当前对象是否被选中的状态
- public void setSelected(boolean selected) {
- isSelected = selected; // 将参数值直接赋值给成员变量isSelected
- }
- // 设置当前对象是否处于拖拽状态
- public void setDragging(boolean dragging) {
- isDragging = dragging; // 将参数值直接赋值给成员变量isDragging
- }
- // (被注释的获取选中状态方法)
- // public boolean isSelected() {
- // return isSelected; // 原设计用于返回当前选中状态
- // }
- // 获取当前拖拽状态
- public boolean isDragging() {
- return isDragging; // 直接返回成员变量isDragging的值
- }
- // 通用点击检测方法(使用Android的Region类)
- // 检查指定坐标(x,y)是否在Path路径构成的区域内
- protected boolean checkInRegion(Path path, float x, float y) {
- // 阶段1:计算路径的包围矩形
- RectF bounds = new RectF(); // 创建存储边界信息的矩形对象
- path.computeBounds(bounds, true); // 计算路径的最小包围矩形,true表示精确计算
- // 阶段2:创建路径对应的区域对象
- Region region = new Region(); // 创建空区域对象
- region.setPath(path, new Region( // 将Path转换为Region,需要指定剪裁范围
- (int) bounds.left, // 左边界取整
- (int) bounds.top, // 上边界取整
- (int) bounds.right, // 右边界取整
- (int) bounds.bottom // 下边界取整
- )); // 这里创建了一个与路径边界匹配的剪裁区域
- // 阶段3:坐标检测
- return region.contains((int)x, (int)y); // 将坐标转为整型后检测是否在区域内
- }
- }
复制代码 3.ShapeFactory类的构建意义和代码
构建意义:由于是点击空白处举行随机图形的天生,所以使用随机数举行实现
ShapeFactory:
- class ShapeFactory {
- static Shape createRandomShape(float x, float y, Random random) {
- // 生成随机类型(1-7)
- int type = random.nextInt(7) + 1;
- // 生成随机颜色(RGB各分量0-255)
- int color = Color.rgb(
- random.nextInt(256),
- random.nextInt(256),
- random.nextInt(256)
- );
- // 根据类型创建具体图形
- switch (type) {
- case 1: return new Shape1_Triangle(x, y, color); // 三角形
- case 2: return new Shape2_Circle(x, y, color); // 圆形
- case 3: return new Shape3_Rectangle(x, y, color); // 矩形
- case 4: return new Shape4_ConcaveShape(x, y, color); // 凹形
- case 5: return new Shape5_ConvexShape(x, y, color); // 凸形
- case 6: return new s6_ArchShape(x, y, color); // 拱门形
- case 7: return new Shape7_HeartShape(x, y, color); // 心形
- default: return new Shape2_Circle(x, y, color); // 默认返回圆形
- }
- }
- }
复制代码 4、各个图形类应包罗图形绘制、流动边框绘制和界限检测
Shape1_Triangle:
- class Shape1_Triangle extends Shape {
- public Shape1_Triangle(float x, float y, int color) {
- super(x, y, color);
- }
- @Override
- public void draw(Paint paint, Canvas canvas) {
- paint.setColor(color); // 设置颜色
- Path path = createPath(); // 创建路径
- canvas.drawPath(path, paint); // 绘制路径
- }
- @Override
- public void drawBorder(Paint borderPaint, Canvas canvas, float phase) {
- //设置虚线路径效果,实线20像素+虚线20像素,phase控制起始偏移量(用于动画效果)
- borderPaint.setPathEffect(new DashPathEffect(new float[]{20, 20}, phase));
- canvas.drawPath(createPath(), borderPaint); // 绘制相同路径作为边框
- }
- @Override
- public boolean contains(float x, float y) {
- return checkInRegion(createPath(), x, y);// 使用基类检测方法
- }
- private Path createPath() {
- Path path = new Path();
- path.moveTo(this.x, this.y);
- path.lineTo(x + 100, y + 173);
- path.lineTo(x - 100, y + 173);
- path.close();
- return path;
- }
- }
复制代码 Shape2_Circle
- class Shape2_Circle extends Shape {
- public Shape2_Circle(float x, float y, int color) {
- super(x, y, color);
- }
- @Override
- public void draw(Paint paint, Canvas canvas) {
- paint.setColor(color);
- canvas.drawCircle(x, y, 50, paint);
- }
- @Override
- public void drawBorder(Paint borderPaint, Canvas canvas, float phase) {
- borderPaint.setPathEffect(new DashPathEffect(new float[]{20, 20}, phase));
- canvas.drawCircle(x, y, 55, borderPaint);
- }
- @Override
- public boolean contains(float x, float y) {
- return Math.hypot(x - this.x, y - this.y) <= 50;
- }
- }
复制代码 3、Shape3_Rectangle
- class Shape3_Rectangle extends Shape {
- public Shape3_Rectangle(float x, float y, int color) {
- super(x, y, color);
- }
- @Override
- public void draw(Paint paint, Canvas canvas) {
- paint.setColor(color);
- canvas.drawRect(x-50, y-50, x+50, y+50, paint);
- }
- @Override
- public void drawBorder(Paint borderPaint, Canvas canvas, float phase) {
- borderPaint.setPathEffect(new DashPathEffect(new float[]{20, 20}, phase));
- canvas.drawRect(x-55, y-55, x+55, y+55, borderPaint);
- }
- @Override
- public boolean contains(float x, float y) {
- return x >= this.x-50 && x <= this.x+50 &&
- y >= this.y-50 && y <= this.y+50;
- }
- }
复制代码 4、Shape4_ConcaveShape(凹形)
- class Shape4_ConcaveShape extends Shape {
- public Shape4_ConcaveShape(float x, float y, int color) {
- super(x, y, color);
- }
- @Override
- public void draw(Paint paint, Canvas canvas) {
- paint.setColor(color);
- canvas.drawPath(createMainPath(), paint);
- }
- @Override
- public void drawBorder(Paint borderPaint, Canvas canvas, float phase) {
- borderPaint.setPathEffect(new DashPathEffect(new float[]{20, 20}, phase));
- canvas.drawPath(createBorderPath(), borderPaint);
- }
- @Override
- public boolean contains(float x, float y) {
- return checkInRegion(createMainPath(), x, y);
- }
- private Path createMainPath() {
- Path path = new Path();
- path.moveTo(this.x - 50, this.y - 50);
- path.lineTo(x + 50, y - 50);
- path.lineTo(x + 50, y + 30);
- path.arcTo(new RectF(x - 40, y + 30, x + 40, y + 70), 0, 180, false);
- path.lineTo(x - 50, y + 30);
- path.close();
- return path;
- }
- private Path createBorderPath() {
- Path path = new Path();
- path.moveTo(x - 55, y - 55);
- path.lineTo(x + 55, y - 55);
- path.lineTo(x + 55, y + 35);
- path.arcTo(new RectF(x - 45, y + 35, x + 45, y + 75), 0, 180, false);
- path.lineTo(x - 55, y + 35);
- path.close();
- return path;
- }
- }
复制代码 剩下图形以及添加其他图形,各人可以按需添加。
注意:对于图形的绘制如 (x+100,y+100),这里的盘算方向于坐标轴相反的,(x,y)都加100像素其位置在(x,y)的左下角!!
5、完备步伐演示
1、点击产生的图形
2、点击图形弹出是否删除的会话框,可以看到所点击的图形有虚线框举行标识
3、移动图形,所选图形向上移动,(静态截图显示不了鼠标)
6、如有必要完备代码请私信,谢谢各人的浏览,如有发起接待私信^_^
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |