转载请注明出处:http://blog.csdn.net/qq347198688/article/details/53070253
本文出自【何嘉龙的博客】
前言
当年,有一本书刊特别流行,叫《故事会》。《故事会》后来销声匿迹了,在我眼里,取代《故事会》的是知乎。对,就是那个知乎。 我其实一直很反对一个论调:“上知乎,学知识”。醒醒吧。知乎明显就是现代版的故事会好吗?! 我不否认知乎存在有价值的知识,但,根本不值得你花那么多时间,去发现那少的可怜的知识。去读书吧。用最系统的逻辑,去学会一项技能,哪怕你去《硅谷百年史》里去读那些振奋人心的仙童半导体的故事,也比你不断的知乎看着水的一笔的帖子要强的多。现在,最可怕的是大家在知乎上消费着垃圾,却依然觉得自己在吃着奶酪,学习的过程都是伴随着思考和痛苦的,如果你边笑边学习,那你真的得醒醒了。
引用了代码家的这句话。我很同意这个观点,不要在知乎上刷垃圾了,这个跟在微博刷鸡汤是一个道理,虽然我们有时候需要这种鸡汤,但是绝大多数时刻都是不需要的。好了,回到正题,让我们一起来看到第三次作业吧。
作业要求
- 使用contentprovider获取本机音乐文件,列表展示,可参考例子工程MusicLoader
- 点击任意列表项,可用service播放,并可通过按钮控制(同作业二,本地文件播放参考例子工程MusicLoader)
- 点击列表项跳转至另一Activity,通过网络请求获取歌词下载链接,并用gson解析得到的json文件,将所有下载链接显示在界面上。可参考例子工程GetLyricByWebService,有关android studio中GSON的使用请参考博文(http://blog.csdn.net/qq_32583189/article/details/51440297)。获取歌词的请求地址为http://gecimi.com/api/lyric/,方式为get,参数为歌名(如http://gecimi.com/api/lyric/海阔天空),具体可参看API文档http://api.geci.me/en/latest/
- 本作业需要SD卡读取权限和internet访问权限,请注意配置AndroidManifest
- 调试并运行程序,并在作业平台提交该项目工程目录的压缩包和运行效果截图。
项目分析
首先,我们看到作业要求1 使用contentprovide获取本机音乐文件,列表展示,可参考例子工程MusicLoader
,那我们就把老师给的例子工程给下载下来参考参考。将下载下来的项目解压,然后进入java文件夹里面,可以看到两个java文件MainActivity.java
和 MusicLoader.java
。这里给大家简单解释一下吧。
MusicLoader.java
通过ContentProvider从外部存储中获取音乐的信息,并将其封装在MusicInfo
里面,然后再将MusicInfo
封装在ArrayList
中,并对外提供一个getMusicList()
方法,返回所有本地音乐信息的集合。
MainActivity.java
继承自ListActivity
,那什么是ListActivity
呢?其实就是Activity
里面嵌套了一个ListView,并可以直接通过getListView()
获得ListView
。MainActivity
将音乐的消息(包括歌名、作者名、图片)展示在界面中。
当然,为了能让你的程序读你的存储器,需要加权限<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
。大概的思路就是这样了。如果大家把老师给的代码添加到自己的工程的话,是可以直接点击Item就听到歌声的。但是我们需要的不仅仅如此,我们还需要更加强大的功能。再看到作业要求3点击列表项跳转至另一Activity,通过网络请求获取歌词下载链接,并用gson解析得到的json文件,将所有下载链接显示在界面上,可参考例子工程GetLyricByWebService
,那我们参考参考例子工程GetLyricByWebService
,打开工程目录,可以看到有四个java文件LoadDialog
、LyricActivity
、LyricListBean
、MainActivity
,给大家稍微解释一下。(千万不要着急,代码在后面马上就要贴了)
LoadDialog.java
是自定义的一个Dialog
(对话框),这里用来展示网络请求状态,如果是正在请求,会有一个小球不停地旋转。并在里面提供了show()
和dismiss()
方法。
MainActivity.java
是将歌曲的信息传给LyricActivity.java
文件,并跳转至LyricActivity
。
LyricActivity.java
是获取从MainActivity
传过来的歌曲信息,并通过HttpURLConnection
网络请求得到数据显示在界面上。
LyricListBean
是歌曲信息的实体类,这个是Gson解析json数据的一种格式,使用Gson解析json数据,详情请戳 Android Studio配置与使用GSON框架解析json数据。
当然,为了能进行操作,需要加入<uses-permission android:name="android.permission.INTERNET"/>
权限。这个时候我们如果运行这个例子的话,点击App中的Button,会跳转到LyricActivity
界面,并且会显示歌词的所有下载链接。
那么所有要求里面只剩下作业要求2了,结合我们上次的作业,没看过的同学可以先看第二次作业——Service、Listview与ACtivity参数传递,然后将这三个项目整合到一起,就可以大功告成了。那让我们一起动手来做吧。
作业实战
我们需要建立一个新的项目job3。在写代码之前,我们先把那三个例子里面的drawable目录下的图片都放进自己的drawable目录下。可以添加的有music.png,default_img.png,然后还有作业二的三个按钮pause.png,stop.png,play.png。另外建议先将结构视图从Android换到Project。
点击红色箭头处的下拉菜单,将结构视图变为Project。为了让工程项目更加清晰,然后多建立几个包。
右键红色箭头处,然后选中New -> Package,新建五个Package(包),activity,entry,service,ui以及utils。其中activity用来存放Activity,service用来存放Service,entry用来存放Bean实体类,ui用来存放界面相关的类,utils用来存放工具类。这样代码是不是很清晰,当我们需要找Activity代码的时候,只需要点开activity包即可。在新建完Package之后,我们把MainActivity
剪切到activity包内,这时AndroidStudio会自动地把AndroidManifest.xml
的关于MainActivity
的配置信息改变,而不需要我们手动去改。MainActivity.java
代码如下所示:
package edu.whut.xucheng.job3.activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import edu.whut.xucheng.job3.R;
import edu.whut.xucheng.job3.entry.MusicLoader;
import edu.whut.xucheng.job3.service.PlayerService;
import edu.whut.xucheng.job3.utils.TextUtils;
import static edu.whut.xucheng.job3.R.drawable.music;
public class MainActivity extends AppCompatActivity {
/**
* 播放按钮
*/
private ImageButton play;
/**
* 暂停按钮
*/
private ImageButton pause;
/**
* 停止按钮
*/
private ImageButton stop;
/**
* ListView的实例对象
*/
private ListView mListView = null;
/**
* PlayerService的实例对象
*/
private PlayerService mPlayService;
/**
* MediaPlayer的实例对象
*/
MediaPlayer mediaPlayer;
/**
* 存放歌曲信息
*/
List<MusicLoader.MusicInfo> my_list;
/**
* 存取从MusicInfo获得的信息
*/
ArrayList<Map<String, Object>> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
init();
initData();
initList();
}
/**
* bindService将Activty跟Service建立连接,在Activity中可以调用Service的方法,用startService做不到
* 如果要用startService做的话,只能传递数据过去告诉Service什么时候该做什么。
*/
private void init() {
Intent serviceIntent = new Intent(MainActivity.this, PlayerService.class);
bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
}
/**
* 初始化数据
*/
private void initData() {
MusicLoader musicLoader = MusicLoader.instance(this.getContentResolver());
my_list = musicLoader.getMusicList();
int lengh = my_list.size();
for (int i = 0; i < lengh; i++) {
Map<String, Object> item = new HashMap<>();
item.put("image", music);
item.put("title", my_list.get(i).getTitle().substring(0, my_list.get(i).getTitle().lastIndexOf('.')));
item.put("text", my_list.get(i).getArtist());
item.put("url", my_list.get(i).getUrl());
mData.add(item);
}
}
/**
* 初始化View
*/
private void initView() {
mListView = (ListView) this.findViewById(R.id.list_item);
play = (ImageButton) this.findViewById(R.id.play);
pause = (ImageButton) this.findViewById(R.id.pause);
stop = (ImageButton) this.findViewById(R.id.stop);
play.setOnClickListener(new MyClickLister());
pause.setOnClickListener(new MyClickLister());
stop.setOnClickListener(new MyClickLister());
}
/**
* 初始化ListView
*/
private void initList() {
SimpleAdapter adapter = new SimpleAdapter(this, mData, R.layout.icon_list,
new String[]{"image", "title", "text"}, new int[]{R.id.image, R.id.title, R.id.text});
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position,
long id) {
// ToastUtils.showToast(MainActivity.this, "您选择的歌曲路劲为:" + my_list.get(position).getUrl());
mPlayService.play(my_list.get(position).getUrl());
//在跳转到LyricActivity的时候,把歌曲的名字跟作者的名字传过去
Intent intent = new Intent(MainActivity.this, LyricActivity.class);
Bundle bundle = new Bundle();
bundle.putString("title", TextUtils.getTitleString(my_list.get(position).getTitle().substring(0, my_list.get(position).getTitle().lastIndexOf('.'))));
bundle.putString("text", my_list.get(position).getArtist());
intent.putExtras(bundle);
startActivity(intent);
}
});
}
/**
* 跟服务Service建立连接。
* 连接成功回调onServiceConnected方法,连接失败回调onServiceDisconnected方法。
*/
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayService = ((PlayerService.MyBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mPlayService = null;
}
};
/**
* 在Activity退出的时候,释放掉MediaPlayer的资源
*/
@Override
protected void onStop() {
if (mediaPlayer != null) {
mediaPlayer.release();
}
super.onStop();
}
/**
* 写一个自己的类实现View.OnClickListener。
*/
class MyClickLister implements View.OnClickListener {
@Override
public void onClick(View view) {
switch (view.getId()) {
//播放
case R.id.play:
mPlayService.replay();
break;
//暂停
case R.id.pause:
mPlayService.pause();
break;
//停止
case R.id.stop:
//说明:这里我本来想用stop函数的,但是我在真机上面调试的时候,发现stop之后,
//声音会断断续续的,我就用了pause()函数,stop跟pause的唯一区别就是stop之后要prepare()。
//如果你的手机可以用stop的话,将下面的pause()注释掉,将stop()取消注释。
mPlayService.pause();
// mPlayService.stop();
break;
}
}
}
}
这里我们没有直接继承自ListActivity
,为什么呢?看到作业要求2点击任意列表项,可用service播放,并可通过按钮控制(同作业二,本地文件播放参考例子工程MusicLoader)
因为我们还需要在底下加几个按钮。如果直接继承自ListActivity,这个时候Activity只有一个ListView布局,我们不能往里面添加按钮。大家观察MainActivity.java
有没有发现这些代码都很熟悉,是的,大多数跟第二次作业——Service、Listview与ACtivity参数传递 是重合的。
那区别在哪里呢?
在ListView的Item点击事件中,服务mPlayService的
play()
方法,传递的不是position,而是url。因为第二次作业的歌曲是自己写死的,定义在一个数组里面,故可以传position。例如:我们点开第二个Item,对应的position为1,又服务里面定义了三首歌的数组,假设a数组为歌曲的数组,那么a[postion = 1]对应的是第二首歌。而第三次作业的歌曲不是的,你并不知道你的Item对应哪首歌,你新下载几首歌,ListView就变了。然后我还需要简单地说一下startService()
跟bindService()
这两种启动服务的方法的区别,用startService()
启动服务后,调用者(这里是MainActivity
)跟服务没关系了,而用bindService()
启动服务的话,调用者跟服务之间还存在联系,服务可以通过下面的方式给调用者传递自身。public class MyBinder extends Binder { public PlayerService getService() { return PlayerService.this; } }
而调用者可以通过下面方式得到服务本身。
private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mPlayService = ((PlayerService.MyBinder) service).getService(); } @Override public void onServiceDisconnected(ComponentName name) { mPlayService = null; } };
这样,就可以在调用者端直接使用服务的代码了。
MainActivity
向LyricActivity
传递过去了参数,包括(歌名还有作者名)。
另外我们可以看到,在Bundle
的putString()
方法中,用到了TextUtils.getTitleString()
方法,这是一个处理文本的工具类。在utils包下,新建一个TextUtils.java
文件,代码如下:
package edu.whut.xucheng.job3.utils;
/**
* 文本的工具类
* Created by dragonhaw on 2016/11/6.
*/
public class TextUtils {
/**
* 如果前面有歌手名字的话,去掉前面的歌手名字。例如:Beyond - 海阔天空 变为海阔天空
*/
public static String getTitleString(String oldTitle) {
if(!oldTitle.contains("-")) {
return oldTitle;
} else {
int index = oldTitle.indexOf("-");
return oldTitle.substring(index + 2);
}
}
}
一首歌存入外部存储中,歌曲的名字会是“作者名字 - 歌名”,然而这样的歌曲名字,我们查Api会发现没有。比如说“http://gecimi.com/api/lyric/Beyond - 海阔天空”,我们通过浏览器进去,可得到如下结果:
可以看到,结果为空。而如果直接以歌名去查找“http://gecimi.com/api/lyric/海阔天空”,可以得到如下结果(json数据):
这样就有结果了。为了寻找到有json数据的API,所以我们自己写了这个方法将“作者名字 - 歌名”变为“歌名”。
MainActivity.java
相对应的布局文件activity_main.xml
代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_weight="6"
android:layout_height="0dp"
android:divider="#000"
android:dividerHeight="1dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:orientation='horizontal'>
<ImageButton
android:id="@+id/play"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="@drawable/btn_selector"
android:src="@drawable/play" />
<ImageButton
android:id="@+id/pause"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="@drawable/btn_selector"
android:src="@drawable/pause" />
<ImageButton
android:id="@+id/stop"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="@drawable/btn_selector"
android:src="@drawable/stop" />
</LinearLayout>
</LinearLayout>
可以看到,ImageButton的android:background="@drawable/btn_selector"
属性使用了btn_selector
,故我们需要在drawable目录下,新建一个btn_selector.xml文件,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/colorAccent"/>
</selector>
ListView对应的Item的布局文件,在layout布局文件夹下新建一个icon_list.xml文件,其代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:padding="2dip" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:maxLines="1"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@+id/image"
android:gravity="center_vertical"
android:textSize="15dip" />
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/image"
android:ellipsize="marquee"
android:singleLine="true"
android:textSize="20dip" />
</RelativeLayout>
这几个布局文件就不讲了。为了能让歌曲播放,我们需要在service包下新建一个PlayerService.java
文件,代码如下:
package edu.whut.xucheng.job3.service;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import java.io.IOException;
import edu.whut.xucheng.job3.utils.ToastUtils;
public class PlayerService extends Service {
private MediaPlayer mediaPlayer;
//播放文件url
private String path;
private final IBinder binder = new MyBinder();
/**
* 客户端的bindService对应的是服务Service的onBind()方法(startService -> onstartCommand)
* @param intent 客户端即Activity的bindService()传入的Intent
*/
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class MyBinder extends Binder {
public PlayerService getService() {
return PlayerService.this;
}
}
/**
* 播放音乐,有参
* 给ListView提供,当点击不同的Item时,会播放不同的音乐。
*/
public void play(String path) {
if (mediaPlayer != null) {
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(path);
mediaPlayer.prepare();
} catch (Exception e) {
e.printStackTrace();
}
mediaPlayer.start();
}
/**
* 播放音乐,无参数
* 给按钮的点击事件提供的,因为在按钮中没有position,不知道要播放哪一个。
*/
public void replay() {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
try {
if(path != null) {
mediaPlayer.setDataSource(path);
mediaPlayer.prepare();
} else {
ToastUtils.showToast(getApplicationContext(), "请选择歌曲");
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
/**
* 暂停音乐
* 每次暂停isPause标识设为false并得到当前的播放位置
*/
public void pause() {
if (mediaPlayer.isPlaying() && mediaPlayer != null) {
mediaPlayer.pause();
}
}
/**
* 停止音乐
* 这个stop和pause不一样,stop之后再想播放音乐要重新prepare一次
*/
public void stop() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
try {
mediaPlayer.prepare();
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
}
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
}
与作业二不同的是,这个服务的play()
函数中用了mediaPlayer.setDataSource(path)
代码来找到歌曲的本地路径,然后再调用mediaPlayer.start()
进行播放。另外,我们的replay()函数,是没有参数的,又必须要需要用到歌曲存储的url,为了不报错,而且用户体验良好一点,我们先判断path为不为空,如果为空,弹出Toast,不然则播放音乐。这里的Toast我们也进行了处理,系统提供的Toast,在我们点击几次之后,即使我们已经停止点击了,它仍会不停地弹出Toast,并且你点几下,它会弹几下。所以我们自己在utils包下,新建一个ToastUtils.java
文件,其代码如下所示:
package edu.whut.xucheng.job3.utils;
import android.content.Context;
import android.widget.Toast;
/**
* Toast的工具类,当不停地点击出Toast的时候,Toast不会一直显示
* Created by dragonhaw on 2016/11/6
*/
public class ToastUtils {
/** 之前显示的内容 */
private static String oldMsg;
/** Toast对象 */
private static Toast toast = null;
/** 第一次时间 */
private static long oneTime = 0;
/** 第二次时间 */
private static long twoTime = 0;
/**
* 显示Toast
*/
public static void showToast(Context context, String message) {
if (toast == null) {
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.show();
oneTime = System.currentTimeMillis();
} else {
twoTime = System.currentTimeMillis();
if (message.equals(oldMsg)) {
if (twoTime - oneTime > Toast.LENGTH_SHORT) {
toast.show();
}
} else {
oldMsg = message;
toast.setText(message);
toast.show();
}
}
oneTime = twoTime;
}
}
主页面跟服务都写好了,我们继续来写歌词界面,在activty包下新建LyricActivty.java
文件,其代码如下所示:
package edu.whut.xucheng.job3.activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import com.google.gson.GsonBuilder;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import edu.whut.xucheng.job3.ui.LoadDialog;
import edu.whut.xucheng.job3.R;
import edu.whut.xucheng.job3.entry.LyricListBean;
public class LyricActivity extends AppCompatActivity {
String result="";
LyricListBean my_lyricList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lyric);
//新页面接收数据
Bundle bundle = this.getIntent().getExtras();
final String Title = bundle.getString("title");
String Text = bundle.getString("text");
TextView title2=(TextView)findViewById(R.id.title2);
TextView text2=(TextView)findViewById(R.id.text2);
final TextView Lyric_list=(TextView)findViewById(R.id.lyrics_list);
title2.setText(Title);
text2.setText(Text);
LoadDialog.show(this);
new Thread(new Runnable() {
@Override
public void run() {
String song_name="";
String path ="http://gecimi.com/api/lyric/";
try {
song_name = URLEncoder.encode(Title,"UTF-8");
URL url = new URL(path+song_name);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
//conn.setReadTimeout(5000);// 设置超时的时间
//conn.setConnectTimeout(5000);// 设置链接超时的时间
conn.setDoOutput(true);
conn.setDoInput(true);
if (conn.getResponseCode() == 200) {
// 获取响应的输入流对象
InputStream is = conn.getInputStream();
// 创建字节输出流对象
ByteArrayOutputStream os = new ByteArrayOutputStream();
// 定义读取的长度
int len = 0;
// 定义缓冲区
byte buffer[] = new byte[1024];
// 按照缓冲区的大小,循环读取
while ((len = is.read(buffer)) != -1) {
// 根据读取的长度写入到os对象中
os.write(buffer, 0, len);
}
// 释放资源
is.close();
os.close();
// 返回字符串
result = new String(os.toByteArray());
my_lyricList=new GsonBuilder().create().fromJson(result,LyricListBean.class);
result="";
if(my_lyricList.getResult().size()!=0){
for (LyricListBean.ResultBean tmp:my_lyricList.getResult()){
result=result+tmp.getLrc()+"\n";
}
}else{
result="未找到匹配歌词";
}
} else {
System.out.println("------------------链接失败-----------------");
}
} catch (Exception e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
LoadDialog.dismiss(LyricActivity.this);
Lyric_list.setText(result);
}
});
}
}).start();
}
}
这个也是老师提供的例子的源码,我没有丝毫改动。因为这里用到了gson库,所以我们需要在module里面的(也就是app目录下的)build.gradle文件添加gson的依赖 compile 'com.google.code.gson:gson:2.8.0'
,如下图所示:
可以看到LyricActivity
用到了LyricListBean.java
,我们在entry包下面新建一个文件LyricListBean.java
, 代码如下所示:
package edu.whut.xucheng.job3.entry;
import java.util.List;
/**
* Created by 珀石 on 2016/11/4.
*/
public class LyricListBean {
/**
* count : 15
* code : 0
* result : [{"aid":2848529,"lrc":"http://s.gecimi.com/lrc/344/34435/3443588.lrc","song":"海阔天空","artist_id":2,"sid":3443588},{"aid":2346662,"lrc":"http://s.gecimi.com/lrc/274/27442/2744281.lrc","song":"海阔天空","artist_id":2396,"sid":2744281},{"aid":1889264,"lrc":"http://s.gecimi.com/lrc/210/21070/2107014.lrc","song":"海阔天空","artist_id":8715,"sid":2107014},{"aid":2075717,"lrc":"http://s.gecimi.com/lrc/236/23651/2365157.lrc","song":"海阔天空","artist_id":8715,"sid":2365157},{"aid":1563419,"lrc":"http://s.gecimi.com/lrc/166/16685/1668536.lrc","song":"海阔天空","artist_id":9208,"sid":1668536},{"aid":1567586,"lrc":"http://s.gecimi.com/lrc/167/16739/1673997.lrc","song":"海阔天空","artist_id":9208,"sid":1673997},{"aid":1571906,"lrc":"http://s.gecimi.com/lrc/167/16796/1679605.lrc","song":"海阔天空","artist_id":9208,"sid":1679605},{"aid":1573814,"lrc":"http://s.gecimi.com/lrc/168/16819/1681961.lrc","song":"海阔天空","artist_id":9208,"sid":1681961},{"aid":1656038,"lrc":"http://s.gecimi.com/lrc/179/17907/1790768.lrc","song":"海阔天空","artist_id":9208,"sid":1790768},{"aid":1718741,"lrc":"http://s.gecimi.com/lrc/187/18757/1875769.lrc","song":"海阔天空","artist_id":9208,"sid":1875769},{"aid":2003267,"lrc":"http://s.gecimi.com/lrc/226/22642/2264296.lrc","song":"海阔天空","artist_id":9208,"sid":2264296},{"aid":2020610,"lrc":"http://s.gecimi.com/lrc/228/22889/2288967.lrc","song":"海阔天空","artist_id":9208,"sid":2288967},{"aid":2051678,"lrc":"http://s.gecimi.com/lrc/233/23323/2332322.lrc","song":"海阔天空","artist_id":9208,"sid":2332322},{"aid":2412704,"lrc":"http://s.gecimi.com/lrc/283/28376/2837689.lrc","song":"海阔天空","artist_id":9208,"sid":2837689},{"aid":2607041,"lrc":"http://s.gecimi.com/lrc/311/31116/3111659.lrc","song":"海阔天空","artist_id":9208,"sid":3111659}]
*/
private int count;
private int code;
/**
* aid : 2848529
* lrc : http://s.gecimi.com/lrc/344/34435/3443588.lrc
* song : 海阔天空
* artist_id : 2
* sid : 3443588
*/
private List<ResultBean> result;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public List<ResultBean> getResult() {
return result;
}
public void setResult(List<ResultBean> result) {
this.result = result;
}
public static class ResultBean {
private int aid;
private String lrc;
private String song;
private int artist_id;
private int sid;
public int getAid() {
return aid;
}
public void setAid(int aid) {
this.aid = aid;
}
public String getLrc() {
return lrc;
}
public void setLrc(String lrc) {
this.lrc = lrc;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
public int getArtist_id() {
return artist_id;
}
public void setArtist_id(int artist_id) {
this.artist_id = artist_id;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
}
}
然后再在layout布局文件夹中新建一个activity_lyric.xml
文件,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="edu.whut.xucheng.job3.activity.LyricActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/title2"
android:layout_alignStart="@+id/text2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="@+id/text2"
android:layout_below="@+id/title2"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Small Text"
android:id="@+id/lyrics_list"
android:layout_marginTop="60dp"
android:layout_below="@+id/text2"
android:layout_centerHorizontal="true" />
</RelativeLayout>
在ui包下,将老师的LoadDialog.java
文件复制进去,代码如下所示:
package edu.whut.xucheng.job3.ui;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.text.TextUtils;
import android.view.Display;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import edu.whut.xucheng.job3.R;
/**
* Created by 珀石 on 2016/11/4.
*/
public class LoadDialog extends Dialog {
/**
* LoadDialog
*/
private static LoadDialog loadDialog;
/**
* cancelable, the dialog dimiss or undimiss flag
*/
private boolean cancelable;
/**
* if the dialog don't dimiss, what is the tips.
*/
private String tipMsg;
/**
* the LoadDialog constructor
*
* @param ctx Context
* @param cancelable boolean
* @param tipMsg String
*/
public LoadDialog(final Context ctx, boolean cancelable, String tipMsg) {
super(ctx);
this.cancelable = cancelable;
this.tipMsg = tipMsg;
this.getContext().setTheme(android.R.style.Theme_DeviceDefault_Dialog_NoActionBar_MinWidth);
setContentView(R.layout.dialog_layout);
// 必须放在加载布局后
setparams();
TextView tv = (TextView) findViewById(R.id.tvLoad);
if (!TextUtils.isEmpty(tipMsg)) {
tv.setVisibility(View.VISIBLE);
tv.setText(tipMsg);
}
}
private void setparams() {
this.setCancelable(cancelable);
this.setCanceledOnTouchOutside(false);
WindowManager windowManager = getWindow().getWindowManager();
Display display = windowManager.getDefaultDisplay();
WindowManager.LayoutParams lp = this.getWindow().getAttributes();
// Dialog宽度
lp.width = (int) (display.getWidth() * 0.7);
Window window = getWindow();
window.setAttributes(lp);
window.getDecorView().getBackground().setAlpha(0);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (!cancelable) {
Toast.makeText(getContext(), tipMsg, Toast.LENGTH_SHORT).show();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
/**
* show the dialog
*
* @param context
*/
public static void show(Context context) {
show(context, null, true);
}
/**
* show the dialog
*
* @param context Context
* @param message String
*/
public static void show(Context context, String message) {
show(context, message, true);
}
/**
* show the dialog
*
* @param context Context
* @param resourceId resourceId
*/
public static void show(Context context, int resourceId) {
show(context, context.getResources().getString(resourceId), true);
}
/**
* show the dialog
*
* @param context Context
* @param message String, show the message to user when isCancel is true.
* @param cancelable boolean, true is can't dimiss,false is can dimiss
*/
private static void show(Context context, String message, boolean cancelable) {
if (context instanceof Activity) {
if (((Activity) context).isFinishing()) {
return;
}
}
if (loadDialog != null && loadDialog.isShowing()) {
return;
}
loadDialog = new LoadDialog(context, cancelable, message);
loadDialog.show();
}
/**
* dismiss the dialog
*/
public static void dismiss(Context context) {
try {
if (context instanceof Activity) {
if (((Activity) context).isFinishing()) {
loadDialog = null;
return;
}
}
if (loadDialog != null && loadDialog.isShowing()) {
Context loadContext = loadDialog.getContext();
if (loadContext != null && loadContext instanceof Activity) {
if (((Activity) loadContext).isFinishing()) {
loadDialog = null;
return;
}
}
loadDialog.dismiss();
loadDialog = null;
}
} catch (Exception e) {
e.printStackTrace();
loadDialog = null;
}
}
}
然后再在entry包下,把老师例子中的MusicLoader.java
文件复制进去,如果不想找,也可以直接复制博客中的,MusicLoader.java
代码如下所示:
package edu.whut.xucheng.job3.entry;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore.Audio.Media;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by 珀石 on 2016/11/4.
*/
public class MusicLoader {
private static final String TAG = "edu.whut.xucheng.job3.entry.MusicLoader";
private static List<MusicInfo> musicList = new ArrayList<MusicInfo>();
private static MusicLoader musicLoader;
private static ContentResolver contentResolver;
//Uri,指向external的database
private Uri contentUri = Media.EXTERNAL_CONTENT_URI;
//projection:选择的列; where:过滤条件; sortOrder:排序。
private String[] projection = {
Media._ID,
Media.DISPLAY_NAME,
Media.DATA,
Media.ALBUM,
Media.ARTIST,
Media.DURATION,
Media.SIZE
};
private String where = "mime_type in ('audio/mpeg','audio/x-ms-wma') and is_music > 0 " ;
private String sortOrder = Media.DATA;
public static MusicLoader instance(ContentResolver pContentResolver){
if(musicLoader == null){
contentResolver = pContentResolver;
musicLoader = new MusicLoader();
}
return musicLoader;
}
@SuppressLint("LongLogTag")
private MusicLoader(){ //利用ContentResolver的query函数来查询数据,然后将得到的结果放到MusicInfo对象中,最后放到数组中
Cursor cursor = contentResolver.query(contentUri, projection, where, null, sortOrder);
if(cursor == null){
Log.v(TAG,"cursor == null.");
}else if(!cursor.moveToFirst()){
Log.v(TAG,"moveToFirst()->false.");
}else{
int displayNameCol = cursor.getColumnIndex(Media.DISPLAY_NAME);
int albumCol = cursor.getColumnIndex(Media.ALBUM);
int idCol = cursor.getColumnIndex(Media._ID);
int durationCol = cursor.getColumnIndex(Media.DURATION);
int sizeCol = cursor.getColumnIndex(Media.SIZE);
int artistCol = cursor.getColumnIndex(Media.ARTIST);
int urlCol = cursor.getColumnIndex(Media.DATA);
do{
String title = cursor.getString(displayNameCol);
String album = cursor.getString(albumCol);
long id = cursor.getLong(idCol);
int duration = cursor.getInt(durationCol);
long size = cursor.getLong(sizeCol);
String artist = cursor.getString(artistCol);
String url = cursor.getString(urlCol);
MusicInfo musicInfo = new MusicInfo(id, title);
musicInfo.setAlbum(album);
musicInfo.setDuration(duration);
musicInfo.setSize(size);
musicInfo.setArtist(artist);
musicInfo.setUrl(url);
musicList.add(musicInfo);
}while(cursor.moveToNext());
}
}
public List<MusicInfo> getMusicList(){
return musicList;
}
public Uri getMusicUriById(long id){
Uri uri = ContentUris.withAppendedId(contentUri, id);
return uri;
}
//下面是自定义的一个MusicInfo子类
public static class MusicInfo {
private long id;
private String title;
private String album;
private int duration;
private long size;
private String artist;
private String url;
public MusicInfo(){
}
public MusicInfo(long pId, String pTitle){
id = pId;
title = pTitle;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
}
LoadDialog.java
对应的布局文件dialog_layout.xml
,也是在layout布局文件夹中新建,dialog_layout.xml
代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_centerInParent="true"
android:background="@drawable/shape_cir_bg">
<ProgressBar
android:id="@+id/loading_progress"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp" />
<TextView
android:id="@+id/tvLoad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/loading_progress"
android:gravity="center_vertical|left"
android:text="正在加载..."
android:textColor="@android:color/white" />
</RelativeLayout>
</RelativeLayout>
可以看到RelativeLayout
的属性android:background="@drawable/shape_cir_bg"
中使用了shape_cir_bg
,故我们需要在drawable目录下,右键drawable,选中New -> Drawable resource file -> filename为shape_cir_bg.xml
,RootElement为“selector”,shape_cir_bg.xml
代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#cc333333" />
<corners android:radius="5dp" />
</shape>
整个项目的项目结构如图所示:
最后,别忘了在AndroidManifest.xml
文件中加入
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
...
<activity android:name=".activity.LyricActivity"/>
<service android:name=".service.PlayerService">
</service>
如下图所示:
这样,我们就可以运行看下效果了。
嘿嘿,这样我们第三次作业就做好了。最好用真机调试,因为模拟器里面是没有歌曲的,当然也可以自己下载几首歌曲。界面丑了一点,大家如果大家喜欢的话,可以自己设计几个好看的按钮。大家写完之后别忘了提交哦!