利用 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)事件,可以进行移动图形

注意:流动边框的添加应伴随于鼠标点击的过程。


#这是我写的类的分类,以下会逐步解释每个部分

这是我的类文件分类

 

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、如有需要完整代码请私信,谢谢大家的浏览,如有建议欢迎私信^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值