首页 > 分享 > 用JAVAFX做一个简单的桌面宠物(三)

用JAVAFX做一个简单的桌面宠物(三)

添加系统托盘和自定义功能(UI类)

类成员

private ImageView imageView;private int petID;private EventListener listen;private VBox messageBox;private CheckboxMenuItem itemWalkable;private CheckboxMenuItem autoPlay;private CheckboxMenuItem itemSay;private MenuItem itemSwitch;private Stage primaryStage;Thread thread;double x;//罗小黑的说话内容String[] lxhStrings; //这里可以自己设计内容//比丢的说话内容String[] biuStrings; //同样自己设计内容 123456789101112131415

添加系统托盘

因为只有AWT里可以设置系统托盘,所以不能用JAVAFX里的MenuItem。这里令"自行走动"、"自娱自乐"和"碎碎念"不能同时生效,是因为觉得如果允许同时生效,处理起来会比较混乱,为了避免麻烦,就这样安排了。如果你有兴趣,可以尝试允许它们同时生效。同样,读入托盘图标的图片也要用 getResourceAsStream 函数。

public void setTray(Stage stage) { SystemTray tray = SystemTray.getSystemTray(); BufferedImage image;//托盘图标try {// 为托盘添加一个右键弹出菜单PopupMenu popMenu = new PopupMenu();popMenu.setFont(new Font("微软雅黑", Font.PLAIN,18));itemSwitch = new MenuItem("切换宠物");itemSwitch.addActionListener(e -> switchPet());itemWalkable = new CheckboxMenuItem("自行走动");autoPlay = new CheckboxMenuItem("自娱自乐");itemSay = new CheckboxMenuItem("碎碎念");//令"自行走动"、"自娱自乐"和"碎碎念"不能同时生效itemWalkable.addItemListener(il -> {if(itemWalkable.getState()) {autoPlay.setEnabled(false);itemSay.setEnabled(false);}else {autoPlay.setEnabled(true);itemSay.setEnabled(true);}});autoPlay.addItemListener(il -> {if(autoPlay.getState()) {itemWalkable.setEnabled(false);itemSay.setEnabled(false);}else {itemWalkable.setEnabled(true);itemSay.setEnabled(true);}});itemSay.addItemListener(il -> {if(itemSay.getState()) {itemWalkable.setEnabled(false);autoPlay.setEnabled(false);}else {itemWalkable.setEnabled(true);autoPlay.setEnabled(true);}});MenuItem itemShow = new MenuItem("显示");itemShow.addActionListener(e -> Platform.runLater(() -> stage.show()));MenuItem itemHide = new MenuItem("隐藏");//要先setImplicitExit(false),否则stage.hide()会直接关闭stage//stage.hide()等同于stage.close()itemHide.addActionListener(e ->{Platform.setImplicitExit(false);Platform.runLater(() -> stage.hide());});MenuItem itemExit = new MenuItem("退出");itemExit.addActionListener(e -> end());popMenu.add(itemSwitch);popMenu.addSeparator();//添加分割线popMenu.add(itemWalkable);popMenu.add(autoPlay);popMenu.add(itemSay);popMenu.addSeparator();//添加分割线popMenu.add(itemShow);popMenu.add(itemHide);popMenu.add(itemExit);//设置托盘图标image = ImageIO.read(getClass().getResourceAsStream("icon.png"));TrayIcon trayIcon = new TrayIcon(image, "桌面宠物", popMenu); trayIcon.setToolTip("桌面宠物");//当鼠标移到图标上时,显示的文字提示 trayIcon.setImageAutoSize(true);//自动调整图片大小。这步很重要,不然显示的是空白 tray.add(trayIcon);} catch (IOException | AWTException e) {e.printStackTrace();} }

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576

功能:切换宠物

切换宠物之后要更新listen.petID,以避免出现以下bug:
在运行三个功能之一时点击切换宠物,图片会切换,但宠物动作不会停止,且动作完成后恢复的主图还是上一个宠物,直到下一个动作执行才变正常。
原因在于那三个功能调用listen.loadimg()时传递的是旧petID。

private void switchPet() {imageView.removeEventHandler(MouseEvent.MOUSE_CLICKED, listen);//移除原宠物的事件//切换宠物IDif(petID == 0) {petID = 1; //切换成比丢imageView.setFitHeight(150);imageView.setFitWidth(150);}else {petID = 0; //切换成罗小黑imageView.setFitHeight(200);imageView.setFitWidth(200);}listen.petID = petID;listen.mainImg(petID,0);//切换至该宠物的主图(图片编号为0)//因为listen更新了,所以要重新添加点击事件imageView.addEventHandler(MouseEvent.MOUSE_CLICKED, listen); }

123456789101112131415161718

退出程序时展示动画

在系统托盘点击“退出”或在任务栏点击“关闭窗口”时,让宠物做了告别动作之后再退出。

void end() {listen.mainImg(petID,99);//播放宠物的告别动画————编号为99的图片double time;//罗小黑的告别动画1.5秒,比丢的3秒if(petID == 0) time = 1.5;else time = 3;//要用Platform.runLater,不然会报错Not on FX application thread;Platform.runLater(() ->setMsg("再见~"));//动画结束后执行退出new Timeline(new KeyFrame( Duration.seconds(time), ae ->System.exit(0))) .play(); } 1234567891011121314

添加聊天气泡

为“碎碎念”功能服务。聊天气泡容器为VBox或HBox,内含一个三角形和一个圆角矩形,可根据需要选择VBox或HBox。

public void addMessageBox(String message) {Label bubble = new Label(message);//设置气泡的宽度。如果没有这句,就会根据内容多少来自适应宽度bubble.setPrefWidth(100); bubble.setWrapText(true);//自动换行 bubble.setStyle("-fx-background-color: DarkTurquoise; -fx-background-radius: 8px;"); bubble.setPadding(new Insets(7));//标签的内边距的宽度 bubble.setFont(new javafx.scene.text.Font(14)); Polygon triangle = new Polygon(0.0, 0.0,8.0, 10.0,16.0, 0.0);//分别设置三角形三个顶点的X和Y triangle.setFill(Color.DARKTURQUOISE); messageBox = new VBox(); // VBox.setMargin(triangle, new Insets(0, 50, 0, 0));//设置三角形的位置,默认居中 messageBox.getChildren().addAll(bubble, triangle); messageBox.setAlignment(Pos.BOTTOM_CENTER);messageBox.setStyle("-fx-background:transparent;"); //设置相对于父容器的位置 messageBox.setLayoutX(0);messageBox.setLayoutY(0);messageBox.setVisible(true);//设置气泡的显示时间new Timeline(new KeyFrame( Duration.seconds(8), ae ->{messageBox.setVisible(false);})) .play(); }

12345678910111213141516171819202122232425262728

多线程执行自定义功能

public void run() {while(true) {Random rand = new Random();//随机发生自动事件,以下设置间隔为9~24秒。要注意这个时间间隔包含了动画播放的时间long time = (rand.nextInt(15)+10)*1000;System.out.println("Waiting time:"+time);//自动行走if(itemWalkable.getState() & listen.gifID == 0) {walk();}//自娱自乐else if(autoPlay.getState() & listen.gifID == 0) {play();}//碎碎念else if(itemSay.getState() & listen.gifID == 0) {//随机选择要说的话。因为目前只有两个宠物,所以可以用三目运算符String str = (petID == 0) ? lxhStrings[rand.nextInt(5)]:biuStrings[rand.nextInt(4)];Platform.runLater(() ->setMsg(str));}try {Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); }} }

123456789101112131415161718192021222324252627

功能:碎碎念

在宠物上方显示对话气泡。

public void setMsg(String msg) {Label lbl = (Label) messageBox.getChildren().get(0);lbl.setText(msg);messageBox.setVisible(true);//设置气泡的显示时间new Timeline(new KeyFrame( Duration.seconds(4), ae ->{messageBox.setVisible(false);})) .play(); } 12345678910

功能:自动行走

这里只设置了在水平方向上走动,碰到屏幕边缘就停下来。如果有合适的图片,也可以设置竖直方向上移动。

void walk(){Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();x = primaryStage.getX();//stage的左边缘坐标double maxx = screenBounds.getMaxX();//获取屏幕的大小double width = imageView.getBoundsInLocal().getWidth();//获取imageView的宽度,也可使用.getMaxX();Random rand = new Random();double speed=10;//每次移动的距离//如果将要到达屏幕边缘就停下 if(x+speed+width >= maxx | x-speed<=0)return; //随机决定移动的时间,单位微秒mslong time = (rand.nextInt(4)+3)*1000;System.out.println("Walking time:"+time);int direID = rand.nextInt(2);//随机决定方向,0为左,1为右//切换至对应方向的行走图Image newimage;if(petID == 0)newimage = new Image(this.getClass().getResourceAsStream("/lxh/罗小黑w"+direID+".gif"));else {newimage = new Image(this.getClass().getResourceAsStream("/biu/biuw"+direID+".gif"));}imageView.setImage(newimage);//移动Move move = new Move(time, imageView, direID, primaryStage, listen);thread = new Thread(move);thread.start(); }

123456789101112131415161718192021222324252627

PS: Move 类是自定义的类,下一篇会讲。

功能:自娱自乐

空闲时随机做动作,这样如果有很多图片想用,就不用受部位数量的限制,也不会让宠物显得呆板。

void play() {Random rand = new Random();int gifID;double time = 4;//gifID是根据图片文件夹中用途未定义的图片和已设定的动作个数来确定的if(petID == 0) {gifID = rand.nextInt(7)+5;}elsegifID = rand.nextInt(7)+7;listen.loadImg(petID, gifID, time); } 123456789101112

UI类完整代码

public class UI implements Runnable {private ImageView imageView;private int petID;private EventListener listen;private VBox messageBox;private CheckboxMenuItem itemWalkable;private CheckboxMenuItem autoPlay;private CheckboxMenuItem itemSay;private MenuItem itemSwitch;private Stage primaryStage;Thread thread;double x;//罗小黑的说话内容String[] lxhStrings= {"好无聊。。。","陪我玩会儿吧~","《罗小黑战记》怎么还没更新","想师父了","不就是拿了颗珠子嘛,至于把我打回猫形嘛"};//比丢的说话内容String[] biuStrings = {"想吃东西。。","biu~","揉揉小肚几","比丢这么可爱,怎么可以欺负比丢"};public UI(ImageView view, int pet, EventListener el, Stage s) {imageView = view;petID = pet;listen = el;primaryStage = s;}//添加系统托盘public void setTray(Stage stage) { SystemTray tray = SystemTray.getSystemTray(); BufferedImage image;//托盘图标try {// 为托盘添加一个右键弹出菜单PopupMenu popMenu = new PopupMenu();popMenu.setFont(new Font("微软雅黑", Font.PLAIN,18));itemSwitch = new MenuItem("切换宠物");itemSwitch.addActionListener(e -> switchPet());itemWalkable = new CheckboxMenuItem("自行走动");autoPlay = new CheckboxMenuItem("自娱自乐");itemSay = new CheckboxMenuItem("碎碎念");//令"自行走动"、"自娱自乐"和"碎碎念"不能同时生效itemWalkable.addItemListener(il -> {if(itemWalkable.getState()) {autoPlay.setEnabled(false);itemSay.setEnabled(false);}else {autoPlay.setEnabled(true);itemSay.setEnabled(true);}});autoPlay.addItemListener(il -> {if(autoPlay.getState()) {itemWalkable.setEnabled(false);itemSay.setEnabled(false);}else {itemWalkable.setEnabled(true);itemSay.setEnabled(true);}});itemSay.addItemListener(il -> {if(itemSay.getState()) {itemWalkable.setEnabled(false);autoPlay.setEnabled(false);}else {itemWalkable.setEnabled(true);autoPlay.setEnabled(true);}});MenuItem itemShow = new MenuItem("显示");itemShow.addActionListener(e -> Platform.runLater(() -> stage.show()));MenuItem itemHide = new MenuItem("隐藏");//要先setImplicitExit(false),否则stage.hide()会直接关闭stage//stage.hide()等同于stage.close()itemHide.addActionListener(e ->{Platform.setImplicitExit(false);Platform.runLater(() -> stage.hide());});MenuItem itemExit = new MenuItem("退出");itemExit.addActionListener(e -> end());popMenu.add(itemSwitch);popMenu.addSeparator();//添加分割线popMenu.add(itemWalkable);popMenu.add(autoPlay);popMenu.add(itemSay);popMenu.addSeparator();//添加分割线popMenu.add(itemShow);popMenu.add(itemHide);popMenu.add(itemExit);//设置托盘图标image = ImageIO.read(getClass().getResourceAsStream("icon.png"));TrayIcon trayIcon = new TrayIcon(image, "桌面宠物", popMenu); trayIcon.setToolTip("桌面宠物");//当鼠标移到图标上时,显示的文字提示 trayIcon.setImageAutoSize(true);//自动调整图片大小。这步很重要,不然显示的是空白 tray.add(trayIcon);} catch (IOException | AWTException e) {e.printStackTrace();}}//切换宠物private void switchPet() {imageView.removeEventHandler(MouseEvent.MOUSE_CLICKED, listen);//移除原宠物的事件//切换宠物IDif(petID == 0) {petID = 1; //切换成比丢imageView.setFitHeight(150);imageView.setFitWidth(150);}else {petID = 0; //切换成罗小黑imageView.setFitHeight(200);imageView.setFitWidth(200);}/* *更新listen.petID是为了修复bug: 在运行三个功能之一时点击切换宠物,图片会切换,但宠物动作不会停止 *且动作完成后恢复的主图还是上一个宠物,直到下一个动作执行才变正常。 *原因在于那三个功能调用listen.loadimg()时传递的是旧petID。 */listen.petID = petID;listen.mainImg(petID,0);//切换至该宠物的主图(图片编号为0)//因为listen更新了,所以要重新添加点击事件imageView.addEventHandler(MouseEvent.MOUSE_CLICKED, listen);}//退出程序时展示动画void end() {listen.mainImg(petID,99);//播放宠物的告别动画————编号为99的图片double time;//罗小黑的告别动画1.5秒,比丢的3秒if(petID == 0) time = 1.5;else time = 3;//要用Platform.runLater,不然会报错Not on FX application thread;Platform.runLater(() ->setMsg("再见~"));//动画结束后执行退出new Timeline(new KeyFrame( Duration.seconds(time), ae ->System.exit(0))) .play();}//添加聊天气泡public void addMessageBox(String message) {Label bubble = new Label(message);//设置气泡的宽度。如果没有这句,就会根据内容多少来自适应宽度bubble.setPrefWidth(100); bubble.setWrapText(true);//自动换行 //-fx-background-radius 即设矩形的角为圆角 bubble.setStyle("-fx-background-color: DarkTurquoise; -fx-background-radius: 8px;"); bubble.setPadding(new Insets(7));//标签的内边距的宽度 bubble.setFont(new javafx.scene.text.Font(14)); Polygon triangle = new Polygon(0.0, 0.0,8.0, 10.0,16.0, 0.0);//分别设置三角形三个顶点的X和Y triangle.setFill(Color.DARKTURQUOISE); messageBox = new VBox(); // VBox.setMargin(triangle, new Insets(0, 50, 0, 0));//设置三角形的位置,默认居中 messageBox.getChildren().addAll(bubble, triangle); messageBox.setAlignment(Pos.BOTTOM_CENTER);messageBox.setStyle("-fx-background:transparent;"); //设置相对于父容器的位置 messageBox.setLayoutX(0);messageBox.setLayoutY(0);messageBox.setVisible(true);//设置气泡的显示时间new Timeline(new KeyFrame( Duration.seconds(8), ae ->{messageBox.setVisible(false);})) .play();} //用多线程来实现 经过随机时间间隔执行“自动行走”“自娱自乐”“碎碎念”的功能public void run() {while(true) {Random rand = new Random();//随机发生自动事件,以下设置间隔为9~24秒。要注意这个时间间隔包含了动画播放的时间long time = (rand.nextInt(15)+10)*1000;System.out.println("Waiting time:"+time);//自动行走if(itemWalkable.getState() & listen.gifID == 0) {walk();}//自娱自乐else if(autoPlay.getState() & listen.gifID == 0) {play();}//碎碎念else if(itemSay.getState() & listen.gifID == 0) {//随机选择要说的话。因为目前只有两个宠物,所以可以用三目运算符String str = (petID == 0) ? lxhStrings[rand.nextInt(5)]:biuStrings[rand.nextInt(4)];Platform.runLater(() ->setMsg(str));}try {Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); }}}/* * 执行"碎碎念"的功能——在宠物上方显示对话气泡 * 不默认开启是考虑到用户可能不想被打扰 */public void setMsg(String msg) {Label lbl = (Label) messageBox.getChildren().get(0);lbl.setText(msg);messageBox.setVisible(true);//设置气泡的显示时间new Timeline(new KeyFrame( Duration.seconds(4), ae ->{messageBox.setVisible(false);})) .play();}/* * 执行"自行走动"的功能——在水平方向上走动 * 不默认开启是考虑到用户可能只想宠物安静呆着 */void walk(){Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();x = primaryStage.getX();//stage的左边缘坐标double maxx = screenBounds.getMaxX();//获取屏幕的大小double width = imageView.getBoundsInLocal().getWidth();//获取imageView的宽度,也可使用.getMaxX();Random rand = new Random();double speed=10;//每次移动的距离//如果将要到达屏幕边缘就停下 if(x+speed+width >= maxx | x-speed<=0)return; //随机决定移动的时间,单位微秒mslong time = (rand.nextInt(4)+3)*1000;System.out.println("Walking time:"+time);int direID = rand.nextInt(2);//随机决定方向,0为左,1为右//切换至对应方向的行走图Image newimage;if(petID == 0)newimage = new Image(this.getClass().getResourceAsStream("/lxh/罗小黑w"+direID+".gif"));else {newimage = new Image(this.getClass().getResourceAsStream("/biu/biuw"+direID+".gif"));}imageView.setImage(newimage);//移动Move move = new Move(time, imageView, direID, primaryStage, listen);thread = new Thread(move);thread.start();}/* * 执行"自娱自乐"的功能——空闲时随机做动作 * 这样就不用受部位数量的限制,也不会让宠物显得呆板 * 不默认开启是考虑到用户可能只想宠物安静呆着 */void play() {Random rand = new Random();int gifID;double time = 4;//gifID是根据图片文件夹中用途未定义的图片和已设定的动作个数来确定的if(petID == 0) {gifID = rand.nextInt(7)+5;}elsegifID = rand.nextInt(7)+7;listen.loadImg(petID, gifID, time);}public ImageView getImageView() {return imageView;}public void setImageView(ImageView imageView) {this.imageView = imageView;}public VBox getMessageBox() {return messageBox;}public void setMessageBox(VBox messageBox) {this.messageBox = messageBox;} }

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290

相关知识

用JAVAFX做一个简单的桌面宠物(三)
java开发桌面宠物
用python做一个宠物系统
用简单食材,给狗狗做一个真正能吃的生日蛋糕吧
用QT实现一个简单的桌面宠物
桌面宠物,3只可爱的小猫
怎么用Python制作一个可以聊天的皮卡丘版桌面宠物
用Python制作桌面宠物
用Python写个桌面挂件,手把手带你做只桌面宠物~
Python实现桌面挂件,做一只可爱的桌面宠物~

网址: 用JAVAFX做一个简单的桌面宠物(三) https://m.mcbbbk.com/newsview644902.html

所属分类:萌宠日常
上一篇: 一天中的5个关键减肥时刻
下一篇: Python实现桌面宠物