Android - 自定义View 实现 文本吉他谱的 显示 实现

2024-02-12 14:59

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

1.背景

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

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

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

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

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

     效果:

                                 

  

2.难点1 实现失败过程

     在这里我没有实现对应和弦键与歌词的对应关系的实现;我认为太多的因素,比如人为因素,以至于无法确定显示的效果;如果哪位前辈,解决了这个问题,还请赐教,谢谢。在此期间我想过几种方式;

     (1)不转换任何格式,直接进行显示

                   通过肉眼的计算,发现两个空格对应1个汉字,就将其中的空格数除以2,进行输出;具体算法实现:

private synchronized String parseLrcKeyString(String lrcKey){StringBuffer buffer=new StringBuffer();//字符串方式分割String[] split = lrcKey.split(" ");int space=1;for(int i=0;i<split.length;i++){//不为""的两个字符之间添加 长度/2个全角空格//1.计数空格if(split[i].equals("")){//是空格space++;}else{//不是空格space=(int) Math.ceil(space/2);for(int j=0;j<space;j++){buffer.append(" ");}
//				mNormalPaint.mebuffer.append(split[i]);space=1;}}return buffer.toString();}

           结果:失败,从显示的角度上,有的是可以显示成功的,有的是显示不成功的;

  (2)第二种方式:经过计算的方式,通过查询资料和测试,发现全角下的空格和汉字的长度一致,于是,就将歌词中的空格进行了替换,将多个空格替换为一个全角空格,代表一个汉字的存在;

             这是找到切入点,就是空格的个数是固定的,和弦之间,和弦之前,空格的个数均是固定不变的,根据长度四个半角空格的显示宽度等于1个全角空格的宽度,也就是一个汉字的宽度,这样来计算的话,应该是可以实现的;

           具体算法实现:

	/*** 操作单个字符* @param canvas* @param currX* @param centerY* @param lrcKey* @param paint* @param currentLine*/private synchronized void parseLrcKeyStr1(Canvas canvas,float currX,float centerY,String lrcKey,Paint paint,int currentLine){//字符串方式分割String[] split = lrcKey.split(" ");int space=1;float spaceLength=24.0f;float length=0;float sl=0;for(int i=0;i<split.length;i++){//不为""的两个字符之间添加 长度/2个全角空格//1.计数空格String str = split[i];str = halfToFull(str);if(str.equals("")||str==""){//是空格space++;}else{space=space/2;for(int j=0;j<space;j++){canvas.drawText(" ",currX+length, centerY, paint);length+=spaceLength/2;}float f = paint.measureText(str);if(mCurrentLine==0){System.out.println("----------------"+f);}if(f>40.0f&&f<50.0f){sl=f-60.0f;}else if(f>50.0f){sl=f-98.0f;}else{sl=f-36.0f;}length+=sl;//确定当前的和弦,1,和弦值一样,2,当前值一样 ,3,str=fullToHalf(str);if(nowChord.equals(str)){canvas.drawText(str,currX+length, centerY, nowChordPaint);}else{canvas.drawText(str,currX+length, centerY, paint);}length=length-sl+f;space=1;}}}

        这里开始进行了,绘制是之间进行绘制的,不在返回,便于操作;

        结果:失败,通过计算出来的,也不行,通过测试字符串的宽度,并没有问题,显示有问题,很纠结,找不到原因,未解决;


3.难点3实现

    难点3 是 当为那个和弦键的时候,需要切换到该和弦键,且该和弦键放大,和变颜色,(问题 A:在这里又要引出一个问题,当放大的时候,字符宽度就要改变,上面通过计算的方式,是不是又要麻烦了)!!!

   效果 :

                                                              

    就好比图中的Am一样,变色且放大,这里的实现思路,在上面的方法中一样有所体现:

    (1)在计算方法中绘制 ,边计算边绘制;

    (2)切入点有三:当前绘制的和弦键Am, 当前行(当前操作的和弦行mCurrentLine),当前列(和弦键所在数组的index);

       (3) 问题来了,如果和弦键一样,那么将全部绘制,所以我们将其标示为唯一的;

            实现算法:

	/*** 得到字符串数组* @param lrcKey* @return*/private String[] getSplitStringArray(String lrcKey) {String[] split = lrcKey.split(" ");int splitIndex=0;for(int i=0;i<split.length;i++){if(!split[i].equals("")){split[i]=split[i]+splitIndex;splitIndex++;}}return split;}
 
      哈哈, 问题B : 这里将每个和弦键后面加了单个数字,计算的时候,长度又变了,计算的方式还可以使用吗?!

    当然绘制的时候,肯定要将数字消掉;

  (4)绘制算法实现

/*** 绘制* @param canvas* @param currX* @param centerY* @param lrcKey* @param paint* @param currentLine*/int nowLineChords=0;private String needChords=null;private synchronized void parseLrcKeyStr(Canvas canvas,float currX,float centerY,String lrcKey,Paint paint,int currentLine){//字符串方式分割String[] split = getSplitStringArray(lrcKey);float spaceLength=mCurrentPaint.measureText(" ");if(currentLine==mCurrentLine){System.out.println("nowLineChords : "+nowLineChords+" chordsIndex:"+chordsIndex);Log.d("Lrc", "needChords: "+needChords + " ");}float length=0;float sl=0;for(int i=0;i<split.length;i++){//不为""的两个字符之间添加 长度/2个全角空格//1.计数空格String str = split[i];if(str.equals("")||str==""){//是空格canvas.drawText(" ",currX+length, centerY, paint);length+=spaceLength*2;}else{float f = paint.measureText(str);//半角的if(f>30.0f&&f<40.0f){sl=f/5*2;}else if(f>=40.0f){sl=f/4;}else{sl=f/2;}length+=sl;//确定当前的和弦,1,和弦值一样,2,当前值一样 ,3,if(needChords.equals(str) && mCurrentLine==currentLine && nowLineChords==chordsIndex){str=str.substring(0,str.length()-1);canvas.drawText(str,currX+length, centerY, nowChordPaint);}else{str=str.substring(0,str.length()-1);canvas.drawText(str,currX+length, centerY, paint);}length=length-sl+f;}}}

4.整个LrcView实现

package cn.labelnet.weiget;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.text.ChoiceFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;/*** LrcView 基本思路 : 1.加载txt 2.解析 ,给 和弦的最后加上 # 以标记 3.解析分别进行存储,当前行没有 # 号,存储 为 no ,为* 当前行歌词 4.解析有# 则进行 存储,切读取下一行进行存储 5.传入参数进行播放 6.切换 7.* */
public class LrcView extends View {private static final String TAG = LrcView.class.getSimpleName();private static final int MSG_NEW_LINE = 0;private static final String MSG_KEY_NO = "no";// 数据private List<String> mLrcKeys;private List<String> mLrcTexts;private LrcHandler mHandler;// 绘制private Paint mNormalPaint;private Paint mCurrentPaint;// 初始化参数private float mTextSize;private float mDividerHeight;private long mAnimationDuration;private int mCurrentLine = 0;private float mAnimOffset;// 计数器private int mNext = -1;// 总行数private int rowNums = 0;// 和弦private String[] chords = null;//和弦集合,反向存储 和弦键及其位置private Map<String,Integer> chordsList=new HashMap<String, Integer>();// 和弦计数器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;// 回调接口private LrcViewInterface lrcViewInterface;//显示处理private Pattern p = Pattern.compile("\\s+");//绘制和弦的画笔private Paint chordPaint;private Paint nomalChordPaint;private Paint nowChordPaint;public void setLrcViewInterface(LrcViewInterface lrcViewInterface) {this.lrcViewInterface = lrcViewInterface;}public LrcView(Context context) {this(context, null);}public LrcView(Context context, AttributeSet attrs) {super(context, attrs);init(attrs);}/*** @param attrs*/private void init(AttributeSet attrs) {// 初始化mTextSize = 24.0f;mDividerHeight = 72.0f;mAnimationDuration = 1000;mAnimationDuration = mAnimationDuration < 0 ? 1000 : mAnimationDuration;mLrcKeys = new ArrayList<String>();mLrcTexts = new ArrayList<String>();WeakReference<LrcView> lrcViewRef = new WeakReference<LrcView>(this);mHandler = new LrcHandler(lrcViewRef);mNormalPaint = new Paint();mCurrentPaint = new Paint();mNormalPaint.setColor(Color.BLACK);mNormalPaint.setTextSize(mTextSize);mCurrentPaint.setColor(Color.RED);mCurrentPaint.setTextSize(mTextSize);		mCurrentPaint.setAntiAlias(true);mNormalPaint.setAntiAlias(true);chordPaint=new Paint();chordPaint.setTextSize(mTextSize);chordPaint.setColor(Color.RED);chordPaint.setAntiAlias(true);nomalChordPaint=new Paint();nomalChordPaint.setTextSize(mTextSize);nomalChordPaint.setColor(Color.BLACK);nomalChordPaint.setAntiAlias(true);nowChordPaint=new Paint();nowChordPaint.setTextSize(30.0f);nowChordPaint.setColor(Color.GREEN);nowChordPaint.setAntiAlias(true);//设置字体mCurrentPaint.setTypeface(Typeface.SANS_SERIF);mNormalPaint.setTypeface(Typeface.SANS_SERIF);chordPaint.setTypeface(Typeface.SANS_SERIF);nowChordPaint.setTypeface(Typeface.SANS_SERIF);nomalChordPaint.setTypeface(Typeface.SANS_SERIF);}@Overrideprotected synchronized void onDraw(Canvas canvas) {super.onDraw(canvas);if (mLrcKeys.isEmpty() || mLrcTexts.isEmpty()) {return;}// 中心Y坐标float centerY = getHeight() / 2 + mTextSize / 2 + mAnimOffset;// 画当前行String currStr = mLrcTexts.get(mCurrentLine);String currKey = mLrcKeys.get(mCurrentLine);float currX = (getWidth() - mCurrentPaint.measureText(currStr)) / 2;if (!MSG_KEY_NO.equals(currKey)) {parseLrcKeyStr(canvas,currX,centerY-30,currKey,chordPaint,mCurrentLine);}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)) {parseLrcKeyStr(canvas,upX,upY-30,upKey,nomalChordPaint,i);}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)) {parseLrcKeyStr(canvas,downX,downY-30,downKey,nomalChordPaint,i);}canvas.drawText(downStr, downX, downY, mNormalPaint);}}/*** 绘制* @param canvas* @param currX* @param centerY* @param lrcKey* @param paint* @param currentLine*/int nowLineChords=0;private String needChords=null;private synchronized void parseLrcKeyStr(Canvas canvas,float currX,float centerY,String lrcKey,Paint paint,int currentLine){//字符串方式分割String[] split = getSplitStringArray(lrcKey);float spaceLength=mCurrentPaint.measureText(" ");if(currentLine==mCurrentLine){System.out.println("nowLineChords : "+nowLineChords+" chordsIndex:"+chordsIndex);Log.d("Lrc", "needChords: "+needChords + " ");}float length=0;float sl=0;for(int i=0;i<split.length;i++){//不为""的两个字符之间添加 长度/2个全角空格//1.计数空格String str = split[i];if(str.equals("")||str==""){//是空格canvas.drawText(" ",currX+length, centerY, paint);length+=spaceLength*2;}else{float f = paint.measureText(str);//半角的if(f>30.0f&&f<40.0f){sl=f/5*2;}else if(f>=40.0f){sl=f/4;}else{sl=f/2;}length+=sl;//确定当前的和弦,1,和弦值一样,2,当前值一样 ,3,if(needChords.equals(str) && mCurrentLine==currentLine && nowLineChords==chordsIndex){str=str.substring(0,str.length()-1);canvas.drawText(str,currX+length, centerY, nowChordPaint);}else{str=str.substring(0,str.length()-1);canvas.drawText(str,currX+length, centerY, paint);}length=length-sl+f;}}}/*** 得到字符串数组* @param lrcKey* @return*/private String[] getSplitStringArray(String lrcKey) {String[] split = lrcKey.split(" ");int splitIndex=0;for(int i=0;i<split.length;i++){if(!split[i].equals("")){split[i]=split[i]+splitIndex;splitIndex++;}}return split;}// 功能:字符串全角转换为半角// 说明:全角空格为12288,半角空格为32
//	 		 其他字符全角(65281-65374)与半角(33-126)的对应关系是:均相差65248 // 输入参数:input -- 需要转换的字符串// 输出参数:无:// 返回值: 转换后的字符串public static String fullToHalf(String input) {  char[] c = input.toCharArray();  for (int i = 0; i< c.length; i++) {  if (c[i] == 12288) //全角空格{  c[i] = (char) 32;  continue;  }if (c[i]> 65280&& c[i]< 65375)  c[i] = (char) (c[i] - 65248);  }  return new String(c);  }// 功能:字符串半角转换为全角// 说明:半角空格为32,全角空格为12288.// 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248// 输入参数:input -- 需要转换的字符串// 输出参数:无:// 返回值: 转换后的字符串public static String halfToFull(String input) {char[] c = input.toCharArray();for (int i = 0; i < c.length; i++) {if (c[i] == 32) // 半角空格{c[i] = (char) 12288;continue;}// 根据实际情况,过滤不需要转换的符号// if (c[i] == 46) //半角点号,不转换// continue;if (c[i] > 32 && c[i] < 127) // 其他符号都转换为全角c[i] = (char) (c[i] + 65248);}return new String(c);}public void loadSDLrc(String lrcName) throws Exception {mLrcTexts.clear();mLrcKeys.clear();File file = new File(Environment.getExternalStorageDirectory(), lrcName);BufferedReader br = new BufferedReader(new FileReader(file));String line;int index = 0;while ((line = br.readLine()) != null) {// 单行加载解析// line = halfToFull(line);// 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());}/**** @param lrcName*            assets下的歌词文件名* @throws Exception*/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);Matcher m = p.matcher(line);line= m.replaceAll(" ");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());}/*** 当一行的和弦执行全部切换完毕的时候,进行下一行切换 基本思路: 内部计数器,总行数; 1. x=1 ,进行下一个和弦的更新* * 2.x=-1,进行上一个和旋的更新 3. 先得到 当前行的所有和弦, 如果有和弦* :将和弦进行解析,后有一个计数器,进行计数;当计数器没有等于和弦总个数的时候,进行下一行显示 如果没有和弦,直接进行下一行显示* 下一行显示之前,先进行和弦判断* * @param x*            , -1 , 1*/public synchronized void updateLrc(int x) {Log.d("Lrc","-----------------------2---------------------------");Log.d("Lrc", "chordsIndex 1: " + chordsIndex);//临界值1,第一行if(x<0){if(mNext==0){//判定有和弦没有if(chordsIndex==-1){//没有和弦lrcViewInterface.isPlayToTop(true);return;}else{//有和弦if(preX<0){if(chordsIndex==0){//第一个Log.d("Lrc", "临界值1 : ");chordsIndex=1;lrcViewInterface.isPlayToTop(true);return;}}if(preX>0){if((chordsIndex-1)==0){//判定是不是第一个元素Log.d("Lrc", "临界值2: ");lrcViewInterface.isPlayToTop(true);return;}}}}}//临界值2: 最后一行了,不在进行更新操作if (x > 0) {if (mNext >= rowNums - 1) {//判定是否有和弦if(chordsIndex==-1){//没有和弦lrcViewInterface.isPlayToEnd(true);return;}else{//有和弦,判定是否等于数组长度if(chordsIndex==chords.length){//最后一个和弦了lrcViewInterface.isPlayToEnd(true);return;}}}}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)  {if(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);needChords=nowChord+(chordsIndex-1);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);needChords=nowChord+chordsIndex;break;}// 这里是上面绘制,将单个和弦绘制的控制条件nowLineChords=chordsIndex;
//			if(x>0){
//			  needChords=nowChord+(chordsIndex-1);
//			}
//			
//			if(x<0){
//			
//			}} else {Log.d("Lrc", "和弦数组chords 为空了: ");	}preX = x;// 获得下一个和弦键nextChordsIndex = x > 0 ? chordsIndex : chordsIndex + 1;getNextChord();// 和弦回调lrcViewInterface.getNowChordAndNextChord(nowChord, nextChord);Log.d("Lrc", "needChords: "+needChords);invalidate();}/*** 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;chordsList.clear();// 获得当前的和弦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] + " ";chordsList.put(chords[i]+i,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);}}// 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];}}}}/*** 换行动画 Note:属性动画只能在主线程使用*/private void newLineAnim() {ValueAnimator animator = ValueAnimator.ofFloat(mTextSize+ mDividerHeight, 0.0f);animator.setDuration(mAnimationDuration);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mAnimOffset = (Float) animation.getAnimatedValue();invalidate();}});animator.start();}private static class LrcHandler extends Handler {private WeakReference<LrcView> mLrcViewRef;public LrcHandler(WeakReference<LrcView> lrcViewRef) {mLrcViewRef = lrcViewRef;}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_NEW_LINE:LrcView lrcView = mLrcViewRef.get();if (lrcView != null) {lrcView.newLineAnim();}break;}super.handleMessage(msg);}}// 回调接口public interface LrcViewInterface {// 回调出来 当前的和弦,和下一个和弦void getNowChordAndNextChord(String nowChord, String nextChord);void isPlayToEnd(boolean isToEnd);void isPlayToTop(boolean isToTop);}}

5.调用实现

   实现LrcView中的接口进行回调实现;

package cn.labelnet.lrcview;import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import cn.labelnet.weiget.LrcView;
import cn.labelnet.weiget.LrcView.LrcViewInterface;public class MainActivity extends Activity implements OnClickListener,LrcViewInterface {private LrcView view_lrc;private Button btn_add, btn_remove;private TextView tv_nowChord, tv_nextChord;/*** 控制已实现 还需要做的 : 1. 对应关系及其显示 2. 歌词过长处理*/@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv_nowChord = (TextView) findViewById(R.id.tv_nowChord);tv_nextChord = (TextView) findViewById(R.id.tv_nextChord);btn_add = (Button) findViewById(R.id.btn_add);btn_add.setOnClickListener(this);btn_remove = (Button) findViewById(R.id.btn_remove);btn_remove.setOnClickListener(this);view_lrc = (LrcView) findViewById(R.id.view_lrc);view_lrc.setLrcViewInterface(this);try {// view_lrc.loadSDLrc("dang.txt");view_lrc.loadLrc("lrc.txt");view_lrc.updateLrc(1);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_add:// 点击增加模拟view_lrc.updateLrc(1);break;case R.id.btn_remove:// 点击减少模拟view_lrc.updateLrc(-1);break;}}@Overridepublic void getNowChordAndNextChord(String nowChord, String nextChord) {tv_nowChord.setText(nowChord);tv_nextChord.setText(nextChord);}@Overridepublic void isPlayToEnd(boolean isToEnd) {if (isToEnd) {showToast("播放完毕了");}}public void showToast(String msg) {Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();}@Overridepublic void isPlayToTop(boolean isToTop) {showToast("准备开始");}//音量键监听@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:// 音量键+view_lrc.updateLrc(1);break;case KeyEvent.KEYCODE_VOLUME_DOWN:// 音量键-view_lrc.updateLrc(-1);break;}return super.onKeyDown(keyCode, event);}}

6.Demo下载

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

  GitHub 交流: https://github.com/LABELNET/LrcView


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



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

相关文章

【前端学习】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

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

【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

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

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影