今天需要展示两个歌手的歌曲列表
需要用到的免费歌曲 API 接口:FREE API
fragment_music_list 布局包含一个 ListView 用来展示音乐列表
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
NewMusicListFragment
public class NewMusicListFragment extends Fragment {
private ListView listView;
private MusicAdapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_music_list, null);
setViews(view);
//调用业务层代码,加载新歌榜榜单
MusicModel model = new MusicModel();
//几乎所有的异步方法都是没有返回值的
//AsyncTask
model.loadNewMusicList("许嵩", new MusicCallback() {
@Override
public void onMusicListLoaded(List<MusicItem> list) {
//更新Adapter
setAdapter(list);
}
});
return view;
}
private void setViews(View view) {
listView = view.findViewById(R.id.listView);
}
/**
* 更新Adapter
*
* @param musics
*/
public void setAdapter(List<MusicItem> musics) {
adapter = new MusicAdapter(getActivity(), musics);
listView.setAdapter(adapter);
}
}
这里我们用一个免费接口查询网易云音乐搜索结果:网易云歌曲搜索
根据返回结果,我们封装一个 Music 类来描述返回歌曲列表信息
Music
public class Music {
public int code;
public MusicDataModel data;
}
public class MusicDataModel {
public List<MusicItem> songs;
}
public class MusicItem {
public String id;
public String name;
public String albumName;
public String albumPic;
public AlbumModel album;
}
public class AlbumModel {
public String name;
public String img1v1Url;
}
MusicAdapter
/**
* 音乐列表适配器
*/
public class MusicAdapter extends BaseAdapter {
private Context context;
private List<MusicItem> musics;
private LayoutInflater inflater;
public MusicAdapter(Context context, List<MusicItem> musics) {
super(context, musics);
this.context = context;
this.musics = musics;
this.inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return musics.size();
}
@Override
public MusicItem getItem(int i) {
return musics.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder = null;
if (view == null) {
view = inflater.inflate(R.layout.item_lv_music, null);
holder = new ViewHolder();
holder.imgPic = view.findViewById(R.id.img_Pic);
holder.tvName = view.findViewById(R.id.tv_name);
holder.tvAlbum = view.findViewById(R.id.tv_album);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
//给控件赋值
MusicItem music = getItem(i);
holder.tvName.setText(music.name);
holder.tvAlbum.setText("《"+music.albumName+"》");
//图片在服务器,本来该起线程显示的,但是,这是adapter.getView()方法
//每次图片显示时,都要启线程的话,手机会炸的...
return view;
}
class ViewHolder {
ImageView imgPic;
TextView tvName;
TextView tvAlbum;
}
}
item_lv_music
<?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="56dp"
android:padding="3dp">
<ImageView
android:id="@+id/img_Pic"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="10dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/img_Pic"
android:textColor="#fff"
android:textSize="15dp" />
<TextView
android:id="@+id/tv_album"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/img_Pic"
android:textColor="#999"
android:textSize="13dp" />
</RelativeLayout>
MusicModel
/**
* 音乐相关的业务层
*/
public class MusicModel {
/**
* 加载新歌榜列表 异步(启线程)
*
* @param singer 歌手
*/
public void loadNewMusicList(final String singer, final MusicCallback callback) {
InnerSyncTask task = new InnerSyncTask(singer, callback);
//执行异步任务
task.execute();
}
private class InnerSyncTask extends AsyncTask<String, String, List<MusicItem>> {
MusicCallback callback;
String singer;
public InnerSyncTask(String singer, MusicCallback callback) {
this.callback = callback;
this.singer = singer;
}
//发送http请求,获取响应数据,并解析
@Override
protected List<MusicItem> doInBackground(String... strings) {
String url = UrlFactory.getNewMusicListUrl(singer);
Log.i("url", "url" + url);
try {
InputStream in = HttpUtils.getInputStream(url);
List<MusicItem> lists = JsonParser.parseMusicList(in);
return lists;
//Log.d("MusicList","音乐列表:"+lists.toString());
/* String xml = HttpUtils.isToString(in);
Log.i("AsyncTask", "响应" + xml);*/
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* 在主线程中执行,我们应该在此更新UI
*
* @param music
*/
@Override
protected void onPostExecute(List<MusicItem> music) {
callback.onMusicListLoaded(music);
}
}
}
UrlFactory
/**
* 用于获取需要的Url地址字符串
*/
public class UrlFactory {
/**
* url地址
*
* @param keyword
* @return json
*/
public static String getNewMusicListUrl(String keyword) {
String url = "https://v2.alapi.cn/api/music/search?token=注册后自己主页获取token&keyword=" + keyword;
return url;
}
}
HttpUtils
/**
* Http请求的工具类
*/
public class HttpUtils {
/**
* 把输入流按照utf-8编码解析为字符串返回
*
* @param is
* @return
*/
public static String isToString(InputStream is) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
/**
* 通过Url路径发送HttpGet请求,获取响应数据
*
* @param path
* @return
*/
public static InputStream getInputStream(String path) throws IOException {
//URL
URL url = new URL(path);
//HttpConnection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//getInputStream
conn.setRequestMethod("GET");
InputStream is = conn.getInputStream();
return is;
}
/**
* 发送post请求,获取响应数据
*
* @param path
* @param params
* @return
*/
public static InputStream postInputStream(String path, Map<String, String> params) throws IOException {
//URL
URL url = new URL(path);
//HttpConnection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//getInputStream
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
//遍历Map,把键值对拼接为参数字符串
StringBuilder sb = new StringBuilder();
Set<String> keys = params.keySet();
Iterator<String> ite = keys.iterator();
while (ite.hasNext()) {
String key = ite.next();
String value = params.get(key);
sb.append(key + "=" + value + "&");
}
//把最后一个&移除
sb.deleteCharAt(sb.length() - 1);
String param = sb.toString();
os.write(param.getBytes("utf-8"));
os.flush();
InputStream is = conn.getInputStream();
return is;
}
}
MusicCallback
/**
* 音乐数据相关的回调接口
*/
public interface MusicCallback {
/**
* 当音乐列表加载完成将会执行该方法
*
* @param list
*/
void onMusicListLoaded(List<MusicItem> list);
}
JsonParser 用来解析json
public class JsonParser {
/**
* 通过输入流,通过XMLPULL解析方式 解析xml内容
*
* @param is
* @return List<Music>
*/
public static List<MusicItem> parseMusicList(InputStream is) throws IOException, JSONException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String json = sb.toString();
JSONObject obj = new JSONObject(json);
JSONObject data = obj.getJSONObject("data");
JSONArray songs = data.getJSONArray("songs");
List<MusicItem> list = new ArrayList<>();
for (int i = 0; i < songs.length(); i++) {
JSONObject song = songs.getJSONObject(i);
String id = song.getString("id");
String name = song.getString("name");
MusicItem musicItem = new MusicItem();
musicItem.id = id;
musicItem.name = name;
musicItem.albumName = song.getJSONObject("album").getString("name");
musicItem.albumPic = song.getJSONObject("album").getString("img1v1Url");
list.add(musicItem);
}
return list;
}
}
AndroidManifest增加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
运行结果
最后,如果返回 xml 格式,可以写一个 xml 解析类,如:
XmlParser
/**
* 解析xml文档的工具类
*/
public class XmlParser {
/**
* 通过输入流,通过XMLPULL解析方式 解析xml内容
*
* @param is
* @return List<Music>
*/
public static List<Music> parseMusicList(InputStream is) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, "utf-8");
int eventType = parser.getEventType();
List<Music> list = new ArrayList<>();
Music music = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
String name = parser.getName();
if(name.equals("song")){//开始创建Music
music = new Music();
//可以先添加到集合,然后再设置值,结果是一样的
list.add(music);
}else if(name.equals("pic_big")){
music.setPic_big(parser.nextText());
}else if(name.equals("pic_small")){
music.setPic_small(parser.nextText());
}else if(name.equals("publishtime")){
music.setPublishtime(parser.nextText());
}else if(name.equals("lrclink")){
music.setLrclink(parser.nextText());
}else if(name.equals("song_id")){
music.setSong_id(parser.nextText());
}else if(name.equals("title")){
music.setTitle(parser.nextText());
}else if(name.equals("author")){
music.setAuthor(parser.nextText());
}else if(name.equals("album_title")){
music.setAlbum_title(parser.nextText());
}else if(name.equals("artist_name")){
music.setArtist_name(parser.nextText());
}
break;
}
eventType = parser.next();
}
return list;
}
}
一旦涉及到异步操作,异步操纵后需要在主线程中执行后续业务时,需要使用回调机制