java之数字彩虹雨

© 版权声明:本文为博主原创文章,转载请注明出处

数字彩虹雨:

  从上至下,随机出现一串字符串,以不同的速度运行到底部;类似于黑客帝国里面的场景

GitHub:https://github.com/TabooNight/BinaryRain

实例:

1.代码结构

2.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
  	<modelVersion>4.0.0</modelVersion>

	<groupId>org.game.BinaryRain</groupId>
	<artifactId>BinaryRain</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<!-- junit -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

3.BinaryRain.properties

foreground=default
background=default
width=default
height=default
characters=default
colorful=true
music=BinaryRainMusic.wav

4.BinaryRain.java

package org.game.BinaryRain;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

/**
 * 数字彩虹雨
 *
 */
public class BinaryRain extends JDialog {
	
	private static final long serialVersionUID = 1L;
	
	private Color foreground, background;// 前景色、背景色
	private Dimension size;//Dimension类封装单个对象中组件的高度和宽度(精确到整数)
	private char[] RAIN_CHARACTERS;// 字符数组
	private boolean isColorful;// 颜色是否铺满
	private boolean hasMusic;// 是否播放音乐
	private AudioClip music;// 音频对象
	private boolean isStart = false;// 是否开始
	private RainPanel panel = new RainPanel();// RainPanel对象
	private ArrayList<Rain> rains = new ArrayList<Rain>();
	private Font rainFont = new Font("arial", Font.BOLD, 15);;// 创建文字雨的字体
	
	/**
	 * 通过构造方法初始化
	 */
	private BinaryRain() {
		
		try {
			initProperties();
			init();
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Failed to init.
" + e, 
					"BinaryRain", JOptionPane.ERROR_MESSAGE);
			System.exit(1);// 非正常退出
		}
		
	}
	
	
	/**
	 * 读取配置文件并初始化
	 * 
	 * @throws Exception
	 */
	private void initProperties() throws Exception {
		
		Properties props = new Properties(); 
		File file = new File(System.getProperty("user.dir") 
				+ "/src/main/resources/BinaryRain.properties");// 获取配置文件
		boolean dw = true, dh = true, df =true, db = true, dc = true, dcf = true;

		if (file.exists() && file.isFile()) {
			InputStream fis = new FileInputStream(file);// 创建文件输入流
			props.load(fis);// 加载属性文件

			// 获取前景色,默认default
			String strFore = props.getProperty("foreground", "default").toLowerCase();
			if (!"default".equals(strFore)) {
				df = false;
				foreground = getColor(strFore);// 获取颜色
				if (foreground == null) {
					foreground = Color.getColor(strFore, Color.GREEN);// 获取颜色对象,默认绿色
				}
			}
			
			// 获取背景色,默认default
			String strBack = props.getProperty("background", "default").toLowerCase();
			if (!"default".equals(strBack)) {
				db = false;
				background = getColor(strBack);// 获取颜色
				if (background == null) {
					background = Color.getColor(strBack, Color.GREEN);// 获取颜色对象,默认绿色
				}
			}
			
			// 获取宽度
			size = new Dimension();
			String strW = props.getProperty("width", "default").toLowerCase();
			if (!"default".equals(strW)) {
				dw = false;
				size.width = Integer.valueOf(strW);
			}
			
			// 获取高度
			String strH = props.getProperty("height", "default").toLowerCase();
			if (!"default".equals(strH)) {
				dh = false;
				size.width = Integer.valueOf(strH);
			}
			
			// 获取字符数组
			String strC = props.getProperty("characters", "default");
			if (!"default".equalsIgnoreCase(strC)) {
				dc = false;
				String[] cs = strC.split(",");
				RAIN_CHARACTERS = new char[cs.length];
				for (int i = 0, s = RAIN_CHARACTERS.length; i < s; i++) {
					RAIN_CHARACTERS[i] = cs[i].charAt(0);
				}
			}
			
			// 判断颜色是否铺满
			String strCF = props.getProperty("colorful", "default");
			if (!"default".equalsIgnoreCase(strCF)) {
				dcf = false;
				isColorful = Boolean.valueOf(strCF);
			}
			
			// 判断是否播放音乐
			String strM = props.getProperty("music", "default");
			if (!"default".equalsIgnoreCase(strM)) {
				File musicFile = new File(System.getProperty("user.dir") 
						+ "/src/main/resources/" + strM);
				if (musicFile.exists() && musicFile.isFile()) {
					if ((music = Applet.newAudioClip(musicFile.toURI().toURL())) != null) {
						hasMusic = true;
					}
				}
			}
			
			fis.close();
			
		}
		
		if (dw & dh) {// 高度和宽度都是default,获取屏幕高和宽
			size = Toolkit.getDefaultToolkit().getScreenSize();
		} else if (dw) {// 宽度是default,获取屏幕宽度
			size.width = Toolkit.getDefaultToolkit().getScreenSize().width;
		} else if (dh) {// 高度是default,获取屏幕高度
			size.height = Toolkit.getDefaultToolkit().getScreenSize().height;
		}
		
		if (df) {// 前景色是default
			foreground = Color.GREEN;
		}
		
		if (db) {// 背景色是default
			background = Color.BLACK;
		}
		
		if (dc) {// 字符数组是的default
			RAIN_CHARACTERS = new char[126 - 33 + 1];
			for (int c = 0, i = 33, l = RAIN_CHARACTERS.length; c < l; c++, i++) {
				RAIN_CHARACTERS[c] = (char) i;
			}
		}
		
		if (dcf) {// 颜色铺满是default
			isColorful = false;
		}
		
	}
	
	/**
	 * 根据字符串获取其对应的颜色
	 * 
	 * @param color
	 * 				颜色
	 * @return
	 */
	private Color getColor(String color) {
		
		if (color == null || color.isEmpty()) {
			return null;
		}
		if (color.startsWith("#")) {
			int i = Integer.valueOf(color.substring(1), 16);
			return new Color(i);
		}
		// [\d]:数字		[\p{Blank}]:空格或制表符
		if (color.matches("[\d]+[\p{Blank}]*,[\p{Blank}]*[\d]+[\p{Blank}]*,[\p{Blank}]*[\d]+")) {
			String[] cs = color.split("[\p{Blank}]*,[\p{Blank}]");
			if (cs.length != 3) {
				return null;
			}
			int r = Integer.valueOf(cs[0]);
			int g = Integer.valueOf(cs[1]);
			int b = Integer.valueOf(cs[2]);
			return new Color(r, g, b);
		}
		return null;
		
	}
	
	/**
	 * 初始化窗口
	 */
	private void init() {
		
		setAlwaysOnTop(true);// 设置窗口靠前
		setResizable(false);// 不能改变大小
		setUndecorated(true);// 设置此frame窗口失去边框和标题栏的修饰(必须在setVisible之前)
		setTitle("Binary Rain");// 设置标题
		// 创建一个BufferedImage对象
		BufferedImage cursor = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB_PRE);
		setCursor(Toolkit.getDefaultToolkit().createCustomCursor(cursor, 
				new Point(8, 8), "Disable Cursor"));// 确认光标的形状
		setSize(size);// 设置窗口大小
		setLocationRelativeTo(null);// 设置窗口相对于指定组件的位置,null表示位于屏幕的中央
		
		addKeyListener(new KeyAdapter() {// 新增一个按键侦听器
			@Override
			public void keyPressed(KeyEvent e) {
				if ((e.isAltDown() && e.getKeyCode() == KeyEvent.VK_F4) 
						|| (e.getKeyCode() == KeyEvent.VK_ESCAPE)) {// 按下Alt+F4或者Esc键
					setVisible(false);// 设置窗口不可见
					System.exit(0);// 正常停止程序
				}
			}
		});
		
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				if (isRaining()) {
					stop();
				}
				System.exit(0);// 正常停止程序
			}
		});
		
		add(panel, BorderLayout.CENTER);// 将指定组件添加到此容器
		
	}
	
	/**
	 * 重写setVisible方法,当不显示时停止创建文字雨
	 */
	public void setVisible(boolean flag) {
		
		super.setVisible(flag);
		if (!flag) {
			stop();
		}
		
	}
	
	/**
	 * 是否开始创建文字雨
	 * 
	 * @return
	 */
	public boolean isRaining() {
		
		return isStart;
		
	}
	
	/**
	 * 停止文字雨
	 */
	public void stop() {
		
		isStart = false;// 开始标识置为false
		if (hasMusic) {// 播放音乐
			music.stop();// 停止播放音乐
		}
		
	}
	
	/**
	 * 开始一个新的线程,创建一条文字雨,使用synchronized保证一个时间内只有一条线程可以执行
	 */
	private synchronized void newRain() {
		
		Rain r = new Rain(getRandomLength(), (int) (Math.random() * size.width), 
				(int) (Math.random() * -60 * 15), (int) (Math.random() * 8 + 2), 
				(float) (Math.random() * 10 + 10));
		rains.add(r);
		new Thread(r).start();
		
	}
	
	/**
	 * 获取随机长度(10-50)
	 * 
	 * @return
	 */
	public int getRandomLength() {
		
		return (int) (Math.random() * 40 + 10);
		
	}
	
	/**
	 * 获取随机字符串
	 * 
	 * @return
	 */
	public String getRandomChar() {
		
		return String.valueOf(RAIN_CHARACTERS[(int) (Math.random() * RAIN_CHARACTERS.length)]);
		
	}
	
	/**
	 * 获取面板大小
	 * 
	 * @return
	 */
	public Dimension getFrameSize() {
		
		return size;
		
	}
	
	public void start() {
		
		if (hasMusic) {// 播放音乐
			music.loop();// 循环播放
		}
		
		for (int c = 0, s = 108; c < s; c++) {// 创建108条文字雨
			newRain();
		}
		
		isStart = true;
		
		for (Rain r: rains) {
			new Thread(r).start();
		}
		
		new Thread(new Runnable() {
			
			public void run() {
			
				while (isStart) {
					panel.repaint();
				}
				
			}
		}).start();
		
	}
	
	/**
	 * 创建文字雨面板
	 *
	 */
	private final class RainPanel extends JPanel {
		
		private static final long serialVersionUID = 1L;

		public RainPanel() {
			
		}

		private final Color[] COLORS = new Color[] {// 文字雨文本颜色集合
			new Color(255, 0, 0),
            new Color(255, 165, 0),
            new Color(255, 255, 0),
            new Color(0, 255, 0),
            new Color(0, 127, 0),
            new Color(0, 127, 255),
            new Color(139, 0, 255)
        };

		@Override
		public void paint(Graphics g) {
			
			if (isStart) {
				BufferedImage img = new BufferedImage(size.width, size.height, 
						BufferedImage.TYPE_INT_RGB);// 创建一个BufferedImage对象
				Graphics2D g2 = (Graphics2D) img.getGraphics();// 获取Graphics2D对象
				g2.setColor(background);// 设置颜色
				g2.fillRect(0, 0, size.width, size.height);// 用预定的颜色填充一个矩形
				g2.setColor(foreground);// 设置颜色
				// 克隆所有文字雨信息到Collection中
				@SuppressWarnings("unchecked")
				Collection<Rain> collection = (Collection<Rain>) rains.clone();
				for (Iterator<Rain> it = collection.iterator(); it.hasNext();) {
					Rain r = it.next();
					if (r.isEnd()) {// 该条文字雨已经结束
						rains.remove(r);// 将该条文字雨从集合中移除
						newRain();// 创建一条新的文字雨
						continue;
					}
					if (isColorful) {// 颜色铺满
						g2.setFont(rainFont.deriveFont(r.getSize()));// 设置文字雨文本大小
						String[] ss = r.getRainChars();// 获取文字雨文本内容
						int x = r.getX();// 获取文字雨X轴坐标
						int y = r.getY() - ss.length * 15;// 获取文字雨Y轴坐标
						for (int i = 0, sl = ss.length; i < sl; i++) {
							if (i < 7) {
								g2.setColor(COLORS[i]);
							} else {
								g2.setColor(COLORS[i % 7]);
							}
							g2.drawString(ss[i], x, y);
							y += 15;
						}
					} else {
						g2.setFont(rainFont.deriveFont(r.getSize()));// 设置文字雨文本大小
						String[] ss = r.getRainChars();// 获取文字雨文本内容
						int x = r.getX();// 获取文字雨X轴坐标
						int y = r.getY() - ss.length * 15;// 获取文字雨Y轴坐标
						for (String s: ss) {
							g2.drawString(s, x, y);
							y += 15;
						}
					}
				}
				g.drawImage(img, 0, 0, this);// 绘制指定图像
			}
			
		}
		
	}
	
	/**
	 * 通过线程创建一条条文字雨
	 *
	 */
	private final class Rain implements Runnable {
		
		private int rainSpeed;// 下雨的速度
		private final String[] rainChars;// 下雨的文本
		private int rainX, rainY;// 文本坐标系
		private float fontSize;// 文本大小

		/**
		 * 初始化一条文字雨
		 * 
		 * @param length
		 * 					文字雨的文本长度
		 * @param x
		 * 					x坐标
		 * @param y
		 * 					y坐标
		 * @param speed
		 * 					下雨速度
		 * @param size
		 * 					文本大小
		 */
		public Rain(int length, int x, int y, int speed, float size) {
			
			if (speed < 1) {
				throw new RuntimeException("The speed must be greater than or equal to 1.");
			}
			
			if (length < 5) {
				length = getRandomLength();
			}
			
			if (size < 1.0f) {
				size = 15.0f;
			}
			
			rainChars = new String[length + 1];
			for (int i = 0; i < length; i++) {
				rainChars[i] = getRandomChar();
			}
			rainChars[length] = " ";
			this.rainX = x;
			this.rainY = y;
			this.rainSpeed = speed;
			this.fontSize = size;
			
		}

		/**
		 * 执行文字雨
		 */
		public void run() {
			
			while (isRaining() && rainY < getFrameSize().height + (rainChars.length + 1) * 15) {
				if (rainSpeed <= 0) {// 文字雨的速度小于等于0,结束
					break;
				}
				try {
					Thread.sleep(rainSpeed);// 睡眠
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}
				rainY += 2;// 每次向下运动2
			}
			rainSpeed = -1;// 文字雨结束,速度置为-1
			
		}
		
		/**
		 * 获取文本内容
		 * 
		 * @return
		 */
		public String[] getRainChars() {
			
			return rainChars;
			
		}
		
		/**
		 * 获取文字雨X轴坐标
		 * 
		 * @return
		 */
		public int getX() {
			
			return rainX;
			
		}
		
		/**
		 * 获取文字雨Y轴坐标
		 * 
		 * @return
		 */
		public int getY() {
			
			return rainY;
			
		}
		
		/**
		 * 获取文字雨文本大小
		 * 
		 * @return
		 */
		public float getSize() {
			
			return fontSize;
			
		}
		
		/**
		 * 判断文字雨是否结束
		 * 
		 * @return
		 */
		public boolean isEnd() {
			
			return rainSpeed <= 0;
			
		}
		
	}
	
	public static void main(String[] args) {
		
		BinaryRain rain = new BinaryRain();
		rain.setVisible(true);
		rain.start();
		
	}
	
}

5.效果预览

参考:http://blog.csdn.net/simon_world/article/details/41791277

原文地址:https://www.cnblogs.com/jinjiyese153/p/7122281.html