使用 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]