目录

Part1拓展

拓展需求:

需求分步:

具体实现

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

 此部分是基于对第一部分功能的拓展!

Part1拓展

拓展需求:

1.1 用户可以添加/删除单个目标/从选择通过控制点击

1.2 用户可以使用讲座中讨论的“橡皮筋-套索”组合方法选择多个目标

1.2.1 简化使用橡皮筋或套索的决定,以选择更多的项目

1.3 用户可以通过按下控制键和使用橡皮带套索控制来添加/删除多个目标

1.4 当多个目标被选中时,操作会影响所有选中的项目(移动,调整大小,删除)

需求分步:

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

具体实现

1.实现多选的功能,包括多选之后的删除,创建,方法缩小都得进行进行拓展

思路:实现多选功能就肯定需要一个列表对选中的目标进行存储,但之前的单选也应该保留,所以我就想到普通单选和多选可以分开存储,那么就必须保证单选和多选不会起冲突(不可以同时有单选和多选两种情况)

实现方法:

1.在iModel类中添加多选列表,并添加相关方法,包括增删改查以及Blob是否在列表中等方法。

public class InteractionModel {

Blob selected;

// 多选列表

List selectedBlobs = new ArrayList<>();

public void setSelected(Blob b) {

clearSelectedBlobs();

selected = b;

notifySubscribers();

}

public void unselect() {

selected = null;

notifySubscribers();

}

public Blob getSelected() {

return selected;

}

// 多选列表操作的相关方法

public void setSelectedBlobs(List blobs){

selectedBlobs = blobs;

notifySubscribers();

}

public void addSelectedBlobs(Blob blob){

selectedBlobs.add(blob);

notifySubscribers();

}

public void deleteSelectedBlob(Blob blob){

Iterator it = selectedBlobs.iterator();

while (it.hasNext()) {

Blob s = it.next();

if (blob.equals(s)) {

it.remove();

}

}

notifySubscribers();

}

public void clearSelectedBlobs(){

selectedBlobs.clear();

notifySubscribers();

}

public List getSelectedBlobs(){

return selectedBlobs;

}

public boolean containedInSelected(Blob blob){

Iterator it = selectedBlobs.iterator();

while (it.hasNext()) {

Blob s = it.next();

if (blob.equals(s)) {

return true;

}

}

return false;

}

}

2.在View类中的画操作中添加对多选目标的显示

仅仅只需要对imodel的多选列表中的对象显示即可

public class BlobView extends StackPane implements BlobModelListener, IModelListener {

private void draw() {

gc.clearRect(0,0,myCanvas.getWidth(),myCanvas.getHeight());

model.getBlobs().forEach(b -> {

// 仅仅只需要对imodel的多选列表中的对象显示即可

if (b == iModel.getSelected() || iModel.containedInSelected(b)) {

gc.setFill(Color.TOMATO);

} else {

gc.setFill(Color.BEIGE);

}

gc.fillOval(b.x-b.r,b.y-b.r,b.r*2,b.r*2);

gc.setFill(Color.BLACK);

gc.fillText(String.valueOf(b.getCount()), b.x-3,b.y+3);

});

}

}

3.修改controller类中的多选操作以及对单选与多选逻辑上修改即可

鼠标按压时,判断按压背景还是某个圆,是某个圆,则就是选择操作

选择 从是否按压Ctrl上可以区分为多选还是单选。

若是多选 则通过判断该圆是否被选中后 对圆进行选中/取消(为了分开单选和多选,在多选时就要将单选取消)

若是单选 若此时多选不为空(即正在进行多选操作,就单选不成功)

为空 就可以正常使用

之后的每一个增加,删除,修改操作都是对两种情况进行判断

若单选 正常操作即可

多选 对多个目标边里进行操作即可

public class BlobController {

BlobModel model;

InteractionModel iModel;

double prevX,prevY;

double dX,dY;

enum State {READY,PREPARE_CREATE, DRAGGING, DRAGGING_TOOL}

State currentState = State.READY;

public void handlePressed(MouseEvent event) {

switch (currentState) {

case READY -> {

if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪

Blob b = model.whichHit(event.getX(),event.getY());

// 对多选和单选就行区分

if (event.isControlDown()){

if (iModel.containedInSelected(b)){

iModel.deleteSelectedBlob(b);

}else {

iModel.addSelectedBlobs(b);

iModel.unselect();

}

}else if (iModel.getSelectedBlobs().isEmpty()){

iModel.setSelected(b);

}

prevX = event.getX();

prevY = event.getY();

currentState = State.DRAGGING; // 选中之后就改变状态为dragging

} else {

if (event.isControlDown()){

iModel.clearPoints();

iModel.setPathComplete(false);

System.out.println("设置起点");

iModel.setInitialPointX(event.getX());

iModel.setInitialPointY(event.getY());

iModel.addPoint(new Point2D(event.getX(), event.getY()));

System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());

currentState = State.DRAGGING_TOOL;

} else {

currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪

}

}

}

}

}

public void handleDragged(MouseEvent event) { // 鼠标拖动

switch (currentState){

case PREPARE_CREATE -> {

currentState = State.READY;

}

case DRAGGING -> {

if (!event.isShiftDown()){

// 拖动被选中的⚪

dX = event.getX() - prevX;

dY = event.getY() - prevY;

prevX = event.getX();

prevY = event.getY();

if (iModel.getSelected() != null){

System.out.println("移动之前"+iModel.getSelected());

BlobAction blobAction = BlobAction.DraggedBlobRecord(iModel.getSelected());

model.moveBlob(iModel.getSelected(), dX,dY);

System.out.println("移动之后"+iModel.getSelected());

// 创建一个移动操作

List blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction);

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

}else {

// 同时移动多个选中目标

List blobActions = iModel.createBlobActions();

List selectedBlobs = iModel.getSelectedBlobs();

selectedBlobs.forEach(b -> {

BlobAction blobAction = BlobAction.DraggedBlobRecord(b);

blobActions.add(blobAction);

});

model.moveBlobs(selectedBlobs, dX, dY);

// 创建多个移动操作

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

System.out.println("多个移动栈"+blobActions);

}

}else{

// 更改⚪的大小

dX = event.getX() - prevX;

if (iModel.getSelected() != null){

Blob blob = iModel.getSelected();

BlobAction blobAction = BlobAction.MotifiedBlobRecord(blob);

model.motifyR(blob,dX);

// 创建更改大小操作

List blobActions = iModel.addBlobAction(iModel.createBlobActions(), blobAction);

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

}else {

List blobActions = iModel.createBlobActions();

// 修改多选的多个圆大小

List selectedBlobs = iModel.getSelectedBlobs();

selectedBlobs.forEach(b -> {

BlobAction blobAction = BlobAction.MotifiedBlobRecord(b);

blobActions.add(blobAction);

});

model.motifyRs(selectedBlobs, dX);

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

System.out.println("多个更改大小栈"+blobActions);

}

}

}

}

}

public void handleReleased(MouseEvent event) { // 鼠标释放

switch (currentState) {

case PREPARE_CREATE -> { // 创建一个⚪

if (event.isShiftDown()){

Blob blob = model.addBlob(event.getX(), event.getY());

currentState = State.READY;

// 注册一个创建操作

List blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(blob,"CREATE"));

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

}else if (event.isControlDown()){

// 删除所有⚪

model.clear();

iModel.clearUndoStack();

iModel.clearRedoStack();

}else {

iModel.unselect();

iModel.clearSelectedBlobs();

}

}

case DRAGGING -> {

// iModel.unselect();

currentState = State.READY;

}

case DRAGGING_TOOL -> {

currentState = State.READY;

iModel.setPathComplete(true);

}

}

}

public void keyHandlePressed(KeyEvent event) { // 键盘输入

System.out.println(event.getCode());

List blobActions;

if (event.getCode() == KeyCode.DELETE){

System.out.println("正在执行删除操作");

System.out.println(iModel.getSelected());

System.out.println(currentState);

if (iModel.getSelected() != null){

System.out.println("删除单个选中目标");

model.deleteBlob(iModel.getSelected());

// 注册删除操作 blob type

blobActions = iModel.addBlobAction(iModel.createBlobActions(), new BlobAction(iModel.getSelected(), "DELETE"));

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

}else {

System.out.println("删除多个选中目标");

blobActions = iModel.createBlobActions();

List selectedBlobs = iModel.getSelectedBlobs();

selectedBlobs.forEach(b -> {

BlobAction blobAction = new BlobAction(b,"DELETE");

blobActions.add(blobAction);

});

model.deleteBlobs(selectedBlobs);

iModel.registerUndoStack(blobActions);

iModel.clearRedoStack();

System.out.println("多个删除栈"+blobActions);

}

}

}

}

2.实现ctrl+单击/“橡皮筋-套索”组合方法选择多个目标

1.实现Ctrl + 单击 (上面其实已经实现了)

2.“橡皮筋-套索”组合方法选择多个目标

1.矩形

思路:在鼠标按压时,设置起始位置点,在拖动时,鼠标的位置就是终止点。这两个点就是矩形的对角点。然后通过这两个点在视图层中画出矩形

实现步骤:

1.在iModel类中添加起始点和游标位置的存储

设置游标点时,view层就要刷新,所以要添加检测方法dragNotifySubscribers();

public class InteractionModel {

// 起始位置

double initialPointX,initialPointY;

// 游标位置

double cursorX,cursorY;

public void setCursorXY(double cursorX,double cursorY) {

this.cursorX = cursorX;

this.cursorY = cursorY;

dragNotifySubscribers();

}

get/set方法。。。。

}

2.在View类中画出矩形,并设置动态检测方法

(1).在检测接口类添加检测方法

public interface IModelListener {

void iModelChanged();

void iModelDragChanged();

}

(2).在视图类中实现该方法并画出矩形

public class BlobView extends StackPane implements BlobModelListener, IModelListener {

private void drawDrag() {

// 矩形

if (!iModel.isPathComplete()){

gc.setStroke(Color.GREEN);

gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()),

Math.min(iModel.getInitialPointY(),iModel.getCursorY()),

Math.abs(iModel.getCursorX() - iModel.getInitialPointX()),

Math.abs(iModel.getCursorY() - iModel.getInitialPointY()));

}

}

@Override

public void iModelChanged() {

draw();

drawDrag();

}

@Override

public void iModelDragChanged() {

draw();

drawDrag();

}

}

(3)iModel类中设置检测方法(也就是第一步中用于监测的方法)

public class InteractionModel {

// 对矩形之外的图形进行监测

private void notifySubscribers() {

subscribers.forEach(s -> s.iModelChanged());

}

// 对矩形进行监测

private void dragNotifySubscribers() {

subscribers.forEach(s -> s.iModelDragChanged());

}

}

3.控制层中实现矩形多选圆的算法

该算法很简单就是判断圆心是否在矩形中,并且圆心到四条边的距离是否大于圆的半径即可判断

public boolean isBlobInStrokeRect(Blob blob){

if (!(blob.getX() > iModel.getInitialPointX() == blob.getX() < iModel.getCursorX())){

return false;

}

if (!(blob.getY() > iModel.getInitialPointY() == blob.getY() < iModel.getCursorY())){

return false ;

}

if (Math.abs(blob.getX() - iModel.getInitialPointX()) > blob.getR() && Math.abs(blob.getX() - iModel.getCursorX()) > blob.getR()

&& Math.abs(blob.getY() - iModel.getInitialPointY()) > blob.getR() && Math.abs(blob.getY() - iModel.getCursorY()) > blob.getR()){

return true;

}

return false;

}

4.就是在controller增重设置起始点与游标点即可

拖动时设置新的状态机 DRAGGING_TOOL 用于画橡皮筋和矩形时使用(和更改大小,拖动等操作区分开)

public class BlobController {

public void handlePressed(MouseEvent event) {

switch (currentState) {

case READY -> {

if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪

.......... // 选中之后就改变状态为dragging

} else {

if (event.isControlDown()){

iModel.clearPoints();

iModel.setPathComplete(false);

System.out.println("设置起点");

// 按压时添加起始点

iModel.setInitialPointX(event.getX());

iModel.setInitialPointY(event.getY());

iModel.addPoint(new Point2D(event.getX(), event.getY()));

System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());

currentState = State.DRAGGING_TOOL;

} else {

currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪

}

}

}

}

}

public void handleDragged(MouseEvent event) { // 鼠标拖动

switch (currentState) {

case DRAGGING_TOOL -> {

if (event.isControlDown()){

iModel.clearSelectedBlobs();

System.out.println("设置游标点");

iModel.setCursorXY(event.getX(), event.getY());

iModel.addPoint(new Point2D(event.getX(), event.getY()));

// 设置之后对所有圆遍历判断 是否在矩形中

model.getBlobs().forEach(b -> {

System.out.println("开始运行。。。");

if (isBlobInStrokeRect(b)){

// 是 则被选中

iModel.addSelectedBlobs(b);

}

if (isBlobInPoints(b)){

iModel.addSelectedBlobs(b);

}

});

}

}

}

}

}

2.橡皮筋-套索”

思路:就是将鼠标移动时的点全纪录下来,通过这些点判断圆是否被圈中

实现步骤:

1.将鼠标移动时的点记录在iModel类中,同时使用pathComplete来判断在鼠标移动过程以及鼠标按压/释放状态

刚按压时 pathComplete -> false

过程中 false

释放时 true

public class InteractionModel {

// 记录鼠标移动时的点

List points = new ArrayList<>();

boolean pathComplete;

public boolean isPathComplete() {

return pathComplete;

}

public void setPathComplete(boolean pathComplete) {

this.pathComplete = pathComplete;

dragNotifySubscribers();

if (pathComplete == true){

notifySubscribers();

}

}

public List getPoints() {

return points;

}

public void addPoint(Point2D point){

points.add(point);

dragNotifySubscribers();

}

public void clearPoints(){

points.clear();

dragNotifySubscribers();

}

}

2.通过算法判断圆是否被圈中

算法思路:

首先排除经过的点有经过圆的。

然后将圆心放在坐标轴中心,四个象限必须全部有经过的点才算作被包裹。(可以使用的大多数情况)

但是也有例外。就是经过圆四周但是不包裹圆的情况,这个属于算法上的缺陷

如上图,就无法判定圆不在索套中

public boolean isBlobInPoints(Blob blob){

List list = new ArrayList<>();

List points = iModel.getPoints();

for (Point2D point : points) {

if (getLength(blob,point) < blob.getR()) {

return false;

}

if (point.getX() < blob.getX() && point.getY() < blob.getY()){

if (list.size() == 0){

list.add(1);

}

if (list.size() != 0 && list.get(list.size() - 1) != 1){

list.add(1);

}

}

if (point.getX() > blob.getX() && point.getY() < blob.getY()){

if (list.size() == 0){

list.add(2);

}

if (list.size() != 0 && list.get(list.size() - 1) != 2){

list.add(2);

}

}

if (point.getX() > blob.getX() && point.getY() > blob.getY()){

if (list.size() == 0){

list.add(3);

}

if (list.size() != 0 && list.get(list.size() - 1) != 3){

list.add(3);

}

}

if (point.getX() < blob.getX() && point.getY() > blob.getY()){

if (list.size() == 0){

list.add(4);

}

if (list.size() != 0 && list.get(list.size() - 1) != 4){

list.add(4);

}

}

}

if (list.size() >= 4 && list.contains(1) && list.contains(2) && list.contains(3) && list.contains(4)){

return true;

}

return false;

}

3.在controller类中拖动时将点记录在iModel中

public class BlobController {

public void handlePressed(MouseEvent event) {

switch (currentState) {

case READY -> {

if (model.hitBlob(event.getX(),event.getY())) { // 选中⚪

Blob b = model.whichHit(event.getX(),event.getY());

if (event.isControlDown()){

if (iModel.containedInSelected(b)){

iModel.deleteSelectedBlob(b);

}else {

iModel.addSelectedBlobs(b);

iModel.unselect();

}

}else if (iModel.getSelectedBlobs().isEmpty()){

iModel.setSelected(b);

}

prevX = event.getX();

prevY = event.getY();

currentState = State.DRAGGING; // 选中之后就改变状态为dragging

} else {

if (event.isControlDown()){

iModel.clearPoints();

// 按压时将pathComplete -> false

iModel.setPathComplete(false);

System.out.println("设置起点");

iModel.setInitialPointX(event.getX());

iModel.setInitialPointY(event.getY());

// 记录起始点

iModel.addPoint(new Point2D(event.getX(), event.getY()));

currentState = State.DRAGGING_TOOL;

} else {

currentState = State.PREPARE_CREATE; // 未选中 就意味着在释放时要创建新的⚪

}

}

}

}

}

public void handleDragged(MouseEvent event) { // 鼠标拖动

switch (currentState) {

case DRAGGING_TOOL -> {

if (event.isControlDown()){

iModel.clearSelectedBlobs();

System.out.println("设置游标点");

iModel.setCursorXY(event.getX(), event.getY());

// 添加拖动时的点

iModel.addPoint(new Point2D(event.getX(), event.getY()));

System.out.println("起点为"+iModel.getInitialPointX()+","+iModel.getInitialPointY());

System.out.println("游标点:"+iModel.getCursorX()+","+iModel.getCursorY());

model.getBlobs().forEach(b -> {

if (isBlobInStrokeRect(b)){

iModel.addSelectedBlobs(b);

}

// 判断是否被套中

if (isBlobInPoints(b)){

iModel.addSelectedBlobs(b);

}

});

}

}

}

}

}

4.将imodel记录的点在view层显示出来

public class BlobView extends StackPane implements BlobModelListener, IModelListener {

private void drawDrag() {

// 矩形

if (!iModel.isPathComplete()){

gc.setStroke(Color.GREEN);

gc.strokeRect(Math.min(iModel.getInitialPointX(),iModel.getCursorX()),

Math.min(iModel.getInitialPointY(),iModel.getCursorY()),

Math.abs(iModel.getCursorX() - iModel.getInitialPointX()),

Math.abs(iModel.getCursorY() - iModel.getInitialPointY()));

}

// 橡皮筋

if (!iModel.isPathComplete()) {

gc.setFill(Color.DARKGRAY);

iModel.getPoints().forEach(p -> gc.fillOval(p.getX()-3,p.getY()-3,6,6));

}

}

}

3.最后对part2,part3中的撤销重做,剪切板等功能进行完善,实现对多选之后的操作的适配

思路:将撤销重做栈 和 剪切板 修改为列表数据类型即可,之前的所有操作都修改为基于列表的操作即可。就是个体力活,不多介绍啦。

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: