Android 音乐播放器的开发教程: 歌词的显示

歌词的显示


        现在的播放器已经能够切歌咯,进度条也可以自由的滑动,有没有觉得很爽滑~~~~大笑,今天就来介绍怎么将歌词显示到屏幕上面,歌词的文件形式有很多种,例如lrc,trc,krc,,我手机上面是天天动听的播放器,其歌词的形式为.trc的,所以今天我们以这个为例,lrc是最简单解析的,下面第一章图就是TRC的格式,第二张为LRC格式的歌词,不难发现,TRC就是在LRC基础上对每个字都加了时间,更加精确.

,,


        显示歌词的原理就是,自定义一个View,将歌词文件取出来,每行每行的显示出来,对比当前歌曲的播放进度与歌词前面标注的时间来判定已经播放到了哪句话, 将当前的那句话用不同的颜色标出来.


        这里小达用了一个专门显示歌词的Fragment,来存放那个自定义的View,Fragment在前面也有讲过,所以这里直接给出源代码.

fragment_play.xml

[java]  view plain  copy
 print ?
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context="com.example.dada.myapplication.PlayFragment">  
  6.   
  7.     <com.example.dada.myapplication.LyricView                               //显示歌词的自定义View  
  8.         android:id="@+id/lrcShowView"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="300dip" />  
  11.   
  12.     <ImageButton  
  13.         android:id="@+id/dismiss_lyric_button"                                    //回退到主Fragment的按钮  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:layout_alignParentTop="true"  
  17.         android:layout_alignParentRight="true"  
  18.         android:layout_alignParentEnd="true"  
  19.         android:background="@drawable/arrow_down"/>  
  20.   
  21.     <ImageButton  
  22.   
  23.         android:id="@+id/my_favorite_button"  
  24.         android:layout_width="wrap_content"  
  25.         android:layout_height="wrap_content"  
  26.         android:background="#00000000"  
  27.         android:layout_alignBottom="@+id/dismiss_lyric_button"  
  28.         android:layout_alignParentLeft="true"  
  29.         android:layout_alignParentStart="true" />  
  30.   
  31. </RelativeLayout>  

PlayFragment.xml

[java]  view plain  copy
 print ?
  1. package com.example.dada.myapplication;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.app.Fragment;  
  6. import android.os.Handler;  
  7. import android.os.Message;  
  8. import android.view.LayoutInflater;  
  9. import android.view.View;  
  10. import android.view.ViewGroup;  
  11. import android.widget.ArrayAdapter;  
  12. import android.widget.AutoCompleteTextView;  
  13. import android.widget.ListAdapter;  
  14. import android.widget.Toast;  
  15.   
  16. import java.util.ArrayList;  
  17. import java.util.List;  
  18.   
  19. public class PlayFragment extends Fragment {  
  20.   
  21.   
  22.     private int duration;  
  23.     private int index = 0;  
  24.     private int currentTime;  
  25.     private LyricView lyricView;  
  26.     private Activity myActivity;  
  27.     private LyricProgress lyricProgress;  
  28.     private static String music_url = "";  
  29.     private OnPlayFragmentInteractionListener mListener;  
  30.     private List<LyricContent> lyricContents = new ArrayList<LyricContent>();  
  31.   
  32.   
  33.     Runnable myRunnable = new Runnable(){   
  34.         @Override  
  35.         public void run() {  
  36.             lyricView.setIndex(lyricIndex());  
  37.             lyricView.invalidate();                                    //调用后自定义View会自动调用onDraw()方法来重新绘制歌词  
[java]  view plain  copy
 print ?
  1.             myHandler.postDelayed(myRunnable, 300);  
  2.   
  3.         }  
  4.     };  
  5.   
  6.     Handler myHandler = new Handler(){                          //通过用myHandler来不断改变歌词的显示,达到同步的效果  
  7.         @Override  
  8.         public void handleMessage(Message msg) {  
  9.             super.handleMessage(msg);  
  10.         }  
  11.     };  
  12.   
  13.     public static PlayFragment newInstance(String url) {  
  14.         PlayFragment fragment = new PlayFragment();  
  15.         Bundle args = new Bundle();  
  16.         music_url = url;  
  17.         args.putString("url",url);  
  18.         fragment.setArguments(args);  
  19.         return fragment;  
  20.     }  
  21.   
  22.     public PlayFragment() {  
  23.         // Required empty public constructor  
  24.     }  
  25.   
  26.     @Override  
  27.     public void onCreate(Bundle savedInstanceState) {  
  28.         super.onCreate(savedInstanceState);  
  29.     }  
  30.   
  31.     @Override  
  32.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  33.                              Bundle savedInstanceState) {  
  34.   
  35.         myActivity = getActivity();  
  36.         View rootView = inflater.inflate(R.layout.fragment_play, container, false);  
  37.   
  38.         initialize(rootView);             //自定义的初始化函数  
  39.   
  40.         return rootView;  
  41.     }  
  42.   
  43.     protected void initialize(View v){  
  44.   
  45.         lyricView = (LyricView)v.findViewById(R.id.lrcShowView);  
  46.   
  47.         (v.findViewById(R.id.dismiss_lyric_button))  
  48.                 .setOnClickListener(new View.OnClickListener() {  
  49.                     @Override  
  50.                     public void onClick(View v) {  
  51.                         mListener.onPlayFragmentInteraction(AppConstant.PlayerMsg.DISMISS_CLICK);  
  52.                     }  
  53.                 });  
  54.   
  55.         initLyric(music_url);                          //初始化歌词  
  56.   
  57.         myHandler.post(myRunnable);    //将myRunnable传给handler来执行  
  58.   
  59.     }  
  60.   
  61.     // TODO: Rename method, update argument and hook method into UI event  
  62.     public void onButtonPressed() {  
  63.   
  64.     }  
  65.   
  66.     @Override  
  67.     public void onAttach(Activity activity) {  
  68.         super.onAttach(activity);  
  69.         try {  
  70.             mListener = (OnPlayFragmentInteractionListener) activity;  
  71.         } catch (ClassCastException e) {  
  72.             throw new ClassCastException(activity.toString()  
  73.                     + " must implement OnFragmentInteractionListener");  
  74.         }  
  75.     }  
  76.   
  77.     @Override  
  78.     public void onDetach() {  
  79.         super.onDetach();  
  80.         mListener = null;  
  81.     }  
  82.   
  83.     public interface OnPlayFragmentInteractionListener {                         //不要忘了在Activity中实现这个接口,重写这个里面包含的回调函数.  
  84.         // TODO: Update argument type and name  
  85.         public void onPlayFragmentInteraction(int message);  
  86.     }  
  87.   
  88.     public void initLyric(String url) {                                                             //里面包含了一些即将要介绍的自定义类  
  89.         lyricProgress = new LyricProgress();  
  90.         lyricProgress.readLyric(url);  
  91.         lyricContents = lyricProgress.getLyricList();  
  92.   
  93.         try{  
  94.             lyricView.setMyLyricList(lyricContents);  
  95.         }  
  96.         catch(Exception e){  
  97.             e.printStackTrace();  
  98.         }  
  99.         myHandler.post(myRunnable);  
  100.     }  
  101.   
  102.     public int lyricIndex() {                                     //用来寻找当前歌曲播放的位置,返回当前歌词的索引值  
  103.         int size = lyricContents.size();  
  104.         if(PlayerService.mediaPlayer.isPlaying()) {  
  105.             currentTime = PlayerService.mediaPlayer.getCurrentPosition();  
  106.             duration = PlayerService.mediaPlayer.getDuration();  
  107.         }  
  108.         if(currentTime < duration) {  
  109.             for (int i = 0; i < size; i++) {  
  110.                 if (i < size - 1) {  
  111.                     if (currentTime < lyricContents.get(i).getLyricTime() && i==0) {  
  112.                         index = i;  
  113.                         break;  
  114.                     }  
  115.                     if (currentTime > lyricContents.get(i).getLyricTime()  
  116.                             && currentTime < lyricContents.get(i + 1).getLyricTime()) {  
  117.                         index = i;  
  118.                         break;  
  119.                     }  
  120.                 }  
  121.                 if (i == size - 1  
  122.                         && currentTime > lyricContents.get(i).getLyricTime()) {  
  123.                     index = i;  
  124.                     break;  
  125.                 }  
  126.             }  
  127.         }  
  128.         return index;  
  129.     }  
  130. }  


上面定义了一个Fragment,这个Fragment在用户点击Activity中的控制台时,显示出来,所以需要给Activity最下面的布局文件添加一个按钮监听,代码如下:

[java]  view plain  copy
 print ?
  1. public void main_activity_bottom_layout_listener(View v){  
  2.   
  3.        String current_music_url = mp3Infos.get(music_position).getUrl();  
  4.   
  5.        PlayFragment playFragment = PlayFragment.newInstance(current_music_url);  
  6.   
  7.   
  8.        FragmentManager fragmentManager = getFragmentManager();  
  9.        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();  
  10.        fragmentTransaction.replace(R.id.fragment_layout, playFragment);  
  11.        fragmentTransaction.addToBackStack(null);  
  12.   
  13.        fragmentTransaction.commit();  
  14.   
  15.    }  


里面包含了自定义组件LyricView,下面是自定义组件的代码:

LyricView.Java:

[java]  view plain  copy
 print ?
  1. package com.example.dada.myapplication;  
  2.   
  3.   
  4. import android.content.Context;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Paint;  
  7. import android.graphics.Typeface;  
  8. import android.util.AttributeSet;  
  9. import android.widget.TextView;  
  10.   
  11.   
  12. import java.util.ArrayList;  
  13. import java.util.List;  
  14.   
  15.   
  16. public class LyricView extends TextView{  
  17.     private float width;  
  18.     private float height;  
  19.     private Paint currentPaint;             //用来描绘当前正在播放的那句歌词  
  20.     private Paint notCurrentPaint;          //用来描绘非当前歌词  
  21.     private float textHeight = 50;  
  22.     private float textSize = 30;  
  23.     private int index = 0;                  //当前歌词的索引  
  24.   
  25.   
  26.     /* 
  27.     观察歌词文件发现,每句话都对应着一个时间 
  28.     所以专门写一个类LyricContent.java 
  29.     后面马上介绍到,来存放时间和该时间对应的歌词 
  30.     然后再用一个List将很多这个类的实例包裹起来 
  31.     这样就能很好的将每句歌词和他们的时间对应起来 
  32.      */  
  33.     private List<LyricContent> myLyricList = null;        //每个LyricCOntent对应着一句话,这个List就是整个解析后的歌词文件  
  34.   
  35.   
  36.     public void setIndex(int index){  
  37.         this.index = index;  
  38.     }  
  39.   
  40.   
  41.     public void setMyLyricList(List<LyricContent> lyricList){  
  42.         this.myLyricList = lyricList;  
  43.     }  
  44.   
  45.   
  46.     public List<LyricContent> getMyLyricList(){  
  47.         return this.myLyricList;  
  48.     }  
  49.   
  50.   
  51.     public LyricView(Context context){  
  52.         super(context);  
  53.         init();  
  54.     }  
  55.   
  56.   
  57.     public LyricView(Context context,AttributeSet attributeSet){  
  58.         super(context,attributeSet);  
  59.         init();  
  60.     }  
  61.   
  62.   
  63.     public LyricView(Context context,AttributeSet attributeSet,int defSytle){  
  64.         super(context,attributeSet,defSytle);  
  65.         init();  
  66.     }  
  67.   
  68.   
  69.     private void init(){                            //初始化画笔  
  70.         setFocusable(true);  
  71.   
  72.   
  73.         currentPaint = new Paint();  
  74.         currentPaint.setAntiAlias(true);  
  75.         currentPaint.setTextAlign(Paint.Align.CENTER);  
  76.   
  77.   
  78.         notCurrentPaint = new Paint();  
  79.         notCurrentPaint.setAntiAlias(true);  
  80.         notCurrentPaint.setTextAlign(Paint.Align.CENTER);  
  81.   
  82.   
  83.     }  
  84.       
  85.     /* 
  86.     onDraw()就是画歌词的主要方法了 
  87.     在PlayFragment中会不停地调用 
  88.     lyricView.invalidate();这个方法 
  89.     此方法写在了一个Runnable的run()函数中 
  90.     通过不断的给一个handler发送消息,不断的重新绘制歌词 
  91.     来达到歌词同步的效果 
  92.      */  
  93.   
  94.   
  95.     @Override  
  96.     protected void onDraw(Canvas canvas){  
  97.         super.onDraw(canvas);  
  98.         if(canvas == null){  
  99.             return ;  
  100.         }  
  101.   
  102.   
  103.         currentPaint.setColor(getResources().getColor(R.color.greenyellow));  
  104.         notCurrentPaint.setColor(getResources().getColor(R.color.rosybrown));  
  105.   
  106.   
  107.         currentPaint.setTextSize(40);  
  108.         currentPaint.setTypeface(Typeface.DEFAULT_BOLD);  
  109.   
  110.   
  111.         notCurrentPaint.setTextSize(textSize);  
  112.         notCurrentPaint.setTypeface(Typeface.DEFAULT);  
  113.   
  114.   
  115.         try{  
  116.             setText("");  
  117.   
  118.   
  119.             float tempY = height / 2;                                                                     //画出之前的句子  
  120.             for(int i =index - 1;i >= 0; i --){  
  121.                 tempY -= textHeight;  
  122.                 canvas.drawText(myLyricList.get(i).getLyricString(),width/2,tempY,notCurrentPaint);  
  123.             }  
  124.             canvas.drawText(myLyricList.get(index).getLyricString(),width/2,height/2,currentPaint);       //画出当前的句子  
  125.   
  126.   
  127.             tempY = height / 2;                                                                           //画出之后的句子  
  128.             for(int i =index + 1;i<myLyricList.size(); i ++){  
  129.                 tempY += textHeight;  
  130.                 canvas.drawText(myLyricList.get(i).getLyricString(),width/2,tempY,notCurrentPaint);  
  131.             }  
  132.   
  133.   
  134.         }  
  135.         catch(Exception e){  
  136.             setText("一丁点儿歌词都没找到,下载后再来找我把.......");  
  137.         }  
  138.     }  
  139.   
  140.   
  141.     @Override  
  142.     protected void onSizeChanged(int w,int h,int oldW,int oldH){  
  143.         super.onSizeChanged(w,h,oldW,oldH);  
  144.         this.width = w;  
  145.         this.height = h;  
  146.     }  
  147. }  

上面提到过一个封装歌词的类,里面包含着歌词对应的时间以及内容,下面给出源代码,

LyricContent.java:

[java]  view plain  copy
 print ?
  1. package com.example.dada.myapplication;  
  2.   
  3.   
  4. public class LyricContent {  
  5.     private String lyricString;            //歌词的内容  
  6.     private int lyricTime;                 //歌词当前的时间  
  7.   
  8.     public String getLyricString(){  
  9.         return this.lyricString;  
  10.     }  
  11.   
  12.     public void setLyricString(String str){  
  13.         this.lyricString = str;  
  14.     }  
  15.   
  16.     public int getLyricTime(){  
  17.         return this.lyricTime;  
  18.     }  
  19.   
  20.     public void setLyricTime(int time){  
  21.         this.lyricTime = time;  
  22.     }  
  23. }  

还需要一个类来将歌词解析出来,转换时间,解析格式等功能,

LyricProgress.java:

[java]  view plain  copy
 print ?
  1. package com.example.dada.myapplication;  
  2.   
  3.   
  4. import android.os.PatternMatcher;  
  5. import android.provider.ContactsContract;  
  6.   
  7. import java.io.BufferedReader;  
  8. import java.io.File;  
  9. import java.io.FileInputStream;  
  10. import java.io.FileNotFoundException;  
  11. import java.io.IOException;  
  12. import java.io.InputStreamReader;  
  13. import java.util.ArrayList;  
  14. import java.util.List;  
  15. import java.util.regex.Matcher;  
  16. import java.util.regex.Pattern;  
  17.   
  18. public class LyricProgress {  
  19.   
  20.     private List<LyricContent> lyricList;  
  21.     private LyricContent myLyricContent;  
  22.   
  23.     public LyricProgress(){  
  24.         myLyricContent = new LyricContent();  
  25.         lyricList = new ArrayList<LyricContent>();  
  26.     }  
  27.   
  28.     public String readLyric(String path){                        //从文件中读出歌词并解析的函数  
  29.         StringBuilder stringBuilder = new StringBuilder();  
  30.         path = path.replace("song","lyric");                     //这个是针对天天动听的目录结构下手的,,,不知道有没有什么适合所有文件结构的方法呢..  
  31.         File f = new File(path.replace(".mp3",".trc"));  
  32.   
  33.         try{  
  34.             FileInputStream fis = new FileInputStream(f);  
  35.             InputStreamReader isr = new InputStreamReader(fis,"utf-8");  
  36.             BufferedReader br = new BufferedReader(isr);  
  37.             String s= "";  
  38.   
  39.             while((s = br.readLine()) != null){  
  40.                 s = s.replace("[","");  
  41.                 s = s.replace("]","@");                                 //每一句话的分隔符  
  42.   
  43.                 s = s.replaceAll("<[0-9]{3,5}>","");                    //去掉每个字的时间标签,这里用到了正则表达式  
  44.   
  45.   
  46.                 String spiltLrcData[] = s.split("@");  
  47.   
  48.                 if(spiltLrcData.length > 1){  
  49.   
  50.                     myLyricContent.setLyricString(spiltLrcData[1]);     //将每句话创建一个类的实例,歌词和对应时间赋值  
  51.                     int lycTime = time2Str(spiltLrcData[0]);  
  52.                     myLyricContent.setLyricTime(lycTime);  
  53.                     lyricList.add(myLyricContent);  
  54.   
  55.                     myLyricContent = new LyricContent();  
  56.                 }  
  57.             }  
  58.         }  
  59.         catch(FileNotFoundException e){  
  60.             e.printStackTrace();  
  61.             stringBuilder.append("一丁点儿歌词都没找到,下载后再来找我把.......");  
  62.         }  
  63.         catch(IOException e){  
  64.             e.printStackTrace();  
  65.             stringBuilder.append("没有读取到歌词.....");  
  66.         }  
  67.         return stringBuilder.toString();  
  68.     }  
  69.   
  70.     public int time2Str(String timeStr){                 //将分:秒:毫秒转化为长整型的数  
  71.         timeStr = timeStr.replace(":",".");  
  72.         timeStr = timeStr.replace(".","@");  
  73.   
  74.         String timeData[] = timeStr.split("@");  
  75.   
  76.         int min = Integer.parseInt(timeData[0]);  
  77.         int sec = Integer.parseInt(timeData[1]);  
  78.         int millSec = Integer.parseInt(timeData[2]);  
  79.   
  80.         int currentTime = (min * 60 + sec) * 1000 + millSec * 10;  
  81.         return currentTime;  
  82.     }  
  83.   
  84.     public List<LyricContent> getLyricList(){  
  85.         return this.lyricList;  
  86.     }  
  87. }  

上面的注释不知道写的清楚不,如果还有什么问题看不懂的话,直接问我就好咯,这个歌词解析我用的方法好笨的感觉,,而且有些歌词还不能应对,比如KRC格式的歌词,(在显示歌词之前确保已经存在歌词文件,不然什么都木有的,,,),下面是做出来的效果:

  ,嘿嘿,今天的歌词显示就讲到这里咯,到下篇博客的时候,小达将介绍哈通知栏的显示,也就是Notification的应用,可以在下拉的控制台上控制我们的播放器切歌和暂停哟~~~~大笑,88

要在VB音乐播放器中实现歌词显示,可以按照以下步骤进行: 1. 获取歌词文件:通常歌词文件是以 .lrc 格式保存的,可以在网络上搜索并下载。也可以让用户手动输入歌词,然后保存到本地文件中。 2. 解析歌词文件:读取歌词文件,将歌词解析成时间和歌词文本的对应关系。可以使用正则表达式或字符串分割等方法来解析歌词文件。 3. 监听播放器进度:在播放器播放音乐时,可以使用 Timer 控件定时获取当前播放的时间,然后根据当前时间查找对应的歌词,并将其显示在界面上。 4. 显示歌词:将歌词显示在窗口中,可以使用 Label 控件来显示歌词文本。可以设置 Label 控件的字体、颜色等属性,以及控制歌词的滚动速度和显示位置。 下面是一个简单的示例代码,可以实现基本的歌词显示功能: ```vb Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 获取当前播放时间 Dim currentTime As Double = AxWindowsMediaPlayer1.Ctlcontrols.currentPosition ' 查找对应的歌词 Dim lyrics As String = FindLyrics(currentTime) ' 显示歌词 Label1.Text = lyrics End Sub Private Function FindLyrics(time As Double) As String ' TODO: 解析歌词文件,并根据时间查找对应的歌词 ' 返回格式化后的歌词文本,例如:"[00:10.00]Hello world" End Function ``` 注意:上述代码只是一个简单的示例,实际实现中还需要处理歌词时间格式化、歌词滚动等问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值