Android 手机影音 开发过程记录(六)
前一篇已经将音乐播放及切换的相关逻辑弄好了,今天主要理一下剩余的部分,包含:
1. 自己定义通知栏的布局及逻辑处理
2. 滚动歌词的绘制
3. 歌词解析
效果图
通知栏
自己定义布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/layout_notification"
android:padding="10dp" > <ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@mipmap/ic_launcher" /> <LinearLayout
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:orientation="vertical" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="标题"
android:id="@+id/tv_notification_title"
android:textColor="@color/white"
android:textSize="17sp" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="艺术家"
android:id="@+id/tv_notification_content"
android:textColor="@color/gray_white"
android:textSize="14sp" />
</LinearLayout> <LinearLayout android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:layout_height="40dp"> <ImageView android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="20dp"
android:id="@+id/btn_notification_pre"
android:background="@mipmap/icon_notification_pre"/> <ImageView android:layout_width="30dp"
android:layout_height="30dp"
android:id="@+id/btn_notification_next"
android:background="@mipmap/icon_notification_next"/> </LinearLayout> </LinearLayout>通知栏的相关逻辑:
- 下一首
- 上一首
- 进入播放页
/**
* 发送自己定义布局的通知
*/
private void sendNotification() {
Notification.Builder builder = new Notification.Builder(AudioPlayerService.this);
builder.setOngoing(true)
.setSmallIcon(R.mipmap.notification_music_playing)
.setTicker("正在播放:" + StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()))
.setWhen(System.currentTimeMillis())
.setContent(getRemoteViews()); startForeground(1, builder.build());
} private RemoteViews getRemoteViews() {
RemoteViews remoteViews = new RemoteViews(getPackageName(),
R.layout.layout_music_notification);
remoteViews.setTextViewText(R.id.tv_notification_title, StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()));
remoteViews.setTextViewText(R.id.tv_notification_content, audioList.get(currentPosition).getArtist()); remoteViews.setOnClickPendingIntent(R.id.btn_notification_pre, getPrePendingIntent());
remoteViews.setOnClickPendingIntent(R.id.btn_notification_next, getNextPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.layout_notification, getContentPendingIntent()); return remoteViews;
} private PendingIntent getPrePendingIntent() {
Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
intent.putExtra("viewAction", ACTION_NOTIFICATION_PRE);
intent.putExtra("isFromNotification", true);
PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
} private PendingIntent getNextPendingIntent() {
Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
intent.putExtra("viewAction", ACTION_NOTIFICATION_NEXT);
intent.putExtra("isFromNotification", true);
PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
} private PendingIntent getContentPendingIntent() {
Intent intent = new Intent(AudioPlayerService.this, AudioPlayerActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("viewAction", ACTION_NOTIFICATION_LAYOUT);
bundle.putBoolean("isFromNotification", true);
intent.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getActivity(AudioPlayerService.this, 3, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
}/*发送通知的方法应该在音乐准备完毕和開始播放的时候调用*/ private OnPreparedListener onPreparedListener = new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
notifyPrepared();
sendNotification();
}
};
public void start() {
if (mediaPlayer != null) {
mediaPlayer.start();
}
sendNotification();
} //音乐准备暂停时移除通知
public void pause() {
if (mediaPlayer != null) {
mediaPlayer.pause();
}
stopForeground(true);//移除通知
}
歌词绘制
思路:自己定义LyricView继承TextView。覆盖onSizeChanged(),onDraw()方法。
绘制一行居中文本
/**
* 绘制水平居中的歌词文本
*
* @param canvas 画布
* @param text 文本
* @param y 竖直方向的y坐标
* @param isLight 是否高亮
*/
private void drawCenterHorizontalText(Canvas canvas, String text, float y, boolean isLight) {
paint.setColor(isLight ? LYRCI_HIGHLIGHT_COLOR : LYRIC_DEFAULT_COLOR);
paint.setTextSize(isLight ?
getResources().getDimension(R.dimen.lyric_highlight_textsize)
: getResources().getDimension(R.dimen.lyric_default_textsize));
float x = width / 2 - paint.measureText(text) / 2;
canvas.drawText(text, x, y, paint);
}
绘制多行歌词
/**
* 绘制全部的歌词
*
* @param canvas 画布
*/
private void drawLyricList(Canvas canvas) {
Lyric lightLyric = lyricList.get(lightLyricIndex); //1.首先将高亮行的歌词绘制出来,作为一个參照物
float lightLyricY = height / 2 + getTextHeight(lightLyric.getContent()) / 2;
drawCenterHorizontalText(canvas, lightLyric.getContent(), lightLyricY, true);
//2.遍历高亮行之前的歌词,并绘制出来
for (int pre = 0; pre < lightLyricIndex; pre++) {
Lyric lyric = lyricList.get(pre);
float y = lightLyricY - (lightLyricIndex - pre) * LYRIC_ROW_HEIGHT;
drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
}
//3.遍历高亮行之后的歌词,并绘制出来
for (int next = lightLyricIndex + 1; next < lyricList.size(); next++) {
Lyric lyric = lyricList.get(next);
float y = lightLyricY + (next - lightLyricIndex) * LYRIC_ROW_HEIGHT;
drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
} }
/**
* 获取文本的高度
*
* @param text 文本
* @return 文本的高度
*/
private float getTextHeight(String text) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds.height();
}滚动歌词
/**
* 滚动歌词
*/
public void roll(long currentPosition,long audioDuration){
this.currentPosition = currentPosition;
this.audioDuration = audioDuration;
//1. 依据歌词播放的position。计算出高亮行的索引lightLyricIndex
if(lyricList.size() != 0){
//1.依据当前歌曲播放的位置去计算lightLyricIndex
caculateLightLyricIndex();
} //2. 拿到新的lightLyricIndex之后,更新view
invalidate();
}/**
* 计算高亮歌词的索引值
* 仅仅要当前音乐的position大于当前行的startPoint。
* 而且小于下一行的startPoint,就是高亮行
*/
private void caculateLightLyricIndex() {
for (int i = 0; i < lyricList.size(); i++) {
long startPoint = lyricList.get(i).getStartPoint();
if(i == lyricList.size() - 1){//最后一行
if(currentPosition > startPoint){
lightLyricIndex = i;
}
}else{//不是最后一行
Lyric next = lyricList.get(i + 1);
if(currentPosition > startPoint && currentPosition < next.getStartPoint()){
lightLyricIndex = i;
}
} }
}平滑滚动歌词
/**
* 绘制全部的歌词
*
* @param canvas 画布
*/
private void drawLyricList(Canvas canvas) {
Lyric lightLyric = lyricList.get(lightLyricIndex);
//平滑移动歌词
//1. 算出歌词的总的播放时间 即 下一行的startPoint - 当前的startPoint
int totalDuration;
if(lightLyricIndex==(lyricList.size()-1)){
//假设最后一行是高亮行,则拿歌曲总时间减去当前的startPoint
totalDuration = (int) (audioDuration - lightLyric.getStartPoint());
}else {
totalDuration = (int) (lyricList.get(lightLyricIndex+1).getStartPoint()-lightLyric.getStartPoint());
}
//2. 算出当前已经播放的秒数占总时间的百分比 currentAudioPosition - startPoint
float offsetPosition = (int) (currentPosition - lightLyric.getStartPoint());
float percent = offsetPosition/totalDuration;
//3. 依据百分比算出应该移动的距离 percent * LYRIC_ROW_HEIGHT
float dy = LYRIC_ROW_HEIGHT * percent;
canvas.translate(0, -dy); //1.首先将高亮行的歌词绘制出来,作为一个參照物
float lightLyricY = height / 2 + getTextHeight(lightLyric.getContent()) / 2;
drawCenterHorizontalText(canvas, lightLyric.getContent(), lightLyricY, true);
//2.遍历高亮行之前的歌词。并绘制出来
for (int pre = 0; pre < lightLyricIndex; pre++) {
Lyric lyric = lyricList.get(pre);
float y = lightLyricY - (lightLyricIndex - pre) * LYRIC_ROW_HEIGHT;
drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
}
//3.遍历高亮行之后的歌词,并绘制出来
for (int next = lightLyricIndex + 1; next < lyricList.size(); next++) {
Lyric lyric = lyricList.get(next);
float y = lightLyricY + (next - lightLyricIndex) * LYRIC_ROW_HEIGHT;
drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
} }提供设置歌词的方法
public void setLyricList(ArrayList<Lyric> lyricList){
this.lyricList = lyricList;
if(this.lyricList==null){
hasNoLyric = true;
}
}
歌词解析
- 读取每一行歌词文本
- 解析每一行歌词
对歌词集合进行排序
/**
* 歌词解析的工具类
*/
public class LyricParser {
public static ArrayList<Lyric> parseLyricFromFile(File lyricFile){
if(lyricFile==null || !lyricFile.exists())return null;
ArrayList<Lyric> list = new ArrayList<Lyric>(); try {
//1.读取每一行歌词文本
BufferedReader reader = new BufferedReader(new InputStreamReader
(new FileInputStream(lyricFile),"utf-8"));
String line;
while((line=reader.readLine())!=null){
//2.解析每一行歌词
//[00:04.05][00:24.05][01:24.05]北京北京 -> split("\\]")
//[00:04.05 [00:24.05 [01:24.05 北京北京
String[] arr = line.split("\\]");
for (int i = 0; i < arr.length-1; i++) {
Lyric lyric = new Lyric();
lyric.setContent(arr[arr.length-1]);//设置歌词内容
lyric.setStartPoint(formatStartPoint(arr[i])); list.add(lyric);
}
}
//3.对歌词集合进行排序
Collections.sort(list);//从小到大
} catch (Exception e) {
e.printStackTrace();
} return list;
} /**
* 将[00:04.05转long类型的时间
* @param str
* @return
*/
private static long formatStartPoint(String str){
str = str.substring(1);//00:04.05
//1.先以冒号切割
String[] arr1 = str.split("~i");//00 04.05
String[] arr2 = arr1[1].split("\\.");//04 05
int minute = Integer.parseInt(arr1[0]);//得到多少分钟
int second = Integer.parseInt(arr2[0]);//得到多少秒
int mills = Integer.parseInt(arr2[1]);//得到多少10毫秒
return mills*10 + second*1000 + minute*60*1000;
} }
/**模拟歌词载入模块
* TODO:拿歌曲id去server请求相应的歌词文件
*/
public class LyricLoader {
// private static final String LYRIC_DIR = Environment.getExternalStorageDirectory()+"/MIUI/music/lyric";
private static final String LYRIC_DIR = Environment.getExternalStorageDirectory()+"/test/audio";
public static File loadLyricFileByName(String audioName){
File file = new File(LYRIC_DIR,StringUtil.formatAudioName(audioName)+".lrc");
LogUtils.i(LYRIC_DIR);
if(!file.exists()){
file = new File(LYRIC_DIR,StringUtil.formatAudioName(audioName)+".txt");
}
return file;
}
}
好了。手机影音项目的整理就到这里。
Android 手机影音 开发过程记录(六)的更多相关文章
- 记录android学习、开发过程温故知新
记录android学习.开发过程温故知新
- GPS部标平台的架构设计(六)-Android手机客户端和手机查车设计
对于GPS软件平台,虽然有功能非常丰富的PC端或BS客户端,但是客户也是需要移动客户端来作为自己的辅助工具,也是需要的.做为GPS平台的设计者和开发者,在开发移动客户端的时候,也需要从常规的服务器开发 ...
- 三星 S4 手机误删除相片(相册)后的恢复问题,仅记录处理过程,其它Android手机同样适用
无意中删除了三星S4手机中相机的相册.过程是这样的,用手机拍了几张照片,觉得最后那张拍得不好,想删除,于是进入相册,看到有那张照片的图标,选择,删除,悲剧发生了! 这里得说三星的不好:在相册中,相册文 ...
- 如何在 Android 手机上实现抓包?
如何在 Android 手机上实现抓包? http://www.zhihu.com/question/20467503 我想知道某个应用究竟在数据提交到哪里,提交了什么.网上的教程太复杂,不想麻烦.有 ...
- 【朝花夕拾】Android性能篇之(六)Android进程管理机制
前言 Android系统与其他操作系统有个很不一样的地方,就是其他操作系统尽可能移除不再活动的进程,从而尽可能保证多的内存空间,而Android系统却是反其道而行之,尽可能保留进程.An ...
- 为什么Android手机总是越用越慢?
根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢”,也有超过460万条结果.在业内,Android手机一直有着“越用越慢”的口碑 ...
- 为啥Android手机总会越用越慢?
转自:http://www.androidchina.net/818.html 根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢 ...
- ubuntu下USB连接Android手机
初始工作:将Android手机通过usb连接到电脑,之后点击VM-Removable Devices-google Android - Connect,即可. 若通过usb连接到电脑,Removabl ...
- Android应用基础学习记录
01_前言 前言,了解了Android的情况.这里也介绍一下本文.本文是记录学习Android应用程序开发过程,视频中使用的Android2.2版本号,我以4.2版本号为基础,找出当中的差异并记录下来 ...
随机推荐
- [LeetCode] 350. 两个数组的交集 II intersection-of-two-arrays-ii(排序)
思路: 先找到set的交集,然后分别计算交集中的每个元素在两个原始数组中出现的最小次数. class Solution(object): def intersect(self, nums1, nums ...
- npm API文档
npm API文档 https://docs.npmjs.com/
- Struts(19)Struts集成
Struts2 MVC架构 模型视图控制器(Model View Controller)或MVC,MVC是俗称.是一种软件设计模式,用于开发Web应用程序.模型 - 视图 - 控制器模式是由下面三个部 ...
- hiho模拟面试题2 补提交卡 (贪心,枚举)
题目: 时间限制:2000ms 单点时限:1000ms 内存限制:256MB 描写叙述 小Ho给自己定了一个雄伟的目标:连续100天每天坚持在hihoCoder上提交一个程序.100天过去了.小Ho查 ...
- CoreData 从入门到精通(六)模型版本和数据迁移
前面几篇文章中讲的所有内容,都是在同一个模型版本上进行操作的.但在真实开发中,基本上不会一直停留在一个版本上,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模 ...
- sicily 1342 开心的金明 (动规)
刷一下简单的背包问题 以下为代码: //1342. 开心的金明 #include <iostream> using namespace std; #define MAX(a,b) a> ...
- GPG加密windows中使用
在Windows系统使用Gpg4win进行加密解密 2015-06-15 by u014076884 GPG,又称为GnuPG,全称是Gnu Private Guard,即GNU隐私卫士.GPG是以P ...
- WPF学习(四) - 附加属性
冷静了一晚,我就当这次学习的过程是在看狗血剧情的武打小说吧:没有垃圾的武术,只有垃圾的武者…… 还有个话儿怎么说来着:你们是用户,不是客户,也就有个使用的权力.搞清楚身份,别叽叽歪歪的! 没办法,全世 ...
- Java获取环境变量和系统属性
Java获取服务器环境变量和JVM系统变量 当程序中需要使用与操作系统相关的变量(例如:文件分隔符.换行符)时,Java提供了System类的静态方法getenv()和getProperty() ...
- 关于lncRNA数据收集
最近需要自己收集数据库里的核酸序列,于是直接面对一些神文 http://www.360doc.com/content/17/0120/08/30227855_623625901.shtml http: ...