用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; //同样自己设计内容

添加系统托盘

  • 因为只有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();
	}
}

功能:切换宠物

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

private void switchPet() {
	imageView.removeEventHandler(MouseEvent.MOUSE_CLICKED, listen);//移除原宠物的事件
	//切换宠物ID
	if(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);
}

退出程序时展示动画

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

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();
}

添加聊天气泡

为“碎碎念”功能服务。聊天气泡容器为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();
}

多线程执行自定义功能

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;
       //随机决定移动的时间,单位微秒ms
	long 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();
}

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

功能:自娱自乐

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

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

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);//移除原宠物的事件
		//切换宠物ID
		if(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;
        //随机决定移动的时间,单位微秒ms
		long 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;
		}
		else
			gifID = 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;
	}
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用JavaFX开发电商管理系统时,可以通过以下方式实现钱包余额功能: 1. 用户注册:在用户注册页面中,添加一个钱包余额输入框,当用户填写完其他必填信息后,可以选择输入一个初始的钱包余额。这个初始余额可以是系统预设的默认值,或者由用户自行填写。注册成功后,系统会保存用户的钱包余额信息。 2. 用户登录:在用户登录页面中,显示用户的钱包余额。用户成功登录后,系统会获取该用户的钱包余额并显示在界面上。 3. 购物结算:用户选择商品加入购物车后,进入结算页面。在结算页面中,显示用户的钱包余额和所购商品的总金额。用户可以选择使用钱包余额进行支付,系统会根据用户选择的支付方式计算应支付的金额和钱包余额的扣除情况。 4. 充值功能:用户可以选择充值按钮,进入充值页面。用户在充值页面中填写充值金额,并选择支付方式完成充值操作。充值后,系统会更新用户的钱包余额,并将充值记录保存到数据库中。 5. 提现功能:用户可以选择提现按钮,进入提现页面。用户在提现页面中填写提现金额,并选择提现方式完成提现操作。系统会根据用户的提现金额和提现方式进行相应的处理,并更新用户的钱包余额。 6. 钱包余额变动记录:系统会记录用户钱包余额的变动情况,包括充值、提现和购物结算的扣款记录。用户可以通过查看余额变动记录来了解自己的钱包使用情况。 通过以上方法,可以在电商管理系统中实现钱包余额功能,用户可以方便地查看余额、进行充值提现和使用余额进行购物结算。同时,对于用户的钱包操作,系统也会记录变动情况,以方便用户查看和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值