Android实战 – 音心播放器 (项目总结,应用打包发布)
最后更新于:2022-04-01 10:53:10
# 1.心得
音心播放器是在之鱼APP的基础上来的,在做之鱼APP的时候,需要用到音乐播放的效果,脑子不够用,就单独的将其提取出来了,当然之鱼中也实现音乐的方法,但是没有歌词,其次之鱼目前还没有完成,纠结死了。毕业设计题目定下来了,这又没时间去弄了,就放在那吧, 下面就要开始学习Spring MVC , MyBatis ,Spring 了。以后只能再接再厉了,我想说声 f ` a ` c` k `;
音心欣赏 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576501952f.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_57157650372d8.jpg)
# 2.应用打包
(1)右击应用 Export , 进入下面界面 ,Next
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576506c3d1.jpg)
(2)选择你要导出的工程
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576507ac5f.jpg)
(3)创建key , 旧的输入密码就可以,新的创建密码就可以了
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576508a66e.jpg)
(4)创建 keystore
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576508a66e.jpg)
(5)输出 ,选择路径,点击finish
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_571576509f626.jpg)
# 3.音心
APP访问地址 :[http://www.pgyer.com/yuanmusic](http://www.pgyer.com/yuanmusic)
APP下载地址 : [点击我下载音心](http://www.pgyer.com/apiv1/app/install?aId=d017cb7c061ef9a56efb139c098685d9&_api_key=38975cd846bcdb263616e7d0f628241e)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_57157650b324e.jpg)
完
Android实战 – 音心播放器 (优化Service退出,按两下退出应用实现)
最后更新于:2022-04-01 10:53:07
# 1.背景
在通知栏控制优化进行点击close退出的时候,通知栏往往关闭不了,后台服务关闭不了,音乐可以停止,但是会发生崩溃现象,让人很不爽。其次就是我们常见的按两次退出应用的方法实现。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764fce934.jpg)
# 2.优化通知栏关闭服务
问题:通知栏的close 关闭服务按钮,关闭不了?
解决 : 经使用酷狗音乐,发现在关闭通知栏的时候,应用会随着通知栏的关闭而关闭,故在优化上进行了一些思考:
1)关闭音乐播放器,释放资源
2)停止当前音乐服务(stopService)
3)取消Notification
4)取消注册的BroadcastReceiver
5)System.exit(0); 退出应用
~~~
@Override
public void onDestroy() {
//释放音乐资源
if (mp != null) {
mp.stop();
mp.release();
mp = null;
musics = null;
}
//停止当前的Service
stopService(stopIntent);
//取消通知
musicNotifi.onCancelMusicNotifi();
// 取消注册的广播
unregisterReceiver(musicBroadCast);
//关闭应用
System.exit(0);
}
~~~
# 3.退出优化
点击两次退出应用,一个boolean 变量控制 :
~~~
@Override
public void onBackPressed() {
if(isback){
isback=false;
finish();
}else{
showToast("再按一次退出应用");
isback=true;
}
}
~~~
4.总结
其他如果有问题,将会总结到这里。
Android实战 – 音心播放器 (启动页与社交分享(ShareSDK))
最后更新于:2022-04-01 10:53:05
# 1.背景
好了临近结尾,我们做下启动页和社交化分享,包括新浪微博,QQ 。好了看下效果 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764f95879.jpg)
# 2.启动页实现
(1)新建一个Activity , 启动3s后 finish() ;
~~~
public class WelcomeActivity extends Activity {
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
handler.postDelayed(new Runnable() {
@Override
public void run() {
startActivity(new Intent(WelcomeActivity.this,
MainActivity.class));
finish();
}
}, 3000);
}
}
~~~
(2)布局实现 : 使用了很多半透明颜色,在这里就不多说明了;
~~~
<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"
android:background="@drawable/richu" >
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentTop="true"
android:layout_marginTop="100dp"
android:layout_centerHorizontal="true"
android:background="@color/app_color_whrit"
android:gravity="center"
android:text="@string/tv_main_title"
android:textColor="@color/app_color"
android:textSize="30sp" />
<TextView
android:id="@+id/tv_welcome_logo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:padding="20dp"
android:text="@string/maskmusic_welcome"
android:textSize="18sp"
android:textColor="@color/app_color_zhu" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginTop="50dp"
android:gravity="center"
android:padding="20dp"
android:text="@string/music_net"
android:textColor="@color/app_color_zhu"
android:textStyle="italic" />
</RelativeLayout>
~~~
# 3.社交化分享
在这里使用了ShareSdk , 具体集成在这里就不多说了,官方提供了很全面的文档说明
[ShareSDK Android 开发详细文档](http://wiki.mob.com/Android_%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E6%8C%87%E5%8D%97/)
# 4.总结
在这里我们已经实现了全部的功能,点击下面链接访问,可以下载试用 :
APP 访问地址 : [http://www.pgyer.com/yuanmusic](http://www.pgyer.com/yuanmusic)
Android实战 – 音心播放器 (MusicListActivity – 音乐播放和MainActivity的一个问题)
最后更新于:2022-04-01 10:53:03
# 1.背景
在上篇中实现了分类信息音乐列表的实现,包括页面和设计与实现,在此实现数据的请求和解决一个问题!
1)网络数据请求
2)优化点击事件
3)一个bug修复
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764f212c4.jpg)
# 2.网络数据请求
里面的数据还是接口上提供的数据,所以在之前我们已经使用过SDK的网络请求做了一些优化和总结,在此进行在此回顾下 :
# (1)图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d83595.jpg)
# (2)说明
官方的SDK提供了Request方法,所以我们需要AsyncHandler 回调函数,我在这里进行了在此的封装,使得AsyncHandler函数可以进行JSON数据解析操作,返回我们想要的数据。在这里只需要写一个自定义的回调函数实现就可以了。
# 3.优化点击事件
在此之间ListView,GridView , RecyclerView没有点击效果,我怎么感觉怎么别扭,后就统一的用了一个点击效。
本身是半透明的效果,点击后透明度为0.8 ,这个还不错。
* selector.xml *
~~~
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/app_color_zhu" android:state_pressed="true"></item>
<item android:drawable="@color/app_color_whrit"></item>
</selector>
~~~
*两个颜色值 :*
~~~
<color name="app_color_whrit">#55FCFCFC</color>
<color name="app_color_zhu">#88FCFCFC</color>
~~~
# 4.一个问题实现
在之前我们实现的时候,在加载MusicListActivity ,重新加载了数据,后重新启动了MusicService ,变重新赋值了List集合,然后退出点击下面热门榜单的音乐,播放的不是点击的音乐,而是刚刚打开MusicListActivity 的音乐。这是很简单的道理,因为在返回MainActivity 的时候,并没有重新给MusicService 赋值,所以问题来了?
解决:
* 一张图看清楚过程 :*
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764f7797f.jpg)
也就是说 在MusicListActivity 返回 MainActivity 的时候,我们需要重新的启动MusicService ,赋值给List ;
# 5.MusicListActivity 源码
~~~
package cn.labelnet.maskmusic;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import cn.labelnet.adapter.MusicTypeListAdapter;
import cn.labelnet.event.MusicListRecyclerOnItemClick;
import cn.labelnet.framework.MusicService;
import cn.labelnet.maskmusic.MainActivity.SwifRefushListener;
import cn.labelnet.model.MusicModel;
import cn.labelnet.net.MusicAsync;
import cn.labelnet.net.MusicAsyncHandler;
import cn.labelnet.net.MusicRequest;
import cn.labelnet.ui.SpacesItemDecoration;
public class MusicListActivity extends Activity implements MusicAsync,
MusicListRecyclerOnItemClick, OnClickListener {
/**
* 控件
*/
private TextView tv_list_type,tv_list_item_show;
private RecyclerView list_recycler_view;
private MusicTypeListAdapter musicTypeAdapter;
private ImageView tv_list_return;
private SparseArray<String> maps = new SparseArray<String>();
private final int REQUEST_CODE=123;
/**
* 网络数据
*/
private MusicRequest musicRequest = null;
private MusicAsyncHandler musicHandler = null;
/**
* 数据保留
*/
private List<MusicModel> musics = new ArrayList<MusicModel>();
private final String MUSIC_INTENT_KEY = "musics";
private final int MUSIC_INTENT_FLAG = 20001;
/**
* 和MusicService 通信
*/
private Intent musicIntent = null;
private final String MAIN_ACTIVIY_ACTION = "mainActivity.To.MusicService";
private final String MAIN_MUSIC_INTENT_KEY = "mIntent";
// 滚动条
private SwipeRefreshLayout swiperefresh_wei;
private int typeid = 5;
//
private Handler handler=new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music_list);
initView();
initData();
initFrameWork();
initTypes();
initRefresh();
typeid=getIntent().getIntExtra("musictype", 5);
// 进行数据请求
musicRequest.requestStringData(typeid);
tv_list_type.setText(maps.get(typeid));
// Log.d("MaskMusic", typeid + "typeid");
}
/**
* 初始化进度条
*/
private void initRefresh() {
swiperefresh_wei = (SwipeRefreshLayout) findViewById(R.id.swiperefresh_wei);
SwifRefushListener swifRefushListener = new SwifRefushListener();
swiperefresh_wei.setColorSchemeResources(R.color.app_color_zhu,
R.color.app_color_zi, R.color.text_color_black,
R.color.text_color_main);
swiperefresh_wei.setSize(SwipeRefreshLayout.LARGE);
// 设置进度条的位置
swiperefresh_wei.setProgressViewEndTarget(true, 800);
// 设置监听
swiperefresh_wei.setOnRefreshListener(swifRefushListener);
// 首次加载开启刷新
swiperefresh_wei.post(new Runnable() {
@Override
public void run() {
swiperefresh_wei.setRefreshing(true);
}
});
swifRefushListener.onRefresh();
}
/**
* 初始化分类信息
*/
private void initTypes() {
maps.put(18, getString(R.string.music_fenlei_mingyao));
maps.put(23, getString(R.string.msuic_fenlei_xiaoliang));
maps.put(5,getString(R.string.music_fenlei_china));
maps.put(3,getString(R.string.music_fenlei_oumei));
maps.put(6,getString(R.string.music_fenlei_hangkang));
maps.put(16,getString(R.string.music_fenlei_hanguo));
maps.put(17,getString(R.string.music_fenlei_riben));
maps.put(19,getString(R.string.music_fenlei_yaogun));
maps.put(26,getString(R.string.music_list_resou));
}
/**
* 初始化组件
*/
private void initFrameWork() {
// 初始化MusicService 广播组件Intent
musicIntent = new Intent();
// 设置识别Action
musicIntent.setAction(MAIN_ACTIVIY_ACTION);
// 设置来源
musicIntent.addFlags(MUSIC_INTENT_FLAG);
}
/**
* 初始化网络请求数据
*/
private void initData() {
// 请求基本数据
musicHandler = new MusicAsyncHandler();
musicHandler.setMAsync(this);
musicRequest = new MusicRequest();
musicRequest.setMusicAsyncHandler(musicHandler);
}
/**
* 初始化界面
*/
private void initView() {
tv_list_item_show=(TextView) findViewById(R.id.tv_list_item_show);
tv_list_return = (ImageView) findViewById(R.id.tv_list_return);
tv_list_return.setOnClickListener(this);
tv_list_type = (TextView) findViewById(R.id.tv_list_type);
list_recycler_view = (RecyclerView) findViewById(R.id.list_recycler_view);
// 设置是否固定长度
list_recycler_view.setHasFixedSize(true);
// 添加样式
list_recycler_view.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
// 添加item动画
list_recycler_view.setItemAnimator(new DefaultItemAnimator());
// 添加item分割线
list_recycler_view.addItemDecoration(new SpacesItemDecoration(2));
// 添加适配器
musicTypeAdapter = new MusicTypeListAdapter(musics, this, this);
list_recycler_view.setAdapter(musicTypeAdapter);
}
@Override
public void onSuccess(List<MusicModel> mms) {
// Log.d("MaskMusic", "MMS : " + mms);
// 请求成功
synchronized (musics) {
musics.clear();
musics.addAll(mms);
musicTypeAdapter.notifyDataSetChanged();
}
Intent intent = new Intent(MusicListActivity.this, MusicService.class);
intent.putExtra(MUSIC_INTENT_KEY, (Serializable) musics);
intent.addFlags(MUSIC_INTENT_FLAG);
startService(intent);
hideProgress();
}
/**
* 隐藏进度条
* 隐藏提示
*/
private void hideProgress() {
swiperefresh_wei.setRefreshing(false);
tv_list_item_show.setVisibility(View.GONE);
}
@Override
public void onFail(String msg) {
// 请求失败
showToast(msg);
hideProgress();
}
/**
* Toast
*
* @param msg
*/
private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
/**
* 点击事件
*/
@Override
public void onRecyclerItemClick(int position) {
// Log.d("MaskMusic", "Position :"+position);
musicIntent.putExtra(MAIN_MUSIC_INTENT_KEY, position);
sendBroadcast(musicIntent);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_list_return:
startActivityForResult(new Intent(MusicListActivity.this,MainActivity.class),REQUEST_CODE);
finish();
break;
}
}
@Override
public void onBackPressed() {
startActivityForResult(new Intent(MusicListActivity.this,MainActivity.class),REQUEST_CODE);
finish();
}
/**
* 1.下拉刷新监听 下拉刷新
*/
class SwifRefushListener implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onRefresh() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
//隐藏进度条
hideProgress();
}
}, 3000);
}
}
}
~~~
# 6.总结
在这里使用了v4兼容包下的SwipeRefreshLayout 进度条,在这里就不说明了,即方便又简单的可以实现进度条功能;
Android实战 – 音心播放器 (MusicListActivity – 分类信息界面实现)
最后更新于:2022-04-01 10:53:01
### 1.背景
还记得主页上GridView的内容吗?对,是分类信息,在这里将要实现音乐分类的显示播放,在这里使用了v7兼容包下的RecyclerView实现,同时包含两个布局,一个有图的一个没图的!
如下图所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764e823fc.gif)
### 2.实现步骤
在这里我们的RecyclerView采用了混排的形式,就是根据不同的位置,设置不同的布局实现,我们在这里有两个布局一个有图的View和一个没图的View, 对应两个ViewHolder .
### (1)两个Item 布局实现
1)有图的Item Layout
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/app_color_whrit">
<ImageView
android:id="@+id/mlist_iv_music_songpic"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:scaleType="fitXY"
android:src="@drawable/moren_big" />
<TextView
android:id="@+id/mlist_tv_music_songname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="18dp"
android:layout_toRightOf="@+id/mlist_iv_music_songpic"
android:maxLines="2"
android:text="@string/list_item_song_name"
android:textColor="@color/text_color_whrit"
android:textSize="15sp" />
<TextView
android:id="@+id/mlist_tv_music_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:text="@string/list_item_singer_name"
android:textColor="@color/app_color_zi"
android:textSize="12sp" />
</RelativeLayout>
~~~
2)没图的Item Layout 实现
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/app_color_whrit"
android:layout_height="70dp" >
<TextView
android:id="@+id/list_item_play"
android:layout_width="30dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_margin="10dp"
android:gravity="center"
android:textColor="@color/app_color"
android:text="@string/main_item_num" />
<TextView
android:id="@+id/tv_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_toRightOf="@+id/list_item_play"
android:maxLines="2"
android:text="@string/list_item_song_name"
android:textColor="@color/text_color_whrit"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_item_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:layout_toRightOf="@+id/list_item_play"
android:text="@string/list_item_singer_name"
android:textColor="@color/app_color_zi"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_item_heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:drawablePadding="3dp"
android:drawableRight="@drawable/heart_net"
android:text="@string/list_item_singer_name"
android:textColor="@color/text_color_whrit"
android:textSize="12sp" />
</RelativeLayout>
~~~
### (2)ViewHolder实现
1)有图的ViewHolder实现
~~~
package cn.labelnet.ui;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import cn.labelnet.event.MusicListRecyclerOnItemClick;
import cn.labelnet.maskmusic.R;
/**
* 有图 ViewHolder 作者 :原明卓 时间 :2015年12月11日 上午10:19:05 描述 :TODO
*/
public class MusicListBigViewHolder extends ViewHolder implements
OnClickListener {
private ImageView mlist_iv_music_songpic;
private TextView mlist_tv_music_songname, mlist_tv_music_singer;
private MusicListRecyclerOnItemClick itemClick;
public ImageView getMlist_iv_music_songpic() {
return mlist_iv_music_songpic;
}
public TextView getMlist_tv_music_songname() {
return mlist_tv_music_songname;
}
public TextView getMlist_tv_music_singer() {
return mlist_tv_music_singer;
}
public MusicListBigViewHolder(View itemView,MusicListRecyclerOnItemClick itemClick) {
super(itemView);
this.itemClick=itemClick;
initView(itemView);
}
private void initView(View itemView) {
mlist_iv_music_songpic = (ImageView) itemView
.findViewById(R.id.mlist_iv_music_songpic);
mlist_tv_music_songname = (TextView) itemView
.findViewById(R.id.mlist_tv_music_songname);
mlist_tv_music_singer = (TextView) itemView
.findViewById(R.id.mlist_tv_music_singer);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
itemClick.onRecyclerItemClick(getPosition());
}
}
~~~
2)没图的ViewHolder实现
~~~
package cn.labelnet.ui;
import cn.labelnet.event.MusicListRecyclerOnItemClick;
import cn.labelnet.maskmusic.R;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
/**
* 无图 ViewHolder
* 作者 :原明卓
* 时间 :2015年12月11日 上午10:20:25
* 描述 :TODO
*/
public class MusicListNoViewHolder extends ViewHolder implements OnClickListener {
private TextView list_item_play,tv_item_name,tv_item_singer,tv_item_heart;
private MusicListRecyclerOnItemClick itemClick;
public TextView getList_item_play() {
return list_item_play;
}
public TextView getTv_item_name() {
return tv_item_name;
}
public TextView getTv_item_singer() {
return tv_item_singer;
}
public MusicListNoViewHolder(View itemView,MusicListRecyclerOnItemClick itemClick) {
super(itemView);
this.itemClick=itemClick;
initView(itemView);
}
private void initView(View itemView) {
list_item_play=(TextView) itemView.findViewById(R.id.list_item_play);
tv_item_name=(TextView) itemView.findViewById(R.id.tv_item_name);
tv_item_singer=(TextView) itemView.findViewById(R.id.tv_item_singer);
tv_item_heart=(TextView) itemView.findViewById(R.id.tv_item_heart);
itemView.setOnClickListener(this);
}
public TextView getTv_item_heart() {
return tv_item_heart;
}
@Override
public void onClick(View v) {
itemClick.onRecyclerItemClick(getPosition());
}
}
~~~
### (3)Recycler.Adapter实现
getItemViewType() 方法 ,返回不同的结果,用来区分不同的item view;
onCreateViewHolder 创建ViewHolder返回子类;
onBindViewHolder 给ViewHolder 绑定数据;
~~~
package cn.labelnet.adapter;
import java.util.List;
import java.util.Random;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import cn.labelnet.event.MusicListRecyclerOnItemClick;
import cn.labelnet.maskmusic.R;
import cn.labelnet.model.MusicModel;
import cn.labelnet.net.MusicAsyncGetUrl;
import cn.labelnet.net.MusicAsyncHandlerGetUrl;
import cn.labelnet.net.MusicRequest;
import cn.labelnet.net.VolleyHttpRequest;
import cn.labelnet.ui.MusicListBigViewHolder;
import cn.labelnet.ui.MusicListNoViewHolder;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageListener;
/**
* MusicListActivity 适配器 作者 :原明卓 时间 :2015年12月11日 上午10:33:42 描述 :TODO
*/
public class MusicTypeListAdapter extends RecyclerView.Adapter<ViewHolder>
implements MusicAsyncGetUrl {
private List<MusicModel> musics = null;
private Random random = new Random();
private int r = 7;
private Context context;
private MusicModel mm = null;
// 布局控制
private MusicListBigViewHolder bigViewHolder = null;
private MusicListNoViewHolder listNoViewHolder = null;
private MusicListRecyclerOnItemClick itemClick;
// 图片请求
private MusicAsyncHandlerGetUrl musicAsyncHandlerGetUrl;
private ImageListener imageListener = null;
private MusicRequest musicRequest = null;
private ImageView iv_list;
//
private String urlStr = null;
public MusicTypeListAdapter(List<MusicModel> musics, Context context,
MusicListRecyclerOnItemClick itemClick) {
this.musics = musics;
this.context = context;
this.itemClick = itemClick;
// 请求图片资源
musicAsyncHandlerGetUrl = new MusicAsyncHandlerGetUrl();
musicAsyncHandlerGetUrl.setMusicasyncGetUrl(this);
musicRequest = new MusicRequest();
musicRequest.setMusicAsyncHandler(musicAsyncHandlerGetUrl);
}
@Override
public int getItemViewType(int position) {
return position == 0 ? 0 : 1;
}
@Override
public int getItemCount() {
return musics.size() > 0 ? musics.size() : 0;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup vg, int flag) {
ViewHolder holder = null;
if (flag == 0) {
// 有图的布局
View itemView = LayoutInflater.from(context).inflate(
R.layout.list_item_music_layout, vg, false);
holder = new MusicListBigViewHolder(itemView, itemClick);
} else {
// 没图的布局
View itemView = LayoutInflater.from(context).inflate(
R.layout.list_item_music_no_layout, vg, false);
holder = new MusicListNoViewHolder(itemView, itemClick);
}
return holder;
}
@Override
public void onBindViewHolder(ViewHolder vh, int position) {
mm = musics.get(position);
if (mm != null) {
// 不建议,待优化
if (vh instanceof MusicListNoViewHolder) {
// 无图适配
listNoViewHolder = (MusicListNoViewHolder) vh;
// Log.d("MaskMusic", "无图适配");
listNoViewHolder.getList_item_play().setText("" + position);
listNoViewHolder.getTv_item_singer()
.setText(mm.getSingername());
listNoViewHolder.getTv_item_name().setText(mm.getSongname());
int heartNum = (mm.getSongid() + mm.getSingerid()) / 134;
listNoViewHolder.getTv_item_heart().setText("" + heartNum);
} else {
// 有图适配
bigViewHolder = (MusicListBigViewHolder) vh;
bigViewHolder.getMlist_tv_music_singer().setText(
mm.getSingername());
bigViewHolder.getMlist_tv_music_songname().setText(
mm.getSongname());
if (urlStr == null) {
iv_list = bigViewHolder.getMlist_iv_music_songpic();
imageListener = ImageLoader.getImageListener(iv_list,
R.drawable.moren, R.drawable.moren_big);
musicRequest.setMusicAsyncHandler(musicAsyncHandlerGetUrl);
musicRequest.requestStringData(mm.getSingername() + "");
}
}
}
}
@Override
public void getSongImageURL(String imgUrl) {
urlStr = imgUrl;
VolleyHttpRequest.Image_Loader(imgUrl, imageListener);
}
}
~~~
### 3. 使用
### (1)绑定 RecyclerView
~~~
list_recycler_view = (RecyclerView) findViewById(R.id.list_recycler_view);
// 设置是否固定长度
list_recycler_view.setHasFixedSize(true);
// 添加样式
list_recycler_view.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
// 添加item动画
list_recycler_view.setItemAnimator(new DefaultItemAnimator());
// 添加item分割线
list_recycler_view.addItemDecoration(new SpacesItemDecoration(2));
// 添加适配器
musicTypeAdapter = new MusicTypeListAdapter(musics, this, this);
list_recycler_view.setAdapter(musicTypeAdapter);
~~~
### (2)点击事件回调
一个接口实现 :
~~~
/**
* MusicListActivity RecycleView点击事件
* 作者 :原明卓
* 时间 :2015年12月11日 下午5:13:55
* 描述 :TODO
*/
public interface MusicListRecyclerOnItemClick {
void onRecyclerItemClick(int position);
}
~~~
具体的步骤是 :
在ViewHolder 中实现OnClickListener 点击事件,在MusicListActivity 中进行回调使用,前面已经有解释了。
### 4.总结
下篇讲实现网络请求数据和实现音乐播放
Android实战 – 音心播放器 (MusicActivity ,MusicNotification,MusicService总结)
最后更新于:2022-04-01 10:52:58
1.背景
在MusicActivity ,MusicService 和MusicNotification 中,BroadcastReceiver 起着至关重要的作用。BroadcastReceiver 存在于 MusicActivity 和 MusicService 中 ,是两者沟通的桥梁,是整个App的核心功能。MusicSerice 有着控制音乐,后台播放的作用,MusicActivity 有着前台显示当前歌曲的信息和当前的歌词信息等,同时还显示当前的进度,剩余时间等。而MusicNotification ,在应用在后台运行的时候,可以显示当前歌曲信息。三者之间的通信,以MusicService为核心,衔接着三者之间的通信。
2.BoradCastReceiver功能实现回顾
(1)图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c432dd.jpg)
(2)实现步骤:
1)首先在实现BroadCastReceiver接口,实现OnReceiver() 方法
2)其次创建 BoradCastReceiver 对象,并添加IntentFilier的Action xxx
3)在者注册 BroadCastReceiver, registerReceiver ()
4)不使用的时候,解除注册 unRegisterReceiver()
(3)使用步骤
1)创建Intent 对象
2)添加BoradCastReceiver 中 IntentFiliter 的 Actionxxx
3)给intent对象 put参数
4)sendBroadCast(intent)
3.MusicService 和 MusicNotification 回顾
(1)图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c5c679.jpg)
(2)说明
MusicNotification 的初始化 ,更新, 修改 均在MusicService 中实现,包括MusicActivity 更改歌曲信息,更新到通知栏,均在MusicService中调用实现。而MusicNotification 对 音乐的控制 通过 BroadcastReceiver实现,具体过程见 :
# [Android实战 - 音心播放器 (通知实现音乐的播放/暂停/下一曲控制)](http://blog.csdn.net/lablenet/article/details/50323249)
#
4.MusicActivity , MusicNotification ,MusicService
(1)图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764dba587.jpg)
(2)说明
[点击查看原](http://img.blog.csdn.net/20151216151717257)图
1) 基本过程:当点击播放的时候,发送广播到MusicService ,MusicSrvice接受广播取得参数,对其进行控 制, 1)播放当前音乐 ;2)更新通知栏;3)发送广播给MusicActivity,更新MusicActivity界面状态;
2)Notification 控制播放 (实际上和上面一样)
基本过程:当点击播放的时候,发送广播到MusicService ,MusicSrvice接受广播取得参数,对其进行控 制, 1)播放当前音乐 ;2)更新通知栏;3)发送广播给MusicActivity,更新MusicActivity界面状态;
5.总结
在学习使用过程中,其实BoradCastReceiver 是可以静态实现,即在 AndroidManfest.xml 中进行配置的,这个使用了动态注册的方式实现。在这里理解清楚 整个控制流程尤为重要,在自己做的过程中,没有去想,而直接去做了,实在不应该。以后要多用图来理解每个过程。
Android实战 – 音心播放器 (MusicActivity – 倒计时 ,进度条实现)
最后更新于:2022-04-01 10:52:56
# 1.背景
还是音乐播放界面,实现倒计时和进度条功能,基本实现过程: 当打开MusicActivity 的时候,MusicService会发送广播给MusicActivity ,后开始当前播放的时间进度,从而实现倒计时和进度条;
这里说明下 进度条是 从小到大 ,倒计时是 从大到小 ;
效果展示 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764e0a91b.jpg)
# 2.倒计时实现
实现通过CountDownTimer实现,提供了start()和cancel() 两个方法,可以开始倒计时和取消倒计时,但是,(Android5.0以下)不可以停止,这是非常不给力的;
### (1)解决方法1
在使用的时候,每次更新,将CountDownTimer 对象先调用cancel()方法,后进行销毁(赋值为null),重新创建和初始化时间,并start();
### (2)解决办法2
在网上查阅资料后,有人提供了android5.0的CountDownTimmer 源码,使用这个可以cancel(); 但是我没有成功;
源码分享:
~~~
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.labelnet.ui;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
/**
* Schedule a countdown until a time in the future, with regular notifications
* on intervals along the way.
*
* Example of showing a 30 second countdown in a text field:
*
* <pre class="prettyprint">
* new CountdownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
* }
*
* public void onFinish() {
* mTextField.setText("done!");
* }
* }.start();
* </pre>
*
* The calls to {@link #onTick(long)} are synchronized to this object so that
* one call to {@link #onTick(long)} won't ever occur before the previous
* callback is complete. This is only relevant when the implementation of
* {@link #onTick(long)} takes an amount of time to execute that is significant
* compared to the countdown interval.
*/
public abstract class CountDownTimer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
private long mStopTimeInFuture;
private boolean mCancelled = false;
/**
* @param millisInFuture
* The number of millis in the future from the call to
* {@link #start()} until the countdown is done and
* {@link #onFinish()} is called.
* @param countDownInterval
* The interval along the way to receive {@link #onTick(long)}
* callbacks.
*/
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
/**
* Cancel the countdown.
*
* Do not call it from inside CountDownTimer threads
*/
public final void cancel() {
mHandler.removeMessages(MSG);
mCancelled = true;
}
/**
* Start the countdown.
*/
public synchronized final CountDownTimer start() {
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
mCancelled = false;
return this;
}
/**
* Callback fired on regular interval.
*
* @param millisUntilFinished
* The amount of time until finished.
*/
public abstract void onTick(long millisUntilFinished);
/**
* Callback fired when the time is up.
*/
public abstract void onFinish();
private static final int MSG = 1;
// handles counting down
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
final long millisLeft = mStopTimeInFuture
- SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval
- SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0)
delay += mCountdownInterval;
if (!mCancelled) {
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
}
};
}
~~~
### (3)倒计时实现
作用:
1)倒计时时间显示
2)进度条实时更新显示
3)歌词进度显示
4)播放结束 :进度条设置为0,时间设置为总时长,回调LrcView播放结束,进行歌词初始化,显示第一行。
~~~
/**
* 倒计时
*/
private class CountDownTime extends CountDownTimer {
private double second = 0;
public CountDownTime(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
//倒计时显示操作
second = millisUntilFinished / 1000;
tv_time_sheng.setText(TimeUtil.getMinuteBySecond((int) second));
// 进度条实现更新操作
second = (allSecond - second) / allSecond * 100;
//
progressbar_music.setProgress((int) second);
// 歌词更新操作
second = allSecond * 1000 - millisUntilFinished;
// Log.d("MaskMusic", "geci : "+(long)second);
lrc.updateTime((long) second);
// lrcplaytoend.playToPause((long)
// (allSecond*1000-millisUntilFinished));
}
@Override
public void onFinish() {
// showToast("MusicActivity 播放完毕");
lrc.destroyDrawingCache();
// 播放完毕显示歌词
// showLrc();
// 播放完毕需要进行 ,初始化界面 1.进度条初始值,2.歌词回归到第一行 3.时间恢复到总时间
// 播放中 ,暂停恢复 : 1.进度条进度保持 2.歌词保持位置 3.时间保持(可以从MusicService获取)
progressbar_music.setProgress(0);
tv_time_sheng.setText(TimeUtil.getMinuteBySecond((int) allSecond));
allSecond = 0;
lrcplaytoend.playToEnd();
}
}
~~~
### (4)一个单独的方法来初始化倒计时
~~~
private void CountDown(int allTime) {
countDown = new CountDownTime(allTime, COUNT_DOWN_INTERVAL);
}
~~~
### (5)初始化倒计时所需要的判断
410001不需要看,里面的countDown是CountDownTimmer的对象,清楚该对象,重新重建倒计时,这是这里面使用的;
~~~
if (code == 41001) {
// 初始化 时间
if (countDown != null) {
countDown.cancel();
countDown = null;
}
CountDown(mm.getSeconds() * 1000);
} else {
// 销毁上一个对象
if (countDown != null) {
countDown.cancel();
countDown = null;
}
// 倒计时同步
currentTime = intent.getIntExtra(
MUSIC_SERVICE_TO_ACTIVITY_NOWTIME, 0);
CountDown(currentTime);
}
~~~
### (6)将秒转化为分
~~~
/**
* 1.秒转分
*/
public static String getMinuteBySecond(int seconds) {
StringBuffer buffer = new StringBuffer();
int second = seconds % 60;
int minute = seconds / 60;
if (minute <= 9) {
buffer.append("0" + minute + ":");
} else {
buffer.append(minute + ":");
}
if (second <= 9) {
buffer.append("0" + second);
} else {
buffer.append(second);
}
return buffer.toString();
}
~~~
# 3.进度条实现
### (1)布局实现
直接使用的是系统的ProgressBar , 不是很漂亮但很是可以使用的;
~~~
<ProgressBar
android:id="@+id/progressbar_music"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_time_sheng"
android:layout_centerInParent="true"
android:max="100"
android:progress="50" />
~~~
### (2)控制实现
思路是倒计时在执行的时候,总时间减去当前的时间,后将时间换位百分制来使用,实时更新,如倒计时实现;
# 4.总结
倒计时和进度条的实现是相对简单的,主要是倒计时的实现,它决定了歌词显示,进度条显示,倒计时显示,三个主要的模块;在使用过程中遇到的问题就是上面的无法cancel() 。当然倒计时也可以自己去封装一个类使用,这里就不实现了。
Android实战 – 音心播放器(MusicActivity – 歌词实现)
最后更新于:2022-04-01 10:52:54
### 1.背景
歌词是音乐软件必备的,没有它的存在就感觉少点什么,故实现了歌曲歌词的显示,使用LrcView实现,当然是在GitHub上找到的,是一个自定义View :
LrcView 地址 : [https://github.com/ChanWong21/LrcView](https://github.com/ChanWong21/LrcView)
效果预览 :
现在说说我使用过程中对它的不足之处做一下总结:
(1)只能加载本地asserts文件夹中的lrc文件,不能请求网络上的歌词;
(2)不能设置当前播放到得时间,也就不能显示当前时间的歌词,只可以顺序播放;
(3)当播放完毕后,重新播放一直停留在最后(因为时间是最后,永远大于当前播放的时间);
(4)没事回调事件,无法判断有没有歌词存在;
下面我将一一解决;
# 2.歌词LrcView实现
### (1)实现 attr.xml
~~~
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LrcView">
<attr name="textSize" format="dimension" />
<attr name="dividerHeight" format="dimension" />
<attr name="normalTextColor" format="reference|color" />
<attr name="currentTextColor" format="reference|color" />
<attr name="animationDuration" format="integer" />
</declare-styleable>
</resources>
~~~
### (2)LrcView实现
歌词的加载也是使用了上篇中的网络加载模块,可以轻松的实现数据请求;
[Android实战 - 音心播放器 (MusicActivity-音乐播放页面界面实现,网络模块实现)](http://blog.csdn.net/lablenet/article/details/50324913)
几个重要的方法 说明:
onDraw() : 绘制当前显示的歌词;
updateTime() : 外部调用,切换歌词;
parseLine() : 解析歌词的每一行;
lrcViewToMusicActivity 对象 : 回调事件,MusicActivity 实现该接口;
LrcPlayToEnd : LrcView实现该接口,为了使得MusicActivity告诉LrcView,播放完毕,重新初始化LrcView,其实就是讲当前时间改为0,非最大值;
~~~
package cn.labelnet.ui;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import cn.labelnet.event.LrcPlayToEnd;
import cn.labelnet.event.LrcViewToMusicActivity;
import cn.labelnet.maskmusic.R;
import cn.labelnet.net.MusicAsyncGetUrl;
import cn.labelnet.net.MusicAsyncHandlerGetLrc;
import cn.labelnet.net.MusicRequest;
/**
* LrcView
*/
public class LrcView extends View implements MusicAsyncGetUrl, LrcPlayToEnd {
private static final String TAG = LrcView.class.getSimpleName();
private static final int MSG_NEW_LINE = 0;
private List<Long> mLrcTimes;
private List<String> mLrcTexts;
private LrcHandler mHandler;
private Paint mNormalPaint;
private Paint mCurrentPaint;
private float mTextSize;
private float mDividerHeight;
private long mAnimationDuration;
private long mNextTime = 0l;
private int mCurrentLine = 0;
private float mAnimOffset;
private boolean mIsEnd = false;
// 网络
private MusicAsyncHandlerGetLrc musicAsyncHandlerGetLrc;
private MusicRequest musicRequest;
// 回调事件
private LrcViewToMusicActivity lrcViewToMusicActivity;
private boolean isLrc = false;
public void setLrcViewToMusicActivity(
LrcViewToMusicActivity lrcViewToMusicActivity) {
this.lrcViewToMusicActivity = lrcViewToMusicActivity;
}
private String songId = 001 + "";
public LrcView(Context context) {
this(context, null);
}
public LrcView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
/**
* 初始化
*
* @param attrs
* attrs
*/
private void init(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs,
R.styleable.LrcView);
mTextSize = ta.getDimension(R.styleable.LrcView_textSize, 48.0f);
mDividerHeight = ta.getDimension(R.styleable.LrcView_dividerHeight,
72.0f);
mAnimationDuration = ta.getInt(R.styleable.LrcView_animationDuration,
1000);
mAnimationDuration = mAnimationDuration < 0 ? 1000 : mAnimationDuration;
// int normalColor = ta.getColor(R.color.app_color_whrit,
// 0xffffffff);
// int currentColor = ta.getColor(R.color.app_color,
// 0xffff4081);
ta.recycle();
mLrcTimes = new ArrayList<Long>();
mLrcTexts = new ArrayList<String>();
WeakReference<LrcView> lrcViewRef = new WeakReference<LrcView>(this);
mHandler = new LrcHandler(lrcViewRef);
mNormalPaint = new Paint();
mCurrentPaint = new Paint();
mNormalPaint.setColor(Color.WHITE);
mNormalPaint.setTextSize(mTextSize);
mCurrentPaint.setColor(Color.RED);
mCurrentPaint.setTextSize(mTextSize);
// 设置网络监听
musicAsyncHandlerGetLrc = new MusicAsyncHandlerGetLrc();
musicAsyncHandlerGetLrc.setMusicasyncGetUrl(this);
musicRequest = new MusicRequest();
musicRequest.setMusicAsyncHandler(musicAsyncHandlerGetLrc);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mLrcTimes.isEmpty() || mLrcTexts.isEmpty()) {
return;
}
// 中心Y坐标
float centerY = getHeight() / 2 + mTextSize / 2 + mAnimOffset;
// 画当前行
String currStr = mLrcTexts.get(mCurrentLine);
float currX = (getWidth() - mCurrentPaint.measureText(currStr)) / 2;
canvas.drawText(currStr, currX, centerY, mCurrentPaint);
// 画当前行上面的
for (int i = mCurrentLine - 1; i >= 0; i--) {
String upStr = mLrcTexts.get(i);
float upX = (getWidth() - mNormalPaint.measureText(upStr)) / 2;
float upY = centerY - (mTextSize + mDividerHeight)
* (mCurrentLine - i);
canvas.drawText(upStr, upX, upY, mNormalPaint);
}
// 画当前行下面的
for (int i = mCurrentLine + 1; i < mLrcTimes.size(); i++) {
String downStr = mLrcTexts.get(i);
float downX = (getWidth() - mNormalPaint.measureText(downStr)) / 2;
float downY = centerY + (mTextSize + mDividerHeight)
* (i - mCurrentLine);
canvas.drawText(downStr, downX, downY, mNormalPaint);
}
}
/**
* 加载歌词文件
*
* @param lrcName
* assets下的歌词文件名
* @throws Exception
*/
public void loadLrc(String lrcName) throws Exception {
mLrcTexts.clear();
mLrcTimes.clear();
BufferedReader br = new BufferedReader(new InputStreamReader(
getResources().getAssets().open(lrcName)));
String line;
while ((line = br.readLine()) != null) {
String[] arr = parseLine(line);
if (arr != null) {
mLrcTimes.add(Long.parseLong(arr[0]));
mLrcTexts.add(arr[1]);
}
}
br.close();
}
/**
* 加载歌词文件
*
* @param isr
* @throws Exception
*/
public void loadLrcByUrl(String songid) throws Exception {
if (!songId.equals(songid)) {
mLrcTexts.clear();
mLrcTimes.clear();
mNextTime = 0;
mCurrentLine = 0;
mIsEnd = false;
musicRequest.requestStringLrcData(songid);
this.songId = songid;
}
}
/**
* 更新进度
*
* @param time
* 当前时间
*/
public synchronized void updateTime(long time) {
// 避免重复绘制
if (time < mNextTime || mIsEnd) {
return;
}
for (int i = 0; i < mLrcTimes.size(); i++) {
if (mLrcTimes.get(i) > time) {
Log.i(TAG, "newline ...");
mNextTime = mLrcTimes.get(i);
mCurrentLine = i < 1 ? 0 : i - 1;
// 属性动画只能在主线程使用,因此用Handler转发操作
mHandler.sendEmptyMessage(MSG_NEW_LINE);
break;
} else if (i == mLrcTimes.size() - 1) {
// 最后一行
Log.i(TAG, "end ...");
mCurrentLine = mLrcTimes.size() - 1;
mIsEnd = true;
// 属性动画只能在主线程使用,因此用Handler转发操作
mHandler.sendEmptyMessage(MSG_NEW_LINE);
break;
}
}
}
/**
* 解析一行
*
* @param line
* [00:10.61]走过了人来人往
* @return {10610, 走过了人来人往}
*/
private String[] parseLine(String line) {
Matcher matcher = Pattern.compile("\\[(\\d)+:(\\d)+(\\.)(\\d+)\\].+")
.matcher(line);
if (!matcher.matches()) {
Log.e(TAG, line);
return null;
}
line = line.replaceAll("\\[", "");
String[] result = line.split("\\]");
result[0] = parseTime(result[0]);
return result;
}
/**
* 解析时间
*
* @param time
* 00:10.61
* @return long
*/
private String parseTime(String time) {
time = time.replaceAll(":", "\\.");
String[] times = time.split("\\.");
long l = 0l;
try {
long min = Long.parseLong(times[0]);
long sec = Long.parseLong(times[1]);
long mil = Long.parseLong(times[2]);
l = min * 60 * 1000 + sec * 1000 + mil * 10;
} catch (NumberFormatException e) {
e.printStackTrace();
}
return String.valueOf(l);
}
/**
* 换行动画 Note:属性动画只能在主线程使用
*/
private void newLineAnim() {
ValueAnimator animator = ValueAnimator.ofFloat(mTextSize
+ mDividerHeight, 0.0f);
animator.setDuration(mAnimationDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimOffset = (Float) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
private static class LrcHandler extends Handler {
private WeakReference<LrcView> mLrcViewRef;
public LrcHandler(WeakReference<LrcView> lrcViewRef) {
mLrcViewRef = lrcViewRef;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_NEW_LINE:
LrcView lrcView = mLrcViewRef.get();
if (lrcView != null) {
lrcView.newLineAnim();
}
break;
}
super.handleMessage(msg);
}
}
@Override
public void getSongImageURL(String songLrc) {
// 网络请求成功歌词
// Log.d("MaskMusic", songLrc);
parseSongLrc(songLrc);
}
private void parseSongLrc(String songLrc) {
Log.d("MaskMusic", songLrc);
String[] strs = songLrc.split("\\[");
for (String line : strs) {
line = ("[" + line).replace(":", ":").replace(".", ".")
.replace("
", "").replace(" ", " ")
.replace("-", "-").replace("(", "")
.replace(")", "").replace("&", "")
.replace(";", "").replace("'", "").replace("","");
String[] arr = parseLine(line);
if (arr != null) {
mLrcTimes.add(Long.parseLong(arr[0]));
mLrcTexts.add(arr[1]);
}
// Log.d("MaskMusic", line);
}
// 回调判断有没有歌词
if (mLrcTexts.size() > 0 && mLrcTimes.size() > 0) {
isLrc = true;
}
lrcViewToMusicActivity.LrcViewIsLrc(isLrc);
}
@Override
public void playToEnd() {
// 播放完毕,进行初始化
// Log.d("MaskMusic", "playToEnd : 播放完毕");
mNextTime = 0;
mCurrentLine = 0;
mIsEnd = false;
updateTime(mNextTime);
}
@Override
public void playToPause(final long mt) {
Log.d("MaskMusic", "mNextTime CurrentTime : " + mt);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("执行了");
if (mLrcTexts.size() > 0 && mLrcTimes.size() > 0) {
for (int i = 0; i < mLrcTimes.size() - 1; i++) {
if (mt >= mLrcTimes.get(i)
&& mt <= mLrcTimes.get(i + 1)) {
Log.d("MaskMusic", mt + " 毫秒的歌词为 "
+ mLrcTexts.get(i));
mNextTime = mLrcTimes.get(i);
mCurrentLine = i;
updateTime(mNextTime);
}
}
}else{
lrcViewToMusicActivity.LrcViewIsLrc(false);
}
Log.d("MaskMusic", "playToPause over");
}
}, 2000);
// 遇到问题 ,从MusicService 的 时间,很难与 集合中的时间匹配成功!
}
}
~~~
### (3)回调事件1 (LrcView-MusicActivity)
作用是给MusicActivity 回调,判断是否有歌词
~~~
public interface LrcViewToMusicActivity {
/**
* LrcView的自定义事件,给
*/
/**
*
* 1.判断是否有歌词
* 2.在进行初始化成功后,2s之内没有加载到歌词就显示提示
* @param isLrc,是否有歌词
*/
void LrcViewIsLrc(boolean isLrc);
}
~~~
### (4)回调事件2(MusicActivity - LrcView)
~~~
<pre name="code" class="java">/**
* 接口实现意图 :LrcView实现此接口,后在MusciActivity中,使用其接口,将调用LrcView中实现的playToEnd()方法,
* 进行歌词初始化操作
*/
public interface LrcPlayToEnd {
/**
* 播放到最后,回调初始化 歌词显示
*/
void playToEnd();
/**
* 暂停后,初始化节面时,将歌词设置到当前时间位置
*/
void playToPause(long mNextTime);
}
~~~
~~~
~~~
# 3.Activity与LrcView控制实现
### (1)一张图看明白
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764dde417.jpg)
### (2)LrcView - MusicActivity
回调事件,一个接口作为目标的属性,使用者实现这个接口,来使用,我们在前面已经使用了很多次,比如Fragment - > MainActivity 通信过程 等;
在这里,接口 LrcViewToMusicActivity ,作为LrcView的属性,进行回调出是否有歌词;在MusicActivity中实现该接口,使用有没有歌词;在初始化LrcView的时候,setLrcViewToMusicActivity(this)就可以实现;
### (3)MusicActivity -> LrcView
作用 : 判断歌曲有没接触,当结束后出发,LrcView进行初始化操作;
实现过程:
1)使得LrcView实现LrcViewToEnd 接口;
2)在MusicActivity中使用LrcViewtoEnd ,作为属性使用,初始化的时候直接将
~~~
lrc = (LrcView) findViewById(R.id.lrc);
//初始化接口,多态实现
LrcViewToEnd lrcplaytoend = lrc;
~~~
# 4.总结
在使用过程中,最纠结的就是当音乐播放完毕的时候进行初始化歌词了,当初使用了很多方法,比如观察者模式,应该也是可以解决的,但无意当中,想到了这个简单的方法,可以实现这个功能,得益于面向对象的多态的再次学习;所以基础不能忘记,要时常学习,则会事半功倍。
Android实战 – 音心播放器 (MusciActivity-专辑图片获得,基本控制实现)
最后更新于:2022-04-01 10:52:52
### 1.背景
上篇已经实现通过使用官方SDK进行基本数据请求再次封装的实现,这篇将实现对音乐的控制 播放、暂停、上一曲、下一曲的控制。
播放页面控制歌曲 - > 通知栏做出更改;通知栏控制歌曲-> 播放页面做出更改;
### 2.获得专辑图片思路总结
如果没有使用ShowApi上的接口就跳过这里吧;
(1)分析
在第一篇中我们已经知道歌曲列表中没有歌曲图片,但是提供的通过歌曲名或歌手名搜索歌曲接口,提供了歌手或者歌曲的图片,我们可以通过使用这个来进行使用获得当前播放歌曲的图片信息,从而给歌曲信息中加载出来。
(2)基本流程
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764da3c55.jpg)
(3)说明
通过上面的已经知道使用的是官方SDK提供的接口实现,基本步骤如上图所示,加载图片使用的是Volley网络请求框架,进行加载专辑图片。
[Android-Volley网络通信框架(二次封装数据请求和图片请求(包括处理请求队列和图片缓存))](http://blog.csdn.net/LABLENET/article/details/47859613)
### 3.基本控制
(1)先来个基本的控制图解
从图上可以看出三个MusicNotification , MusicService , MusicActivity 三个过程,以MusicService为核心,MusicNotification和MusicActivity 相互控制实现;
如下图所示 ([点击查看原图](http://img.blog.csdn.net/20151216151717257)):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764dba587.jpg)
(2)MusicActivity 播放与暂停
基本过程:当点击播放的时候,发送广播到MusicService ,MusicSrvice接受广播取得参数,对其进行控 制, 1)播放当前音乐 ;2)更新通知栏;3)发送广播给MusicActivity,更新MusicActivity界面状态;
(3)Notification 控制播放 (实际上和上面一样)
基本过程:当点击播放的时候,发送广播到MusicService ,MusicSrvice接受广播取得参数,对其进行控 制, 1)播放当前音乐 ;2)更新通知栏;3)发送广播给MusicActivity,更新MusicActivity界面状态;
### 4.过程实现
过程 : MusicActivity 初始化操作 、基本播放,暂停,下一曲,上一曲控制操作,均一样;
(1)OnStart() 是发送广播给MusicService
~~~
@Override
protected void onStart() {
initData();
try {
serviceIntent.putExtra(MUSIC_ACTIVITY_SERVICE_KEY, 40001);
sendBroadcast(serviceIntent);
} catch (Exception e) {
showToast("new Intent 异常 : " + e.getMessage());
}
super.onStart();
}
~~~
(2)MusicService 将 当前音乐数据信息,进度,发送给MusicActivity
~~~
/**
* 来自 MusicActivity 的控制
*
* @param musictype2
*/
private void musicActivityService(int musictype2) {
// showToast("musicActivityService 执行了 musictype2 :" + musictype2);
switch (musictype2) {
case 40001:
sendModelToMusicActivity();
break;
case 40002:
//播放与暂停
playSong();
break;
case 40003:
//下一曲
nextSong();
break;
case 40004:
//上一曲
preSong();
break;
}
}
~~~
~~~
/**
* 发送Model给MusicActivity
*/
private void sendModelToMusicActivity() {
if (mm != null) {
// 正在播放的歌曲实体
if(mm.getSeconds()==0){
mm.setSeconds(mp.getDuration()/1000);
}
mActivityIntent.putExtra(MUSIC_SERVICE_TOACTIVITY_CODE, 41002);
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_ISPLAY,
mp.isPlaying());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_NOWTIME,
mp.getDuration() - mp.getCurrentPosition());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_MODEL,
(Serializable) mm);
} else {
// 默认发送第一首歌信息
mm = musics.get(0);
mActivityIntent.putExtra(MUSIC_SERVICE_TOACTIVITY_CODE, 41001);
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_ISPLAY,
mp.isPlaying());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_MODEL,
(Serializable) mm);
}
sendBroadcast(mActivityIntent);
}
~~~
重点 :
在进行播放时,在MeidaPlayer中的OnPrepered()事件方法中进行发送 给MusicActivity 信息:
~~~
@Override
public void onPrepared(MediaPlayer mp) {
// 准备加载的时候
resume();
sendModelToMusicActivity();
}
~~~
(3)MusicActivityBoradCast进行接收信息并进行更新操作
~~~
@Override
public void onReceive(Context context, Intent intent) {
code = intent.getIntExtra(MUSIC_SERVICE_TOACTIVITY_CODE, 0);
if (code > 0) {
// MusicService 来的控制
musicServiceKong(code, intent);
}
Log.d("MaskMusic", "MusicActivity - code : " + code);
// 隐藏进度条
music_show_gone.setVisibility(View.GONE);
}
~~~
### 5.总结
在实现中,最纠结的就是在播放的时候,通知栏进行控制的时候,无法发送当前歌曲信息给MusicActivity, 后在onPrepered() 方法中尝试后,十分方便的使用;
下篇,歌词实现,滚动歌词;
Android实战 – 音心播放器 (MusicActivity-音乐播放页面界面实现)
最后更新于:2022-04-01 10:52:49
### 1.背景
音乐播放页面实现的功能有 :
(1)当前歌曲的信息
(2)歌词显示
(3)进度条显示当前进度
(4)倒计时
(5)返回 和(下载或分享)
(6)当前音乐的控制 :播放/暂停/下一曲/下一曲
如下图所示 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764cb5718.jpg)
这篇将实现整个界面的布局;
### 2.布局实现
(1)整个模糊背景实现
整个布局,设置了一张背景图片;
(2)当前歌曲信息
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d3b818.jpg)
最主要的是它的背景颜色,在color.xml 中配置 颜色为 :其中55表示透明度,半透明状态实现;
~~~
<color name="app_color_whrit">#55FCFCFC</color>
~~~
总布局为RelativeLayout :
~~~
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@color/app_color_whrit" >
<ImageView
android:id="@+id/iv_music_songpic"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:scaleType="fitXY"
android:src="@drawable/moren_big" />
<TextView
android:id="@+id/tv_music_songname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="18dp"
android:layout_toRightOf="@+id/iv_music_songpic"
android:maxLines="2"
android:text="@string/list_item_song_name"
android:textColor="@color/text_color_black"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_music_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:text="@string/list_item_singer_name"
android:textColor="@color/app_color_zi"
android:textSize="12sp" />
</RelativeLayout>
~~~
(3)歌词的实现是自定义View
在这里不分享歌词的自定义View ,歌词的显示 在后面将要实现,因为内容稍微有点多。
~~~
<cn.labelnet.ui.LrcView
android:id="@+id/lrc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="5dp"
android:text="@string/music_song_ci"
lrc:animationDuration="1000"
lrc:currentTextColor="?attr/currentTextColor"
lrc:dividerHeight="24dp"
lrc:normalTextColor="@android:color/white"
lrc:textSize="16sp" />
~~~
(4)控制实现
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d4ba5a.jpg)
基本控制的实现,图标在这里就不分享了,自己可以在网上找到。
~~~
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:padding="10dp" >
<ImageView
android:id="@+id/music_play"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:src="@drawable/pause" />
<ImageView
android:id="@+id/music_next"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/music_play"
android:src="@drawable/next" />
<ImageView
android:id="@+id/music_prev"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerInParent="true"
android:layout_marginRight="20dp"
android:layout_toLeftOf="@+id/music_play"
android:src="@drawable/prevers" />
</RelativeLayout>
~~~
(5)进度条/倒计时实现
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d5c417.jpg)
倒计时和进度条实现,因为要同步歌曲的进度,所以内容也将单独的写一篇文章,敬请期待;
~~~
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@color/app_color_whrit" >
<ProgressBar
android:id="@+id/progressbar_music"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_time_sheng"
android:layout_centerInParent="true"
android:max="100"
android:progress="50" />
<TextView
android:id="@+id/tv_time_sheng"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="@string/procress_1_23"
android:textColor="@color/app_color_zi" />
<TextView
android:id="@+id/tv_time_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="@string/procress_1_23"
android:textColor="@color/app_color_zi" />
</RelativeLayout>
~~~
### 3.歌曲信息适配实现
这里使用了广播(BoradCastReceiver )来实现,上面几篇中已经使用了,当MusicActivity启动的时候,发送广播到MusicService ,MusicService收到后,发送广播给MusicActivity ,进行初始化界面。这就是为什么前面进行开始就启动MusicService了吧。
基本过程图解 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d6bb04.jpg)
(1)初始化基本过程如上图所示,初始化流程。
(2)有一个问题,在第一篇中不是说了,音乐专辑中没有提供图片,所以我们需要自己动手去搜索里获得图片链接,在去请求图片;
### 4.SDK网络请求封装与图解
(1)首先实现NetRequest 接口,所有的showAPI SDK请求都来自它
~~~
public interface NetRequest {
/**
* 网络请求接口
*/
//请求字符串
String requestStringData();
void requestStringData(int topid);
void requestStringData(String keyword);
void requestStringLrcData(String songId);
}
~~~
(2)NetRequestImp中实现接口NetRequest ,但不写方法内容,提供空方法
~~~
public class NetRequestImp implements NetRequest {
/**
* 实现NetRequest接口,给想要实现方法的提供想要的方法
*
*/
@Override
public String requestStringData() {
return null;
}
@Override
public void requestStringData(int topid) {
// 这里给MusicRequest 提供此方法,在MusicRequest 中 重写这个方法即可
}
@Override
public void requestStringData(String keyword) {
}
@Override
public void requestStringLrcData(String songId) {
}
}
~~~
(3)实现MusicRequest类 继承自 NetRequestImp , 重写使用的方法, 实现里面的方法
~~~
/**
* 音乐请求工具类 ShowAPi 提供的方法
*
*/
public class MusicRequest extends NetRequestImp {
/**
* 音乐列表信息
*/
private final String HOT_MUSIC_URL = "http://route.showapi.com/213-4";
private final String SHOW_API_APPID = "xxxxx";
private final String SHOW_API_SECRET = "xxxxx";
/**
* 音乐id , 查询信息
*/
private final String SELECT_SONG_BY_KEYWORD_URL="http://route.showapi.com/213-1";
/**
* 歌词地址
*/
private final String GET_SONG_LRC_URL="http://route.showapi.com/213-2";
ShowApiRequest apiRequest = null;
// 提供异步回调方法
private AsyncHttpResponseHandler musicAsyncHandler;
public void setMusicAsyncHandler(AsyncHttpResponseHandler handler) {
this.musicAsyncHandler = handler;
}
/**
* 热门榜 : 数据请求
*/
@Override
public void requestStringData(int topid) {
new ShowApiRequest(HOT_MUSIC_URL, SHOW_API_APPID, SHOW_API_SECRET)
.setResponseHandler(musicAsyncHandler)
.addTextPara("topid", topid + "").post();
}
/**
* 关键字搜索
*/
@Override
public void requestStringData(String keyword) {
new ShowApiRequest(SELECT_SONG_BY_KEYWORD_URL, SHOW_API_APPID, SHOW_API_SECRET)
.setResponseHandler(musicAsyncHandler)
.addTextPara("keyword", keyword)
.addTextPara("page", "1")
.post();
}
/**
* 获取歌词
*/
@Override
public void requestStringLrcData(String songId) {
new ShowApiRequest(GET_SONG_LRC_URL ,SHOW_API_APPID,SHOW_API_SECRET)
.setResponseHandler(musicAsyncHandler)
.addTextPara("musicid",songId+"")
.post();
}
}
~~~
(4)需要实现AsyncHandler ,单独的实现该类,后使用回调函数,将值回调出来;
~~~
public class MusicAsyncHandler extends AsyncHttpResponseHandler {
private MusicAsync mAsync;
private List<MusicModel> mms = null;
public void setMAsync(MusicAsync mAsync) {
this.mAsync = mAsync;
mms = new ArrayList<MusicModel>();
}
@Override
public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) {
// 失败
mAsync.onFail(arg3.getMessage() + " : " );
}
@Override
public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {
// 成功
String msg = "no msg";
if (arg2.length > 0) {
msg = new String(arg2);
mms = StringUtil.getMusicList(msg);
if(mms!=null){
mAsync.onSuccess(mms);
}else{
msg="解析为null";
mAsync.onFail(msg);
}
} else {
mAsync.onFail(msg);
}
}
}
~~~
(5)回调接口 函数
~~~
public interface MusicAsync {
//成功
void onSuccess(List<MusicModel> mms);
//失败
void onFail(String msg);
}
~~~
(6)图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764d83595.jpg)
### 5.总结
播放页面的实现,与MusciSerivce 通信密切,同时还有Notification ,比如说 音乐暂停,播放页面暂停后,通知栏上也要暂停;若控制通知栏上播放,那么播放页面既要同步歌曲信息,也要保持进度进度一致性,时间一致性和状态一致性。
Android实战 – 音心播发器 (MusicService ,Notification, MainActivity 总结)
最后更新于:2022-04-01 10:52:47
## 1.背景
前面已经总结了MainActivity,MusicService ,MusicNotification的实现,在这里将总结MainActivity 与 MusciService ,MusicService 与 MusicNotification , 以及三者的关系;
## 2.MainActivity 与 MusicService
(1)过程图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c72f25.jpg)
(2)说明
1)当应用启动的时候,就初始化MusicService , 包括 初始化的音乐数据 ,通知,广播,MediaPlay ;
2)在主页中音乐信息展示的数据是在Fragment中,所以需要回调函数回调出点击的位置,从而播放当前点击的音乐;
3)在MainActivity播放当前点击的音乐,通过广播发送Intent,进行播放;
## 3.Notification与Serivice
(1)关系图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c5c679.jpg)
(2)说明
1)Notification在Service中初始化;
2)Notification 在音乐状态发生改变的时候,发生改变(Service调用更新);
3)Notification 在Service 关闭的时候,移除通知;
4)Service 在音乐状态发生改变的时候,更新Notification;
5)Service 正在播放的Music信息,会更新到通知栏;
6)Service与Notification 通信通过 BoradCastReceiver实现;
(3)BoradCastReceiver过程图
广播的使用,不仅仅是在Notification与MusicService ,还用在 MainActvity与MusicService ,当然后面还有使用的地方,尽情期待;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c432dd.jpg)
## 4.总结
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c97c57.jpg)
MainActivity,MusicService , Notification 的关系,通信以 BroadCastReceiver为媒介,来传递信息;MusicService作为播放器的心脏,BoradCastReceiver 在MusicService中以内部类的形式实现,并没有单独的写成一个类,这样的话,方便了音乐的控制,不然要写很多回调函数。
Android实战 – 音心播放器 (通知实现音乐的播放/暂停/下一曲控制)
最后更新于:2022-04-01 10:52:45
## 1.背景
**通知 -> Service :**
上一篇的MusicService 中提高了通知是Service的前台显示,这篇将介绍通知(MusicNotification).通知在这里有四个作用:
(1)显示当前音乐的信息
(2)播放/暂停音乐
(3)下一曲播放音乐
(4)关闭通知栏(实际上也是停止音乐播放并关闭Service)
**Service -> 通知 :**
通知和Service是紧密相连的,当Service结束的时候,取消通知;当Service启动的时候,初始化通知/创建通知;当音乐状态发生改变的时候,更新通知;如下图所示 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c1ff57.jpg)
## 2.通知实现
在这里单独的将通知单独的类中,方便使用;没有使用到通知的震动提醒/铃声提醒/闪光灯提醒,如果需要使用,可以看博客 :
[Android-Notification (通知实现)](http://blog.csdn.net/lablenet/article/details/47999961).
### (1)通知布局实现
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp" >
<ImageView
android:id="@+id/iv_notification_logo"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_margin="5dp"
android:src="@drawable/logo" />
<TextView
android:id="@+id/tv_nofitication_singname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_toRightOf="@+id/iv_notification_logo"
android:maxLines="1"
android:textSize="12sp"
android:text="@string/item_notification_singname"
android:textColor="@color/text_color_whrit" />
<TextView
android:id="@+id/tv_nofitication_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:textSize="10sp"
android:layout_toRightOf="@+id/iv_notification_logo"
android:text="@string/item_notifiaction_singer"
android:textColor="@color/bg_color" />
<ImageView
android:id="@+id/iv_nofitication_kzhi_play"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_toLeftOf="@+id/iv_nofitication_kzhi_next"
android:src="@android:drawable/ic_media_play" />
<ImageView
android:id="@+id/iv_nofitication_kzhi_next"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_toLeftOf="@+id/iv_nofitication_kzhi_colse"
android:src="@android:drawable/ic_media_next" />
<ImageView
android:id="@+id/iv_nofitication_kzhi_colse"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:textColor="@color/text_color_main" />
</RelativeLayout>
~~~
效果 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c30864.jpg)
### (2)代码实现
说明: 1)通知的实现是单例模式实现(恶汉式)(
[设计模式之单例模式(Singleton pattern)](http://blog.csdn.net/lablenet/article/details/50014001)
);
2)通知中通过使用RemoteViews ,进行自定义布局实现;
3)通过PendingIntent来封装响应点击事件的Intent ;
4)第3条中的Intent,经过测试,只能每个响应事件,填充参数的Intent,只能对应一个点击事件 , 否则是得不到Intent中的参数的。但如果不需要区分的话,一个Intent就可以实现点击事件的响应;
5)通过PendingInetnt.getBroadcast() 方法来通知MusicService,进行响应事件的本质作用;
6)广播:如果MusicService想要接收到通知来的点击事件,那么需要在MusicServiceBroadcast中 注册(IntentFiliter/addAction(xxx)),同时通知中的Inent需 要setAction(xxx), xxx 要一样;
**实现的通知源码分享 :**
~~~
package cn.labelnet.framework;
import com.android.volley.toolbox.ImageLoader.ImageListener;
import android.R.anim;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.widget.RemoteViews;
import cn.labelnet.maskmusic.R;
import cn.labelnet.model.MusicModel;
import cn.labelnet.net.VolleyHttpRequest;
public class MusicNotification extends Notification {
/**
* Music播放控制 的 Notification
* 动态的显示后台的MusicService的前台展示
*/
/**
* 恶汉式实现单例模式加载
*/
private static MusicNotification notifyInstance = null;
// 通知id
private final int NOTIFICATION_ID = 10001;
// 通知
private Notification musicNotifi = null;
// 管理通知
private NotificationManager manager = null;
// 界面实现
private Builder builder = null;
// 上下文
private Context context;
// 布局
private RemoteViews remoteViews;
private final int REQUEST_CODE = 30000;
// 给Service 发送广播
private final String MUSIC_NOTIFICATION_ACTION_PLAY = "musicnotificaion.To.PLAY";
private final String MUSIC_NOTIFICATION_ACTION_NEXT = "musicnotificaion.To.NEXT";
private final String MUSIC_NOTIFICATION_ACTION_CLOSE = "musicnotificaion.To.CLOSE";
private final String MUSIC_NOTIFICAION_INTENT_KEY = "type";
private final int MUSIC_NOTIFICATION_VALUE_PLAY = 30001;
private final int MUSIC_NOTIFICATION_VALUE_NEXT = 30002;
private final int MUSIC_NOTIFICATION_VALUE_CLOSE =30003;
private Intent play=null,next=null,close = null;
private PendingIntent musicPendIntent = null;
// 给进度条页面广播
// 待实现
// 网络 : 加载图片实现
private ImageListener imageListener = null;
public void setManager(NotificationManager manager) {
this.manager = manager;
}
public void setContext(Context context) {
this.context = context;
}
private MusicNotification() {
// 初始化操作
remoteViews = new RemoteViews("cn.labelnet.maskmusic",
R.layout.list_item_notification);
builder = new Builder(context);
// 初始化控制的Intent
play = new Intent();
play.setAction(MUSIC_NOTIFICATION_ACTION_PLAY);
next = new Intent();
next.setAction(MUSIC_NOTIFICATION_ACTION_NEXT);
close = new Intent();
close.setAction(MUSIC_NOTIFICATION_ACTION_CLOSE);
}
/**
* 恶汉式实现 通知
*
* @return
*/
public static MusicNotification getMusicNotification() {
if (notifyInstance == null) {
notifyInstance = new MusicNotification();
}
return notifyInstance;
}
/**
* 创建通知
* 初始化通知
*/
@SuppressLint("NewApi")
public void onCreateMusicNotifi() {
// 设置点击事件
// 1.注册控制点击事件
play.putExtra("type",
MUSIC_NOTIFICATION_VALUE_PLAY);
PendingIntent pplay = PendingIntent.getBroadcast(context, REQUEST_CODE,
play, NOTIFICATION_ID);
remoteViews.setOnClickPendingIntent(R.id.iv_nofitication_kzhi_play,
pplay);
// 2.注册下一首点击事件
next.putExtra("type",
MUSIC_NOTIFICATION_VALUE_NEXT);
PendingIntent pnext = PendingIntent.getBroadcast(context, REQUEST_CODE,
next, NOTIFICATION_ID);
remoteViews.setOnClickPendingIntent(R.id.iv_nofitication_kzhi_next,
pnext);
// 3.注册关闭点击事件
close.putExtra("type",
MUSIC_NOTIFICATION_VALUE_CLOSE);
PendingIntent pclose = PendingIntent.getBroadcast(context, REQUEST_CODE,
close, NOTIFICATION_ID);
remoteViews.setOnClickPendingIntent(R.id.iv_nofitication_kzhi_colse,
pclose);
builder.setContent(remoteViews).setWhen(System.currentTimeMillis())
// 通知产生的时间,会在通知信息里显示
// .setPriority(Notification.PRIORITY_DEFAULT)
// 设置该通知优先级
.setOngoing(true).setTicker("播放新的一首歌")
.setSmallIcon(R.drawable.logo);
// 兼容性实现
musicNotifi = builder.getNotification();
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
// musicNotifi = builder.getNotification();
// } else {
// musicNotifi = builder.build();
// }
musicNotifi.flags = Notification.FLAG_ONGOING_EVENT;
manager.notify(NOTIFICATION_ID, musicNotifi);
}
/**
* 更新通知
*/
public void onUpdataMusicNotifi(MusicModel mm, boolean isplay) {
// 设置添加内容
remoteViews.setTextViewText(R.id.tv_nofitication_singname,
(mm.getSongname()!=null?mm.getSongname():"什么东东") + "");
remoteViews.setTextViewText(R.id.tv_nofitication_singer,
(mm.getSingername()!=null?mm.getSingername():"未知") + "");
//判断是否播放
if (isplay) {
remoteViews.setImageViewResource(R.id.iv_nofitication_kzhi_play,
android.R.drawable.ic_media_pause);
} else {
remoteViews.setImageViewResource(R.id.iv_nofitication_kzhi_play,
android.R.drawable.ic_media_play);
}
onCreateMusicNotifi();
}
/**
* 取消通知栏
*/
public void onCancelMusicNotifi(){
manager.cancel(NOTIFICATION_ID);
}
}
~~~
### (3)广播的基本使用图解
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c432dd.jpg)
## 3.总结
通知的更新控制是在音乐状态发生改变的时候,就会更新通知的内容;相反通知也可以控制音乐当前的状态;后面还有音乐播放界面的控制,省去了很多事,简单的在Service 中调用就可以实现。
MusicService 与 MusicNotification 的基本控制图 ,如下图所示 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764c5c679.jpg)
音乐的播放/暂停/上一曲/下一曲/停止 基本控制 是在MusicService中实现的,所以请看
[Android实战 - 音心播放器 (Music Service 实现)](http://blog.csdn.net/lablenet/article/details/50322487);
Android实战 – 音心播放器 (Music Service 实现)
最后更新于:2022-04-01 10:52:42
## 1.背景
音乐的播放,为了实现在后台播放,将在Service 中进行音乐的基本控制,所以 MediaPlay 将在Service 中实现。
在这里使用了,简单的 startService 方式,非 Ibind 方式操作服务,故在关闭Service 上还有些bug , 但并不影响使用;
*Service 学习参考资料 : [http://www.android-doc.com/guide/components/services.html](http://www.android-doc.com/guide/components/services.html)*
*Service 生命周期 : [http://blog.csdn.net/LABLENET/article/details/48073093](http://blog.csdn.net/LABLENET/article/details/48073093)*
在这个播放器中,为了播放音乐方便,故在应用启动后,当数据加载成功就启动了MusicService 服务。后台服务本身是不可见的,但是在这个为了使其可见就使用了通知(MusicNotification )来人性话,显示服务当前的状态和当前播放音乐的信息,通知下篇实现,这里的主角是Service.
MusicService 服务 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764bc7a84.jpg)
## 2.Service 实现
(1)实现MusicService
如果直接看源码的时候,十分头疼这些是什么东西,我在这个简单的说明的一下 :
MusicService 实现的功能是对音乐的基本控制:
1.应用启动 (随着应用的启动而启动) 就启动
2.状态栏显示 : notification
3.注册 : bordcastReceiver
4.请求第1首歌 : 更新状态栏
5.实现上一曲,下一曲,播放,暂停控制
6.初始化 MusicActivity 内容数据 (这个是歌词/进度条显示页面)
*MusicService 源码 : 使用了斜杠进行了分类,应该是可以理解的,boradcastReceiver 的内容将在后面说明。*
~~~
package cn.labelnet.framework;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import cn.labelnet.model.MusicModel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.IBinder;
import android.widget.MediaController.MediaPlayerControl;
import android.widget.Toast;
public class MusicService extends Service implements OnPreparedListener,
OnCompletionListener, OnErrorListener {
/**
* MusicService 音乐播放控制 : 随着应用的启动而启动 基本步揍 : 1.应用启动 : 就启动 2.状态栏显示 :
* notification 3.注册 : bordcastReceiver 4.请求第1首歌 : 更新状态栏 5.实现上一曲,下一曲,播放,暂停控制
*/
// 常量
private final String MUSIC_INTENT_KEY = "musics";
private final int MUSIC_INTENT_FLAG = 20001;
private final int MAIN_MUSIC_INTENT_FLAG = 20017;
// 音乐列表
private List<MusicModel> musics = null;
private int mmSize = 0;
// 通知栏
private MusicNotification musicNotifi = null;
private MusicModel mm = null;
// MediaPlay
private MediaPlayer mp = null;
private int currentTime = 0;
// Music广播接收
private MusicBroadCast musicBroadCast = null;
// MainActivity 来的 Action
private final String MAIN_ACTIVIY_ACTION = "mainActivity.To.MusicService";
// 来自通知栏的Action
private final String MUSIC_NOTIFICATION_ACTION_PLAY = "musicnotificaion.To.PLAY";
private final String MUSIC_NOTIFICATION_ACTION_NEXT = "musicnotificaion.To.NEXT";
private final String MUSIC_NOTIFICATION_ACTION_CLOSE = "musicnotificaion.To.CLOSE";
private final String MUSIC_NOTIFICAION_INTENT_KEY = "notify.music";
// MusicService 来的 Action
private final String MUSIC_ACTIVITY_SERVICE_ACTION = "activity.to.musicservice";
private final String MUSIC_ACTIVITY_SERVICE_KEY = "musictype";
private final int MUSIC_ACTIVITY_SERVICE_REQUEST = 40001;
// 给MusicActivity 的 Action
private final String MUSIC_SERVICE_RECEIVER_ACTION = "service.to.musicactivity";
private Intent mActivityIntent = null;
private final String MUSIC_SERVICE_TO_ACTIVITY_MODEL = "model";
private final String MUSIC_SERVICE_TO_ACTIVITY_ISPLAY = "isplay";
private final String MUSIC_SERVICE_TO_ACTIVITY_NOWTIME = "nowtime";
// 响应码 : 41001 没数据 , 41002 : 有数据
private final String MUSIC_SERVICE_TOACTIVITY_CODE = "mpcode";
// Intent keys
private final String MAIN_MUSIC_INTENT_KEY = "mIntent";
@Override
public void onCreate() {
// 初始化MusicActivity 的 Intent ,给 MusicActivity 发送广播 ,修改音乐播放界面
mActivityIntent = new Intent();
mActivityIntent.setAction(MUSIC_SERVICE_RECEIVER_ACTION);
// 初始化通知栏
musicNotifi = MusicNotification.getMusicNotification();
musicNotifi.setContext(getApplicationContext());
musicNotifi
.setManager((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
musicNotifi.onCreateMusicNotifi();
// 初始化MediaPlay : 设置监听事件
mp = new MediaPlayer();
mp.setOnPreparedListener(this);
mp.setOnCompletionListener(this);
mp.setOnErrorListener(this);
// 注册广播
musicBroadCast = new MusicBroadCast();
IntentFilter filter = new IntentFilter();
filter.addAction(MAIN_ACTIVIY_ACTION);
filter.addAction(MUSIC_ACTIVITY_SERVICE_ACTION);
filter.addAction(MUSIC_NOTIFICATION_ACTION_PLAY);
filter.addAction(MUSIC_NOTIFICATION_ACTION_NEXT);
filter.addAction(MUSIC_NOTIFICATION_ACTION_CLOSE);
registerReceiver(musicBroadCast, filter);
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@SuppressWarnings("unchecked")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try{
musics = (List<MusicModel>) intent
.getSerializableExtra(MUSIC_INTENT_KEY);
}catch(Exception e){
}
if (musics != null) {
mmSize = musics.size();
}
// showToast("1." + musics.get(1).getSongname());
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
if (mp != null) {
mp.stop();
mp.release();
mp = null;
musics = null;
}
// 取消注册的广播
unregisterReceiver(musicBroadCast);
}
// //////////////////////////////Music Util//////////////////////
// Toast
private void showToast(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
// 音乐播放
private void play(String musicUrl) {
mp.reset();
try {
mp.setDataSource(getApplicationContext(), Uri.parse(musicUrl));
mp.prepareAsync();
} catch (IOException e) {
showToast("网络错误,播放失败");
}
musicNotifi.onUpdataMusicNotifi(mm, true);
}
// 音乐暂停
private void pause() {
if (mp.isPlaying()) {
currentTime = mp.getCurrentPosition();
mp.pause();
}
musicNotifi.onUpdataMusicNotifi(mm, false);
}
// 音乐继续播放
private void resume() {
mp.start();
if (currentTime > 0) {
mp.seekTo(currentTime);
}
musicNotifi.onUpdataMusicNotifi(mm, true);
}
// 音乐停止
private void stop() {
mp.stop();
try {
mp.prepare();
} catch (IOException e) {
showToast("音乐停止异常");
}
musicNotifi.onUpdataMusicNotifi(mm, false);
}
// //////////////////////////////Music MediaPlayListener////////////
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 出错的时候
return false;
}
@Override
public void onCompletion(MediaPlayer mp) {
// 播放完毕的时候
// showToast("播放完毕,准备播放下一首!");
currentTime = 0;
// 改变通知栏
musicNotifi.onUpdataMusicNotifi(mm, false);
//改变MusicActivity
sendModelToMusicActivity();
}
@Override
public void onPrepared(MediaPlayer mp) {
// 准备加载的时候
resume();
sendModelToMusicActivity();
}
// ////////////////////////////其他工具方法//////////////////////////////////
/**
* 发送Model给MusicActivity
*/
private void sendModelToMusicActivity() {
if (mm != null) {
// 正在播放的歌曲实体
if(mm.getSeconds()==0){
mm.setSeconds(mp.getDuration()/1000);
}
mActivityIntent.putExtra(MUSIC_SERVICE_TOACTIVITY_CODE, 41002);
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_ISPLAY,
mp.isPlaying());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_NOWTIME,
mp.getDuration() - mp.getCurrentPosition());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_MODEL,
(Serializable) mm);
} else {
// 默认发送第一首歌信息
mm = musics.get(0);
mActivityIntent.putExtra(MUSIC_SERVICE_TOACTIVITY_CODE, 41001);
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_ISPLAY,
mp.isPlaying());
mActivityIntent.putExtra(MUSIC_SERVICE_TO_ACTIVITY_MODEL,
(Serializable) mm);
}
sendBroadcast(mActivityIntent);
}
// //////////////////////////////Music BroadCastReceiver////////////
// 接收广播
private class MusicBroadCast extends BroadcastReceiver {
private int flag = 0, position = -1, kzhi = 0, musictype = 0;
@Override
public void onReceive(Context context, Intent intent) {
// 2.MainActivity 控制
flag = intent.getFlags();
mainToService(intent);
// 3.MusicNotification控制
kzhi = intent.getIntExtra("type", -1);
if (kzhi > 0) {
musicNotificationService(kzhi);
}
// 4.MusicActivity 来的控制
musictype = intent.getIntExtra(MUSIC_ACTIVITY_SERVICE_KEY, 0);
if (musictype > 0) {
musicActivityService(musictype);
}
}
/**
* 来自 MusicActivity 的控制
*
* @param musictype2
*/
private void musicActivityService(int musictype2) {
// showToast("musicActivityService 执行了 musictype2 :" + musictype2);
switch (musictype2) {
case 40001:
sendModelToMusicActivity();
break;
case 40002:
//播放与暂停
playSong();
break;
case 40003:
//下一曲
nextSong();
break;
case 40004:
//上一曲
preSong();
break;
}
}
/**
* musicNotification 来的控制
*
* @param intent
*/
private void musicNotificationService(int k) {
switch (k) {
case 30001:
// 播放
playSong();
break;
case 30002:
// 下一首
nextSong();
break;
case 30003:
// 关闭通知栏
musicNotifi.onCancelMusicNotifi();
// 停止音乐
stop();
break;
}
}
/**
* 播放
*/
private void playSong() {
if (mp.isPlaying()) {
pause();
} else {
if (currentTime > 0) {
resume();
} else {
if (mm != null) {
play(mm.getUrl());
}
}
}
sendModelToMusicActivity();
}
/**
* 下一曲
*/
private void nextSong() {
currentTime=0;
if(position<0){
position=0;
}
if (mmSize > 0) {
position++;
if (position < mmSize) {
// 不超过长度
mm = musics.get(position);
play(mm.getUrl());
} else {
// 超过长度 播放第一首
mm = musics.get(0);
play(mm.getUrl());
}
}
}
/**
* 上一曲
*/
private void preSong(){
currentTime=0;
if(position<0){
position=0;
}
if (mmSize > 0) {
position--;
if (position>=0) {
//不小于0
mm = musics.get(position);
play(mm.getUrl());
} else {
// 超过长度 播放第一首,小于0 ,播放第一首
mm = musics.get(0);
play(mm.getUrl());
}
}
}
/**
* MainActivity来的数据
*
* @param intent
*/
private void mainToService(Intent intent) {
if (MAIN_MUSIC_INTENT_FLAG == flag) {
// 来自MainActivity 的操作
position = intent.getIntExtra(MAIN_MUSIC_INTENT_KEY, -1);
// showToast("3.来自MainActivity 问候 : " + position);
if (position > -1) {
// 播放
if (musics != null) {
mm = musics.get(position);
} else {
// showToast("4.MUSICS IS NULL");
}
if (mm != null) {
/**
* 1.播放音乐 2.更新状态栏 3.如果进度条运行的话,通知改变
*/
play(mm.getUrl());
} else {
// showToast("5.musics 数据去哪里了!");
}
} else {
// showToast("6.这怎么可能发生呢?!");
}
} else {
// showToast("不是MainActivity 来的数据");
}
}
}
}
~~~
(2)注册Service
在AndroidMainfest.xml中注册
~~~
<service android:name="cn.labelnet.framework.MusicService" >
</service>
~~~
## (3)启动服务
这里使用了 Serializable序列化进行了数据传递,不建议使用这个,效率低,可以使用Parcleable,效率高;
参考文章 :
[Android - Parcelable接口用法 和 与 Serializable 的区别](http://blog.csdn.net/lablenet/article/details/50315349)
~~~
@Override
public void getMusicModelList(List<MusicModel> models) {
// 初始化 Service : 开启MUSIC服务
Intent intent = new Intent(MainActivity.this, MusicService.class);
intent.putExtra(MUSIC_INTENT_KEY, (Serializable) models);
intent.addFlags(MUSIC_INTENT_FLAG);
startService(intent);
//关闭进度条
swiperefresh_wei.setRefreshing(false);
isRefresh=false;
}
~~~
### 3.播放器控制
播放器控制主要实现是实现 play() , pause() , resume(),stop() 四个方法,进行播放,暂停,重新播放控制。同时实现onPrepaed() , onCompletion() , onError() 三个监听,实现对播放器的控制,最基本的操作就在此了。后面的通知栏控制,歌词/进度条页面控制 均是使用 这几个方法进行控制实现。
~~~
<span style="font-family:Comic Sans MS;font-size:18px;">// 音乐播放
private void play(String musicUrl) {
mp.reset();
try {
mp.setDataSource(getApplicationContext(), Uri.parse(musicUrl));
mp.prepareAsync();
} catch (IOException e) {
showToast("网络错误,播放失败");
}
musicNotifi.onUpdataMusicNotifi(mm, true);
}
// 音乐暂停
private void pause() {
if (mp.isPlaying()) {
currentTime = mp.getCurrentPosition();
mp.pause();
}
musicNotifi.onUpdataMusicNotifi(mm, false);
}
// 音乐继续播放
private void resume() {
mp.start();
if (currentTime > 0) {
mp.seekTo(currentTime);
}
musicNotifi.onUpdataMusicNotifi(mm, true);
}
// 音乐停止
private void stop() {
mp.stop();
try {
mp.prepare();
} catch (IOException e) {
showToast("音乐停止异常");
}
musicNotifi.onUpdataMusicNotifi(mm, false);
}
// //////////////////////////////Music MediaPlayListener////////////
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 出错的时候
return false;
}
@Override
public void onCompletion(MediaPlayer mp) {
// 播放完毕的时候
// showToast("播放完毕,准备播放下一首!");
currentTime = 0;
// 改变通知栏
musicNotifi.onUpdataMusicNotifi(mm, false);
//改变MusicActivity
sendModelToMusicActivity();
}
@Override
public void onPrepared(MediaPlayer mp) {
// 准备加载的时候
resume();
sendModelToMusicActivity();
}</span>
~~~
### 4.总结
MusicService 的实现是整个播放器的核心,所以进步的音乐控制均在此实现,算是播放器的心脏。
下篇将实现通知栏实现- MusicSerivce 的脸;
Android实战 – 音心音乐播发器 (主界面实现)
最后更新于:2022-04-01 10:52:40
开发平台 : eclipse , ubuntu ,android sdk 4.0+
### 1.背景
主页的设计从上往下依次是滚动广告(ViewFlipper ),分类信息( GridView ),热门榜单( ListView ),整个界面可以滑动,通过ScrollView 包裹,使得整个页面可滑动。
界面展示 :
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764b5b653.jpg)
后发现遇到的问题 , ListView 滑动和 ScrollView 冲突,后整个布局进行了修改,下面的热门榜单是通过 Fragment 实现,在启动的时候进行数据加载。但是有一个问题,如果上面分类信息(GridView)没有实现的话,下面Fragment加载完成并适配好数据的时候,重新定义Fragment 所要适配的布局的高度,进行重新定义,将会蹦到该Fragment上,故在完成GridView的时候,就不在‘蹦’了,如上图所示,效果还不错。
### 2.广告栏实现(ViewFlipper )
ViewFlipper 的实现 ,布局是自定义布局实现的,包括背景,透明层,左右的文字显示;
(1)ViewFlipper 内容布局实现
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="200dp" >
<ImageView
android:id="@+id/iv_list_item_flipper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/moren_big" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:background="@color/app_color_borwn" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center|left"
android:layout_marginLeft="20dp"
android:text="@string/tv_main_xindie"
android:textColor="@color/text_color_whrit" />
</FrameLayout>
<TextView
android:id="@+id/tv_list_item_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"
android:text="@string/list_item_flipper_tv"
android:textColor="@color/text_color_whrit" />
</RelativeLayout>
~~~
(2)布局实现
~~~
<ViewFlipper
android:id="@+id/main_view_flipper"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="10dp" >
</ViewFlipper>
~~~
(3)初始化VIewFlipper实现
~~~
private void initViewFlipper() {
main_view_flipper.setInAnimation(this, R.drawable.main_fliper_in);
main_view_flipper.setOutAnimation(this, R.drawable.main_fliper_out);
main_view_flipper.setOnTouchListener(new viewFlipperListener());
for (int i = 0; i < 4; i++) {
flipperView = LayoutInflater.from(this).inflate(
R.layout.list_item_main_flipper, main_scroll_view, false);
flipperTv = (TextView) flipperView
.findViewById(R.id.tv_list_item_num);
flipperIv = (ImageView) flipperView
.findViewById(R.id.iv_list_item_flipper);
flipperIv.setTag(VolleyHttpPath.RANDOM_IMAGE_URL);
flipperTv.setText((i + 1) + "/4");
~~~
~~~
//加载网络图片实现Volley 框架实现
imageListener = ImageLoader.getImageListener(flipperIv,
R.drawable.moren, R.drawable.moren_big);
main_view_flipper.addView(flipperView);
VolleyHttpRequest.Image_Loader(VolleyHttpPath.RANDOM_IMAGE_URL
+ "?" + i, imageListener);
}
main_view_flipper.setFlipInterval(7000);
main_view_flipper.startFlipping();
}
~~~
(4)两个动画
---fade in
~~~
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="800"
android:fromXDelta="-100%p"
android:toXDelta="0" />
</set>
~~~
---fade out
~~~
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="800"
android:fromXDelta="0"
android:toXDelta="100%p" />
</set>
~~~
(5)手势控制
~~~
/**
* ViewFlipper 手势控制
*
*/
private class viewFlipperListener implements OnTouchListener {
private int start = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
}
/**
* 手势判断
*
*/
private class gestureDetectorListener extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (e1.getX() - e2.getX() < 1000) {
main_view_flipper.showPrevious();
}
return true;
}
}
~~~
### 3. 分类信息 (GridView)
静态数据,非网络请求,使用了最简单的数据适配,并添加点击事件;
(1) 布局实现
1)GridView 布局
~~~
<GridView
android:id="@+id/main_gridview"
android:layout_width="356dp"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:gravity="center"
android:horizontalSpacing="0dp"
android:numColumns="4"
android:scrollbars="none"
android:verticalSpacing="0dp" >
</GridView>
~~~
2)Item布局
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="1dp"
android:background="@color/text_color_whrit" >
<ImageView
android:id="@+id/iv_item_main_gird"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerInParent="true"
android:src="@drawable/logo" />
<TextView
android:id="@+id/tv_item_main_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center"
android:text="@string/list_item_grid_tv"
android:textColor="@color/text_color_main" />
</RelativeLayout>
~~~
(2)Adapter实现
~~~
public class MusicGridAdapter extends BaseAdapter {
/**
* 主界面 分类信息,适配Adapter
*/
private SparseArray<String> gridItems;
private Context context;
private ViewHolder holder = null;
private int[] ids = { R.drawable.mingyao, R.drawable.xiaoliang,
R.drawable.china, R.drawable.oumei, R.drawable.hongkang,
R.drawable.hanguo, R.drawable.riben, R.drawable.yaogun };
public void setGridItems(SparseArray<String> gridItems) {
this.gridItems = gridItems;
}
public void setContext(Context context) {
this.context = context;
}
@Override
public int getCount() {
return gridItems.size() > 0 ? gridItems.size() : 0;
}
@Override
public String getItem(int position) {
// TODO Auto-generated method stub
return gridItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
R.layout.list_item_main_gridview, parent, false);
holder = new ViewHolder(convertView);
}
holder.tv_item_main_grid.setText(gridItems.get(position) + "");
holder.iv_item_main_gird.setImageResource(ids[position]);
return convertView;
}
private class ViewHolder {
/**
* ViewHolder
*/
public ImageView iv_item_main_gird;
public TextView tv_item_main_grid;
public ViewHolder(View convertView) {
iv_item_main_gird = (ImageView) convertView
.findViewById(R.id.iv_item_main_gird);
tv_item_main_grid = (TextView) convertView
.findViewById(R.id.tv_item_main_grid);
}
}
}
~~~
(3)基本业务实现
~~~
/**
* 初始化 数据
*/
private void initData() {
gridItems.put(0, getString(R.string.music_fenlei_mingyao));
gridItems.put(1, getString(R.string.msuic_fenlei_xiaoliang));
gridItems.put(2, getString(R.string.music_fenlei_china));
gridItems.put(3, getString(R.string.music_fenlei_oumei));
gridItems.put(4, getString(R.string.music_fenlei_hangkang));
gridItems.put(5, getString(R.string.music_fenlei_hanguo));
gridItems.put(6, getString(R.string.music_fenlei_riben));
gridItems.put(7, getString(R.string.music_fenlei_yaogun));
getFenlei();
}
public void getFenlei() {
maps.put(getString(R.string.music_fenlei_mingyao), 18);
maps.put(getString(R.string.msuic_fenlei_xiaoliang), 23);
maps.put(getString(R.string.music_fenlei_china), 5);
maps.put(getString(R.string.music_fenlei_oumei), 3);
maps.put(getString(R.string.music_fenlei_hangkang), 6);
maps.put(getString(R.string.music_fenlei_hanguo), 16);
maps.put(getString(R.string.music_fenlei_riben), 17);
maps.put(getString(R.string.music_fenlei_yaogun), 19);
// 热歌 26
}
~~~
~~~
// GridView初始化分类信息
MusicGridAdapter musicGridAdapter = new MusicGridAdapter();
musicGridAdapter.setContext(this);
musicGridAdapter.setGridItems(gridItems);
main_gridview.setAdapter(musicGridAdapter);
main_gridview.setOnItemClickListener(new main_gridviewListener());
~~~
(4)点击监听事件
~~~
/**
* GirdView点击事件
*/
private class main_gridviewListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// showToast("点击了乡村 " + maps.get(gridItems.get(position)));
Intent intent = new Intent(MainActivity.this,
MusicListActivity.class);
intent.putExtra("musictype", maps.get(gridItems.get(position)));
startActivity(intent);
}
}
~~~
### 4.热门榜单实现
使用Fragment 实现 (非v4 包下的),动态的添加到布局中;
实现思路:在MainActivity.xml 给 Fragment一个空白的布局,等待Fragment的填充;
(1)布局实现
--- mainactivity.xml 中给fragment 预留的布局
~~~
<RelativeLayout
android:id="@+id/main_listview_fragement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp" />
~~~
---- fragment.xml 布局实现
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/main_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</RelativeLayout>
~~~
---- list_item.xml
~~~
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/text_color_whrit"
android:layout_height="70dp" >
<TextView
android:id="@+id/list_item_play"
android:layout_width="30dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_margin="10dp"
android:gravity="center"
android:textColor="@color/app_color"
android:text="@string/main_item_num" />
<TextView
android:id="@+id/tv_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:layout_toRightOf="@+id/list_item_play"
android:maxLines="2"
android:text="@string/list_item_song_name"
android:textColor="@color/text_color_black"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_item_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_item_name"
android:layout_toRightOf="@+id/list_item_play"
android:text="@string/list_item_singer_name"
android:textColor="@color/text_color_main"
android:textSize="12sp" />
</RelativeLayout>
~~~
(2)Fragment实现
~~~
package cn.labelnet.fragment;
import java.util.ArrayList;
import java.util.List;
import cn.labelnet.adapter.MusicListAdapter;
import cn.labelnet.event.MainToFragmentRefrsh;
import cn.labelnet.maskmusic.R;
import cn.labelnet.model.MusicModel;
import cn.labelnet.net.MusicAsync;
import cn.labelnet.net.MusicAsyncHandler;
import cn.labelnet.net.MusicRequest;
import android.annotation.SuppressLint;
import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
public class MainListViewFragment extends Fragment implements MusicAsync {
/**
* 热歌榜单实现
* MainActivity界面内容填充
*/
// 数据请求
private MusicRequest musicRequest = null;
private MusicAsyncHandler musicHandler = null;
// listview
private ListView main_list_view;
// adapter
private MusicListAdapter musicAdapter;
private List<MusicModel> mmsList = new ArrayList<MusicModel>();
// 接口 : 给 Main传递参数
private MainToFragmentRefrsh mainToFragmentRefrsh;
// 给Fragment 添加此事件
public void setMainToFragmentRefrsh(
MainToFragmentRefrsh mainToFragmentRefrsh) {
this.mainToFragmentRefrsh = mainToFragmentRefrsh;
}
public android.view.View onCreateView(android.view.LayoutInflater inflater,
android.view.ViewGroup container,
android.os.Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main_listview_layout,
container, false);
return view;
};
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 初始化数据
initData();
// 初始化View
initView(view);
// 数据请求
musicRequest.requestStringData(5);
}
/**
* 初始化View
*
* @param view
*/
private void initView(View view) {
main_list_view = (ListView) view.findViewById(R.id.main_list_view);
musicAdapter = new MusicListAdapter(mmsList, getActivity());
main_list_view.setAdapter(musicAdapter);
main_list_view.setOnItemClickListener(new Main_list_viewListener());
}
/**
* 初始化数据请求
*/
private void initData() {
musicHandler = new MusicAsyncHandler();
musicHandler.setMAsync(this);
musicRequest = new MusicRequest();
musicRequest.setMusicAsyncHandler(musicHandler);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if(isVisibleToUser){
musicRequest.requestStringData(5);
}
}
@Override
public void onSuccess(List<MusicModel> mms) {
// 给MainActivity返回size
mainToFragmentRefrsh.changeFragmentHeight(mms.size());
mainToFragmentRefrsh.getMusicModelList(mms);
// 请求成功
// String name = mms.get(1).getSingername();
// showToast(name);
// Log.i("MaskMusic", name);
mmsList.addAll(mms);
musicAdapter.notifyDataSetChanged();
}
@Override
public void onFail(String msg) {
// 请求失败
showToast(msg);
mainToFragmentRefrsh.onFailListener();
}
/**
* Toast
*
* @param msg
* 消息
*/
private void showToast(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
// listview点击事件
private class Main_list_viewListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
mainToFragmentRefrsh.onListviewOnItemClickListener(position);
}
}
}
~~~
(3)Fragment 中的List 适配器实现
~~~
package cn.labelnet.adapter;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import cn.labelnet.maskmusic.R;
import cn.labelnet.model.MusicModel;
public class MusicListAdapter extends BaseAdapter {
/**
* 主页 listview 音乐列表
* 在MainListViewFragment中实现,实现初始化界面适配
*/
private List<MusicModel> list;
private Context context;
private ViewHolder holder = null;
public MusicListAdapter(List<MusicModel> list, Context content) {
this.list = list;
this.context = content;
}
@Override
public int getCount() {
return list.size() > 0 ? list.size() : 0;
}
@Override
public MusicModel getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
R.layout.list_item_main_layout, parent, false);
holder = new ViewHolder(convertView);
}
MusicModel musicModel = list.get(position);
String songName = musicModel.getSongname() != null ? musicModel
.getSongname() : "什么东东";
holder.tv_item_name.setText(songName);
String singerName = musicModel.getSingername() != null ? musicModel
.getSingername() : "未知";
position += 1;
String num = position >= 10 ? (position + "") : ("0" + position);
holder.list_item_play.setText(num);
holder.tv_item_singer.setText(singerName);
return convertView;
}
class ViewHolder {
public TextView tv_item_name;
public TextView tv_item_singer, list_item_play;
public ViewHolder(View convertView) {
tv_item_name = (TextView) convertView
.findViewById(R.id.tv_item_name);
tv_item_singer = (TextView) convertView
.findViewById(R.id.tv_item_singer);
list_item_play = (TextView) convertView
.findViewById(R.id.list_item_play);
}
}
}
~~~
(4)Fragment 回调事件 (重点)
回调图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764ba8319.jpg)
作用:1)当网络请求成功后,总的条数,从而改变mainactivity.xml中需要填充布局的高度;
2)回调出所有的数据 models,进行初始化 Service 和通知栏,消除进度条;
3)点击实现回调,传递item的poistion ,在MainActivity中通过广播通知Service播放这个音乐;
4)数据请求失败调用,提示和消除进度条;
~~~
public interface MainToFragmentRefrsh {
/**
* 传条数,改变布局高度
*
* @param size
*/
void changeFragmentHeight(int size);
/**
* 得到音乐列表
*
* @param models
*/
void getMusicModelList(List<MusicModel> models);
/**
* ListView点击事件
*
* @param postion
*/
void onListviewOnItemClickListener(int postion);
/**
* 失败的回调
*/
void onFailListener();
}
~~~
(5)改变Fragment填充到的布局高度
1)工具类 : 像素px和dp的转化
~~~
public class ViewUtil {
/**
* dp转px
* @param context
* @param dpValue
* @return
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* px转dp
* @param context
* @param pxValue
* @return
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
~~~
2)实现动态改变布局高度
~~~
/**
* 作用 : 从 Fragment拿过来 总长度,后 设置 Fragment 所在布局的总高度
*/
@Override
public void changeFragmentHeight(int size) {
// 给listView 设置 高度
main_listview_parames.height = ViewUtil.px2dip(this,
ViewUtil.dip2px(this, 70) * size);
main_listview_fragement.setLayoutParams(main_listview_parames);
}
~~~
### 5.总结
通过主页的实现,可以通过fragment实现复杂的页面布局(这个布局实现单独的网络请求与数据适配),这是目前我可以想到的并可以实现的;在此应用中请求图片使用了Volley 网络请求框架,使用的是自己进行[二次封装](http://blog.csdn.net/LABLENET/article/details/47859613)的。在此之前有人说,可能发生内存泄露问题,经过使用,并没有发生内存泄露问题,请放心使用。
*使用地址 : [http://blog.csdn.net/LABLENET/article/details/47859613](http://blog.csdn.net/LABLENET/article/details/47859613)*
下篇将进行Service 的实现,在Service 中实现 进行音乐的控制实现;
Android实战 - 音心音乐播放器 (开启篇)
最后更新于:2022-04-01 10:52:38
## 1.背景
在开发之鱼APP的时候,本来音乐播放器是之鱼中的一个模块,用来播放音乐使用,但是随着项目代码的编写,越来越复杂,决定将其单独的拉出来,制作为单独的APP - 音心播放器 。
写一个 音乐播放器 可以很好的锻炼自己在Android 高级上的学习 比如 :BroadCastReceiver , Service , Notification ,Activity 等之间的信息传递与控制。
## 2.数据来源
因为音乐的资源API不好找,大多数都不提供音乐播放接口,所以选择了[易源AP](https://www.showapi.com/api/apiList)I(showAPi .com) 中的 QQ音乐接口,用着还行,而且还是免费的,只需要注册,订阅就可以使用了,返回的均是JSON数据。其他的音乐数据API的话,找了很久,应该就只有豆瓣FM 提供的 API了 ,没有使用过,这里就不做过多的评价。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_57157646957b6.jpg)
提供的API 包括 :
(1)热门榜单 : 可以根据不同的分类信息, 获得不同的 榜单列表数据 ,比如 欧美 ,乡音 ,摇滚 ;不过返回的数据中是没有 专辑图片的 ,可以通过下面的接口 获取专辑图片;
**单条数据展示 :**
~~~
{
"albumid": 1209391,
"downUrl": "http://tsmusic24.tc.qq.com/105013301.mp3",
"seconds": 233,
"singerid": 20697,
"singername": "原子霏",
"songid": 105013301,
"songname": "芈月传",
"url": "http://ws.stream.qqmusic.qq.com/105013301.m4a?fromtag=46"
}
~~~
(2)根据歌曲ID 查询 歌词 :这个用起来还可以,提供了歌词信息,需要自己写一个单独的解析方法,进行解析;
**歌词数据 展示 :**
~~~
{
"showapi_res_code": 0,
"showapi_res_error": "",
"showapi_res_body": {
"lyric": "[ti:海阔天空 (Edited Version)]
[ar:BEYOND]
[al:Words & Music Final Live With 家驹]
[by:]
[offset:0]
[00:00.92]海阔天空 - BEYOND
[00:02.27]词:黄家驹
[00:03.32]曲:黄家驹
[00:04.30]
[00:19.17]今天我 寒夜里看雪飘过
[00:25.75]怀着冷却了的心窝飘远方
[00:30.77]
[00:31.60]风雨里追赶 雾里分不清影踪
[00:37.82]天空海阔你与我 可会变
[00:43.27]
[00:44.14]多少次迎着冷眼与嘲笑
[00:50.55]从没有放弃过心中的理想
[00:56.02]
[00:56.67]一刹那恍惚 若有所失的感觉
[01:02.65]不知不觉已变淡 心里爱
[01:08.64]
[01:09.66]原谅我这一生不羁放纵爱自由
[01:15.56]
[01:16.40]也会怕有一天会跌倒
[01:22.72]背弃了理想谁人都可以
[01:27.84]
[01:28.51]哪会怕有一天只你共我
[01:33.89]
[01:43.41]今天我 寒夜里看雪飘过
[01:49.76]怀着冷却了的心窝飘远方
[01:54.86]
[01:55.60]风雨里追赶 雾里分不清影踪
[02:01.92]天空海阔你与我 可会变
[02:06.61]
[02:08.70]原谅我这一生不羁放纵爱自由
[02:14.86]
[02:15.55]也会怕有一天会跌倒
[02:21.30]
[02:21.83]背弃了理想谁人都可以
[02:27.17]
[02:28.08]哪会怕有一天只你共我
[02:33.08]
[02:38.06]仍然自由自我
[02:40.57]
[02:41.42]永远高唱我歌
[02:44.42]走遍千里 原谅我这一生不羁放纵爱自由
[02:55.20]
[02:56.14]也会怕有一天会跌倒
[03:02.26]背弃了理想 谁人都可以
[03:07.48]
[03:08.67]哪会怕有一天只你共我
[03:13.58]
[03:14.51]原谅我这一生不羁放纵爱自由
[03:21.27]也会怕有一天会跌倒
[03:26.00]
[03:27.38]背弃了理想谁人都可以
[03:31.94]
[03:33.61]哪会怕有一天只你共我",
"lyric_txt": " 海阔天空 BEYOND 词:黄家驹 曲:黄家驹 今天我 寒夜里看雪飘过 怀着冷却了的心窝飘远方 风雨里追赶 雾里分不清影踪 天空海阔你与我 可会变 多少次迎着冷眼与嘲笑 从没有放弃过心中的理想 一刹那恍惚 若有所失的感觉 不知不觉已变淡 心里爱 原谅我这一生不羁放纵爱自由 也会怕有一天会跌倒 背弃了理想谁人都可以 哪会怕有一天只你共我 今天我 寒夜里看雪飘过 怀着冷却了的心窝飘远方 风雨里追赶 雾里分不清影踪 天空海阔你与我 可会变 原谅我这一生不羁放纵爱自由 也会怕有一天会跌倒 背弃了理想谁人都可以 哪会怕有一天只你共我 仍然自由自我 永远高唱我歌 走遍千里 原谅我这一生不羁放纵爱自由 也会怕有一天会跌倒 背弃了理想 谁人都可以 哪会怕有一天只你共我 原谅我这一生不羁放纵爱自由 也会怕有一天会跌倒 背弃了理想谁人都可以 哪会怕有一天只你共我",
"ret_code": 0
}
}
~~~
(3) 根据人名,歌名查询歌曲 :这个可以做搜索歌曲,也提供图片信息,在热门榜单接口中,不提供图片信息的,所以可以使用这个接口,拿到歌曲的图片信息包括歌手图片信息;
**单条数据信息展示 :**
******
~~~
{
"albumid": 62660,
"albummid": "00449cf44ccf8n",
"albumname": "Words & Music Final Live With 家驹",
"albumpic_big": "http://i.gtimg.cn/music/photo/mid_album_300/8/n/00449cf44ccf8n.jpg",
"albumpic_small": "http://i.gtimg.cn/music/photo/mid_album_90/8/n/00449cf44ccf8n.jpg",
"downUrl": "http://tsmusic24.tc.qq.com/4833285.mp3",
"m4a": "http://ws.stream.qqmusic.qq.com/4833285.m4a?fromtag=46",
"media_mid": "001fhSpB0P7buZ",
"singerid": 2,
"singername": "BEYOND",
"songid": 4833285,
"songname": "海阔天空 (Edited Version)"
~~~
***
***
***
***
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764b33606.jpg)
3.易源API SDK的使用
必须使用易源SDK ,才可以进行数据请求,这样可以省很多事,SDK中封装了,请求所必须的系统级参数,所以使用官方提供的SDK,很简单就可以实现调用,方便开发,如果你能力可以,自己可以写一个类,来提供系统级参数。
官方提供了多个开发语言的版本SDK, 下载对应版本即可,我们使用的是Android ,所以下载Android 的(非Java)版本。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-19_5715764b43914.jpg)
最后,还提供了请求源码,自己可以进行简单封装(不封装也可以)使用。
Android 的 请求源码 :
参数 : (1) appid 需要申请;
(2) secret 创建后就生成了;
(3) typeid 分类 信息
~~~
//以下代码仅为演示用,具体传入参数请参看接口描述详情页.
//需要引用android-async-http库(sdk中已经包括此jar包) ,其项目地址为: http://loopj.com/android-async-http/
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView txt = (TextView) this.findViewById(R.id.textView1);
Button myBtn = (Button) this.findViewById(R.id.button1);
final AsyncHttpResponseHandler resHandler=new AsyncHttpResponseHandler(){
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable e) {
//做一些异常处理
e.printStackTrace();
}
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
try {
System.out.println("response is :"+new String(responseBody,"utf-8"));
txt.setText(new String(responseBody,"utf-8")+new Date());
//在此对返回内容做处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}};
myBtn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
new ShowApiRequest( "http://route.showapi.com/213-4", "appid", "secret")
.setResponseHandler(resHandler)
.addTextPara("topid", "")
.post();
}
});
}
~~~
4.总结
一个音乐播放器,不仅可以练习四大组件,而且还练习整个app的结构,包括网络请求等。其中最重要的是Service ,广播 ,通知 之间的通信将在后面详细列出。功能很简单 ,实现音乐列表,播放控制(上一曲,下一曲,播放,暂停),歌词滚动显示,进度条显示,倒计时实现。
前言
最后更新于:2022-04-01 10:52:36
> 原文出处:[安卓实战音心APP开发](http://blog.csdn.net/column/details/yuan-music.html)
作者:[lablenet](http://blog.csdn.net/lablenet)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 安卓实战音心APP开发
> 音心-音乐播放器,从开发开启到结束应用打包总结,均已总结,包括详细的过程图,方便理解Android上的Service , BroadCastReceiver 和 Notification .