Android - 自定义View 实现 文本吉他谱的 动态控制 实现

2023-10-17 03:30

本文主要是介绍Android - 自定义View 实现 文本吉他谱的 动态控制 实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.背景

     前天同学推荐,要做一个吉他谱的显示,与控制功能;想了想,感觉难度有些大,主要有几个难点:

    难点1 :显示:文本吉他谱是txt ,需要 和弦键与对应的文字对应显示出来;

    难点2: 控制:需要通过音量键进行动态的控制,+ ,向下控制当前的和弦键,-,向上控制当前的和弦键;(完成)

    难点3: 显示:当前控制的和弦键需要放大和变颜色,和当前行歌词颜色,正常行歌词颜色,均不一样;

    难点4: 控制:通过和弦键来控制整体歌词的滚动;(完成)

    难点5: 回调:需要回调 当前正在使用的和弦键,和下一个和弦键;(完成)


2. 动态控制思路

    2.1 普通歌词思路

          正常情况下,歌词的控制是通过时间作为切入点进行控制,根据时间的毫秒数与当前播放的时间大小,进行判断,可以控制其播放,暂停,和播放位置的设置;

          这是之前写的效果实现:

http://blog.csdn.net/lablenet/article/details/50328987

          而文本吉他谱,比如《都是你》的吉他谱的歌谱:

都是你
光良
原 调:1=#F
选 调:1=C
变调夹:6品C             F      Dm7   G      C
*谁 改变了我的世界 没有方向 没有日夜F         C Em/B Am      Dm           G
我看著天 这一刻在想你 是否会对我一样思念C            F        Dm7   G       C
#你 曾说我们有一个梦 等到那天 我们来实现F         C Em/B Am      Dm   G       C
我望著天 在心中默默念 下一秒你出现在眼前F    G        C
想念的心 装满的都是你F    G        C
我的钢琴 弹奏的都是你F    G       C Em/B Am
我的日记 写满的都是你的名F     G       C
才发现 又另一个黎明F     G      C Em/B Am
我的日记 写满的都是你的名F     G       C
才发现 又是一个黎明F     G       C
这是我 对你爱的累积

     上面的英文字母是和弦键,下面的中文为其这首歌的歌词,相比普通的LRC歌词就麻烦对了;

     下面分享下我解决的思路 :

  2.2 吉他谱歌词思路

        首先是切入点,肯定是和弦键了,通过和弦键的控制,进而对整个歌谱的控制;

          

        实现步骤 :

               (1)读取歌谱 ,将 和弦键 所在行 存储在 List1中(没有和弦的存储为no),歌词存储在 List2中;

                        比如:

public void loadLrc(String lrcName) throws Exception {mLrcTexts.clear();mLrcKeys.clear();BufferedReader br = new BufferedReader(new InputStreamReader(getResources().getAssets().open(lrcName)));String line;int index = 0;while ((line = br.readLine()) != null) {// 单行加载解析// 1.判断最后是否有 $ 符号index = line.lastIndexOf("$");if (index > 0) {// 则此行为 和弦行,进行解析,存储下一行数据// 存储和弦值line = line.substring(0, index);mLrcKeys.add(line);// 存储对应的歌词line = br.readLine();if (line != null) {mLrcTexts.add(line);} else {break;}} else {// 没有$符号,存储歌词,存储和弦为nomLrcKeys.add(MSG_KEY_NO);mLrcTexts.add(line);}}br.close();// 记录总行数rowNums = mLrcTexts.size();for (int i = 0; i < mLrcKeys.size() - 1; i++) {Log.d("Lrc", mLrcKeys.get(i));}Log.d("Lrc", " mLrcKeys : " + mLrcKeys.size());Log.d("Lrc", " mLrcTexts : " + mLrcTexts.size());}


               (2)普通显示:通过重写 onDraw() 方法,将文字显示在view上,没有经过处理;如果为no , 则不显示

                        注意:在drawText的时候,需要将和弦行放在歌词上面,则使得其y坐标,错开50 ,而坐标相等,

                  

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mLrcKeys.isEmpty() || mLrcTexts.isEmpty()) {return;}// 中心Y坐标float centerY = getHeight() / 2 + mTextSize / 2 + mAnimOffset;// mCurrentLine=5;// 画当前行String currStr = mLrcTexts.get(mCurrentLine);String currKey = mLrcKeys.get(mCurrentLine);float currX = (getWidth() - mCurrentPaint.measureText(currStr)) / 2;if (!MSG_KEY_NO.equals(currKey)) {canvas.drawText(currKey, currX, centerY - 50, mCurrentPaint);}canvas.drawText(currStr, currX, centerY, mCurrentPaint);// 画当前行上面的for (int i = mCurrentLine - 1; i >= 0; i--) {String upStr = mLrcTexts.get(i);String upKey = mLrcKeys.get(i);float upX = (getWidth() - mNormalPaint.measureText(upStr)) / 2;float upY = centerY - (mTextSize + mDividerHeight)* (mCurrentLine - i);if (!MSG_KEY_NO.equals(upKey)) {canvas.drawText(upKey, upX, upY - 50, mNormalPaint);}canvas.drawText(upStr, upX, upY, mNormalPaint);}// 画当前行下面的for (int i = mCurrentLine + 1; i < mLrcKeys.size(); i++) {String downStr = mLrcTexts.get(i);String downKey = mLrcKeys.get(i);float downX = (getWidth() - mNormalPaint.measureText(downStr)) / 2;float downY = centerY + (mTextSize + mDividerHeight)* (i - mCurrentLine);if (!MSG_KEY_NO.equals(downKey)) {canvas.drawText(downKey, downX, downY - 50, mNormalPaint);}canvas.drawText(downStr, downX, downY, mNormalPaint);}}


       (3)控制实现

                基本思路:通过和弦键数组进行判断和切换行;

                1) 给其一个计数器chrodsIndex,也是每行和弦数组的的下标:

                   chrodsIndex=-1 , 则代表这一行没有和弦键 ,切换下一行;

                   chrodsIndex >-1 ,则代表有和弦键;

                2)有和弦,进行解析这一行,获得和弦数组,通过改变 chrodsIndex的值,进行控制

                     临界值1 :数组上边界,切换到下一行,同时读取这一行的和弦键;

                     临界值2:数组下边界,切换到上一行,同时读取这一行的和弦键;

               3)控制输入 x : 1 ,切换和弦向下,-1,切换和弦向上;

                     

    (4)解析实现

	/*** 1. 解析当前行的和弦*/private void parseLine(int isNext) {if (isNext > 0) {mNext++;} else {mNext--;}mCurrentLine = mNext == rowNums ? 0 : mNext;mCurrentLine = mNext < 1 ? 0 : mNext;mNext = mCurrentLine;chords = null;// 获得当前的和弦String key = mLrcKeys.get(mCurrentLine);// 解析if (MSG_KEY_NO.equals(key)) {chordsIndex = -1;// 没有和弦,更新下一行mHandler.sendEmptyMessage(MSG_NEW_LINE);} else {// 有和弦chords = key.trim().split("\\s+");// 测试String t = " ";for (int i = 0; i < chords.length; i++) {t += chords[i] + " ";}Log.d("Lrc", "和弦 :" + t);Log.d("Lrc", "歌词 :" + mLrcTexts.get(mCurrentLine));// 如果进行下一行,则if (isNext < 0) {chordsIndex = chords.length;} else {chordsIndex = 0;}mHandler.sendEmptyMessage(MSG_NEW_LINE);}}


    (5)获得下一个和弦

// 3. 获得下一个和弦值/** 基本思路:* * 根据当前和弦键的下标+1=下一个和弦下标, 如果下标==数组长度, 则向下循环遍历 下面的和弦,取得最近的和弦返回即可* * !=数组长度,返回即可*/public void getNextChord() {if (nextChordsIndex == -1) {return;}if (chords != null) {if (nextChordsIndex >= chords.length) {// 当前行+1nextCurrentRow = mCurrentLine + 1;for (int i = nextCurrentRow; i < rowNums; i++) {String lrcKey = mLrcKeys.get(i);if (lrcKey.equals(MSG_KEY_NO)) {continue;} else {// 有和弦的行,取得第一个返回nextChord = lrcKey.trim().split("\\s+")[0];break;}}} else {if (nextChordsIndex > 0) {nextChord = chords[nextChordsIndex];}}}}


   (6)控制实现

/*** 基本思路: 内部计数器,总行数; 1. x=1 ,进行下一个和弦的更新* * 2.x=-1,进行上一个和旋的更新 3. 先得到 当前行的所有和弦, 如果有和弦* :将和弦进行解析,后有一个计数器,进行计数;当计数器没有等于和弦总个数的时候,进行下一行显示 如果没有和弦,直接进行下一行显示* 下一行显示之前,先进行和弦判断*/// 和弦计数器private int chordsIndex = -1;// 当前的和弦键private String nowChord = MSG_KEY_NO;// 记录上一次执行的操作private int preX = 0;// 下一个和弦键的下标private int nextChordsIndex = chordsIndex;// 当前行private int nextCurrentRow = mCurrentLine;// 下一个和弦值private String nextChord = MSG_KEY_NO;/*** 2. 当一行的和弦执行全部切换完毕的时候,进行下一行切换* * @param x*/public synchronized void updateLrc(int x) {// 最后一行了,不在进行更新操作if (x > 0) {if (mNext >= rowNums - 1) {lrcViewInterface.isPlayToEnd(true);return;}}Log.d("Lrc", "chordsIndex 1: " + chordsIndex);if (chordsIndex == -1) {// 如果chordsIndex等于-1 ,代表着 这一行没有 和弦,则进行滚动到下一行// x是1还是-1,如果是1,则代表向下一行,是-1则代表向上一行;parseLine(x);}Log.d("Lrc", "chordsIndex 2: " + chordsIndex);if (chords != null) {// 判断当前执行的动作和上一次执行的动作是否一样,// 如果一样,不进行chordsIndex操作,如果不一样,分别对其进行不同的操作if (preX == -1 && x == 1 && chordsIndex != 0) {chordsIndex += 1;} else if (preX == 1 && x == -1) {chordsIndex -= 1;}// 不等于空,开始控制switch (x) {case 1:// 下一个和弦的切换,更新界面,不更新行// 最后一个和弦if (chords.length == chordsIndex) {// 临界值为最大的时候,切换下一行parseLine(1);if (chordsIndex > -1) {nowChord = chords[chordsIndex];Log.d("Lrc", "和弦 +1 if: " + nowChord);chordsIndex++;}} else {nowChord = chords[chordsIndex];Log.d("Lrc", "和弦 +1 else : " + nowChord);chordsIndex++;}Log.d("Lrc", "chordsIndex 3 : " + chordsIndex);break;case -1:chordsIndex--;chordsIndex = chordsIndex < -1 ? -1 : chordsIndex;// 上一个和弦的切换,更新界面,不更新行// Log.d("Lrc", "和弦 -1 执行了");Log.d("Lrc", "chordsIndex -3: " + chordsIndex);if (chordsIndex == -1) {// 临界值为最小的时候,切换上一行parseLine(-1);if (chordsIndex > -1) {chordsIndex--;nowChord = chords[chordsIndex];Log.d("Lrc", "和弦 if -1 : " + nowChord);}} else {nowChord = chords[chordsIndex];Log.d("Lrc", "和弦 else -1 : " + nowChord);}Log.d("Lrc", "chordsIndex -4: " + chordsIndex);break;}} else {Log.d("Lrc", "和弦数组chords 为空了: ");}preX = x;// 获得下一个和弦键nextChordsIndex = x > 0 ? chordsIndex : chordsIndex + 1;getNextChord();// 和弦回调lrcViewInterface.getNowChordAndNextChord(nowChord, nextChord);}


     (7)回调实现

              回调通过接口实现回调,这里就不多说了;


3.效果预览

 


4.Demo 下载

   http://download.csdn.net/detail/lablenet/9404201

这篇关于Android - 自定义View 实现 文本吉他谱的 动态控制 实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/222649

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、