Android自定义View实战:简约风歌词控件

26[01:00.10]笑声更迷人

27[01:02.37]

28[01:03.15]愿再可 轻抚你

29[01:08.56]

30[01:09.35]那可爱面容

31[01:12.40]挽手说梦话

32[01:14.78]

33[01:15.48]像昨天 你共我

34[01:20.84]

35[01:26.32]满带理想的我曾经多冲动

36[01:32.45]屡怨与她相爱难有自由

37[01:37.82]愿你此刻可会知

38[01:40.40]

39[01:41.25]是我衷心的说声

40[01:44.81]

41[01:46.39]喜欢你 那双眼动人

42[01:51.72]

43[01:52.42]笑声更迷人

44[01:54.75]

45[01:55.48]愿再可 轻抚你

46[02:00.93]

47[02:01.68]那可爱面容

48[02:03.99]

49[02:04.73]挽手说梦话

50[02:07.13]

51[02:07.82]像昨天 你共我

52[02:14.53]

53[02:25.54]每晚夜里自我独行

54[02:29.30]随处荡 多冰冷

55[02:35.40]

56[02:37.83]以往为了自我挣扎

57[02:41.62]从不知 她的痛苦

58[02:52.02]

59[02:54.11]喜欢你 那双眼动人

60[03:00.13]笑声更迷人

61[03:02.38]

62[03:03.14]愿再可 轻抚你

63[03:08.77]

64[03:09.33]那可爱面容

65[03:11.71]

66[03:12.41]挽手说梦话

67[03:14.61]

68[03:15.45]像昨天 你共我

从上面可以看出这种格式前面是开始时间,从左往右一一对应分,秒,毫秒,后面就是歌词。所以我们要创建一个实体类来保存每一句的歌词信息。

1.歌词实体类LrcBean

==========================================================================

1public class LrcBean {

2    private String lrc;//歌词

3    private long start;//开始时间

4    private long end;//结束时间

5

6    public String getLrc() {

7        return lrc;

8    }

9

10    public void setLrc(String lrc) {

11        this.lrc = lrc;

12    }

13

14    public long getStart() {

15        return start;

16    }

17

18    public void setStart(long start) {

19        this.start = start;

20    }

21

22    public long getEnd() {

23        return end;

24    }

25

26    public void setEnd(long end) {

27        this.end = end;

28    }

29}

每句歌词,我们需要开始时间,结束时间和歌词这些信息,那么你就会有疑问了?上面提到的歌词格式好像只有歌词开始时间,那我们怎么知道结束时间呢?其实很简单,这一句歌词的开始时间就是上一句歌词的结束时间。有了歌词实体类,我们就得开始对歌词进行解析了!

2. 解析歌词工具类LrcUtil

==============================================================================

1public class LrcUtil {

2

3    /**

4     * 解析歌词,将字符串歌词封装成LrcBean的集合

5     * @param lrcStr 字符串的歌词,歌词有固定的格式,一般为

6     * [ti:喜欢你]

7     * [ar:.]

8     * [al:]

9     * [by:]

10     * [offset:0]

11     * [00:00.10]喜欢你 - G.E.M. 邓紫棋 (Gem Tang)

12     * [00:00.20]词:黄家驹

13     * [00:00.30]曲:黄家驹

14     * [00:00.40]编曲:Lupo Groinig

15     * @return 歌词集合

16     */

17    public static List parseStr2List(String lrcStr){

18        List res = new ArrayList<>();

19        //根据转行字符对字符串进行分割

20        String[] subLrc = lrcStr.split(“\n”);

21        //跳过前四行,从第五行开始,因为前四行的歌词我们并不需要

22        for (int i = 5; i < subLrc.length; i++) {

23            String lineLrc = subLrc[i];

24            //[00:00.10]喜欢你 - G.E.M. 邓紫棋 (Gem Tang)

25            String min = lineLrc.substring(lineLrc.indexOf(“[”)+1,lineLrc.indexOf(“[”)+3);

26            String sec = lineLrc.substring(lineLrc.indexOf(“:”)+1,lineLrc.indexOf(“:”)+3);

27            String mills = lineLrc.substring(lineLrc.indexOf(“.”)+1,lineLrc.indexOf(“.”)+3);

28            //进制转化,转化成毫秒形式的时间

29            long startTime = getTime(min,sec,mills);

30            //歌词

31            String lrcText = lineLrc.substring(lineLrc.indexOf(“]”)+1);

32            //有可能是某个时间段是没有歌词,则跳过下面

33            if(lrcText.equals(“”)) continue;

34            //在第一句歌词中有可能是很长的,我们只截取一部分,即歌曲加演唱者

35            //比如 光年之外 (《太空旅客(Passengers)》电影中国区主题曲) - G.E.M. 邓紫棋 (Gem Tang)

36            if (i == 5) {

37                int lineIndex = lrcText.indexOf(“-”);

38                int first = lrcText.indexOf(“(”);

39                if(first<lineIndex&&first!=-1){

40                    lrcText = lrcText.substring(0,first)+lrcText.substring(lineIndex);

41                }

42                LrcBean lrcBean = new LrcBean();

43                lrcBean.setStart(startTime);

44                lrcBean.setLrc(lrcText);

45                res.add(lrcBean);

46                continue;

47            }

48            //添加到歌词集合中

49            LrcBean lrcBean = new LrcBean();

50            lrcBean.setStart(startTime);

51            lrcBean.setLrc(lrcText);

52            res.add(lrcBean);

53            //如果是最后一句歌词,其结束时间是不知道的,我们将人为的设置为开始时间加上100s

54            if(i == subLrc.length-1){

55                res.get(res.size()-1).setEnd(startTime+100000);

56            }else if(res.size()>1){

57                //当集合数目大于1时,这句的歌词的开始时间就是上一句歌词的结束时间

58                res.get(res.size()-2).setEnd(startTime);

59            }

60

61        }

62        return res;

63    }

64

65    /**

66     *  根据时分秒获得总时间

67     * @param min 分钟

68     * @param sec 秒

69     * @param mills 毫秒

70     * @return 总时间

71     */

72    private static long getTime(String min,String sec,String mills){

73        return Long.valueOf(min)601000+Long.valueOf(sec)*1000+Long.valueOf(mills);

74    }

75}

相信上面的代码和注释已经将这个歌词解析解释的挺明白了,需要注意的是上面对i=5,也就是歌词真正开始的第一句做了特殊处理,因为i=5这句有可能是很长的,假设i=5是“光年之外

(《太空旅客(Passengers)》电影中国区主题曲) - G.E.M. 邓紫棋 (Gem

Tang)”这句歌词,如果我们不做特殊处理,在后面绘制的时候,就会发现这句歌词会超过屏幕大小,很影响美观,所以我们只截取歌曲名和演唱者,有些说明直接省略掉了。解析好了歌词,接下来就是重头戏-歌词绘制!

二、歌词绘制

==================================================================

歌词绘制就涉及到了自定义View的知识,所以还未接触自定义View的小伙伴需要先去看看自定View的基础知识。歌词绘制的主要工作主要由下面几部分构成:

  • 为歌词控件设置自定义属性,在构造方法中获取并设置自定义属性的默认值

  • 初始化两支画笔。分别是歌词普通画笔,歌词高亮画笔。

  • 获取当前播放歌词的位置

  • 画歌词,根据当前播放歌词的位置来决定用哪支画笔画

  • 歌词随歌曲播放同步滑动

  • 重新绘制

1.设置自定View属性,在代码中设置默认值

==================================================================================

在res文件中的values中新建一个attrs.xml文件,然后定义歌词的自定义View属性

1<?xml version="1.0" encoding="utf-8"?>

2

3    

4        

5        

6        

7        

8    

9

这里只自定义了歌词颜色,歌词高亮颜色,歌词大小,歌词行间距的属性,可根据自己需要自行添加。

然后在Java代码中,设置默认值。

1    private int lrcTextColor;//歌词颜色

2    private int highLineTextColor;//当前歌词颜色

3    private int width, height;//屏幕宽高

4    private int lineSpacing;//行间距

5    private int textSize;//字体大小

6

7    public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

8        super(context, attrs, defStyleAttr);

9        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LrcView);

10        lrcTextColor = ta.getColor(R.styleable.LrcView_lrcTextColor, Color.GRAY);

11        highLineTextColor = ta.getColor(R.styleable.LrcView_highLineTextColor, Color.BLUE);

12        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;

13        float scale = context.getResources().getDisplayMetrics().density;

14        //默认字体大小为16sp

15        textSize = ta.getDimensionPixelSize(R.styleable.LrcView_textSize, (int) (16 * fontScale));

16        //默认行间距为30dp

17        lineSpacing = ta.getDimensionPixelSize(R.styleable.LrcView_lineSpacing, (int) (30 * scale));

18        //回收

19        ta.recycle();

20    }

2. 初始化两支画笔

=======================================================================

1    private void init() {

2        //初始化歌词画笔

3        dPaint = new Paint();

4        dPaint.setStyle(Paint.Style.FILL);//填满

5        dPaint.setAntiAlias(true);//抗锯齿

6        dPaint.setColor(lrcTextColor);//画笔颜色

7        dPaint.setTextSize(textSize);//歌词大小

8        dPaint.setTextAlign(Paint.Align.CENTER);//文字居中

9

10        //初始化当前歌词画笔

11        hPaint = new Paint();

12        hPaint.setStyle(Paint.Style.FILL);

13        hPaint.setAntiAlias(true);

14        hPaint.setColor(highLineTextColor);

15        hPaint.setTextSize(textSize);

16        hPaint.setTextAlign(Paint.Align.CENTER);

17    }

我们把初始化的方法放到了构造方法中,这样就可以避免在重绘时再次初始化。另外由于我们把init方法只放到了第三个构造方法中,所以在上面两个构造方法需要将super改成this,这样就能保证哪个构造方法都能执行init方法

1    public LrcView(Context context) {

2        this(context, null);

3    }

4

5    public LrcView(Context context, @Nullable AttributeSet attrs) {

6        this(context, attrs, 0);

7    }

8

9    public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

10        super(context, attrs, defStyleAttr);

11        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LrcView);

12        …

13        //回收

14        ta.recycle();

15        init();

16    }

3. 重复执行onDraw方法

============================================================================

因为后面的步骤都是在onDraw方法中执行的,所以我们先贴出onDraw方法中的代码

1    @Override

2    protected void onDraw(Canvas canvas) {

3        super.onDraw(canvas);

4

5        getMeasuredWidthAndHeight();//得到测量后的宽高

6        getCurrentPosition();//得到当前歌词的位置

7        drawLrc(canvas);//画歌词

8        scrollLrc();//歌词滑动

9        postInvalidateDelayed(100);//延迟0.1s刷新

10    }

1.获得控件的测量后的宽高

1     private int width, height;//屏幕宽高

2    private void getMeasuredWidthAndHeight(){

3        if (width == 0 || height == 0) {

4            width = getMeasuredWidth();

5            height = getMeasuredHeight();

6        }

7    }

为什么要获得控件的宽高呢?因为在下面我们需要画歌词,画歌词时需要画的位置,这时候就需要用到控件的宽高了。

2. 得到当前歌词的位置

1     private List lrcBeanList;//歌词集合

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

关于面试的充分准备

一些基础知识和理论肯定是要背的,要理解的背,用自己的语言总结一下背下来。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,我能明显感觉到国庆后多了很多高级职位,所以努力让自己成为高级工程师才是最重要的。

好了,希望对大家有所帮助。

接下来是整理的一些Android学习资料,有兴趣的朋友们可以关注下我免费领取方式

①Android开发核心知识点笔记

②对标“阿里 P7” 40W+年薪企业资深架构师成长学习路线图

③面试精品集锦汇总

④全套体系化高级架构视频

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

级以下的岗位饱和了,现在高级工程师还是比较缺少的,我能明显感觉到国庆后多了很多高级职位,所以努力让自己成为高级工程师才是最重要的。

好了,希望对大家有所帮助。

接下来是整理的一些Android学习资料,有兴趣的朋友们可以关注下我免费领取方式

①Android开发核心知识点笔记

②对标“阿里 P7” 40W+年薪企业资深架构师成长学习路线图

[外链图片转存中…(img-n4BAqTF7-1711805094368)]

③面试精品集锦汇总

[外链图片转存中…(img-ei3OM5eN-1711805094368)]

④全套体系化高级架构视频

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

[外链图片转存中…(img-2mw4jn4z-1711805094368)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 25
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值