宁睿 发表于 2025-4-12 19:46:00

使用 Android Studio 举行画布等功能的操纵,Java语言,对小白友爱

##实现功能

用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)变乱,可以举行移动图形
注意:流动边框的添加应陪同于鼠标点击的过程。
#这是我写的类的分类,以下会徐徐解释每个部分
   https://i-blog.csdnimg.cn/direct/dd9a884fa19640eaad3ad7a505a9848b.png   这是我的类文件分类     
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:
abstractclass 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、点击产生的图形

https://i-blog.csdnimg.cn/direct/b80478d491db481d80e9f0fba8b9db7e.png
2、点击图形弹出是否删除的会话框,可以看到所点击的图形有虚线框举行标识

https://i-blog.csdnimg.cn/direct/944478bd7bde41f68e0b93948058a117.png
3、移动图形,所选图形向上移动,(静态截图显示不了鼠标)

https://i-blog.csdnimg.cn/direct/db4cc48639404ffa9b9f90dc7bb49782.png           https://i-blog.csdnimg.cn/direct/6e1ee0ca601d4df68a334493aa384987.png
6、如有必要完备代码请私信,谢谢各人的浏览,如有发起接待私信^_^

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 使用 Android Studio 举行画布等功能的操纵,Java语言,对小白友爱