首先上图一张,为最终制作的效果图,不喜欢或感到失望的朋友可以先行离开

大家已经看到效果图了。那么下面就介绍设计思路和源代码

首先要想显示歌词,就要对歌词文件进行抽象。下面这个类是对某一行歌词文件进行了抽象。

/*

* 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 这是为了和歌词文件中时间的单位统一。

某一行歌词可以用一个LyricStatement类的实例来表示。那么一个歌词文件就可以解析为一个List。为了方便测试,以下附上本人自己写的一个歌词文件解释器。

/*

* 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 statements = new ArrayList();//歌词

/*

* 实例化一个歌词数据. 歌词数据信息由指定的文件提供.

* 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 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

//{

//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));

//}

/*

* 测试排序

有了歌词解释器和一个歌词列表,下面就可以进行歌词显示控件的设计了。

由于在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 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 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();

}

}

这段代码用于从硬盘中读取背景文件并缩放至JPanel的大小。如果JPanel大小没有变化,而且之前已经初始化过背景图片,那么不要重复初始化。

之后主要就是应用Graphics2D中的drawString函数来将一个字符串绘制在Image上面。

FontMetrics fm = g2d.getFontMetrics();

上面这语句初始化一个FontMerics对象,可以调用它的stringWidth函数来计算它对应的graphics2D对象中的一行字的高度,方便你计算绘制的位置。

在调用drawString函数之前,你可以调用setComposite函数,如以下代码

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

}

这句话是判断如果原来已经绘制过Image并且JPanel尺寸和绘制Image的时候相比没有改变,那么不用重新绘制Image,直接把它刷到屏幕上来。

java 显示多行歌词_Java Swing制作多行滚动歌词显示控件 | 学步园相关推荐

  1. java判断简体和繁体字_java获取系统语言(区分简体中文和繁体中文) | 学步园...

    之前做android应用时遇到过一个问题,就是根据语言的不同而显示不同的内容 网上很多代码都是错误的,起码无法区分简体和繁体,这里给出一种方法 不涉及android任何知识,所以就归类到java这边了 ...

  2. java序列化和反序列化工具_Java 序列化和反序列化工具类并解决StreamCorruptedException问题 | 学步园...

    问题: 若通过ObjectOutputStream向一个文件中多次以追加的方式写入Object,为什么用ObjectInputStream读取这些Object时,会产生StreamCorruptedE ...

  3. java中bean是什么_java中bean是什么意思?有什么作用 | 学步园

    Bean的中文含义是"豆子",顾名思义JavaBean是一段Java小程序.JavaBean实际上是指一种特殊的Java类,它通常用来实现一些比较常用的简单功能,并可以很容易的被重 ...

  4. java给界面添加滚动条_Java Swing学习笔记:要求会默写或熟练的,GUI,控件,设置列或行,加滚动条,新界面...

    GUI:Java的图形化用户界面 学习其控件的使用 JLabel(文本控件),JTextField(文本框),JPassworldField(密码框),JButton(普通按钮) JRadioButt ...

  5. java jtable 复选框_java swing如何在JTable一个单元格添加多个复选框

    展开全部 java swing中在jTable中添加多个复选框的方32313133353236313431303231363533e59b9ee7ad9431333337616566式如下:impor ...

  6. java怎么开发图形界面_Java Swing 图形界面开发简介

    1. Swing简介 Swing 是 Java 为图形界面应用开发提供的一组工具包,是 Java 基础类的一部分. Swing 包含了构建图形界面(GUI)的各种组件,如: 窗口.标签.按钮.文本框等 ...

  7. java gui 读取文件夹_java Swing GUI 入门-文件读写器

    java Swing GUI 入门-文件读写器 觉得有用的话,欢迎一起讨论相互学习~ 首先创建一个独立的窗口 public CoupPad(){} public static void main(St ...

  8. java scrollpane 设置透明_java swing 之 JScrollPane(滚动面板)的使用

    /** * java swing 之JScrollPane面板 * 在设置界面时,可能会遇到在一个较小的容器窗体中显示一个较大部分的内容,这时可以使用 * JScrollPane面板,JscrollP ...

  9. java 窗体添加背景图片_Java Swing实现窗体添加背景图片的2种方法详解

    本文实例讲述了java Swing实现窗体添加背景图片的2种方法.分享给大家供大家参考,具体如下: 在美化程序时,常常需要在窗体上添加背景图片.通过搜索和测试,发现了2种有效方式.下面分别介绍. 1. ...

最新文章

  1. python getattr调用自己模块_在Python中通过getattr获取对象引用的方法
  2. Python使用aiohttp异步爬取糗事百科
  3. 临沧计算机教试报名,2019下半年临沧小学计算机教师资格证考什么?
  4. 深度学习(三十二)——AlphaGo, AlphaStar
  5. Java String类型变量的比较问题
  6. ssis zip压缩文件_SSIS平面文件与原始文件
  7. 将标签重新定义为4个空格
  8. java商城源码_盘点这些年被黑的最惨的语言,Java瑟瑟发抖
  9. Java中的断言assert的用法
  10. Android应用程序消息处理机制(Looper、Handler)分析(3)
  11. SharedPreferences的制作
  12. 【026】国务院督查组莅临翼辉信息参观调研
  13. 计算机视觉面试宝典--目标检测篇(二)
  14. 代理服务是个什么东西?
  15. 小米生态链布局遇阻,求快的小米却更慢了
  16. html添加视频背景
  17. 如何提升自己的打字速度?
  18. 彩旗飘飘 彩灯烁烁的桥
  19. (附源码)计算机毕业设计ssm高校社团管理系统
  20. 特征工程 | 信息价值IV与群体稳定性PSI

热门文章

  1. 入门C++————1.c++基础知识
  2. 微信小程序开发之卡片分享
  3. 保留thinkvantage一键恢复功能的Linux与vista双系统安装
  4. 神域传奇java,神域领地超变传奇
  5. mysql 查看 udf_MySQL的UDF
  6. 解决文件夹隐藏属性无法取消的办法
  7. NTC温度以及模拟量湿度传感器计算公式
  8. XmlDocument类的常见使用
  9. MOS管发热原因分析一
  10. 微信号正则校验,qq正则,邮箱正则,英文名正则