-
首先上图一张,为最终制作的效果图,不喜欢或感到失望的朋友可以先行离开
大家已经看到效果图了。那么下面就介绍设计思路和源代码
首先要想显示歌词,就要对歌词文件进行抽象。下面这个类是对某一行歌词文件进行了抽象。
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package musicbox.model.lyric;
- /**
- *
- * @author Randyzhao
- */
- public class LyricStatement {
- private long time = 0;//时间, 单位为10ms
- private String lyric = "";//歌词
- /*
- * 获取时间
- */
- public long getTime() {
- return time;
- }
- /*
- * 设置时间
- * time: 被设置成的时间
- */
- public void setTime(int time) {
- this.time = time;
- }
- /*
- * 设置时间
- * time: 被设置成的时间字符串, 格式为mm:ss.ms
- */
- public void setTime(String time) {
- String str[] = time.split(":|\\.");
- this.time = Integer.parseInt(str[0]) * 6000 + Integer.parseInt(str[1]) * 100 +
- Integer.parseInt(str[2]);
- }
- /*
- * 获取歌词
- */
- public String getLyric() {
- return lyric;
- }
- /*
- * 设置歌词
- */
- public void setLyric(String lyric) {
- this.lyric = lyric;
- }
- /*
- * 打印歌词
- */
- public void printLyric() {
- System.out.println(time + ": " + lyric);
- }
- }
特别注意成员变量time表示该行歌词显示的时间,单位是 10ms 这是为了和歌词文件中时间的单位统一。/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package musicbox.model.lyric; /** * * @author Randyzhao */ public class LyricStatement { private long time = 0;//时间, 单位为10ms private String lyric = "";//歌词 /* * 获取时间 */ public long getTime() { return time; } /* * 设置时间 * time: 被设置成的时间 */ public void setTime(int time) { this.time = time; } /* * 设置时间 * time: 被设置成的时间字符串, 格式为mm:ss.ms */ public void setTime(String time) { String str[] = time.split(":|\\."); this.time = Integer.parseInt(str[0]) * 6000 + Integer.parseInt(str[1]) * 100 + Integer.parseInt(str[2]); } /* * 获取歌词 */ public String getLyric() { return lyric; } /* * 设置歌词 */ public void setLyric(String lyric) { this.lyric = lyric; } /* * 打印歌词 */ public void printLyric() { System.out.println(time + ": " + lyric); } }
某一行歌词可以用一个LyricStatement类的实例来表示。那么一个歌词文件就可以解析为一个List<LyricStatement>。为了方便测试,以下附上本人自己写的一个歌词文件解释器。
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package musicbox.model.lyric;
- import java.io.BufferedReader;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.net.URLDecoder;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- *
- * @author Randyzhao
- */
- public class LyricReader {
- BufferedReader bufferReader = null; //读取文件实例
- public String title = ""; //歌曲题目
- public String artist = ""; //演唱者
- public String album = ""; //专辑
- public String lrcMaker = ""; //歌词制作者
- List<LyricStatement> statements = new ArrayList<LyricStatement>(); //歌词
- /*
- * 实例化一个歌词数据. 歌词数据信息由指定的文件提供.
- * fileName: 指定的歌词文件.
- */
- public LyricReader(String fileName) throws IOException {
- //in case the space in the fileName is replaced by the %20
- FileInputStream file = new FileInputStream(URLDecoder.decode(fileName, "UTF-8"));
- bufferReader = new BufferedReader(new InputStreamReader(file, "GB2312"));
- //将文件数据读入内存
- readData();
- }
- public List<LyricStatement> getStatements() {
- return statements;
- }
- /*
- * 读取文件中数据至内存.
- */
- private void readData() throws IOException {
- statements.clear();
- String strLine;
- //循环读入所有行
- while (null != (strLine = bufferReader.readLine())) {
- //判断该行是否为空行
- if ("".equals(strLine.trim())) {
- continue;
- }
- //判断该行数据是否表示歌名
- if (null == title || "".equals(title.trim())) {
- Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");
- Matcher matcher = pattern.matcher(strLine);
- if (matcher.find()) {
- title = matcher.group(1);
- continue;
- }
- }
- //判断该行数据是否表示演唱者
- if (null == artist || "".equals(artist.trim())) {
- Pattern pattern = Pattern.compile("\\[ar:(.+?)\\]");
- Matcher matcher = pattern.matcher(strLine);
- if (matcher.find()) {
- artist = matcher.group(1);
- continue;
- }
- }
- //判断该行数据是否表示专辑
- if (null == album || "".equals(album.trim())) {
- Pattern pattern = Pattern.compile("\\[al:(.+?)\\]");
- Matcher matcher = pattern.matcher(strLine);
- if (matcher.find()) {
- album = matcher.group(1);
- continue;
- }
- }
- //判断该行数据是否表示歌词制作者
- if (null == lrcMaker || "".equals(lrcMaker.trim())) {
- Pattern pattern = Pattern.compile("\\[by:(.+?)\\]");
- Matcher matcher = pattern.matcher(strLine);
- if (matcher.find()) {
- lrcMaker = matcher.group(1);
- continue;
- }
- }
- //读取并分析歌词
- int timeNum = 0; //本行含时间个数
- String str[] = strLine.split("\\]"); //以]分隔
- for (int i = 0; i < str.length; ++i) {
- String str2[] = str[i].split("\\["); //以[分隔
- str[i] = str2[str2.length - 1];
- if (isTime(str[i])) {
- ++timeNum;
- }
- }
- for (int i = 0; i < timeNum; ++i) //处理歌词复用的情况
- {
- LyricStatement sm = new LyricStatement();
- sm.setTime(str[i]);
- if (timeNum < str.length) //如果有歌词
- {
- sm.setLyric(str[str.length - 1]);
- }
- statements.add(sm);
- }
- // if(1==str.length) //处理没有歌词的情况
- // {
- // Statement sm = new Statement();
- // sm.setTime(str[0]);
- // sm.setLyric("");
- // statements.add(sm);
- // }
- }
- //将读取的歌词按时间排序
- sortLyric();
- }
- /*
- * 判断给定的字符串是否表示时间.
- */
- private boolean isTime(String string) {
- String str[] = string.split(":|\\.");
- if (3 != str.length) {
- return false;
- }
- try {
- for (int i = 0; i < str.length; ++i) {
- Integer.parseInt(str[i]);
- }
- } catch (NumberFormatException e) {
- return false;
- }
- return true;
- }
- /*
- * 将读取的歌词按时间排序.
- */
- private void sortLyric() {
- for (int i = 0; i < statements.size() - 1; ++i) {
- int index = i;
- double delta = Double.MAX_VALUE;
- boolean moveFlag = false;
- for (int j = i + 1; j < statements.size(); ++j) {
- double sub;
- if (0 >= (sub = statements.get(i).getTime() - statements.get(j).getTime())) {
- continue;
- }
- moveFlag = true;
- if (sub < delta) {
- delta = sub;
- index = j + 1;
- }
- }
- if (moveFlag) {
- statements.add(index, statements.get(i));
- statements.remove(i);
- --i;
- }
- }
- }
- /*
- * 打印整个歌词文件
- */
- private void printLrcDate() {
- System.out.println("歌曲名: " + title);
- System.out.println("演唱者: " + artist);
- System.out.println("专辑名: " + album);
- System.out.println("歌词制作: " + lrcMaker);
- for (int i = 0; i < statements.size(); ++i) {
- statements.get(i).printLyric();
- }
- }
- /**
- * @param args
- * @throws IOException
- */
- public static void main(String[] args) throws IOException {
- /*
- * 测试"[", "]"的ASCII码
- */
- // {
- // char a='[', b = ']';
- // int na = (int)a;
- // int nb = (int)b;
- // System.out.println("a="+na+", b="+nb+"\n");
- // }
- /*
- * 测试匹配[]. 注: [应用\[表示. 同理]应用\]表示.
- */
- // {
- // String strLyric = "[02:13.41][02:13.42][02:13.43]错误的泪不想哭却硬要留住";
- // String str[] = strLyric.split("\\]");
- // for(int i=0; i<str.length; ++i)
- // {
- // String str2[] = str[i].split("\\[");
- // str[i] = str2[str2.length-1];
- // System.out.println(str[i]+" ");
- // }
- // }
- /*
- * 测试匹配[ti:]. 注: [应用\[表示. 同理]应用\]表示.
- */
- // {
- // String strLyric = "[ti:Forget]";
- // Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");
- // Matcher matcher = pattern.matcher(strLyric);
- // if(matcher.find())
- // System.out.println(matcher.group(1));
- // }
- /*
- * 测试排序算法
- */
- // {
- // Vector<Double> vect=new Vector<Double>();
- // vect.add(5.0);
- // vect.add(28.0);
- // vect.add(37.0);
- // vect.add(10.0);
- // vect.add(25.0);
- // vect.add(40.0);
- // vect.add(27.0);
- // vect.add(35.0);
- // vect.add(70.0);
- // vect.add(99.0);
- // vect.add(100.0);
- //
- // for(int i=0;i<vect.size();++i)
- // {
- // System.out.println(vect.elementAt(i));
- // }
- //
- // for(int i=0;i<vect.size()-1;++i)
- // {
- // int index=i;
- // double delta=Double.MAX_VALUE;
- // boolean moveFlag = false;
- // for(int j=i+1;j<vect.size();++j)
- // {
- // double sub;
- // if(0>=(sub=vect.get(i)-vect.get(j)))
- // {
- // continue;
- // }
- // moveFlag=true;
- // if(sub<delta)
- // {
- // delta=sub;
- // index=j+1;
- // }
- // }
- // if(moveFlag)
- // {
- // vect.add(index, vect.elementAt(i));
- // vect.remove(i);
- // System.out.println("第"+i);
- // --i;
- // }
- // }
- //
- // System.out.println("排序后");
- // for(int i=0;i<vect.size();++i)
- // {
- // System.out.println(vect.elementAt(i));
- // }
- // }
- /*
- * 测试由字符串转化为双精度时间
- */
- // {
- // String stime="02:03.09";
- // String str[] = stime.split(":|\\.");
- // for(int i=0;i<str.length;++i)
- // {
- // System.out.print("时间"+str[i]+":");
- // }
- // double dtime = Integer.parseInt(str[0])*60+Integer.parseInt(str[1])+Integer.parseInt(str[2])*0.01;
- // System.out.println("time="+dtime);
- // }
- /*
- * 测试整个类
- */
- {
- LyricReader ld = new LyricReader("D:\\music\\海盗.lrc"); //路径\\输入文件名
- ld.printLrcDate();
- }
- }
- }
有了歌词解释器和一个歌词列表,下面就可以进行歌词显示控件的设计了。/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package musicbox.model.lyric; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * * @author Randyzhao */ public class LyricReader { BufferedReader bufferReader = null; //读取文件实例 public String title = ""; //歌曲题目 public String artist = ""; //演唱者 public String album = ""; //专辑 public String lrcMaker = ""; //歌词制作者 List<LyricStatement> statements = new ArrayList<LyricStatement>(); //歌词 /* * 实例化一个歌词数据. 歌词数据信息由指定的文件提供. * fileName: 指定的歌词文件. */ public LyricReader(String fileName) throws IOException { //in case the space in the fileName is replaced by the %20 FileInputStream file = new FileInputStream(URLDecoder.decode(fileName, "UTF-8")); bufferReader = new BufferedReader(new InputStreamReader(file, "GB2312")); //将文件数据读入内存 readData(); } public List<LyricStatement> getStatements() { return statements; } /* * 读取文件中数据至内存. */ private void readData() throws IOException { statements.clear(); String strLine; //循环读入所有行 while (null != (strLine = bufferReader.readLine())) { //判断该行是否为空行 if ("".equals(strLine.trim())) { continue; } //判断该行数据是否表示歌名 if (null == title || "".equals(title.trim())) { Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]"); Matcher matcher = pattern.matcher(strLine); if (matcher.find()) { title = matcher.group(1); continue; } } //判断该行数据是否表示演唱者 if (null == artist || "".equals(artist.trim())) { Pattern pattern = Pattern.compile("\\[ar:(.+?)\\]"); Matcher matcher = pattern.matcher(strLine); if (matcher.find()) { artist = matcher.group(1); continue; } } //判断该行数据是否表示专辑 if (null == album || "".equals(album.trim())) { Pattern pattern = Pattern.compile("\\[al:(.+?)\\]"); Matcher matcher = pattern.matcher(strLine); if (matcher.find()) { album = matcher.group(1); continue; } } //判断该行数据是否表示歌词制作者 if (null == lrcMaker || "".equals(lrcMaker.trim())) { Pattern pattern = Pattern.compile("\\[by:(.+?)\\]"); Matcher matcher = pattern.matcher(strLine); if (matcher.find()) { lrcMaker = matcher.group(1); continue; } } //读取并分析歌词 int timeNum = 0; //本行含时间个数 String str[] = strLine.split("\\]"); //以]分隔 for (int i = 0; i < str.length; ++i) { String str2[] = str[i].split("\\["); //以[分隔 str[i] = str2[str2.length - 1]; if (isTime(str[i])) { ++timeNum; } } for (int i = 0; i < timeNum; ++i) //处理歌词复用的情况 { LyricStatement sm = new LyricStatement(); sm.setTime(str[i]); if (timeNum < str.length) //如果有歌词 { sm.setLyric(str[str.length - 1]); } statements.add(sm); } // if(1==str.length) //处理没有歌词的情况 // { // Statement sm = new Statement(); // sm.setTime(str[0]); // sm.setLyric(""); // statements.add(sm); // } } //将读取的歌词按时间排序 sortLyric(); } /* * 判断给定的字符串是否表示时间. */ private boolean isTime(String string) { String str[] = string.split(":|\\."); if (3 != str.length) { return false; } try { for (int i = 0; i < str.length; ++i) { Integer.parseInt(str[i]); } } catch (NumberFormatException e) { return false; } return true; } /* * 将读取的歌词按时间排序. */ private void sortLyric() { for (int i = 0; i < statements.size() - 1; ++i) { int index = i; double delta = Double.MAX_VALUE; boolean moveFlag = false; for (int j = i + 1; j < statements.size(); ++j) { double sub; if (0 >= (sub = statements.get(i).getTime() - statements.get(j).getTime())) { continue; } moveFlag = true; if (sub < delta) { delta = sub; index = j + 1; } } if (moveFlag) { statements.add(index, statements.get(i)); statements.remove(i); --i; } } } /* * 打印整个歌词文件 */ private void printLrcDate() { System.out.println("歌曲名: " + title); System.out.println("演唱者: " + artist); System.out.println("专辑名: " + album); System.out.println("歌词制作: " + lrcMaker); for (int i = 0; i < statements.size(); ++i) { statements.get(i).printLyric(); } } /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { /* * 测试"[", "]"的ASCII码 */ // { // char a='[', b = ']'; // int na = (int)a; // int nb = (int)b; // System.out.println("a="+na+", b="+nb+"\n"); // } /* * 测试匹配[]. 注: [应用\[表示. 同理]应用\]表示. */ // { // String strLyric = "[02:13.41][02:13.42][02:13.43]错误的泪不想哭却硬要留住"; // String str[] = strLyric.split("\\]"); // for(int i=0; i<str.length; ++i) // { // String str2[] = str[i].split("\\["); // str[i] = str2[str2.length-1]; // System.out.println(str[i]+" "); // } // } /* * 测试匹配[ti:]. 注: [应用\[表示. 同理]应用\]表示. */ // { // String strLyric = "[ti:Forget]"; // Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]"); // Matcher matcher = pattern.matcher(strLyric); // if(matcher.find()) // System.out.println(matcher.group(1)); // } /* * 测试排序算法 */ // { // Vector<Double> vect=new Vector<Double>(); // vect.add(5.0); // vect.add(28.0); // vect.add(37.0); // vect.add(10.0); // vect.add(25.0); // vect.add(40.0); // vect.add(27.0); // vect.add(35.0); // vect.add(70.0); // vect.add(99.0); // vect.add(100.0); // // for(int i=0;i<vect.size();++i) // { // System.out.println(vect.elementAt(i)); // } // // for(int i=0;i<vect.size()-1;++i) // { // int index=i; // double delta=Double.MAX_VALUE; // boolean moveFlag = false; // for(int j=i+1;j<vect.size();++j) // { // double sub; // if(0>=(sub=vect.get(i)-vect.get(j))) // { // continue; // } // moveFlag=true; // if(sub<delta) // { // delta=sub; // index=j+1; // } // } // if(moveFlag) // { // vect.add(index, vect.elementAt(i)); // vect.remove(i); // System.out.println("第"+i); // --i; // } // } // // System.out.println("排序后"); // for(int i=0;i<vect.size();++i) // { // System.out.println(vect.elementAt(i)); // } // } /* * 测试由字符串转化为双精度时间 */ // { // String stime="02:03.09"; // String str[] = stime.split(":|\\."); // for(int i=0;i<str.length;++i) // { // System.out.print("时间"+str[i]+":"); // } // double dtime = Integer.parseInt(str[0])*60+Integer.parseInt(str[1])+Integer.parseInt(str[2])*0.01; // System.out.println("time="+dtime); // } /* * 测试整个类 */ { LyricReader ld = new LyricReader("D:\\music\\海盗.lrc"); //路径\\输入文件名 ld.printLrcDate(); } } }
由于在Swing框架中设计歌词显示控件,那么最好的选择就是继承一个JPanel控件。当需要刷新屏幕上歌词的时候将多行歌词绘制在一个Image上面,然后重写paint函数。
以下是程序代码。
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package musicbox.view;
- import java.awt.AlphaComposite;
- import java.awt.Color;
- import java.awt.Dimension;
- import java.awt.Font;
- import java.awt.FontMetrics;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.Image;
- import java.awt.RenderingHints;
- import java.io.File;
- import java.io.IOException;
- import java.net.URISyntaxException;
- import java.net.URL;
- import java.util.List;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.imageio.ImageIO;
- import javax.swing.JPanel;
- import musicbox.model.lyric.LyricStatement;
- /**
- * Used to display the lyric
- * @author Randyzhao
- */
- public class LyricDisplayer extends JPanel {
- protected final Color CURRENT_LINE_COLOR = Color.green;
- protected final Color OTHER_LINE_COLOR = Color.GRAY;
- //the lines other than the current line to be displayed
- protected final int UP_DOWN_LINES = 8;
- //the list of lyric statements to be displayed
- protected List<LyricStatement> statements;
- //the index of next statement to be dispalyed in the statements
- protected int index;
- protected Image backgroundImage = null;
- private String backGroundImagePath = null;
- protected Image bufferImage = null;
- //the size when the bufferImage is drawn
- private Dimension bufferedSize;
- public String getBackGroundImagePath() {
- return backGroundImagePath;
- }
- public void setBackGroundImagePath(String backGroundImagePath) {
- this.backGroundImagePath = backGroundImagePath;
- }
- /**
- * get ready to display
- * @param statements
- */
- public void prepareDisplay(List<LyricStatement> statements) {
- this.statements = statements;
- this.index = -1;
- this.setFont(new Font("微软雅黑", Font.PLAIN, 20));
- }
- /**
- * display a lyric by the index
- * @param index
- */
- public void displayLyric(int index) {
- this.index = index;
- this.drawBufferImage();
- // System.out.println("draw " + index + " " + this.statements.get(index).getLyric());
- this.paint(this.getGraphics());
- }
- /**
- * draw a line of lyric in the middle of the Graphics2D
- * @param lyric
- * @param g2d
- */
- protected void drawLineInMiddle(int height, String lyric, Graphics2D g2d, Color color) {
- int width = this.getWidth();
- FontMetrics fm = g2d.getFontMetrics();
- g2d.setColor(color);
- int x = (this.getWidth() - fm.stringWidth(lyric)) / 2;
- g2d.drawString(lyric, x, height);
- }
- /**
- * Draw the buffered image. Used to realize the double-buffering.
- */
- protected void drawBufferImage() {
- Image tempBufferedImage = this.createImage(this.getWidth(), this.getHeight());
- this.bufferedSize = this.getSize();
- if (this.backgroundImage == null) {
- //get background image
- URL url = getClass().getResource(this.backGroundImagePath);
- try {
- backgroundImage = ImageIO.read(url);
- //缩放图片
- backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- Graphics2D g2d = (Graphics2D) tempBufferedImage.getGraphics();
- g2d.setFont(new Font("楷体", Font.PLAIN, 25));
- g2d.drawImage(this.backgroundImage, 0, 0, this.getWidth(), this.getHeight(), null);
- g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON);
- if (this.statements != null && this.statements.size() != 0) {
- //draw current line
- g2d.setFont(new Font("楷体", Font.PLAIN, 35));
- this.drawLineInMiddle(this.getHeight() / 2,
- this.statements.get(index).getLyric(), g2d, this.CURRENT_LINE_COLOR);
- int perHeight = g2d.getFontMetrics().getHeight() + 5;
- g2d.setFont(new Font("楷体", Font.PLAIN, 25));
- //draw down lines
- for (int i = index - UP_DOWN_LINES; i < index; i++) {
- if (i < 0) {
- continue;
- }
- if (index - i > UP_DOWN_LINES / 2) {
- //set transparance
- float ratio = (float) (i - index + UP_DOWN_LINES) / (UP_DOWN_LINES / 2) / 1.2f;
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
- ratio));
- } else {
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
- 1.0f));
- }
- this.drawLineInMiddle(this.getHeight() / 2 - (index - i) * perHeight,
- this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);
- }
- //draw up lines
- for (int i = index + 1; i < index + UP_DOWN_LINES; i++) {
- if (i >= this.statements.size()) {
- break;
- }
- if (i - index > UP_DOWN_LINES / 2) {
- //set transparance
- float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
- ratio));
- } else {
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
- 1.0f));
- }
- this.drawLineInMiddle(this.getHeight() / 2 + (i - index) * perHeight,
- this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);
- }
- } else {
- //statements is empty
- this.drawLineInMiddle(this.getHeight() / 2,
- "未找到相应的歌词文件", g2d, this.CURRENT_LINE_COLOR);
- }
- //copyt the buffered image
- this.bufferImage = tempBufferedImage;
- }
- /**
- * This method is override in order to display the lyric in the panel
- * @param g
- */
- @Override
- public void paint(Graphics g) {
- if (this.isVisible() == false) {
- return;
- }
- super.paint(g);
- //draw buffered image
- if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()
- || this.getHeight() != this.bufferedSize.getHeight()) {
- this.drawBufferImage();
- }
- //copy the double buffer
- g.drawImage(bufferImage, 0, 0, null);
- }
- }
下面进行简单的解释。/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package musicbox.view; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JPanel; import musicbox.model.lyric.LyricStatement; /** * Used to display the lyric * @author Randyzhao */ public class LyricDisplayer extends JPanel { protected final Color CURRENT_LINE_COLOR = Color.green; protected final Color OTHER_LINE_COLOR = Color.GRAY; //the lines other than the current line to be displayed protected final int UP_DOWN_LINES = 8; //the list of lyric statements to be displayed protected List<LyricStatement> statements; //the index of next statement to be dispalyed in the statements protected int index; protected Image backgroundImage = null; private String backGroundImagePath = null; protected Image bufferImage = null; //the size when the bufferImage is drawn private Dimension bufferedSize; public String getBackGroundImagePath() { return backGroundImagePath; } public void setBackGroundImagePath(String backGroundImagePath) { this.backGroundImagePath = backGroundImagePath; } /** * get ready to display * @param statements */ public void prepareDisplay(List<LyricStatement> statements) { this.statements = statements; this.index = -1; this.setFont(new Font("微软雅黑", Font.PLAIN, 20)); } /** * display a lyric by the index * @param index */ public void displayLyric(int index) { this.index = index; this.drawBufferImage(); // System.out.println("draw " + index + " " + this.statements.get(index).getLyric()); this.paint(this.getGraphics()); } /** * draw a line of lyric in the middle of the Graphics2D * @param lyric * @param g2d */ protected void drawLineInMiddle(int height, String lyric, Graphics2D g2d, Color color) { int width = this.getWidth(); FontMetrics fm = g2d.getFontMetrics(); g2d.setColor(color); int x = (this.getWidth() - fm.stringWidth(lyric)) / 2; g2d.drawString(lyric, x, height); } /** * Draw the buffered image. Used to realize the double-buffering. */ protected void drawBufferImage() { Image tempBufferedImage = this.createImage(this.getWidth(), this.getHeight()); this.bufferedSize = this.getSize(); if (this.backgroundImage == null) { //get background image URL url = getClass().getResource(this.backGroundImagePath); try { backgroundImage = ImageIO.read(url); //缩放图片 backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20); } catch (IOException ex) { ex.printStackTrace(); } } Graphics2D g2d = (Graphics2D) tempBufferedImage.getGraphics(); g2d.setFont(new Font("楷体", Font.PLAIN, 25)); g2d.drawImage(this.backgroundImage, 0, 0, this.getWidth(), this.getHeight(), null); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (this.statements != null && this.statements.size() != 0) { //draw current line g2d.setFont(new Font("楷体", Font.PLAIN, 35)); this.drawLineInMiddle(this.getHeight() / 2, this.statements.get(index).getLyric(), g2d, this.CURRENT_LINE_COLOR); int perHeight = g2d.getFontMetrics().getHeight() + 5; g2d.setFont(new Font("楷体", Font.PLAIN, 25)); //draw down lines for (int i = index - UP_DOWN_LINES; i < index; i++) { if (i < 0) { continue; } if (index - i > UP_DOWN_LINES / 2) { //set transparance float ratio = (float) (i - index + UP_DOWN_LINES) / (UP_DOWN_LINES / 2) / 1.2f; g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ratio)); } else { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); } this.drawLineInMiddle(this.getHeight() / 2 - (index - i) * perHeight, this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR); } //draw up lines for (int i = index + 1; i < index + UP_DOWN_LINES; i++) { if (i >= this.statements.size()) { break; } if (i - index > UP_DOWN_LINES / 2) { //set transparance float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f; g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ratio)); } else { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); } this.drawLineInMiddle(this.getHeight() / 2 + (i - index) * perHeight, this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR); } } else { //statements is empty this.drawLineInMiddle(this.getHeight() / 2, "未找到相应的歌词文件", g2d, this.CURRENT_LINE_COLOR); } //copyt the buffered image this.bufferImage = tempBufferedImage; } /** * This method is override in order to display the lyric in the panel * @param g */ @Override public void paint(Graphics g) { if (this.isVisible() == false) { return; } super.paint(g); //draw buffered image if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth() || this.getHeight() != this.bufferedSize.getHeight()) { this.drawBufferImage(); } //copy the double buffer g.drawImage(bufferImage, 0, 0, null); } }
当LyricDisplayer的实例初始化之后,外部代码应该调用它的prepareDisplay函数。告诉它显示的歌词列表,调用setBackGroundImagePath函数,告诉它歌词背景图片所在的位置。
之后当需要显示某一句歌词的时候,调用displayLyric函数,参数是prepareDisplay函数参数中歌词列表对应歌词的index。此时LyricDisplayer实例会调用自己的drawBufferImage函数来重新绘制Image。在绘制的时候,
- if (this.backgroundImage == null) {
- //get background image
- URL url = getClass().getResource(this.backGroundImagePath);
- try {
- backgroundImage = ImageIO.read(url);
- //缩放图片
- backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
if (this.backgroundImage == null) { //get background image URL url = getClass().getResource(this.backGroundImagePath); try { backgroundImage = ImageIO.read(url); //缩放图片 backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20); } catch (IOException ex) { ex.printStackTrace(); } }
这段代码用于从硬盘中读取背景文件并缩放至JPanel的大小。如果JPanel大小没有变化,而且之前已经初始化过背景图片,那么不要重复初始化。之后主要就是应用Graphics2D中的drawString函数来将一个字符串绘制在Image上面。
- FontMetrics fm = g2d.getFontMetrics();
上面这语句初始化一个FontMerics对象,可以调用它的stringWidth函数来计算它对应的graphics2D对象中的一行字的高度,方便你计算绘制的位置。FontMetrics fm = g2d.getFontMetrics();
在调用drawString函数之前,你可以调用setComposite函数,如以下代码
- float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
- ratio));
float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f; g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ratio));
这样可以设置接下来绘制的字符串的透明度,这样就实现了淡入淡出效果。
绘制完Image后调用paint函数将Image刷到屏幕上。这样的设计相当于实现了一个双缓冲。如果直接在JPanle上绘制那么屏幕一定会闪。
在paint函数中
- if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()
- || this.getHeight() != this.bufferedSize.getHeight()) {
- this.drawBufferImage();
- }
if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth() || this.getHeight() != this.bufferedSize.getHeight()) { this.drawBufferImage(); }
这句话是判断如果原来已经绘制过Image并且JPanel尺寸和绘制Image的时候相比没有改变,那么不用重新绘制Image,直接把它刷到屏幕上来。
Java Swing制作多行滚动歌词显示控件
最新推荐文章于 2021-03-02 04:58:10 发布