Android开发之音乐播放器添加排行需求

news2024/11/16 5:32:20

Music统计功能需求
1.记录歌曲名称与次数(歌曲播放结束算一次),根据播放次数制作一个排行列表;(开始说要记录歌手,后面debug发现这个字段没有,暂时不记录)
2.记录播放歌曲的时长,时间累加;(经沟通,需要细分成每一个月的播放时长,另外再加一个首次使用的记录)
前几天需要实现这功能,昨天实现了,今天验证Ok,来谈谈我的做法,先上图(暂时版):
在这里插入图片描述
上面是一个简单的例子,具体UI后期需要给图再做调整,目前结构上面是一个通用的带返回键的titlebar,下面recyclerview加文本做数据展示,方便测试看数据,当然我都是用sqlite expert去查看数据:
表1
表2,目前只记录六月份和七月份的数据

设备的数据库
这里需要注意的是当我们从设备导出数据库的时候需要把.db和.db-shm和.db-wal都要导出,不然可能没有表和数据.
1.首先创建音乐播放数据库:

package com.hiby.music.musicinfofetchermaster.db;

import static com.hiby.music.musicinfofetchermaster.db.MusicRecordDao.COLUMN_PLAY_COUNT;
import static com.hiby.music.musicinfofetchermaster.db.MusicRecordDao.KEY_DB_TAB;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class MusicRecordOpenHelper extends SQLiteOpenHelper {

    private volatile static MusicRecordOpenHelper instances = null;

    public static final String DB_NAME = "music_record.db";

    public static final int DB_VERSION = 1;

    public static MusicRecordOpenHelper getInstances(Context context) {
        if (instances == null) {
            synchronized (MusicRecordOpenHelper.class) {
                if (instances == null) {
                    instances = new MusicRecordOpenHelper(context.getApplicationContext());
                }
            }
        }
        return instances;
    }

    public MusicRecordOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(MusicRecordDao.CREATE_TABLE_SONGS);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + KEY_DB_TAB);
        onCreate(db);
    }

    public void insertOrUpdateSong(String name, String author) {
        SQLiteDatabase db = instances.getWritableDatabase();

        // 查询数据库中是否已经存在相同的歌曲
        String selection = MusicRecordDao.COLUMN_NAME + " = ? AND " + MusicRecordDao.COLUMN_AUTHOR + " = ?";
        String[] selectionArgs = {name, author};

        Cursor cursor = db.query(
                KEY_DB_TAB,
                new String[]{COLUMN_PLAY_COUNT},
                selection,
                selectionArgs,
                null,
                null,
                null
        );

        if (cursor.moveToFirst()) {
            // 歌曲已存在,更新播放次数
            int playCount = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PLAY_COUNT));
            playCount++; // 增加播放次数

            ContentValues values = new ContentValues();
            values.put(COLUMN_PLAY_COUNT, playCount);

            db.update(KEY_DB_TAB, values, selection, selectionArgs);
        } else {
            // 歌曲不存在,插入新记录
            ContentValues values = new ContentValues();
            values.put(MusicRecordDao.COLUMN_NAME, name);
            values.put(MusicRecordDao.COLUMN_AUTHOR, author);
            values.put(COLUMN_PLAY_COUNT, 1); // 初始播放次数为1,因为这是第一次插入

            db.insert(KEY_DB_TAB, null, values);
        }

        cursor.close();
        db.close();
    }

    // 插入新歌曲
    public void insertSong(String name, String author) {
        SQLiteDatabase db = instances.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(MusicRecordDao.COLUMN_NAME, name);
        values.put(MusicRecordDao.COLUMN_AUTHOR, author);
        values.put(COLUMN_PLAY_COUNT, 0); // 初始播放次数为0
        db.insert(KEY_DB_TAB, null, values);
        db.close();
    }

    // 更新播放次数
    public void incrementPlayCount(String name, String author) {
        SQLiteDatabase db = instances.getWritableDatabase();
        String selection = MusicRecordDao.COLUMN_NAME + " = ? AND " + MusicRecordDao.COLUMN_AUTHOR + " = ?";
        String[] selectionArgs = {name, author};

        Cursor cursor = db.query(MusicRecordDao.KEY_DB_TAB, new String[]{COLUMN_PLAY_COUNT},
                selection, selectionArgs, null, null, null);

        if (cursor != null && cursor.moveToFirst()) {
            int playCount = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PLAY_COUNT));
            playCount++;

            ContentValues values = new ContentValues();
            values.put(COLUMN_PLAY_COUNT, playCount);

            db.update(MusicRecordDao.KEY_DB_TAB, values, selection, selectionArgs);
        }
        if (cursor != null) {
            cursor.close();
        }
        db.close();
    }

    // 获取按播放次数排序的音乐记录
    public Cursor getMusicSortedByPlayCount() {
        SQLiteDatabase db = this.getReadableDatabase();
        return db.query(KEY_DB_TAB, null, null, null, null, null, COLUMN_PLAY_COUNT + " DESC");
    }
}

这里我直接把对数据进行插入的逻辑和查询写在里面了,下面是建表,放在Dao里:


public class MusicRecordDao {

    public static final String KEY_DB_TAB = "music_record";
    public static final String COLUMN_ID = "id";
    public static final String COLUMN_NAME = "name";
    public static final String COLUMN_AUTHOR = "author";
    public static final String COLUMN_PLAY_COUNT = "play_count";

    public static final String CREATE_TABLE_SONGS = "CREATE TABLE " + KEY_DB_TAB + " ("
            + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
            + COLUMN_NAME + " TEXT NOT NULL, "
            + COLUMN_AUTHOR + " TEXT NOT NULL, "
            + COLUMN_PLAY_COUNT + " INTEGER DEFAULT 0)";

    public static final String CLEAN_MUSIC_TAB = "DELETE FROM " + KEY_DB_TAB;


}

2.根据音乐播放生命周期的onAudioComplete中,进行数据库的保存:

                @Override
                public void onAudioComplete(IPlayer player, AudioInfo audio) {
                    saveMusicRecord(audio);
                    LogPlus.d("###onAudioComplete###");
                }
                ...
   private static void saveMusicRecord(AudioInfo audio) {
        ThreadPoolExecutor.execute(() -> {
            SmartPlayerApplication.getMusicRecordOpenHelper().getWritableDatabase();
            String displayName = audio.displayName();
            SmartPlayerApplication.getMusicRecordOpenHelper().insertOrUpdateSong(displayName, "");
        });
    }

上面就实现了音乐播放文件名和播放次数的更新插入
3.在具体页面进行查询:

    private List getDataFromDb() {
        List<MusicRankModel> list = new ArrayList<>();
        MusicRecordOpenHelper musicRecordOpenHelper = SmartPlayerApplication.getMusicRecordOpenHelper();
        musicRecordOpenHelper.getReadableDatabase();
        Cursor cursor = musicRecordOpenHelper.getMusicSortedByPlayCount();

        if (cursor != null && cursor.moveToFirst()) {
            do {
                @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(MusicRecordDao.COLUMN_NAME));
                @SuppressLint("Range") int playCount = cursor.getInt(cursor.getColumnIndex(MusicRecordDao.COLUMN_PLAY_COUNT));
                list.add(new MusicRankModel(name, playCount));
            } while (cursor.moveToNext());

            cursor.close();
        }
        if (list.size() > 100) {
            list = list.subList(0, 100);
        }
        return list;
    }

上面是经典的数据库写法,限制100个数量,
4.下面是UI层的展示

    private void initData() {
        recyclerView = findViewById(R.id.rv_rank_list);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        //从数据库获取
        musicList = getDataFromDb();
        //musicList = generateMusicList();
        adapter = new MusicRankAdapter(musicList);
        recyclerView.setAdapter(adapter);
    }

Adapter层做了TYPE_HEADER和TYPE_ITEM的处理:


public class MusicRankAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<MusicRankModel> musicList;

    private static final int VIEW_TYPE_HEADER = 0;
    private static final int VIEW_TYPE_ITEM = 1;

    public static class MusicViewHolder extends RecyclerView.ViewHolder {
        public TextView musicName;
        public TextView playCount;

        public MusicViewHolder(@NonNull View itemView) {
            super(itemView);
            musicName = itemView.findViewById(R.id.musicName);
            playCount = itemView.findViewById(R.id.playCount);
        }
    }

    public static class HeaderViewHolder extends RecyclerView.ViewHolder {
        public HeaderViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    public MusicRankAdapter(List<MusicRankModel> musicList) {
        this.musicList = musicList;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_HEADER) {
            View headerView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_music_header, parent, false);
            return new HeaderViewHolder(headerView);
        } else {
            View itemView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_music, parent, false);
            return new MusicViewHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof MusicViewHolder) {
            MusicRankModel currentMusic = musicList.get(position - 1); // 减1因为第一个位置是头部视图
            ((MusicViewHolder) holder).musicName.setText(currentMusic.getName());
            ((MusicViewHolder) holder).playCount.setText(String.valueOf(currentMusic.getPlayCount()));
        }
    }

    @Override
    public int getItemViewType(int position) {
        return position == 0 ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
    }

    @Override
    public int getItemCount() {
        return musicList.size() + 1; // 加1表示包括头部视图
    }
}

具体item两个的layout:
item_music_header:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="8dp">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="歌曲名"

        android:gravity="center"
        android:textSize="16sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="播放次数"
        android:gravity="center"
        android:textSize="16sp"
        android:textStyle="bold" />
</LinearLayout>

item_music:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="8dp">

    <TextView
        android:id="@+id/musicName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:gravity="center"
        android:text="Music Name"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/playCount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Play Count"
        android:layout_weight="1"
        android:textSize="16sp"
        android:gravity="center"
        android:paddingStart="16dp"/>
</LinearLayout>

这样就实现了上面列表的效果。下面是时间戳记录和按月记录播放时长的思路放在下一篇去讲.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1818437.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于安信可串口调试助手软件调试ESP8266串口WIFI模块ESP-01S应用功能

基于安信可串口调试助手软件调试ESP8266串口WIFI模块ESP-01S应用功能 ESP8266_01S引脚功能图ESP8266_01S原理图ESP8266_01S尺寸图检验工作1、USB-TTL串口工具(推荐使用搭载CP2102芯片的安信可USB-T1串口)与ESP8266_01S WiFi模块(推荐使用安信可ESP8266系列模组)接线(注意当…

使用谷歌 Gemini API 构建自己的 ChatGPT(一)

AI领域一直由OpenAI和微软等公司主导&#xff0c;而Gemini则崭露头角&#xff0c;以更大的规模和多样性脱颖而出。它被设计用于无缝处理文本、图像、音频和视频&#xff1b;这些基础模型重新定义了人工智能交互的边界。随着谷歌在人工智能领域强势回归&#xff0c;了解Gemini如…

HMI(人机交互)应用的15大领域,欢迎补充。

HMI&#xff08;Human-Machine Interface&#xff0c;人机界面&#xff09;可以应用于许多不同的场景和行业&#xff0c;包括但不限于以下几个方面&#xff1a; 工业控制系统&#xff1a;HMI在工业生产中广泛应用&#xff0c;用于监控和控制生产过程。例如&#xff0c;工厂中的…

数据库学霸笔记

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

前端问题整理

Vue vue mvvm&#xff08;Model-View-ViewModel&#xff09;架构模式原理 Model 是数据层&#xff0c;即 vue 实例中的数据View 是视图层&#xff0c; 即 domViewModel&#xff0c;即连接Model和Vue的中间层&#xff0c;Vue实例就是ViewModelViewModel 负责将 Model 的变化反映…

构建企业核心竞争力:拥有自主大模型,引领行业未来

前言 随着人工智能技术的飞速发展&#xff0c;大模型技术已经成为推动行业进步的重要力量。在这个变革的时代&#xff0c;作为一位具有前瞻性的企业家&#xff0c;您深知拥有自主大模型对于提升公司竞争力、引领行业未来的重要性。本文将为您详细介绍大模型的市场现状以及企业…

【机器学习】通用大模型VS垂直大模型,你更加青睐哪一方?

目录 前言AI大模型的战场&#xff1a;通用与垂直的分化通用大模型&#xff1a;广泛适用的利器垂直大模型&#xff1a;深入领域的利器谁能够形成绝对优势&#xff1f;结语通用大模型文章推荐 前言 AI大模型的战场正在分化&#xff1a; 通用大模型在落地场景更广泛毋庸置疑&…

暗色系B端界面有什么好处、应用场景、缺点、该如何设计。

B端管理系统界面蓝色系和浅色系的非常多&#xff0c;暗色系一般不作为首选&#xff0c;这背后是什么原因呢&#xff0c;如果真的要设计暗色系界面&#xff0c;该如何办呢&#xff0c;本文就解决这些问题。 一、暗色系B端界面相对于浅色系有什么好处 1. 减少眩光和视觉疲劳&am…

引入tinyMCE富文本框在vue3中的使用

实现效果&#xff1a; 官网地址&#xff1a;TinyMCE 7 Documentation | TinyMCE Documentation 1.下载依赖&#xff08;我使用的版本是5.0 目前最新版本到7了&#xff09; pnpm/npm install tinymce5.0.0 -S pnpm/npm install tinymce/tinymce-vue -S 2.在public文件夹下…

浅析Spring中Async注解底层异步线程池原理

一、前言 开发中我们经常会用到异步方法调用&#xff0c;具体到代码层面&#xff0c;异步方法调用的实现方式有很多种&#xff0c;比如最原始的通过实现Runnable接口或者继承Thread类创建异步线程&#xff0c;然后启动异步线程&#xff1b;再如&#xff0c;可以直接用java.uti…

数字孪生技术如何赋能智慧工厂

数字孪生技术为什么能在智慧工厂中发挥作用&#xff1f;随着工业4.0的推进和智能制造的普及&#xff0c;数字孪生技术成为智慧工厂的重要推动力。数字孪生是指在虚拟空间中创建一个与现实物理实体相对应的数字模型&#xff0c;通过实时数据交互和分析&#xff0c;实现对物理实体…

即插即用!CVD:第一个生成具有相机控制的多视图一致视频方案!(斯坦福港中文)

论文链接&#xff1a;https://arxiv.org/abs/2405.17414 项目链接&#xff1a;https://collaborativevideodiffusion.github.io/ 最近对视频生成的研究取得了巨大进展&#xff0c;使得可以从文本提示或图像生成高质量的视频。在视频生成过程中添加控制是未来的重要目标&#x…

GIS大赛制图类训练营:从入门到直通决赛,你只差这一步!

为什么要参加GIS大赛&#xff1f; 相信只要是读GIS相关专业的同学&#xff0c;大家在学校应该会经常被老师要求学生参加比赛&#xff0c;往届的学长学姐们也会鼓励大家本科期间多参加GIS大赛。 那是因为参加GIS比赛的好处多多&#xff0c;报名参赛不仅可以提升自身动手能力&…

多校园跑腿小程序源码系统 帮取+代拿+外卖配送 功能超多 带完整的安装代码包+搭建部署教程

系统概述 多校园跑腿小程序源码系统 是一款基于微信小程序平台开发的全功能校园服务应用。该系统采用先进的前后端分离架构&#xff0c;结合云服务技术&#xff0c;确保了系统的高可用性、可扩展性和安全性。系统设计之初就充分考虑到了多校园环境的特殊性&#xff0c;支持多校…

基于flask的网站如何使用https加密通信

文章目录 内容简介网站目录示例生成SSL证书单独使用Flask使用WSGI服务器Nginx反向代理参考资料 内容简介 HTTPS 是一种至关重要的网络安全协议&#xff0c;它通过在 HTTP 协议之上添加 SSL/TLS 层来确保数据传输的安全性和完整性。这有助于防止数据在客户端和服务器之间传输时…

C#——值类型和引用类型的区别详情

值类型和引用类型的区别 值类型 值类型&#xff1a; 常用的基本数据类型都是值类型&#xff1a;bool 、char、int、 double、 float、long 、 byte 、ulong、uint、枚举类型、 结构体类型等特点: 在赋值的过程当中&#xff0c;把值的本身赋值给另一个变量&#xff0c;再修改…

TiKV 源码分析之 PointGet

作者&#xff1a;来自 vivo 互联网存储研发团队-Guo Xiang 本文介绍了TiDB中最基本的PointGet算子在存储层TiKV中的执行流程。 一、背景介绍 TiDB是一款具有HTAP能力(同时支持在线事务处理与在线分析处理 )的融合型分布式数据库产品&#xff0c;具备水平扩容或者缩容等重要特…

如何申请小程序SSL证书

在互联网时代&#xff0c;数据安全和用户隐私保护变得尤为重要。SSL证书作为网站、应用或小程序与用户之间建立安全连接的关键工具&#xff0c;其重要性不言而喻。SSL证书能够加密数据传输&#xff0c;防止信息被窃取&#xff0c;提升用户信任度&#xff0c;对于小程序开发者来…

Python自动化

python操作excel # 安装第三个库 cmd -> pip install xlrb 出现success即安装成功 # 导入库函数 import xlrb # 打开的文件保存为excel文档对象 xlsx xlrb.open_workbook("文件位置") # C:\Users\Adminstator\Desktop\学生版.xlsx # 操作工作簿里的工作表 # 1.…

ICRA 2024:基于视觉触觉传感器的物体表⾯分类的Sim2Real双层适应⽅法

⼈们通常通过视觉来感知物体表⾯的性质&#xff0c;但有时需要通过触觉信息来补充或替代视觉信息。在机器⼈感知物体属性⽅⾯&#xff0c;基于视觉的触觉传感器是⽬前的最新技术&#xff0c;因为它们可以产⽣与表⾯接触的⾼分辨率 RGB 触觉图像。然⽽&#xff0c;这些图像需要⼤…