音乐播放器android歌词

2013-06-02 15:56:28 wwj_748 阅读数 37565

Android应用开发--MP3音乐播放器滚动歌词实现

2013年6月2日  简、美音乐播放器开发记录

-----前话

有网友给我博客评论说,让我借鉴好的Android代码,代码贴出来的时候最好整体先看一下。其实小巫也有参考过别人的代码,主要是具体看某一个功能是怎么实现的,但是因为开发的思路不一样,只能说自己去写一些符合自己思路的代码。编写代码过程中,或多或少有纰漏之处,但基本上能实现功能就行了。小巫的功底还不够,不具备很强的重构代码的能力,一直都是以最直观的想法来编程,并没有太过关注性能的优化啥的,因为我也没发现自己开发的这款音乐播放器用起来不爽。不过,小巫会一直学习的,努力提升自己的编程水平,争取生产出优美的代码供朋友们参考。

-----主题

     这篇博客的主题是:“滚动歌词的实现”

     要的效果如下:

           

----实现过程

1. 建立歌词内容实体类

2. 自定义View

3. 加入布局文件

4. 编写歌词处理类

5. 在Service里面实现同步更新歌词


----代码实现

--LrcContent.java

package com.wwj.sb.domain;
/**
 * 2013/6/1
 * @author wwj
 * 歌词实体类
 */
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;
	}
}


--LrcView.java

package com.wwj.sb.custom;

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 com.wwj.sb.domain.LrcContent;

/**
 * 自定义绘画歌词,产生滚动效果
 * @author wwj
 *
 */
public class LrcView extends android.widget.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;
		}
		
		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;
	}
	
}

--LrcProcess.java

package com.wwj.sb.custom;

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.Xml.Encoding;
import android.widget.SlidingDrawer;

import com.wwj.sb.domain.LrcContent;

/**
 * 2013/6/1
 * @author	wwj
 *	处理歌词的类
 */
public class LrcProcess {
	private List<LrcContent> lrcList;	//List集合存放歌词内容对象
	private LrcContent mLrcContent;		//声明一个歌词内容对象
	/**
	 * 无参构造函数用来实例化对象
	 */
	public LrcProcess() {
		mLrcContent = new LrcContent();
		lrcList = new ArrayList<LrcContent>();
	}
	
	/**
	 * 读取歌词
	 * @param path
	 * @return
	 */
	public String readLRC(String path) {
		//定义一个StringBuilder对象,用来存放歌词内容
		StringBuilder stringBuilder = new StringBuilder();
		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();
	}
	/**
	 * 解析歌词时间
	 * 歌词内容格式如下:
	 * [00:02.32]陈奕迅
	 * [00:03.43]好久不见
	 * [00:05.22]歌词制作  王涛
	 * @param timeStr
	 * @return
	 */
	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;
	}
}


加入布局文件:

	<com.wwj.sb.custom.LrcView
	    android:id="@+id/lrcShowView"
	    android:layout_width="match_parent"
	    android:layout_height="200dip"
	    android:layout_above="@+id/footer_layout"
	    android:layout_below="@+id/header_layout"
	    android:layout_centerHorizontal="true" />


--在Service.java中的实现,这里就不贴完整的Service类了,主要是如何在Service实现歌词同步的。

声明变量:

private LrcProcess mLrcProcess;	//歌词处理
private List<LrcContent> lrcList = new ArrayList<LrcContent>(); //存放歌词列表对象
private int index = 0;			//歌词检索值

核心实现代码:

	/**
	 * 初始化歌词配置
	 */
	public void initLrc(){
		mLrcProcess = new LrcProcess();
		//读取歌词文件
		mLrcProcess.readLRC(mp3Infos.get(current).getUrl());
		//传回处理后的歌词文件
		lrcList = mLrcProcess.getLrcList();
		PlayerActivity.lrcView.setmLrcList(lrcList);
		//切换带动画显示歌词
		PlayerActivity.lrcView.setAnimation(AnimationUtils.loadAnimation(PlayerService.this,R.anim.alpha_z));
		handler.post(mRunnable);
	}
	Runnable mRunnable = new Runnable() {
		
		@Override
		public void run() {
			PlayerActivity.lrcView.setIndex(lrcIndex());
			PlayerActivity.lrcView.invalidate();
			handler.postDelayed(mRunnable, 100);
		}
	};

	/**
	 * 根据时间获取歌词显示的索引值
	 * @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;
	}


其实,小巫还想实现可以拖动歌词来控制播放进度,还有自动搜索歌词等更加完备的实现。


2016-05-08 13:06:02 lbcab 阅读数 4778

         当我们制作音乐播放器中我觉得歌词的处理是比较难的一块, 对于音乐播放和媒体控制我们可以使用MediaPlayer来搞定,它提供了媒体控制的接口,使得我们对于媒体控制来说变得比较简单。但对于显示歌词来说就比较复杂了一点,例如让歌词一个字一个字高亮、快进时控制歌词处理或者倍速播放时歌词的处理等等, 这里我想介绍简单让一行歌词高亮显示, 等这行歌词唱完,让下一行歌词高亮显示。

1. 解析歌词文件

常见的歌词文件有:.lrc 和 .txt格式, 内容格式为:[00:02.59] 飘洋过海来看你

所以我们先要去解析歌词文件, 定义一个类去保存每行解析出来的数据。

public class Lyric {
	public String lricString;
	public int sleepTime;
	public int timePoint;
}
我们对照着 "[00:02.59] 飘洋过海来看你" 来看, lrcString保存的是"飘洋过海来看你", sleepTime保存的是这句歌词播放时间, 就是下一句歌词开始播的时间减去本句歌词播放的时间,timePoint就是将 “[00:02.59]” 时间文本解析出来转换成的秒数。


现在我们开始解析歌词文件然后将数据保存到Lyrc中

public class LrcUtils {
	private static List<Lyric> lyricList;

	/**
	 * 读取文件
	 */
	public static List<Lyric> readLRC(File f) {
		try {
			if (f == null || !f.exists()) {
				lyricList = null;
			} else {
				lyricList = new Vector<Lyric>();
				InputStream is = new BufferedInputStream(new FileInputStream(f));
				BufferedReader br = new BufferedReader(new InputStreamReader(
						is, getCharset(f)));
				String strTemp = "";
				while ((strTemp = br.readLine()) != null) {
					strTemp = <span style="font-family:Arial, Helvetica, sans-serif;font-size:10px;">processLRC</span>(strTemp);
				}
				br.close();
				is.close();
				// 对歌词进行排序
				Collections.sort(lyricList, new Sort());
				// 计算每行歌词的停留时间
				for (int i = 0; i < lyricList.size(); i++) {

					Lyrc one = lyricList.get(i);
					if (i + 1 < lyricList.size()) {
						Lyric two = lyricList.get(i + 1);
						one.sleepTime = two.timePoint - one.timePoint;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return lyricList;
	}

	/**
	 * 处理一行内容
	 */
	private static String processLRC(String text) {
		try {
			int pos1 = text.indexOf("[");
			int pos2 = text.indexOf("]");

			if (pos1 >= 0 && pos2 != -1) {
				Long time[] = new Long[getPossiblyTagCount(text)];
				time[0] = timeToLong(text.substring(pos1 + 1, pos2));
				if (time[0] == -1)
					return "";
				String strLineRemaining = text;
				int i = 1;
				while (pos1 >= 0 && pos2 != -1) {

					strLineRemaining = strLineRemaining.substring(pos2 + 1);
					pos1 = strLineRemaining.indexOf("[");
					pos2 = strLineRemaining.indexOf("]");
					if (pos2 != -1) {
						time[i] = timeToLong(strLineRemaining.substring(
								pos1 + 1, pos2));
						if (time[i] == -1)
							return ""; // LRCText
						i++;
					}
				}

				Lyric tl = null;
				//防止有的歌词文件是这种格式:[00:01:23][00:03:02]重复歌词
				//就是歌词重复的放在一起,将多个时间戳放在一起,所以在解析完歌词需要排序一下。
				for (int j = 0; j < time.length; j++) {
					if (time[j] != null) {
						tl = new Lyric();
						tl.timePoint = time[j].intValue();
						tl.lricString = strLineRemaining;
						lyrcList.add(tl);
					}
				}
				return strLineRemaining;
			} else
				return "";
		} catch (Exception e) {
			return "";
		}
	}
        //获取一行中的时间标签的个数,为了防止将重复歌词放在一行上显示
	private static int getPossiblyTagCount(String Line) {
		String strCount1[] = Line.split("\\[");
		String strCount2[] = Line.split("\\]");
		if (strCount1.length == 0 && strCount2.length == 0)
			return 1;
		else if (strCount1.length > strCount2.length)
			return strCount1.length;
		else
			return strCount2.length;
	}

	/**
	 * 时间转换,将time格式时间转换成秒
	 */
	public static long timeToLong(String Time) {
		try {
			String[] s1 = Time.split(":");
			int min = Integer.parseInt(s1[0]);
			String[] s2 = s1[1].split("\\.");
			int sec = Integer.parseInt(s2[0]);
			int mill = 0;
			if (s2.length > 1)
				mill = Integer.parseInt(s2[1]);
			return min * 60 * 1000 + sec * 1000 + mill * 10;
		} catch (Exception e) {
			return -1;
		}
	}

	/**
	 * 判断文件编码,防止文件解析成乱码
	 */
	public static String getCharset(File file) {
		String charset = "GBK";
		byte[] first3Bytes = new byte[3];
		try {
			boolean checked = false;
			BufferedInputStream bis = new BufferedInputStream(
					new FileInputStream(file));
			bis.mark(0);
                       //一般读取前3个字节就可以判断文件的编码格式
			int read = bis.read(first3Bytes, 0, 3);
			if (read == -1)
				return charset;
			if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
				charset = "UTF-16LE";
				checked = true;
			} else if (first3Bytes[0] == (byte) 0xFE
					&& first3Bytes[1] == (byte) 0xFF) {
				charset = "UTF-16BE";
				checked = true;
			} else if (first3Bytes[0] == (byte) 0xEF
					&& first3Bytes[1] == (byte) 0xBB
					&& first3Bytes[2] == (byte) 0xBF) {
				charset = "UTF-8";
				checked = true;
			}
			bis.reset();
			if (!checked) {
				int loc = 0;
				while ((read = bis.read()) != -1) {
					loc++;
					if (read >= 0xF0)
						break;
					if (0x80 <= read && read <= 0xBF)
						break;
					if (0xC0 <= read && read <= 0xDF) {
						read = bis.read();
						if (0x80 <= read && read <= 0xBF)
							continue;
						else
							break;
					} else if (0xE0 <= read && read <= 0xEF) {
						read = bis.read();
						if (0x80 <= read && read <= 0xBF) {
							read = bis.read();
							if (0x80 <= read && read <= 0xBF) {
								charset = "UTF-8";
								break;
							} else
								break;
						} else
							break;
					}
				}
			}
			bis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return charset;
	}
        //按照timePoint的大小进行升序排列
	private static class Sort implements Comparator<Lyrc> {
		public Sort() {
		}

		public int compare(Lyric tl1, Lyric tl2) {
			return sortUp(tl1, tl2);
		}

		private int sortUp(Lyric tl1, Lyric tl2) {
			if (tl1.timePoint < tl2.timePoint)
				return -1;
			else if (tl1.timePoint > tl2.timePoint)
				return 1;
			else
				return 0;
		}
	}
}

我们可以直接使用LrcUtils类, 导入到你的工程中,调用LrcUtils.readLRC(File);方法传入歌词文件就会返回解析的歌词数据. 

下面介绍LrcUtils类中的逻辑:

(1)首先从歌词文件中读取数据(按行读取, 在读取数据时调用方法getCharset获取文件的编码格式防止读取数据出现乱码), 每读取一行调用processLRC去将此行数据解析保存到Lyrc中.

(2)在processLRC中处理一行中多个时间戳的逻辑, 因为有的歌词文件为了方便把重复的歌词的时间放在一起。

(3)将获取的解析数据按照timePoint排序, 因为防止(2)中描述的情况,重复歌词的时间放在一起的问题,如果不排序的话,会导致歌词顺序乱套.

(4)计算每行歌词停留的时间

什么是为了防止重复歌词放在一行上?

歌词是这样的:

[02:17.62][00:27.46]为你我用了半年的积蓄
[02:21.05][00:31.99]飘洋过海的来看你
[02:24.81][00:35.60]为了这次相聚
[02:27.59][00:38.16]我连见面时的呼吸都曾反复练习
[02:33.48][00:43.79]言语从来没能将我的情谊表达千万分之一
[02:40.91][00:51.47]为了这个遗憾
[02:44.19][00:54.65]我在夜里想了又想不肯睡去
[02:50.40][01:00.88]记忆它总是慢慢的积累
[02:54.50][01:04.92]在我心中无法抹去
[02:58.22][01:07.93]为了你的承诺
[03:00.77][01:10.72]我在最绝望的时候都忍住不哭泣
[03:08.46][01:17.06]陌生的城市啊!
[03:12.81][01:22.20]熟悉的角落里
[03:16.63][01:26.99]也曾彼此安慰
[03:19.31][01:30.13]也曾相拥叹息
[03:21.36][01:31.72]不管将会面对什么样的结局
[03:58.95][03:25.36][01:35.83]在漫天风沙里 望着你远去
[04:02.66][03:29.38][01:39.70]我竟悲伤的不能自己
[04:06.82][03:34.02][01:43.83]多盼能送君千里
[04:09.25][03:35.95][01:46.15]直到山穷水尽
[04:11.46][03:38.51][01:58.96][01:48.44]一生和你相依

一句歌词前面多个时间tag.


2. 编写自定义TextView 去显示歌词

       (1)继承TextView, 处理onDraw方法

        (2)歌词绘制,区分当前行与普通行, 将当前行绘制在控件中心。指定两种Paint,来绘制两种不同文本。

        (3)每隔一个时间段(就是Lyrc中的sleepTime字段)更新显示内容,向上滚动

public class LricView extends TextView {

	private List<Lyric> lyricList;
	// 标记当前行
	private int currentLine = 0;
	private Paint currentPaint;
	private Paint otherPaint;
	private int currentColor = Color.GREEN;
	private int currentTextSize = 18;
	
	private int otherColor = Color.BLACK;
	private int otherTextSize = 15;
	
	// 行间距
	private int lineSpace = 25;
	//当前歌词字体
	private Typeface currentTypeface = Typeface.DEFAULT_BOLD;
	//其他歌词字体
	private Typeface otherTypeface = Typeface.SERIF;

	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			invalidate(); // 刷新,会再次调用onDraw方法
			super.handleMessage(msg);
		}

	};

	public LricView(Context context, AttributeSet attrs) {
		super(context, attrs);
		currentPaint = new Paint();
		otherPaint = new Paint();
		lyricList = LrcUtils.readLRC(new File("/data/local/tmp/123456.lrc"));

		currentPaint.setColor(currentColor);
		currentPaint.setTextSize(currentTextSize);
		currentPaint.setTextAlign(Align.CENTER); // 画在中间
		currentPaint.setTypeface(currentTypeface);
		
		otherPaint.setColor(otherColor);
		otherPaint.setTextSize(otherTextSize);
		otherPaint.setTextAlign(Align.CENTER);
		otherPaint.setTypeface(otherTypeface);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (lyricList != null && currentLine < lyricList.size()) {
			Lyric lyrc = null;
			//绘制播放过的歌词
			for (int i = currentLine - 1; i >= 0; i--) {
				lyric = lyricList.get(i);
			canvas.drawText(lyrc.lricString, getWidth() / 2,
					getHeight() / 2 + lineSpace * (i - currentLine), otherPaint);
			}
			lyric = lyrcList.get(currentLine);
			// 绘制正在播放的歌词
			canvas.drawText(lyrc.lricString, getWidth() / 2,
					getHeight() / 2, currentPaint);
                        //绘制未播放的歌词
			for (int i = currentLine + 1; i < lyrcList.size(); i++) {
				lyric = lyricList.get(i);
				canvas.drawText(lyrc.lricString, getWidth() / 2,
						getHeight() / 2 + lineSpace * (i - currentLine), otherPaint);
			}
			lyric = lyricList.get(currentLine);
			handler.sendEmptyMessageDelayed(10, lyrc.sleepTime);
			currentLine++;
		} else {
			canvas.drawText("未找到歌词", getWidth() / 2,
					getHeight() / 2, currentPaint);
		}
		super.onDraw(canvas);
	}

}

在自定义LricView中调用LrcUtils.readLRC方法传入歌词文件获取歌词信息,然后通过handler去控制多长时间进行绘制.  LricView 可以直接使用.


3. 使用LricView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.example.lrcdemo.LricView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

在布局文件中直接使用即可.

最终实现结果:



自此歌词处理完成, 贴出的代码都是可以直接使用参考, 关于快进快退音乐和倍速播放,歌词的处理逻辑就是修改lyric中的SleepTime, 不过逻辑也挺绕的.  这个歌词处理逻辑是我在做音乐播放器时参考学习别的教程, 在这里整理出来给大家参考, 如果有任何问题可以留言.





2016-04-22 17:03:47 u013766436 阅读数 7651

其实到后面就需要我们如何显示歌词,对于歌词的同步显示还是比较好实现的,主要通过判断当前播放的时间和每个结点的歌词的时间的大小,来同步对应到结点的数据,现在就是如何来实现这个问题。

其实,这个时候就需要自定义控件来实现。
第一步需要自定义View的属性。
第二步需要实现在View的构造方法中获得我们自定义的属性。
主要通过初始化函数,init()
第三步,重写ondraw函数。

LrcView.java

package com.flashmusic.View;


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.widget.TextView;

import com.flashmusic.tool.LrcInfo;
import com.flashmusic.tool.LrcList;

import java.util.Map;

/**
 * Created by zhouchenglin on 2016/4/20.
 */
public class LrcView extends TextView {
    private float width;                   //歌词视图宽度
    private float height;                 //歌词视图高度
    private Paint currentPaint;          //当前画笔对象
    private Paint notCurrentPaint;      //非当前画笔对象
    private float textHeight = 65;      //文本高度
    private float textMaxSize = 50;
    private float textSize = 40;        //文本大小
    private int index = 0;              //list集合下标
    private LrcInfo infos;              //歌词信息

    public void setmLrcList(LrcInfo infos) {
        this.infos = infos;
    }

    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;
        }

        currentPaint.setColor(Color.argb(210, 251, 248, 29));
        notCurrentPaint.setColor(Color.argb(140, 255, 255, 255));

        currentPaint.setTextSize(textMaxSize);
        currentPaint.setTypeface(Typeface.SERIF);

        notCurrentPaint.setTextSize(textSize);
        notCurrentPaint.setTypeface(Typeface.DEFAULT);

        try {

                setText("");

                canvas.drawText(infos.getLrcLists().get(index).getContent(), width / 2, height / 2, currentPaint);

                float tempY = height / 2;
                //画出本句之前的句子
                for (int i = index - 1; i >= 0; i--) {
                    //向上推移
                    tempY = tempY - textHeight;
                    canvas.drawText(infos.getLrcLists().get(i).getContent(), width / 2, tempY, notCurrentPaint);
                }
                tempY = height / 2;
                //画出本句之后的句子
                for (int i = index + 1; i < infos.getLrcLists().size(); i++) {
                    //往下推移
                    tempY = tempY + textHeight;
                    canvas.drawText(infos.getLrcLists().get(i).getContent(), width / 2, tempY, notCurrentPaint);
                }

        } catch (Exception e) {
            setText("...木有歌词文件,赶紧去下载...");
        }

    }

    /**
     * 当view大小改变的时候调用的方法
     */

    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;
    }
}

这个类是别人实现了,我仔细研读了下,觉得写的不错,通过下标的判断来实现文本的显示,这个比较不错,值得学习。

接下来就需要在Musicservice.java里面初始化函数的获得。

 public void initLrc() {

        //建立歌词对象
       LrcParse lrcParser = new LrcParse(path);
        //读歌词,并将数据传给歌词信息类
        lrcInfo = lrcParser.readLrc();
        //获得歌词中的结点
        lrcLists = lrcInfo.getLrcLists();
        //在musicActivity里面设置静态来共享数据
        MusicActivity.lrcView.setmLrcList(lrcInfo);
        //切换带动画显示歌词
        MusicActivity.lrcView.setAnimation(AnimationUtils.loadAnimation(MusicService.this, R.anim.alpha_z));
        mHandler.post(mRunnable);

    }

    Runnable mRunnable = new Runnable() {

        @Override
        public void run() {
            MusicActivity.lrcView.setIndex(lrcIndex());
            MusicActivity.lrcView.invalidate();
            mHandler.postDelayed(mRunnable, 100);
        }
    };

Handle主要通过Runnable来实现MusicActivity里面歌词的同步,但同步还是需要判断自定义控件里面的index属性。

lrcIndex函数的判断,通过比较时间来获得具体的index.

  public int lrcIndex() {
        if (mediaPlayer.isPlaying()) {
            currentTime = mediaPlayer.getCurrentPosition();
            duration = mediaPlayer.getDuration();
        }
        if (currentTime < duration) {
            for (int i = 0; i < lrcLists.size(); i++) {
                if (i < lrcLists.size() - 1) {
                    if (currentTime < lrcLists.get(i).getCurrentTime() && i == 0) {
                        index = i;
                    }
                    if ((currentTime > lrcLists.get(i).getCurrentTime())&& currentTime < lrcLists.get(i+1).getCurrentTime()) {
                        index = i;
                    }
                }
                if ((i == lrcLists.size() - 1)&& currentTime > lrcLists.get(i).getCurrentTime()) {
                    index = i;
                }
            }
        }
        return index;
    }

接下来就是如何调用初始化函数,因为我们每次播放的时候,才显示歌词的同步,所以我将他放在初始化的函数里面,问题就可以搞定了。
看看play函数,只需要添加一行代码就完成了。

 private void play(int currentTime) {
        try {

            mediaPlayer.reset();// 把各项参数恢复到初始状态
            mediaPlayer.setDataSource(path);
            mediaPlayer.prepare(); // 进行缓冲
            mediaPlayer.setOnPreparedListener(new PreparedListener(currentTime));// 注册一个监听器

            initLrc();
            //更新播放状态
            Intent intent = new Intent(PLAY_STATUE);
            // 发送播放完毕的信号,更新播放状态
            intent.putExtra("playstatue", true);
            sendBroadcast(intent);
            mHandler.sendEmptyMessage(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

看看实现的效果:
星弟的歌,本人比较喜欢

jj
没有歌词

对于文章中出现的有些类名,可以看之前写的系列文章。
现在一款本地音乐播放器的功能基本实现了,这里面用到了很多的知识,对android这个系统的了解又更加加深了理解,感谢前辈们的博客,让我受益匪浅,谢谢技术的分享,现在我也是为了更多的人,来分享自己的博客。让我们在技术的海洋中不断进步。

2016-12-30 11:24:07 qq_37057473 阅读数 3385

转载请注明出处:http://blog.csdn.net/iwanghang/article/details/51386317

奇迹在相信它的人眼里才是奇迹

——— 歌德


本文系转载,出处已经标明,以前写播放器苦于音乐歌词的显示问题,当时解决的方法是去github找开源控件,无奈是别人的轮子,尺寸大小不对,只好凑活着用了,无聊逛了下csdn,发现有这篇文章,果断收藏。以后歌词显示就直接自己造轮子用了~~~~~~~~~



实现功能:


歌词显示及滚动事件实现
ViewPager使用

后续将博文,将实现已下载音乐扫描功能和已存在歌曲歌词下载。

因为,没有自己的服务器,所以网络音乐所有相关功能(包含搜索音乐、下载音乐、下载歌词)均无法保证时效性,建议,尽快下载和练习;如果你下载时候,已经因为我采集的服务器更改规则,请给我留言,如果可以解决,我将在有空的时候献上新的源码。

截止到目前的源码下载:
http://download.csdn.net/album/detail/3105
(最新的,请下载最后一个,本博文对应版本2.1;如果需要逐步实现的过程,请下载所有)

欢迎移动开发爱好者交流:我的微信是iwanghang

另外,我打算开始找工作,如果沈阳或周边城市公司有意,也请与我联系。

实现效果如图:


实现代码如下:

PlayActivity如下:

[java] view plain copy
  1. package com.iwanghang.drmplayer;  
  2.   
  3. import android.graphics.Bitmap;  
  4. import android.media.MediaPlayer;  
  5. import android.os.Bundle;  
  6. import android.os.Environment;  
  7. import android.os.Handler;  
  8. import android.os.Message;  
  9. import android.support.v4.view.ViewPager;  
  10. import android.support.v4.view.ViewPager.OnPageChangeListener;  
  11. import android.view.LayoutInflater;  
  12. import android.view.View;  
  13. import android.view.View.OnClickListener;  
  14. import android.widget.ImageView;  
  15. import android.widget.SeekBar;  
  16. import android.widget.SeekBar.OnSeekBarChangeListener;  
  17. import android.widget.TextView;  
  18. import android.widget.Toast;  
  19.   
  20. import com.douzi.android.view.DefaultLrcBuilder;  
  21. import com.douzi.android.view.ILrcBuilder;  
  22. import com.douzi.android.view.ILrcView;  
  23. import com.douzi.android.view.LrcRow;  
  24. import com.douzi.android.view.LrcView;  
  25. import com.iwanghang.drmplayer.adapter.ViewPagerAdapter;  
  26. import com.iwanghang.drmplayer.utils.Constant;  
  27. import com.iwanghang.drmplayer.utils.ImageUtils;  
  28. import com.iwanghang.drmplayer.utils.MediaUtils;  
  29. import com.iwanghang.drmplayer.vo.Mp3Info;  
  30. import com.lidroid.xutils.db.sqlite.Selector;  
  31. import com.lidroid.xutils.exception.DbException;  
  32.   
  33. import java.io.BufferedReader;  
  34. import java.io.File;  
  35. import java.io.FileInputStream;  
  36. import java.io.FileNotFoundException;  
  37. import java.io.IOException;  
  38. import java.io.InputStreamReader;  
  39. import java.lang.ref.WeakReference;  
  40. import java.util.ArrayList;  
  41. import java.util.List;  
  42.   
  43. import static com.iwanghang.drmplayer.PlayService.ORDER_PLAY;  
  44. import static com.iwanghang.drmplayer.PlayService.RANDOM_PLAY;  
  45. import static com.iwanghang.drmplayer.PlayService.SINGLE_PLAY;  
  46.   
  47.   
  48. /** 
  49.  * PlayActivity 点击MyMusicListFragment(本地音乐)底部UI中的专辑封面图片打开的Activity 
  50.  */  
  51. public class PlayActivity extends BaseActivity implements OnClickListener, OnSeekBarChangeListener ,OnPageChangeListener {  
  52.     private TextView textView2_title;//歌名  
  53.     private ImageView imageView1_ablum;//专辑封面图片  
  54.     private SeekBar seekBar1;//进度条  
  55.     private TextView textView1_start_time, textView1_end_time;//开始时间,结束时间  
  56.     private ImageView imageView1_play_mode;//菜单  
  57.     private ImageView imageView3_previous, imageView2_play_pause, imageView1_next;//上一首,播放暂停,下一首  
  58.     private ImageView imageView1_ablum_reflection;//专辑封面图片倒影  
  59.     private ImageView imageView1_favorite;//收藏按钮  
  60.   
  61.     //private ArrayList<Mp3Info> mp3Infos;  
  62.     //private int position;//当前播放的位置  
  63.     private boolean isPause = false;//当前播放的是否为暂停状态  
  64.     private static final int UPDATE_TIME = 0x10;//更新播放事件的标记  
  65.   
  66.     private DRMPlayerApp app;//取出全局对象 方便调用  
  67.   
  68.     //歌词  
  69.     private ViewPager viewPager;  
  70.     private LrcView lrcView;// 自定义歌词视图  
  71.     private ArrayList<View> views = new ArrayList<>();  
  72.     private static final int UPDATE_LRC = 0x20;//更新播放事件的标记  
  73.     ILrcView mLrcView;  
  74.     public final static String TAG = "PlayActivity";  
  75.     private MediaPlayer mPlayer;  
  76.     private ViewPagerAdapter adapter;  
  77.   
  78.     @Override  
  79.     protected void onCreate(Bundle savedInstanceState) {  
  80.         super.onCreate(savedInstanceState);  
  81.         setContentView(R.layout.activity_music_play);  
  82.         //取出全局对象 方便调用  
  83.         app = (DRMPlayerApp) getApplication();  
  84.         //初始化界面信息  
  85.         //textView2_title = (TextView) findViewById(R.id.textView2_title);//歌名  
  86.         //imageView1_ablum = (ImageView) findViewById(R.id.imageView1_ablum);//专辑封面图片  
  87.         seekBar1 = (SeekBar) findViewById(R.id.seekBar1);//进度条  
  88.         textView1_start_time = (TextView) findViewById(R.id.textView1_start_time);//开始时间  
  89.         textView1_end_time = (TextView) findViewById(R.id.textView1_end_time);//结束时间  
  90.         imageView1_play_mode = (ImageView) findViewById(R.id.imageView1_play_mode);//菜单  
  91.         imageView3_previous = (ImageView) findViewById(R.id.imageView3_previous);//上一首  
  92.         imageView2_play_pause = (ImageView) findViewById(R.id.imageView2_play_pause);//播放暂停  
  93.         imageView1_next = (ImageView) findViewById(R.id.imageView1_next);//下一首  
  94.         imageView1_favorite = (ImageView) findViewById(R.id.imageView1_favorite);//收藏按钮  
  95.         lrcView = (LrcView) findViewById(R.id.lrcView);//自定义歌词视图  
  96.   
  97.         //注册按钮点击监听事件  
  98.         imageView1_play_mode.setOnClickListener(this);  
  99.         imageView2_play_pause.setOnClickListener(this);  
  100.         imageView3_previous.setOnClickListener(this);  
  101.         imageView1_next.setOnClickListener(this);  
  102.         seekBar1.setOnSeekBarChangeListener(this);  
  103.         imageView1_favorite.setOnClickListener(this);  
  104.   
  105.   
  106.         //mp3Infos = MediaUtils.getMp3Infos(this);  
  107.         //bindPlayService();//绑定服务,异步过程,绑定后需要取得相应的值,来更新界面  
  108.         myHandler = new MyHandler(this);  
  109.   
  110.         //独立音乐播放界面 和 歌词界面  
  111.         //viewPager = (ViewPager) findViewById(R.id.fgv_player_main);  
  112.         viewPager = (ViewPager) findViewById(R.id.viewpager);  
  113.         initViewPager();//初始化  
  114.           
  115.   
  116.         //以下直接调用change()是不行的,因为异步问题,会发生NullPointerException空指针异常  
  117.         //从MyMusicListFragment的专辑封面图片点击时间传过来的position(当前播放的位置)  
  118.         //position = getIntent().getIntExtra("position",0);  
  119.         //change(position);  
  120.   
  121.         //通过在BaseActivity中绑定Service,添加如下代码实现change()  
  122.         //musicUpdatrListener.onChange(playService.getCurrentProgeress());  
  123.   
  124.         //从MyMusicListFragment的专辑封面图片点击时间传过来的isPause(当前播放的是否为暂停状态)  
  125.         //isPause = getIntent().getBooleanExtra("isPause",false);  
  126.   
  127.         //        mLrcView = new LrcView(this, null);  
  128.         //        setContentView((View) mLrcView);  
  129.         //  
  130.         //        //在目标位置读取lrc文件  
  131.         //        File LrcDirFile = new File(Environment.getExternalStorageDirectory() + Constant.DIR_LRC);  
  132.         //        System.out.println("LrcDirFile : " + LrcDirFile);  
  133.         //        if (!LrcDirFile.exists()) {  
  134.         //            LrcDirFile.mkdirs();  
  135.         //        }  
  136.         //        //把lrc转成字符串  
  137.         //        String lrc = LrcDirFile + "/" + "山丘" + ".lrc";  
  138.         //        //Log.d的输出颜色是蓝色的,仅输出debug调试的意思,但他会输出上层的信息,过滤起来可以通过DDMS的Logcat标签来选择.  
  139.         //        Log.d(TAG, "lrc:" + lrc);  
  140.         //  
  141.         //        //把lrc的字符串 转成数组  
  142.         //        ILrcBuilder builder = new DefaultLrcBuilder();  
  143.         //        List<LrcRow> rows = builder.getLrcRows(lrc);  
  144.         //  
  145.         //        //把lrc数组 设置到mLrcView里  
  146.         //        mLrcView.setLrc(rows);  
  147.         //        //beginLrcPlay();  
  148.         //  
  149.         //        //设置监听器,监听歌词滚动  
  150.         //        mLrcView.setListener(new ILrcView.LrcViewListener() {  
  151.         //            public void onLrcSeeked(int newPosition, LrcRow row) {  
  152.         //                if (mPlayer != null) {  
  153.         //                    Log.d(TAG, "onLrcSeeked:" + row.time);  
  154.         //                    mPlayer.seekTo((int)row.time);//用户滑动歌词界面,调整进度  
  155.         //                }  
  156.         //            }  
  157.         //        });  
  158.     }  
  159.   
  160.   
  161.     private void initViewPager() {//专辑封面图片Pager与歌词Pager  
  162.         //View album_image_layout = getLayoutInflater().inflate(R.layout.album_image_layout, null);  
  163.         //System.out.println("PlayActivity.initViewPager.album_image_layout:" + album_image_layout);  
  164.   
  165.         View album_image_layout = LayoutInflater.from(getApplicationContext()).inflate(R.layout.album_image_layout, null);  
  166.         System.out.println("PlayActivity.initViewPager.album_image_layout:" + album_image_layout);  
  167.         //初始化界面信息  
  168.   
  169.         //textView2_title = (TextView) findViewById(R.id.textView2_title);//歌名  
  170.         //这里要注意下,直接findViewById,返回的是textView2_title是null;像下面这样,加上layout才可以,否则在change的时候会报空指针异常  
  171.         textView2_title = (TextView) album_image_layout.findViewById(R.id.textView2_title);//歌名  
  172.         //System.out.println("PlayActivity.initViewPager.textView2_title:" + textView2_title);  
  173.         //System.out.println("PlayActivity.initViewPager.textView2_title.getText:" + textView2_title.getText());  
  174.   
  175.   
  176.         imageView1_ablum = (ImageView) album_image_layout.findViewById(R.id.imageView1_ablum);//专辑封面图片  
  177.   
  178.         imageView1_ablum_reflection = (ImageView) album_image_layout.findViewById(R.id.imageView1_ablum_reflection);//专辑封面图片倒影  
  179.   
  180.         views.add(album_image_layout);//添加专辑封面图片Pager  
  181.   
  182.   
  183.   
  184.         //View lrc_layout = getLayoutInflater().inflate(R.layout.lrc_layout, null);  
  185.   
  186.   
  187.         View lrc_layout = LayoutInflater.from(getApplicationContext()).inflate(R.layout.lrc_layout, null);  
  188.         System.out.println("PlayActivity.initViewPager.lrc_layout:" + lrc_layout);  
  189.   
  190.   
  191.   
  192.         lrcView = (LrcView) lrc_layout.findViewById(R.id.lrcView);  
  193.         //设置滚动事件  
  194.         lrcView.setListener(new ILrcView.LrcViewListener() {  
  195.             @Override  
  196.             public void onLrcSeeked(int newPosition, LrcRow row) {  
  197.                 if (playService.isPlaying()) {  
  198.                     playService.seekTo((int) row.time);  
  199.                 }  
  200.             }  
  201.         });  
  202.         lrcView.setLoadingTipText("正在加载歌词......");  
  203.         lrcView.setBackgroundResource(R.mipmap.app_splash_bg);  
  204.         lrcView.getBackground().setAlpha(150);//背景透明度0-255  
  205.         views.add(lrc_layout);  
  206.         System.out.println("PlayActivity.initViewPager.views:" + views);  
  207.   
  208.   
  209.   
  210.         //adapter = new ViewPagerAdapter(views);  
  211.         adapter = new ViewPagerAdapter(views);  
  212.         viewPager.setAdapter(adapter);  
  213.   
  214.   
  215.   
  216.   
  217.         //viewPager.setAdapter(new ViewPagerAdapter(views));  
  218.         System.out.println("PlayActivity.initViewPager.viewPager:" + viewPager);  
  219.         viewPager.addOnPageChangeListener(this);  
  220.         System.out.println("PlayActivity.initViewPager.viewPager:" + viewPager);  
  221.     }  
  222.   
  223.   
  224.   
  225.   
  226.   
  227.   
  228.     //把播放服务的绑定和解绑放在onResume,onPause里,好处是,每次回到当前Activity就获取一次播放状态  
  229.     @Override  
  230.     protected void onResume() {  
  231.         super.onResume();  
  232.         bindPlayService();//绑定服务  
  233.     }  
  234.   
  235.     @Override  
  236.     protected void onPause() {  
  237.         super.onPause();  
  238.         unbindPlayService();//解绑服务  
  239.     }  
  240.   
  241.     @Override  
  242.     protected void onDestroy() {  
  243.         super.onDestroy();  
  244.         unbindPlayService();//解绑服务  
  245.     }  
  246.   
  247.     //Handler用于更新已经播放时间  
  248.     private static MyHandler myHandler;  
  249.   
  250.     //进度条改变 (fromUser 是否来自用户的改变 , 而不是程序自身控制的改变)  
  251.     @Override  
  252.     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {  
  253.         if (fromUser) {  
  254.             //playService.pause();//暂停  
  255.             playService.seekTo(progress);//寻找指定的时间位置 (跳到某个时间点进行播放)  
  256.             //playService.start();//播放  
  257.         }  
  258.     }  
  259.   
  260.     //进度条开始触摸  
  261.     @Override  
  262.     public void onStartTrackingTouch(SeekBar seekBar) {  
  263.   
  264.     }  
  265.   
  266.     //进度条停止触摸  
  267.     @Override  
  268.     public void onStopTrackingTouch(SeekBar seekBar) {  
  269.   
  270.     }  
  271.   
  272.     //页面滚动  
  273.     @Override  
  274.     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  275.   
  276.     }  
  277.   
  278.     //页面选择  
  279.     @Override  
  280.     public void onPageSelected(int position) {  
  281.   
  282.     }  
  283.   
  284.     //页面滚动状态改变  
  285.     @Override  
  286.     public void onPageScrollStateChanged(int state) {  
  287.   
  288.     }  
  289.   
  290.   
  291.     static class MyHandler extends Handler {  
  292.         private PlayActivity playActivity;  
  293.         private WeakReference<PlayActivity> weak;//弱引用  
  294.   
  295.         public MyHandler(PlayActivity playActivity) {  
  296.             //this.playActivity = playActivity;  
  297.             weak = new WeakReference<PlayActivity>(playActivity);  
  298.         }  
  299.   
  300.         @Override  
  301.         public void handleMessage(Message msg) {  
  302.             super.handleMessage(msg);  
  303.             //System.out.println("PlayActivity.MyHandler.weak = " + weak);  
  304.             playActivity = weak.get();  
  305.             //System.out.println("PlayActivity.MyHandler.playActivity = " + playActivity);  
  306.             if (playActivity != null) {  
  307.                 switch (msg.what) {  
  308.                     case UPDATE_TIME://更新时间(已经播放时间)  
  309.                         playActivity.textView1_start_time.setText(MediaUtils.formatTime((int) msg.obj));  
  310.                         break;  
  311.                     case UPDATE_LRC:  
  312.                         playActivity.lrcView.seekLrcToTime((int) msg.obj);  
  313.                         break;  
  314.                     default:  
  315.                         break;  
  316.                 }  
  317.             }  
  318.         }  
  319.     }  
  320.   
  321.     @Override  
  322.     public void publish(int progress) {  
  323.         //以下是是直接调用线程,但是不能这样做,会报错,线程异常  
  324.         //textView1_start_time.setText(MediaUtils.formatTime(progress));  
  325.         //所以,我们需要使用Handler  
  326.         //Message msg = myHandler.obtainMessage(UPDATE_TIME);//用于更新已经播放时间  
  327.         //msg.arg1 = progress;//用于更新已经播放时间  
  328.         //myHandler.sendMessage(msg);//用于更新已经播放时间  
  329.   
  330.         //seekBar1.setProgress(progress);  
  331.   
  332.         myHandler.obtainMessage(UPDATE_TIME, progress).sendToTarget();  
  333.         seekBar1.setProgress(progress);  
  334.         myHandler.obtainMessage(UPDATE_LRC, progress).sendToTarget();  
  335.         //System.out.println("PlayActivity.publish.myHandler = " + myHandler);  
  336.         //System.out.println("PlayActivity.publish.UPDATE_LRC = " + UPDATE_LRC);  
  337.         //System.out.println("PlayActivity.publish.progress = " + progress);  
  338.     }  
  339.   
  340.     @Override  
  341.     public void change(int position) {//初始化,独立播放界面的歌曲切换后的初始化界面上的歌曲信息  
  342.         //if (this.playService.isPlaying()) {//获取是否为播放状态  
  343.         //System.out.println("PlayActivity.change.position = " + position);  
  344.         Mp3Info mp3Info = playService.mp3Infos.get(position);  
  345.         //System.out.println("PlayActivity.change.getTitle = " + mp3Info.getTitle());  
  346.         textView2_title.setText(mp3Info.getTitle());//设置歌名  
  347.         //textView2_title.setTtileText(mp3Info.getTitle());//设置歌名  
  348.         //System.out.println("PlayActivity.change.textView2_title = " + textView2_title);  
  349.         //System.out.println("PlayActivity.change.getText : " + textView2_title.getText());  
  350.         //System.out.println("PlayActivity.change.getText : " + textView2_title.getTtileText());  
  351.   
  352.   
  353.         //下面这句话是提交更新UI的,但是这个功能在adapter里面实现了,但是实现的方式,只适合UI需要更新的内容很少的时候  
  354.         //而且,就算使用,也是设置了专辑图片和倒影,以后使用,不是这里.  
  355.         //之所以放在这里,是因为我开始调试的时候,无法setText,最后通过Debug找到了问题所在  
  356.         //以后我会专门开个博文,介绍一下Debug  
  357.         //adapter.notifyDataSetChanged();  
  358.   
  359.         //setText后,歌名没有显示,但是可以getText,尝试隐藏/显示,来刷新UI,结果歌名还是没有显示  
  360.         //viewPager.setVisibility(View.GONE);  
  361.         //getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.activity_music_play);  
  362.         //viewPager.setVisibility(View.VISIBLE);  
  363.   
  364.         //setText后,歌名没有显示,但是可以getText,尝试隐藏/显示,来刷新UI,结果歌名还是没有显示  
  365.         //viewPager.setVisibility(View.GONE);  
  366.         //viewPager.setVisibility(View.VISIBLE);  
  367.   
  368.   
  369.         //获取专辑封面图片  
  370.         Bitmap albumBitmap = MediaUtils.getArtwork(this, mp3Info.getId(), mp3Info.getAlbumId(), truefalse);  
  371.         //改变播放界面专辑封面图片  
  372.         imageView1_ablum.setImageBitmap(albumBitmap);  
  373.         textView1_end_time.setText(MediaUtils.formatTime(mp3Info.getDuration()));//设置结束时间  
  374.         imageView2_play_pause.setImageResource(R.mipmap.app_music_pause);//设置暂停图片  
  375.         seekBar1.setProgress(0);//设置当前进度为0  
  376.         seekBar1.setMax((int) mp3Info.getDuration());//设置进度条最大值为MP3总时间  
  377.         if (playService.isPlaying()) {  
  378.             imageView2_play_pause.setImageResource(R.mipmap.app_music_pause);  
  379.         } else {  
  380.             imageView2_play_pause.setImageResource(R.mipmap.app_music_play);  
  381.         }  
  382.   
  383.         if (imageView1_ablum != null) {  
  384.             imageView1_ablum_reflection.setImageBitmap(ImageUtils.createReflectionBitmapForSingle(albumBitmap));//显示倒影  
  385.         }  
  386.         switch (playService.getPlay_mode()) {  
  387.             case ORDER_PLAY://顺序播放  
  388.                 imageView1_play_mode.setImageResource(R.mipmap.app_mode_order);  
  389.                 //imageView2_play_pause.setTag(ORDER_PLAY);  
  390.                 break;  
  391.             case PlayService.RANDOM_PLAY://随机播放  
  392.                 imageView1_play_mode.setImageResource(R.mipmap.app_mode_random);  
  393.                 //imageView2_play_pause.setTag(RANDOM_PLAY);  
  394.                 break;  
  395.             case PlayService.SINGLE_PLAY://单曲循环  
  396.                 imageView1_play_mode.setImageResource(R.mipmap.app_mode_single);  
  397.                 //imageView2_play_pause.setTag(SINGLE_PLAY);  
  398.                 break;  
  399.             default:  
  400.                 break;  
  401.         }  
  402.   
  403.         long id = getId(mp3Info);  
  404.   
  405.         //初始化收藏状态  
  406.         try {  
  407.             Mp3Info loveMp3Info = app.dbUtils.findFirst(Selector.from(Mp3Info.class).where("mp3InfoId""=", mp3Info.getMp3InfoId()));//查出歌曲,SQL语句  
  408.             System.out.println("初始化收藏状态" + loveMp3Info);  
  409. //            if (loveMp3Info != null) {  
  410. //                imageView1_favorite.setImageResource(R.mipmap.app_love_selected);  
  411. //            } else {  
  412. //                imageView1_favorite.setImageResource(R.mipmap.app_love_unselected);  
  413. //            }  
  414.             if (loveMp3Info != null) {  
  415.                 System.out.println("loveMp3Info.getIsLove() = " + loveMp3Info.getIsLove());  
  416.                 if (loveMp3Info.getIsLove() == 0) {//返回值不为null,且,isLove为0时,也显示为'未收藏'  
  417.                     imageView1_favorite.setImageResource(R.mipmap.app_love_unselected);  
  418.                 }else {//返回值为null,且,isLove为1时,一定显示为'已收藏'  
  419.                     imageView1_favorite.setImageResource(R.mipmap.app_love_selected);  
  420.                 }  
  421.             } else {//返回值为null,一定显示为'未收藏'  
  422.                 imageView1_favorite.setImageResource(R.mipmap.app_love_unselected);  
  423.             }  
  424.         } catch (DbException e) {  
  425.             e.printStackTrace();  
  426.         }  
  427.   
  428.         //歌词  
  429.         String songName = mp3Info.getTitle();  
  430.         //歌词功能还不完善,后面会博文,会继续完善,暂时使用我已经下载好的lrc测试效果.  
  431.         songName = "山丘";//测试用的lrc的歌名  
  432.         String lrcPath = Environment.getExternalStorageDirectory() + Constant.DIR_LRC + "/" + songName + ".lrc";  
  433.         File lrcFile = new File(lrcPath);  
  434.         if (!lrcFile.exists()){  
  435.             //下载  
  436.             System.out.println("请下载歌词");  
  437.         }else{  
  438.             loadLRC(lrcFile);  
  439.         }  
  440.     }  
  441.   
  442.     //如果是本地音乐,id就是id;如果是收藏音乐,id则是mp3InfoId  
  443.     //提供给 收藏按钮 点击事件时 调用.  
  444.     private long getId(Mp3Info mp3Info){  
  445.         //初始收藏状态  
  446.         long id = 0;  
  447.         switch (playService.getChangePlayList()){  
  448.             case PlayService.MY_MUSIC_LIST:  
  449.                 System.out.println("当前为本地列表");  
  450.                 id = mp3Info.getId();  
  451.                 System.out.println("id =" + id);  
  452.                 break;  
  453.             case PlayService.LOVE_MUSIC_LIST:  
  454.                 System.out.println("当前为收藏列表");  
  455.                 id = mp3Info.getMp3InfoId();  
  456.                 System.out.println("id =" + id);  
  457.                 break;  
  458.             default:  
  459.                 break;  
  460.         }  
  461.         return id;  
  462.     }  
  463.   
  464.     //点击事件  
  465.     @Override  
  466.     public void onClick(View v) {  
  467.         switch (v.getId()) {  
  468.             case R.id.imageView2_play_pause: {//播放暂停按钮  
  469.                 if (playService.isPlaying()) {//如果是播放状态  
  470.                     imageView2_play_pause.setImageResource(R.mipmap.app_music_play);//设置播放图片  
  471.                     playService.pause();  
  472.                 } else {  
  473.                     if (playService.isPause()) {  
  474.                         imageView2_play_pause.setImageResource(R.mipmap.app_music_pause);//设置暂停图片  
  475.                         playService.start();//播放事件  
  476.                     } else {  
  477.                         //只有打开APP没有点击任何歌曲,同时,直接点击暂停播放按钮时.才会调用  
  478.                         //playService.play(0);  
  479.   
  480.                         //只有打开APP没有点击任何歌曲,同时,直接点击暂停播放按钮时.才会调用  
  481.                         //默认播放的是,在PlayService的onCreate中 恢复状态值  
  482.                         playService.play(playService.getCurrentPosition());  
  483.                     }  
  484.                 }  
  485.                 break;  
  486.             }  
  487.             case R.id.imageView1_next: {//下一首按钮  
  488.                 playService.next();//下一首  
  489.                 break;  
  490.             }  
  491.             case R.id.imageView3_previous: {//上一首按钮  
  492.                 playService.prev();//上一首  
  493.                 break;  
  494.             }  
  495.             case R.id.imageView1_play_mode: {//循环模式按钮  
  496.                 //int mode = (int) imageView1_play_mode.getTag();  
  497.                 /** 
  498.                  * 以下Tosat内容,在strings.xml里,添加对应代码 
  499.                  *<string name="order_play">顺序播放</string> 
  500.                  *<string name="random_play">随机播放</string> 
  501.                  *<string name="single_play">单曲循环</string> 
  502.                  */  
  503.                 switch (playService.getPlay_mode()) {  
  504.                     case ORDER_PLAY:  
  505.                         imageView1_play_mode.setImageResource(R.mipmap.app_mode_random);  
  506.                         //imageView1_play_mode.setTag(RANDOM_PLAY);  
  507.                         playService.setPlay_mode(RANDOM_PLAY);  
  508.                         //Toast.makeText(getApplicationContext(),"随机播放",Toast.LENGTH_SHORT).show();//这句也可以  
  509.                         //Toast.makeText(PlayActivity.this, "随机播放", Toast.LENGTH_SHORT).show();//这句也可以  
  510.                         Toast.makeText(PlayActivity.this, getString(R.string.random_play), Toast.LENGTH_SHORT).show();  
  511.                         break;  
  512.                     case RANDOM_PLAY:  
  513.                         imageView1_play_mode.setImageResource(R.mipmap.app_mode_single);  
  514.                         //imageView1_play_mode.setTag(SINGLE_PLAY);  
  515.                         playService.setPlay_mode(SINGLE_PLAY);  
  516.                         //Toast.makeText(getApplicationContext(),"单曲循环",Toast.LENGTH_SHORT).show();//这句也可以  
  517.                         //Toast.makeText(PlayActivity.this, "单曲循环", Toast.LENGTH_SHORT).show();//这句也可以  
  518.                         Toast.makeText(PlayActivity.this, getString(R.string.single_play), Toast.LENGTH_SHORT).show();  
  519.                         break;  
  520.                     case SINGLE_PLAY:  
  521.                         imageView1_play_mode.setImageResource(R.mipmap.app_mode_order);  
  522.                         //imageView1_play_mode.setTag(ORDER_PLAY);  
  523.                         playService.setPlay_mode(ORDER_PLAY);  
  524.                         //Toast.makeText(getApplicationContext(),"顺序播放",Toast.LENGTH_SHORT).show();//这句也可以  
  525.                         //Toast.makeText(PlayActivity.this, "顺序播放", Toast.LENGTH_SHORT).show();//这句也可以  
  526.                         Toast.makeText(PlayActivity.this, getString(R.string.order_play), Toast.LENGTH_SHORT).show();  
  527.                         break;  
  528.                 }  
  529.                 break;  
  530.             }  
  531.             case R.id.imageView1_favorite: {//收藏按钮  //在vo.Mp3Info里  private long mp3InfoId;//在收藏音乐时用于保存原始ID  
  532.                 Mp3Info mp3Info = playService.mp3Infos.get(playService.getCurrentPosition());//查出歌曲  
  533.                 System.out.println(mp3Info);  
  534.                 try {  
  535.                     Mp3Info loveMp3Info = app.dbUtils.findFirst(Selector.from(Mp3Info.class).where("mp3InfoId","=",getId(mp3Info)));//查出歌曲,SQL语句  
  536.                     System.out.println(loveMp3Info);  
  537.                     if (loveMp3Info==null){//返回值为null,则一定需要save  
  538.                         System.out.println("不在音乐收藏数据库中 保存音乐数据 原始数据: " + mp3Info);  
  539.                         mp3Info.setMp3InfoId(mp3Info.getId());  
  540.                         mp3Info.setIsLove(1);  
  541.                         System.out.println(mp3Info);  
  542.                         app.dbUtils.save(mp3Info);//在音乐收藏数据库 保存音乐  
  543.                         System.out.println("save");  
  544.                         imageView1_favorite.setImageResource(R.mipmap.app_love_selected);  
  545.   
  546.                         //以下是:调试使用,保存以后再查一遍  
  547.                         loveMp3Info = app.dbUtils.findFirst(Selector.from(Mp3Info.class).where("mp3InfoId","=",getId(mp3Info)));//查出歌曲,SQL语句  
  548.                         System.out.println("调试使用,保存以后再查一遍 最新数据: " + loveMp3Info);  
  549.                     }else {//返回值不为null,则一定需要update  
  550.                         System.out.println("在音乐收藏数据库中 更新音乐数据 原始数据: " + loveMp3Info);  
  551.                         int isLove = loveMp3Info.getIsLove();  
  552.                         if (isLove==1){//返回值不为null,且,isLove为1时;设置isLove为0,同时显示为'未收藏'  
  553.                             loveMp3Info.setIsLove(0);  
  554.                             imageView1_favorite.setImageResource(R.mipmap.app_love_unselected);  
  555.                         }else {//返回值不为null,且,isLove为0时;设置isLove为1,同时显示为'已收藏'  
  556.                             loveMp3Info.setIsLove(1);  
  557.                             imageView1_favorite.setImageResource(R.mipmap.app_love_selected);  
  558.                         }  
  559.                         System.out.println("update");  
  560.                         app.dbUtils.update(loveMp3Info,"isLove");//更新loveMp3Info数据  
  561.   
  562.                         //以下是:调试使用,更新以后再查一遍  
  563.                         loveMp3Info = app.dbUtils.findFirst(Selector.from(Mp3Info.class).where("mp3InfoId""=", getId(mp3Info)));//查出歌曲,SQL语句  
  564.                         System.out.println("调试使用,更新以后再查一遍 最新数据: " + loveMp3Info);  
  565.                     }  
  566.                 } catch (DbException e) {  
  567.                     e.printStackTrace();  
  568.                 }  
  569.                 break;  
  570.             }  
  571.             default:  
  572.                 break;  
  573.         }  
  574.     }  
  575.   
  576.   
  577.     //加载歌词  
  578.     private void loadLRC(File lrcFile){  
  579.         StringBuffer buf = new StringBuffer(1024 * 10);  
  580.         char[] chars = new char[1024];  
  581.         try {  
  582.             BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile)));  
  583.             int len = -1;  
  584.             while ((len = in.read(chars)) != -1){  
  585.                 buf.append(chars,0,len);  
  586.             }  
  587.             in.close();  
  588.         } catch (FileNotFoundException e) {  
  589.             e.printStackTrace();  
  590.         }catch (IOException e) {  
  591.             e.printStackTrace();  
  592.         }  
  593.         ILrcBuilder builder = new DefaultLrcBuilder();  
  594.         List<LrcRow> rows = builder.getLrcRows(buf.toString());  
  595.         lrcView.setLrc(rows);  
  596.         //加载专辑封面图片为背景的方法(实际使用,发现效果不理想)  
  597.         //long id = mp3Info.getMp3InfoId()==0?mp3Info.getId:mp3Info.getMp3InfoId();  
  598.         //Bitmap bg = MediaUtils.getArtwork(this, id ,mp3Info.getAlbumId(),false,false);  
  599.         //if(bg != null){  
  600.         //    lrcView.getBackground(new BitmapDrawable(getResources(),bg));  
  601.         //   lrcView.getBackground().setAlpha(120);  
  602.         //}  
  603.     }  
  604.   
  605. }  

activity_music_play如下:

[java] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:padding="@dimen/activity_horizontal_margin">  
  6.       
  7.     <android.support.v4.view.ViewPager  
  8.         android:id="@+id/viewpager"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:layout_alignParentStart="true"  
  12.         android:layout_gravity="center"  
  13.         android:layout_above="@+id/linearLayout3">  
  14.     </android.support.v4.view.ViewPager>  
  15.   
  16.   
  17.     <LinearLayout  
  18.         android:id="@+id/linearLayout3"  
  19.         android:layout_width="match_parent"  
  20.         android:layout_height="wrap_content"  
  21.         android:padding="10dp"  
  22.         android:orientation="vertical"  
  23.         android:layout_alignParentBottom="true">  
  24.   
  25.         <LinearLayout  
  26.             android:id="@+id/linearLayout"  
  27.             android:layout_width="match_parent"  
  28.             android:layout_height="wrap_content"  
  29.             android:layout_marginBottom="10dp"  
  30.             android:orientation="horizontal">  
  31.   
  32.             <TextView  
  33.                 android:id="@+id/textView1_start_time"  
  34.                 android:layout_width="wrap_content"  
  35.                 android:layout_height="wrap_content"  
  36.                 android:text="00:00"  
  37.                 android:textColor="@android:color/darker_gray" />  
  38.   
  39.             <SeekBar  
  40.                 android:id="@+id/seekBar1"  
  41.                 android:layout_width="235dp"  
  42.                 android:layout_height="wrap_content"  
  43.                 android:indeterminate="false"/>  
  44.   
  45.             <TextView  
  46.                 android:id="@+id/textView1_end_time"  
  47.                 android:layout_width="wrap_content"  
  48.                 android:layout_height="wrap_content"  
  49.                 android:text="00:00"  
  50.                 android:textColor="@android:color/darker_gray" />  
  51.   
  52.         </LinearLayout>  
  53.   
  54.         <RelativeLayout  
  55.             android:id="@+id/linearLayout2"  
  56.             android:layout_width="match_parent"  
  57.             android:layout_height="50dp"  
  58.             android:layout_marginBottom="10dp"  
  59.             android:orientation="horizontal">  
  60.   
  61.             <ImageView  
  62.                 android:id="@+id/imageView1_play_mode"  
  63.                 android:layout_width="wrap_content"  
  64.                 android:layout_height="wrap_content"  
  65.                 android:src="@mipmap/app_music_order"  
  66.                 android:layout_alignBottom="@+id/imageView3_previous"  
  67.                 android:layout_alignParentStart="true" />  
  68.   
  69.             <ImageView  
  70.                 android:id="@+id/imageView1_favorite"  
  71.                 android:layout_width="40dp"  
  72.                 android:layout_height="40dp"  
  73.                 android:src="@mipmap/app_love_unselected"  
  74.                 android:layout_alignParentTop="true"  
  75.                 android:layout_toEndOf="@+id/imageView1_play_mode"  
  76.                 android:layout_marginTop="15dp"  
  77.                 android:layout_marginStart="50dp" />  
  78.   
  79.             <ImageView  
  80.                 android:id="@+id/imageView3_previous"  
  81.                 android:layout_width="50dp"  
  82.                 android:layout_height="50dp"  
  83.                 android:layout_alignTop="@+id/imageView2_play_pause"  
  84.                 android:layout_toLeftOf="@+id/imageView2_play_pause"  
  85.                 android:src="@mipmap/app_music_previous" />  
  86.   
  87.             <ImageView  
  88.                 android:id="@+id/imageView2_play_pause"  
  89.                 android:layout_width="50dp"  
  90.                 android:layout_height="50dp"  
  91.                 android:layout_alignTop="@+id/imageView1_next"  
  92.                 android:layout_toLeftOf="@+id/imageView1_next"  
  93.                 android:src="@mipmap/app_music_play" />  
  94.   
  95.             <ImageView  
  96.                 android:id="@+id/imageView1_next"  
  97.                 android:layout_width="50dp"  
  98.                 android:layout_height="50dp"  
  99.                 android:layout_alignParentBottom="true"  
  100.                 android:layout_alignParentRight="true"  
  101.                 android:src="@mipmap/app_music_next" />  
  102.   
  103.         </RelativeLayout>  
  104.   
  105.     </LinearLayout>  
  106.   
  107. </RelativeLayout>  

ViewPagerAdapter如下:

[java] view plain copy
  1. package com.iwanghang.drmplayer.adapter;  
  2.   
  3. import android.content.Context;  
  4. import android.os.Parcelable;  
  5. import android.support.v4.view.PagerAdapter;  
  6. import android.support.v4.view.ViewPager;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.ImageView;  
  10. import android.widget.TextView;  
  11.   
  12. import java.util.List;  
  13.   
  14. /** 
  15.  * Created by iwanghang on 16/5/10. 
  16.  * ViewPagerAdapter 
  17.  */  
  18. public class ViewPagerAdapter extends PagerAdapter {  
  19.     private List<View> list;  
  20.     private Context context;  
  21.   
  22.     public ViewPagerAdapter(List<View> list) {  
  23.         this.list = list;  
  24.     }  
  25.   
  26.     public ViewPagerAdapter(List<View> list,Context context) {  
  27.         this.list = list;  
  28.         this.context = context;  
  29.     }  
  30.   
  31.     @Override  
  32.     public int getCount() {  
  33.         if (list != null && list.size() > 0) {  
  34.             return list.size();  
  35.         } else {  
  36.             return 0;  
  37.         }  
  38.     }  
  39.   
  40.     @Override  
  41.     public boolean isViewFromObject(View arg0, Object arg1) {  
  42.         return arg0 == arg1;  
  43.     }  
  44.   
  45.     @Override  
  46.     public void destroyItem(ViewGroup container, int position, Object object) {  
  47.         container.removeView((View) object);  
  48.     }  
  49.   
  50.     @Override  
  51.     public Object instantiateItem(ViewGroup container, int position) {  
  52.         container.addView(list.get(position));  
  53.         return list.get(position);  
  54.     }  
  55.   
  56.     @Override  
  57.     public int getItemPosition(Object object) {  
  58.         return POSITION_NONE;  
  59.     }  
  60.   
  61.   
  62.   
  63.   
  64.   
  65.   
  66.   
  67. }