失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > android MusicPlayer 音乐播放器 Lrc歌词控件的实现

android MusicPlayer 音乐播放器 Lrc歌词控件的实现

时间:2021-03-19 22:38:47

相关推荐

android MusicPlayer 音乐播放器 Lrc歌词控件的实现

MusicPlayer Lrc歌词控件的实现

最近在做一个音乐播放器,关于其中歌词控件,上网查过了一些资料,然后进行修改,也算完整的实现了其功能。先看看实现后的效果。 实现的原理实际上是自定义一个View来显示歌词,然后利用View.invalidate()方法来不断的调用onDraw方法,重绘该View。 至于歌词的读取,实际上按照路径读取歌词文件,然后将其时间和每一句歌词分解开并给每一句一个index,通过处理当前播放时间来得到index以此来显示。 具体实现分别如下: 首先创建一个IrcContent类,相当于结构体。每一个IrcContent的实例实际上就是一句歌词,而真个Irc文件就是个List<IrcContent>.

package com.example.zhsmusicplayer;/** 歌词实体类* 每一句带时间的歌词就是一个LrcContent实例* * */public class LrcContent {private String lrcStr; //歌词内容 private int lrcTime; //歌词当前时间 public String getLrcStr() { return lrcStr; } public void setLrcStr(String lrcStr) { this.lrcStr = lrcStr; } public int getLrcTime() { return lrcTime; } public void setLrcTime(int lrcTime) { this.lrcTime = lrcTime; } }

然后创建一个LrcProcess类用来读取歌词并将歌词中的时间Time转化为MediaPlayer类中所使用的getCurrentPosition的毫秒。

package com.example.zhsmusicplayer;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List;import android.util.Log;/** 用来读取歌词并且解析类* * */public class LrcProcess {private List<LrcContent> lrcList;//List集合用来储存歌词对象(每一句歌词)private LrcContent mLrcContent;/* 无参数构造函数*/public LrcProcess() { mLrcContent = new LrcContent(); lrcList = new ArrayList<LrcContent>(); } /* 读取某个地址下的歌词文件*/public String readLRC(String path){StringBuilder stringBuilder = new StringBuilder();//这里是直接将MP3的地址直接赋予lrc,及要求lrc的地址与MP3地址一致且名称与MP3一致File f = new File(path.replace(".mp3", ".lrc")); //创建一个文件输入流try { //创建一个文件输入流对象 FileInputStream fis = new FileInputStream(f); InputStreamReader isr = new InputStreamReader(fis, "utf-8"); BufferedReader br = new BufferedReader(isr); String s = ""; while((s = br.readLine()) != null) { //替换字符 s = s.replace("[", ""); s = s.replace("]", "@"); //分离“@”字符 String splitLrcData[] = s.split("@"); if(splitLrcData.length > 1) { mLrcContent.setLrcStr(splitLrcData[1]); //处理歌词取得歌曲的时间 int lrcTime = time2Str(splitLrcData[0]); mLrcContent.setLrcTime(lrcTime); //添加进列表数组 lrcList.add(mLrcContent); //新创建歌词内容对象 mLrcContent = new LrcContent(); } } } catch (FileNotFoundException e) { e.printStackTrace();stringBuilder.append("木有歌词文件,赶紧去下载!..."); } catch (IOException e) { e.printStackTrace(); stringBuilder.append("木有读取到歌词哦!"); } return stringBuilder.toString(); } /** * 解析歌词时间 * 就是将读取到的Time转换为duration*/ public int time2Str(String timeStr) { timeStr = timeStr.replace(":", "."); timeStr = timeStr.replace(".", "@"); String timeData[] = timeStr.split("@"); //将时间分隔成字符串数组 //分离出分、秒并转换为整型 int minute = Integer.parseInt(timeData[0]); int second = Integer.parseInt(timeData[1]); int millisecond = Integer.parseInt(timeData[2]); //计算上一行与下一行的时间转换为毫秒数 int currentTime = (minute * 60 + second) * 1000 + millisecond * 10; return currentTime; } public List<LrcContent> getLrcList() { return lrcList; } }

<span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;"></span></span>关于File f = new File(path.replace(".mp3", ".lrc")); 这一句是用来读取某地址下的歌词文件,我这里path参数实际上MP3文件的地址,这就意味着,我打开歌词文件的地址必须和MP3地址相同,在同一个文件夹且文件名也相同。

接下来就是自定义View作为IrcView了,这里继承了TextView,复写了onDraw()函数来显示需要的信息。

<pre name="code" class="java">package com.example.zhsmusicplayer;

import java.util.ArrayList;import java.util.List;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Typeface;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.widget.ImageView;import android.widget.ProgressBar;import android.widget.RelativeLayout;import android.widget.TextView;class CustomBar extends RelativeLayout{private TextView musicName;private ImageView musicImg;private TextView musicArtist;private TextView musicTime;private ImageView iconPlay;private ImageView iconNext;private ImageView iconPrev;private ProgressBar timeBar;public CustomBar(Context context){super(context);}public CustomBar(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubLayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater.inflate(R.layout.custom_music_play, this);if(isInEditMode()){return;}musicName=(TextView)findViewById(R.id.name_text);musicArtist=(TextView)findViewById(R.id.author_text);musicTime=(TextView)findViewById(R.id.time_text);musicImg=(ImageView)findViewById(R.id.music_img);iconPlay=(ImageView)findViewById(R.id.play_img);iconNext=(ImageView)findViewById(R.id.next_img);iconPrev=(ImageView)findViewById(R.id.prev_img);timeBar=(ProgressBar)findViewById(R.id.time_progressBar);}public void setMusicName(String name){musicName.setText(name);}public void setMusicArtist(String Author){musicArtist.setText(Author);}public void setMusicTime(String time){musicTime.setText(time);}public void setOnMusicPlayListener(OnClickListener playListener){iconPlay.setOnClickListener(playListener);}public void setOnMusicPlayNextListener(OnClickListener playNextListener){iconNext.setOnClickListener(playNextListener);}public void setOnMusicPlayPrevListener(OnClickListener playPrevListener){iconPrev.setOnClickListener(playPrevListener);}public void setPlayImg(boolean isPause){if(isPause)iconPlay.setImageResource(R.drawable.icon_pause);elseiconPlay.setImageResource(R.drawable.icon_play);}public void setTimeBarMax(int max){timeBar.setMax(max);}public void setTimeBarProgress(int progress){timeBar.setProgress(progress);}}class LrcView extends TextView{private float width; //歌词视图宽度 private float height; //歌词视图高度 private Paint currentPaint; //当前画笔对象 private Paint notCurrentPaint; //非当前画笔对象 private float textHeight = 25; //文本高度 private float textSize = 18; //文本大小 private int index = 0;//list集合下标 private List<LrcContent> mLrcList = new ArrayList<LrcContent>(); public void setmLrcList(List<LrcContent> mLrcList) { this.mLrcList = mLrcList; } public LrcView(Context context) { super(context); init(); } public LrcView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public LrcView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { setFocusable(true);//设置可对焦 //高亮部分 currentPaint = new Paint(); currentPaint.setAntiAlias(true); //设置抗锯齿,让文字美观饱满 currentPaint.setTextAlign(Paint.Align.CENTER);//设置文本对齐方式 //非高亮部分 notCurrentPaint = new Paint(); notCurrentPaint.setAntiAlias(true); notCurrentPaint.setTextAlign(Paint.Align.CENTER); } /** * 绘画歌词 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(canvas == null) { return; } //Log.e("zhs","Index in LrcView Ondraw====="+index);/*if(index!=0)Log.e("zhs",mLrcList.get(index).getLrcStr()); *///设置字体的大小以及绘制出来的画笔currentPaint.setColor(Color.argb(210, 251, 248, 29)); notCurrentPaint.setColor(Color.argb(140, 255, 255, 255)); currentPaint.setTextSize(24); currentPaint.setTypeface(Typeface.SERIF); notCurrentPaint.setTextSize(textSize); notCurrentPaint.setTypeface(Typeface.DEFAULT); try { setText(""); canvas.drawText(mLrcList.get(index).getLrcStr(), width / 2, height / 2, currentPaint); float tempY = height / 2; //画出本句之前的句子 for(int i = index - 1; i >= 0; i--) { //向上推移 tempY = tempY - textHeight; canvas.drawText(mLrcList.get(i).getLrcStr(), width / 2, tempY, notCurrentPaint); } tempY = height / 2; //画出本句之后的句子 for(int i = index + 1; i < mLrcList.size(); i++) { //往下推移 tempY = tempY + textHeight; canvas.drawText(mLrcList.get(i).getLrcStr(), width / 2, tempY, notCurrentPaint); } } catch (Exception e) { setText("异常了"); } } /** * 当view大小改变的时候调用的方法 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.width = w; this.height = h; } public void setIndex(int index) { this.index = index; } }

绘制的原理就是当前index指示的即为当前播放的歌词为一种Paint,而利用另外一种Paint来绘制出其余的歌词。利用setmLrcList(List<LrcContent> mLrcList)函数与setIndex(int index)函数来设置歌词文件以及当前index。 然后就是创建一个用来承载LrcView的MusicPlayActivity.xml文件如下:

<RelativeLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.zhsmusicplayer.MusicPlayActivity"android:background="#ffffff" tools:ignore="MergeRootFrame" ><com.example.zhsmusicplayer.LrcView android:id="@+id/lrcShowView" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#486432" android:layout_centerHorizontal="true" /></RelativeLayout>

接下来要做的就是在播放音乐的service中处理以获取到Index与当前播放音乐的地址并将其传给activity. 获取index,如下:

/*** 根据时间获取歌词显示的索引值* @return*/public int lrcIndex() {if(mediaPlayer.isPlaying()) {currentTime = mediaPlayer.getCurrentPosition();duration = mediaPlayer.getDuration();}if(currentTime < duration) {for (int i = 0; i < lrcList.size(); i++) {if (i < lrcList.size() - 1) {if (currentTime < lrcList.get(i).getLrcTime() && i == 0) {index = i;}if (currentTime > lrcList.get(i).getLrcTime()&& currentTime < lrcList.get(i + 1).getLrcTime()) {index = i;}}if (i == lrcList.size() - 1&& currentTime > lrcList.get(i).getLrcTime()) {index = i;}}}return index;}

初始化歌词,开启线程不断的向Activity中传递数据。

/*** 初始化歌词配置*/public void initLrc(){handler.post(mRunnable);}

Runnable mRunnable = new Runnable() {@Overridepublic void run() {intentForLrc = new Intent("message_for_lrc");intentForLrc.putExtra("path",path);intentForLrc.putExtra("LrcIndext",lrcIndex());sendBroadcast(intentForLrc);handler.postDelayed(mRunnable, 100);}};

runnable线程会不断地向Activity发送包括地址与Index信息的广播。 在播放音乐的同时调用initLrc()函数来不断获取并发送歌词的信息。

/** * 播放音乐 * @param position */ private void play(int position) { try { initLrc();mediaPlayer.reset();//把各项参数恢复到初始状态 mediaPlayer.setDataSource(path); mediaPlayer.prepare(); //进行缓冲 // mediaPlayer.setOnPreparedListener(new PreparedListener(position));//注册一个监听器 mediaPlayer.start();isPlaying = true;} catch (Exception e) { e.printStackTrace(); } }

最后就是Activity中进行广播的接收以及控件的重绘了。

package com.example.zhsmusicplayer;import java.util.List;import com.example.zhsmusicplayer.MusicListActivity.widgetReceiver;import android.app.Activity;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.os.Handler;import android.util.Log;import android.view.animation.AnimationUtils;public class MusicPlayActivity extends Activity {LrcView lrcView;ServiceReceiver LrcReceiver;IntentFilter intentFilter;//接收来自Service的广播,用来跟新歌词信息Handler handler;//handler用来执行实现LrcIndex的检测的接收Runnable mRunnable;int currentTime,duration;String path =null;//当前歌词的pathint Index=-1;//歌词每一句的索引LrcProcess mLrcProcess;List<LrcContent> lrcList;boolean IsRunnable=false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_music_play);lrcView = (LrcView) findViewById(R.id.lrcShowView);//广播的注册LrcReceiver=new ServiceReceiver();intentFilter = new IntentFilter();intentFilter.addAction("message_for_lrc");registerReceiver(LrcReceiver, intentFilter);//handlerhandler=new Handler(){};//该标志位的目的就是每次进入到该activity执行一次handler.post(mRunnable)IsRunnable=false;//RunnablemRunnable = new Runnable() {@Overridepublic void run() {lrcView.setIndex(Index);lrcView.invalidate();handler.postDelayed(mRunnable, 100);}};}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();unregisterReceiver(LrcReceiver);}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();}//接收来自widget的broadcastpublic class ServiceReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// TODO Auto-generated method stubString action = intent.getAction(); if(action.equals("message_for_lrc")){if(intent.hasExtra("path")){ path=intent.getStringExtra("path");mLrcProcess = new LrcProcess();//读取歌词文件mLrcProcess.readLRC(path);//传回处理后的歌词文件lrcList = mLrcProcess.getLrcList();lrcView.setmLrcList(lrcList);if(IsRunnable){}else{handler.post(mRunnable);IsRunnable=true;}}if(intent.hasExtra("LrcIndext")){if(intent.getIntExtra("LrcIndext",-1)!=Index){Index=intent.getIntExtra("LrcIndext",-1);}}}}}}

动态注册广播接收器同时创建ruannable线程来不断更新LrcView。实际上由于只需要在第一次获取到path信息的时候启动Runnable,故设立了标志位IsRunnable 来进行控制。同时保证了当你每次跳到该Activity时,可以调用Runnable来继续刷新IrcView。 IrcView的基本实现就是这样了。

如果觉得《android MusicPlayer 音乐播放器 Lrc歌词控件的实现》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。