Java Swing制作多行滚动歌词显示控件


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


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

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

    1. /* 
    2.  * To change this template, choose Tools | Templates 
    3.  * and open the template in the editor. 
    4.  */  
    5. package musicbox.model.lyric;  
    6.   
    7. /** 
    8.  * 
    9.  * @author Randyzhao 
    10.  */  
    11. public class LyricStatement {  
    12.   
    13.     private long time = 0;//时间, 单位为10ms   
    14.     private String lyric = "";//歌词   
    15.   
    16.     /* 
    17.      * 获取时间 
    18.      */  
    19.     public long getTime() {  
    20.     return time;  
    21.     }  
    22.     /* 
    23.      * 设置时间 
    24.      * time: 被设置成的时间 
    25.      */  
    26.   
    27.     public void setTime(int time) {  
    28.     this.time = time;  
    29.     }  
    30.     /* 
    31.      * 设置时间 
    32.      * time: 被设置成的时间字符串, 格式为mm:ss.ms 
    33.      */  
    34.   
    35.     public void setTime(String time) {  
    36.     String str[] = time.split(":|\\.");  
    37.     this.time = Integer.parseInt(str[0]) * 6000 + Integer.parseInt(str[1]) * 100 +   
    38.         Integer.parseInt(str[2]);  
    39.     }  
    40.     /* 
    41.      * 获取歌词 
    42.      */  
    43.   
    44.     public String getLyric() {  
    45.     return lyric;  
    46.     }  
    47.     /* 
    48.      * 设置歌词 
    49.      */  
    50.   
    51.     public void setLyric(String lyric) {  
    52.     this.lyric = lyric;  
    53.     }  
    54.     /* 
    55.      * 打印歌词 
    56.      */  
    57.   
    58.     public void printLyric() {  
    59.     System.out.println(time + ": " + lyric);  
    60.     }  
    61. }  
    /*
     * 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<LyricStatement>。为了方便测试,以下附上本人自己写的一个歌词文件解释器。

    1. /* 
    2.  * To change this template, choose Tools | Templates 
    3.  * and open the template in the editor. 
    4.  */  
    5. package musicbox.model.lyric;  
    6.   
    7. import java.io.BufferedReader;  
    8. import java.io.FileInputStream;  
    9. import java.io.IOException;  
    10. import java.io.InputStreamReader;  
    11. import java.net.URLDecoder;  
    12. import java.util.ArrayList;  
    13. import java.util.List;  
    14. import java.util.regex.Matcher;  
    15. import java.util.regex.Pattern;  
    16.   
    17. /** 
    18.  * 
    19.  * @author Randyzhao 
    20.  */  
    21. public class LyricReader {  
    22.   
    23.     BufferedReader bufferReader = null;                         //读取文件实例   
    24.     public String title = "";                                   //歌曲题目   
    25.     public String artist = "";                                  //演唱者   
    26.     public String album = "";                                   //专辑   
    27.     public String lrcMaker = "";                                //歌词制作者   
    28.     List<LyricStatement> statements = new ArrayList<LyricStatement>();      //歌词   
    29.     /*  
    30.      * 实例化一个歌词数据. 歌词数据信息由指定的文件提供.  
    31.      * fileName: 指定的歌词文件.  
    32.      */  
    33.   
    34.     public LyricReader(String fileName) throws IOException {  
    35.     //in case the space in the fileName is replaced by the %20   
    36.     FileInputStream file = new FileInputStream(URLDecoder.decode(fileName, "UTF-8"));  
    37.     bufferReader = new BufferedReader(new InputStreamReader(file, "GB2312"));  
    38.   
    39.     //将文件数据读入内存   
    40.     readData();  
    41.     }  
    42.   
    43.     public List<LyricStatement> getStatements() {  
    44.     return statements;  
    45.     }  
    46.   
    47.     /* 
    48.      * 读取文件中数据至内存.  
    49.      */  
    50.     private void readData() throws IOException {  
    51.     statements.clear();  
    52.     String strLine;  
    53.     //循环读入所有行   
    54.     while (null != (strLine = bufferReader.readLine())) {  
    55.         //判断该行是否为空行   
    56.         if ("".equals(strLine.trim())) {  
    57.         continue;  
    58.         }  
    59.         //判断该行数据是否表示歌名   
    60.         if (null == title || "".equals(title.trim())) {  
    61.         Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");  
    62.         Matcher matcher = pattern.matcher(strLine);  
    63.         if (matcher.find()) {  
    64.             title = matcher.group(1);  
    65.             continue;  
    66.         }  
    67.         }  
    68.         //判断该行数据是否表示演唱者   
    69.         if (null == artist || "".equals(artist.trim())) {  
    70.         Pattern pattern = Pattern.compile("\\[ar:(.+?)\\]");  
    71.         Matcher matcher = pattern.matcher(strLine);  
    72.         if (matcher.find()) {  
    73.             artist = matcher.group(1);  
    74.             continue;  
    75.         }  
    76.         }  
    77.         //判断该行数据是否表示专辑   
    78.         if (null == album || "".equals(album.trim())) {  
    79.         Pattern pattern = Pattern.compile("\\[al:(.+?)\\]");  
    80.         Matcher matcher = pattern.matcher(strLine);  
    81.         if (matcher.find()) {  
    82.             album = matcher.group(1);  
    83.             continue;  
    84.         }  
    85.         }  
    86.         //判断该行数据是否表示歌词制作者   
    87.         if (null == lrcMaker || "".equals(lrcMaker.trim())) {  
    88.         Pattern pattern = Pattern.compile("\\[by:(.+?)\\]");  
    89.         Matcher matcher = pattern.matcher(strLine);  
    90.         if (matcher.find()) {  
    91.             lrcMaker = matcher.group(1);  
    92.             continue;  
    93.         }  
    94.         }  
    95.         //读取并分析歌词   
    96.         int timeNum = 0;                                        //本行含时间个数   
    97.         String str[] = strLine.split("\\]");                //以]分隔   
    98.         for (int i = 0; i < str.length; ++i) {  
    99.         String str2[] = str[i].split("\\[");            //以[分隔   
    100.         str[i] = str2[str2.length - 1];  
    101.         if (isTime(str[i])) {  
    102.             ++timeNum;  
    103.         }  
    104.         }  
    105.         for (int i = 0; i < timeNum; ++i) //处理歌词复用的情况   
    106.         {  
    107.         LyricStatement sm = new LyricStatement();  
    108.         sm.setTime(str[i]);  
    109.         if (timeNum < str.length) //如果有歌词   
    110.         {  
    111.             sm.setLyric(str[str.length - 1]);  
    112.         }  
    113.         statements.add(sm);  
    114.         }  
    115. //          if(1==str.length)                                   //处理没有歌词的情况   
    116. //          {   
    117. //              Statement sm = new Statement();   
    118. //              sm.setTime(str[0]);   
    119. //              sm.setLyric("");   
    120. //              statements.add(sm);   
    121. //          }   
    122.     }  
    123.   
    124.     //将读取的歌词按时间排序   
    125.     sortLyric();  
    126.     }  
    127.     /* 
    128.      * 判断给定的字符串是否表示时间.  
    129.      */  
    130.   
    131.     private boolean isTime(String string) {  
    132.     String str[] = string.split(":|\\.");  
    133.     if (3 != str.length) {  
    134.         return false;  
    135.     }  
    136.     try {  
    137.         for (int i = 0; i < str.length; ++i) {  
    138.         Integer.parseInt(str[i]);  
    139.         }  
    140.     } catch (NumberFormatException e) {  
    141.         return false;  
    142.     }  
    143.     return true;  
    144.     }  
    145.     /* 
    146.      * 将读取的歌词按时间排序.  
    147.      */  
    148.   
    149.     private void sortLyric() {  
    150.     for (int i = 0; i < statements.size() - 1; ++i) {  
    151.         int index = i;  
    152.         double delta = Double.MAX_VALUE;  
    153.         boolean moveFlag = false;  
    154.         for (int j = i + 1; j < statements.size(); ++j) {  
    155.         double sub;  
    156.         if (0 >= (sub = statements.get(i).getTime() - statements.get(j).getTime())) {  
    157.             continue;  
    158.         }  
    159.         moveFlag = true;  
    160.         if (sub < delta) {  
    161.             delta = sub;  
    162.             index = j + 1;  
    163.         }  
    164.         }  
    165.         if (moveFlag) {  
    166.         statements.add(index, statements.get(i));  
    167.         statements.remove(i);  
    168.         --i;  
    169.         }  
    170.     }  
    171.     }  
    172.     /* 
    173.      * 打印整个歌词文件 
    174.      */  
    175.   
    176.     private void printLrcDate() {  
    177.     System.out.println("歌曲名: " + title);  
    178.     System.out.println("演唱者: " + artist);  
    179.     System.out.println("专辑名: " + album);  
    180.     System.out.println("歌词制作: " + lrcMaker);  
    181.     for (int i = 0; i < statements.size(); ++i) {  
    182.         statements.get(i).printLyric();  
    183.     }  
    184.     }  
    185.   
    186.     /** 
    187.      * @param args 
    188.      * @throws IOException  
    189.      */  
    190.     public static void main(String[] args) throws IOException {  
    191.     /* 
    192.      * 测试"[", "]"的ASCII码 
    193.      */  
    194. //      {   
    195. //          char a='[', b = ']';   
    196. //          int na = (int)a;   
    197. //          int nb = (int)b;   
    198. //          System.out.println("a="+na+", b="+nb+"\n");   
    199. //      }   
    200.         /* 
    201.      * 测试匹配[]. 注: [应用\[表示. 同理]应用\]表示.  
    202.      */  
    203. //      {   
    204. //          String strLyric = "[02:13.41][02:13.42][02:13.43]错误的泪不想哭却硬要留住";   
    205. //          String str[] = strLyric.split("\\]");   
    206. //          for(int i=0; i<str.length; ++i)   
    207. //          {   
    208. //              String str2[] = str[i].split("\\[");   
    209. //              str[i] = str2[str2.length-1];   
    210. //              System.out.println(str[i]+" ");   
    211. //          }   
    212. //      }   
    213.         /* 
    214.      * 测试匹配[ti:]. 注: [应用\[表示. 同理]应用\]表示.  
    215.      */  
    216. //      {   
    217. //          String strLyric = "[ti:Forget]";   
    218. //          Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");   
    219. //          Matcher matcher = pattern.matcher(strLyric);   
    220. //          if(matcher.find())   
    221. //            System.out.println(matcher.group(1));   
    222. //      }   
    223.         /* 
    224.      * 测试排序算法 
    225.      */  
    226. //      {   
    227. //          Vector<Double> vect=new Vector<Double>();   
    228. //          vect.add(5.0);   
    229. //          vect.add(28.0);   
    230. //          vect.add(37.0);   
    231. //          vect.add(10.0);   
    232. //          vect.add(25.0);   
    233. //          vect.add(40.0);   
    234. //          vect.add(27.0);   
    235. //          vect.add(35.0);   
    236. //          vect.add(70.0);   
    237. //          vect.add(99.0);   
    238. //          vect.add(100.0);   
    239. //             
    240. //          for(int i=0;i<vect.size();++i)   
    241. //          {   
    242. //              System.out.println(vect.elementAt(i));   
    243. //          }   
    244. //             
    245. //          for(int i=0;i<vect.size()-1;++i)   
    246. //          {   
    247. //              int index=i;   
    248. //              double delta=Double.MAX_VALUE;   
    249. //              boolean moveFlag = false;   
    250. //              for(int j=i+1;j<vect.size();++j)   
    251. //              {   
    252. //                  double sub;   
    253. //                  if(0>=(sub=vect.get(i)-vect.get(j)))   
    254. //                  {   
    255. //                      continue;   
    256. //                  }   
    257. //                  moveFlag=true;   
    258. //                  if(sub<delta)   
    259. //                  {   
    260. //                      delta=sub;   
    261. //                      index=j+1;   
    262. //                  }   
    263. //              }   
    264. //              if(moveFlag)   
    265. //              {   
    266. //                  vect.add(index, vect.elementAt(i));   
    267. //                  vect.remove(i);    
    268. //                  System.out.println("第"+i);   
    269. //                  --i;   
    270. //              }   
    271. //          }   
    272. //   
    273. //          System.out.println("排序后");   
    274. //          for(int i=0;i<vect.size();++i)   
    275. //          {   
    276. //              System.out.println(vect.elementAt(i));   
    277. //          }   
    278. //      }   
    279.   
    280.     /* 
    281.      * 测试由字符串转化为双精度时间 
    282.      */  
    283. //      {   
    284. //          String stime="02:03.09";   
    285. //          String str[] = stime.split(":|\\.");   
    286. //          for(int i=0;i<str.length;++i)   
    287. //          {   
    288. //              System.out.print("时间"+str[i]+":");   
    289. //          }   
    290. //          double dtime = Integer.parseInt(str[0])*60+Integer.parseInt(str[1])+Integer.parseInt(str[2])*0.01;   
    291. //          System.out.println("time="+dtime);   
    292. //      }   
    293.   
    294.     /* 
    295.      * 测试整个类 
    296.      */  
    297.     {  
    298.   
    299.         LyricReader ld = new LyricReader("D:\\music\\海盗.lrc");              //路径\\输入文件名   
    300.         ld.printLrcDate();  
    301.     }  
    302.     }  
    303. }  
    /*
     * 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函数。

    以下是程序代码。

    1. /* 
    2.  * To change this template, choose Tools | Templates 
    3.  * and open the template in the editor. 
    4.  */  
    5. package musicbox.view;  
    6.   
    7. import java.awt.AlphaComposite;  
    8. import java.awt.Color;  
    9. import java.awt.Dimension;  
    10. import java.awt.Font;  
    11. import java.awt.FontMetrics;  
    12. import java.awt.Graphics;  
    13. import java.awt.Graphics2D;  
    14. import java.awt.Image;  
    15. import java.awt.RenderingHints;  
    16. import java.io.File;  
    17. import java.io.IOException;  
    18. import java.net.URISyntaxException;  
    19. import java.net.URL;  
    20. import java.util.List;  
    21. import java.util.logging.Level;  
    22. import java.util.logging.Logger;  
    23. import javax.imageio.ImageIO;  
    24. import javax.swing.JPanel;  
    25. import musicbox.model.lyric.LyricStatement;  
    26.   
    27. /** 
    28.  * Used to display the lyric 
    29.  * @author Randyzhao 
    30.  */  
    31. public class LyricDisplayer extends JPanel {  
    32.   
    33.     protected final Color CURRENT_LINE_COLOR = Color.green;  
    34.     protected final Color OTHER_LINE_COLOR = Color.GRAY;  
    35.     //the lines other than the current line to be displayed   
    36.     protected final int UP_DOWN_LINES = 8;  
    37.     //the list of lyric statements to be displayed   
    38.     protected List<LyricStatement> statements;  
    39.     //the index of next statement to be dispalyed in the statements   
    40.     protected int index;  
    41.     protected Image backgroundImage = null;  
    42.     private String backGroundImagePath = null;  
    43.     protected Image bufferImage = null;  
    44.     //the size when the bufferImage is drawn   
    45.     private Dimension bufferedSize;  
    46.   
    47.     public String getBackGroundImagePath() {  
    48.     return backGroundImagePath;  
    49.     }  
    50.   
    51.     public void setBackGroundImagePath(String backGroundImagePath) {  
    52.     this.backGroundImagePath = backGroundImagePath;  
    53.     }  
    54.   
    55.     /** 
    56.      * get ready to display 
    57.      * @param statements  
    58.      */  
    59.     public void prepareDisplay(List<LyricStatement> statements) {  
    60.     this.statements = statements;  
    61.     this.index = -1;  
    62.     this.setFont(new Font("微软雅黑", Font.PLAIN, 20));  
    63.     }  
    64.   
    65.     /** 
    66.      * display a lyric by the index 
    67.      * @param index  
    68.      */  
    69.     public void displayLyric(int index) {  
    70.   
    71.     this.index = index;  
    72.     this.drawBufferImage();  
    73. //  System.out.println("draw " + index + " " + this.statements.get(index).getLyric());   
    74.     this.paint(this.getGraphics());  
    75.     }  
    76.   
    77.     /** 
    78.      * draw a line of lyric in the middle of the Graphics2D 
    79.      * @param lyric 
    80.      * @param g2d  
    81.      */  
    82.     protected void drawLineInMiddle(int height, String lyric, Graphics2D g2d, Color color) {  
    83.     int width = this.getWidth();  
    84.     FontMetrics fm = g2d.getFontMetrics();  
    85.     g2d.setColor(color);  
    86.     int x = (this.getWidth() - fm.stringWidth(lyric)) / 2;  
    87.     g2d.drawString(lyric, x, height);  
    88.     }  
    89.   
    90.     /** 
    91.      * Draw the buffered image. Used to realize the double-buffering. 
    92.      */  
    93.     protected void drawBufferImage() {  
    94.     Image tempBufferedImage = this.createImage(this.getWidth(), this.getHeight());  
    95.     this.bufferedSize = this.getSize();  
    96.     if (this.backgroundImage == null) {  
    97.         //get background image   
    98.         URL url = getClass().getResource(this.backGroundImagePath);  
    99.   
    100.         try {  
    101.         backgroundImage = ImageIO.read(url);  
    102.         //缩放图片   
    103.         backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);  
    104.         } catch (IOException ex) {  
    105.         ex.printStackTrace();  
    106.         }  
    107.   
    108.   
    109.     }  
    110.     Graphics2D g2d = (Graphics2D) tempBufferedImage.getGraphics();  
    111.     g2d.setFont(new Font("楷体", Font.PLAIN, 25));  
    112.     g2d.drawImage(this.backgroundImage, 00this.getWidth(), this.getHeight(), null);  
    113.   
    114.     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,  
    115.         RenderingHints.VALUE_ANTIALIAS_ON);  
    116.     if (this.statements != null && this.statements.size() != 0) {  
    117.         //draw current line   
    118.         g2d.setFont(new Font("楷体", Font.PLAIN, 35));  
    119.         this.drawLineInMiddle(this.getHeight() / 2,  
    120.             this.statements.get(index).getLyric(), g2d, this.CURRENT_LINE_COLOR);  
    121.         int perHeight = g2d.getFontMetrics().getHeight() + 5;  
    122.         g2d.setFont(new Font("楷体", Font.PLAIN, 25));  
    123.         //draw down lines   
    124.         for (int i = index - UP_DOWN_LINES; i < index; i++) {  
    125.         if (i < 0) {  
    126.             continue;  
    127.         }  
    128.         if (index - i > UP_DOWN_LINES / 2) {  
    129.             //set transparance   
    130.             float ratio = (float) (i - index + UP_DOWN_LINES) / (UP_DOWN_LINES / 2) / 1.2f;  
    131.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
    132.                 ratio));  
    133.         } else {  
    134.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
    135.                 1.0f));  
    136.         }  
    137.         this.drawLineInMiddle(this.getHeight() / 2 - (index - i) * perHeight,  
    138.             this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);  
    139.         }  
    140.         //draw up lines   
    141.         for (int i = index + 1; i < index + UP_DOWN_LINES; i++) {  
    142.         if (i >= this.statements.size()) {  
    143.             break;  
    144.         }  
    145.         if (i - index > UP_DOWN_LINES / 2) {  
    146.             //set transparance   
    147.             float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;  
    148.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
    149.                 ratio));  
    150.         } else {  
    151.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
    152.                 1.0f));  
    153.         }  
    154.         this.drawLineInMiddle(this.getHeight() / 2 + (i - index) * perHeight,  
    155.             this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);  
    156.         }  
    157.     } else {  
    158.         //statements is empty   
    159.         this.drawLineInMiddle(this.getHeight() / 2,  
    160.             "未找到相应的歌词文件", g2d, this.CURRENT_LINE_COLOR);  
    161.     }  
    162.   
    163.     //copyt the buffered image   
    164.     this.bufferImage = tempBufferedImage;  
    165.     }  
    166.   
    167.     /** 
    168.      * This method is override in order to display the lyric in the panel 
    169.      * @param g  
    170.      */  
    171.     @Override  
    172.     public void paint(Graphics g) {  
    173.     if (this.isVisible() == false) {  
    174.         return;  
    175.     }  
    176.     super.paint(g);  
    177.   
    178.     //draw buffered image    
    179.     if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()  
    180.         || this.getHeight() != this.bufferedSize.getHeight()) {  
    181.         this.drawBufferImage();  
    182.     }  
    183.     //copy the double buffer   
    184.     g.drawImage(bufferImage, 00null);  
    185.   
    186.     }  
    187. }  
    /*
     * 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。

    在绘制的时候,

    1. if (this.backgroundImage == null) {  
    2.     //get background image   
    3.     URL url = getClass().getResource(this.backGroundImagePath);  
    4.   
    5.     try {  
    6.     backgroundImage = ImageIO.read(url);  
    7.     //缩放图片   
    8.     backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);  
    9.     } catch (IOException ex) {  
    10.     ex.printStackTrace();  
    11.     }  
    12.   
    13.   
    14. }  
    	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上面。

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


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

    1. float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;  
    2.         g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
    3.             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函数中

    1. if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()  
    2.         || this.getHeight() != this.bufferedSize.getHeight()) {  
    3.         this.drawBufferImage();  
    4.     }  
    if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()
    		|| this.getHeight() != this.bufferedSize.getHeight()) {
    	    this.drawBufferImage();
    	}

    这句话是判断如果原来已经绘制过Image并且JPanel尺寸和绘制Image的时候相比没有改变,那么不用重新绘制Image,直接把它刷到屏幕上来。
  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值