【达内课程】音乐播放器4.0(列表)

本文详细介绍了一款音乐APP如何利用免费API接口加载并展示歌手的歌曲列表,包括使用ListView展示音乐列表,自定义适配器,以及通过异步任务加载数据的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天需要展示两个歌手的歌曲列表

需要用到的免费歌曲 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;
    }
}

一旦涉及到异步操作,异步操纵后需要在主线程中执行后续业务时,需要使用回调机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值