Android自定义View--仿QQ音乐歌词

栏目: Android · 发布时间: 6年前

内容简介:国庆长假,祝大家节日愉快,这个控件其实是上周五写的,以前写代码一直都是信马由缰,无拘无束,但是最近开始注重时间和效率,喜欢限时编程,今天这个控件用了4个小时。。。远超当初预订的2个半小时,主要是中间弄了个防火演习,闲话不说,先看效果。列一下功能点: 1.解析lrc格式的文件生成List 2.绘制歌词,绘制高亮歌词 3.高亮歌词移动到中间位置,换行时滚动到中间位置 4.添加滑动事件,快速滑动事件。关于lrc歌词文本,以下摘自百度百科: lrc歌词文本中含有两类标签: 一是标识标签,其格式为“[标识名:值]”

国庆长假,祝大家节日愉快,这个控件其实是上周五写的,以前写代码一直都是信马由缰,无拘无束,但是最近开始注重时间和效率,喜欢限时编程,今天这个控件用了4个小时。。。远超当初预订的2个半小时,主要是中间弄了个防火演习,闲话不说,先看效果。

Android自定义View--仿QQ音乐歌词

1.分析

列一下功能点: 1.解析lrc格式的文件生成List 2.绘制歌词,绘制高亮歌词 3.高亮歌词移动到中间位置,换行时滚动到中间位置 4.添加滑动事件,快速滑动事件。

2.代码

2.1解析lrc格式的文件生成List

关于lrc歌词文本,以下摘自百度百科: lrc歌词文本中含有两类标签: 一是标识标签,其格式为“[标识名:值]”主要包含以下预定义的标签: [ar:歌手名]、[ti:歌曲名]、[al:专辑名]、[by:编辑者(指lrc歌词的制作人)]、[offset:时间补偿值] (其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的,但多数的MP3可能不会支持这种标签)。 二是时间标签,形式为“[mm:ss]”或“[mm:ss.ff]”(分钟数:秒数.百分之一秒数 [2] ),时间标签需位于某行歌词中的句首部分,一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。当歌曲播放到达某一时间点时,MP3就会寻找对应的时间标签并显示标签后面的歌词文本,这样就完成了“歌词同步”的功能。

这里我们使用的是抖音上那首很火的that girl那首歌

[ti:That Girl]
[ar:Morris����]
[al:That Girl]
[by:]
[offset:0]
[00:00.00]That Girl - Morris����
[00:00.13]Lyricist��Stephen Paul Robson/Olly Murs/Claude Kelly
[00:00.34]Composer��Stephen Paul Robson/Olly Murs/Claude Kelly
[00:00.56]There's a girl but I let her get away
[00:05.57]It's all my fault cause pride got in the way
[00:11.12]And I'd be lying if I said I was okay
[00:16.60]About that girl the one I let get away
[00:21.40]I keep saying no
[00:23.82]This can't be the way it was supposed to be
[00:26.92]I keep saying no
[00:29.43]There's gotta be a way to get you close to me
[00:32.54]Now I know you gotta speak up if you want somebody
[00:36.63]Can't let them get away oh no
[00:39.26]You don't wanna end up sorry
[00:41.90]The way that I'm feeling everyday
[00:43.77]Don't you know
[00:44.75]No no no no
[00:47.26]There's no home for the broken heart
[00:49.52]Don't you know
[00:50.06]No no no no
[00:52.68]There's no home for the broken
[00:54.44]There's a girl but I let her get away
[00:59.69]It's my fault cause I said I needed space
[01:05.14]And I've been torturing myself night and day
[01:10.43]About that girl the one I let get away
[01:15.42]I keep saying no
[01:17.96]This can't be the way it was supposed to be
[01:20.80]I keep saying no
[01:23.32]There's gotta be a way
[01:24.54]There's gotta be a way
[01:25.72]To get you close to me
[01:27.13]You gotta speak up if you want somebody
[01:30.50]Can't let them get away oh no
[01:33.09]You don't wanna end up sorry
[01:35.80]The way that I'm feeling everyday
[01:37.91]Don't you know
[01:38.66]No no no no
[01:41.18]There's no home for the broken heart
[01:43.22]Don't you know
[01:44.12]No no no no
[01:46.64]There's no home for the broken
[01:49.42]No home for me
[01:52.10]No home cause I'm broken
[01:54.76]No room to breathe
[01:56.83]And I got no one to blame
[02:00.11]No home for me
[02:02.88]No home cause I'm broken
[02:04.67]About that girl
[02:06.41]The one I let get away
[02:09.57]So you better
[02:10.44]Speak up
[02:13.54]You can't let them get away oh no
[02:16.36]You don't wanna end up sorry
[02:18.90]The way that I'm feeling everyday
[02:21.02]Don't you know
[02:21.97]No no no no
[02:24.39]There's no home for the broken heart
[02:26.23]Don't you know
[02:27.26]No no no no
[02:29.73]There's no home for the broken
[02:31.66]Oh
[02:32.82]You don't wanna lose that love
[02:34.90]It's only gonna hurt too much
[02:36.85]I'm telling you
[02:38.18]You don't wanna lose that love
[02:40.30]It's only gonna hurt too much
[02:42.28]I'm telling you
[02:43.50]You don't wanna lose that love
[02:45.32]Cause there's no hope for the broken heart
[02:47.68]About that girl
[02:49.45]The one I let get away
复制代码

以下是对lrc歌词的解析,解析后程程一个List,每个LyricsItem代表一行歌词

data class LyricsItem(var ti:String="",var ar:String="",var al:String="",var by:String="",var offset:Long=0,var start:Long=0,var duration:Long=0,var lyrics:String="")
复制代码
private fun readLrc(): List<LyricsItem>
    {
        val result = mutableListOf<LyricsItem>()
        try
        {
            val lyricInput = BufferedReader(InputStreamReader(assets.open("thatgirl.lrc")))
            var line = lyricInput.readLine()
            while (line != null)
            {

                val lyricItem = parse(line)
                if(result.size==6)
                {
                    for(i in 0 until 5)
                    {
                        result[i].start=i*lyricItem.start/5
                        result[i].duration=lyricItem.start/5
                    }
                }
                else if (result.size > 6)
                {
                    result[result.size - 1].duration = lyricItem.start - result[result.size - 1].start
                }
                result.add(lyricItem)
                line=lyricInput.readLine()
            }
            lyricInput.close()
        } catch (e: Exception)
        {
            e.printStackTrace()
        }


        return result
    }

    private fun parse(line: String): LyricsItem
    {

        val lyricsItem = LyricsItem()

        val pattern = Pattern.compile("^(\\[(.*?)\\])(.*?)$")

        val matcher = pattern.matcher(line)

        if (matcher.find())
        {
            val front = matcher.group(2)

            when
            {
                front.contains("ti") -> lyricsItem.ti = front.split(":")[1]
                front.contains("ar") -> lyricsItem.ar = front.split(":")[1]
                front.contains("al") -> lyricsItem.al = front.split(":")[1]
                front.contains("by") -> lyricsItem.by = front.split(":")[1]
                front.contains("offset")->lyricsItem.offset=front.split(":")[1].toLong()
                else ->
                {
                    val timeArray = front.split(":")
                    val secondTimeArray=timeArray[1].split(".")
                    val second=secondTimeArray[0].toLong()
                    val micSecond=secondTimeArray[1].toLong()
                    lyricsItem.start = (timeArray[0].toLong() * 60 + second)*1000+micSecond
                    lyricsItem.lyrics = matcher.group(3)
                }
            }

        }

        return lyricsItem
    }
复制代码

2.2绘制歌词,绘制高亮歌词

接着看LyricsView的onDraw方法,这里对歌词进行了绘制

override fun onDraw(canvas: Canvas?)
    {
        super.onDraw(canvas)
        canvas?.let {
            val dstBitmap = Bitmap.createBitmap(width, lyricsHeight, Bitmap.Config.ARGB_8888)
            val dstCanvas = Canvas(dstBitmap)
            drawInfo(dstCanvas)
            drawLyrics(dstCanvas)
            drawHighlight(dstBitmap, it)
        }
    }
复制代码

其中drawInfo方法用来绘制这首歌的一些信息,例如歌手,名称,专辑,作词作曲等

private fun drawInfo(canvas: Canvas)
    {
        for (i in 0 until 4)
        {
            drawLyricItem(canvas, lyricsList[i], i)
        }
    }

复制代码

drawLyrics方法用来绘制歌词

private fun drawLyrics(canvas: Canvas)
    {


        for (i in 5 until lyricsList.size)
        {
            drawLyricItem(canvas, lyricsList[i], i)
        }
    }

    private fun drawLyricItem(canvas: Canvas, lyricsItem: LyricsItem, index: Int)
    {
        paint.color = normalTextColor
        val centerX = width.toFloat() / 2
        val textBound = Rect()
        val lyricContent = getLyricDrawContent(lyricsItem)
        paint.getTextBounds(lyricContent, 0, lyricContent.length, textBound)
        val topOffset = lineHeight.toFloat() * index
        canvas.drawText(lyricContent, centerX - textBound.width() / 2, topOffset + lineHeight / 2 + textBound.height() / 2, paint)
    }
复制代码

drawHightlight方法用来绘制高亮的歌词,也就是唱到的那个歌词

private fun drawHighlight(dstBitmap: Bitmap, canvas: Canvas)
    {
        val centerX = width.toFloat() / 2
        paint.color = normalTextColor
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
        canvas.drawBitmap(dstBitmap, 0f, 0f, paint)
        val lyrics = lyricsList[highLightPos]
        val lyricsContent = getLyricDrawContent(lyrics)
        val textBound = Rect()
        val topOffset = highLightPos * lineHeight.toFloat()
        paint.getTextBounds(lyricsContent, 0, lyricsContent.length, textBound)
        val offset = (time.toFloat() - lyrics.start) / lyrics.duration.toFloat()
        paint.color = highlightTextColor
        canvas.drawRect(centerX - textBound.width() / 2, topOffset, centerX - textBound.width() / 2 + offset * textBound.width(), topOffset + lineHeight, paint)
        paint.xfermode = null
        dstBitmap.recycle()
    }

复制代码

2.3高亮歌词移动到中间位置,换行时滚动到中间位置

歌词的换行移动这里是通过改变scrolley来实现的,首选需要不停的upadet这个控件,将播放时间传入,然后计算出需要进行高亮的歌词进行绘制,并且如果没有进行触摸的话,就将高亮的那行歌词移动到中间位置

fun update(time: Long)
    {
        this.time = time
        for (i in 0 until lyricsList.size)
        {
            val lyricsItem = lyricsList[i]
            if (isInRange(time, lyricsItem))
            {
                if (highLightPos != i)
                {
                    highLightPos = i
                    if (!isTouching && !isScrolling)
                    {
                        scrollToPosition(i)
                    }
                } else
                {
                    postInvalidate()
                }
            }
        }

    }
复制代码

2.4.添加滑动事件,快速滑动事件。

复写onTouchEvent时间,根据滑动距离来跟新scrolly,如果是快速滑动的话则计算出速度,然后让其滑动0.5秒。

override fun onTouchEvent(event: MotionEvent?): Boolean
    {
        val velocityTracker = VelocityTracker.obtain()
        velocityTracker.addMovement(event)
        when (event?.action)
        {
            MotionEvent.ACTION_DOWN ->
            {
                removeCallbacks(resetCallback)
                touchY = event.y
                isTouching = true
            }

            MotionEvent.ACTION_MOVE ->
            {
                scrollY -= (event.y - touchY).toInt()
                scrollY = Math.min(MAX_SCROLLY, Math.max(scrollY, MIN_SCROLLY))
                touchY = event.y
                velocityTracker.computeCurrentVelocity(1000)
                speed = velocityTracker.yVelocity.toInt()
            }

            MotionEvent.ACTION_UP ->
            {
                velocityTracker.clear()
                velocityTracker.recycle()
                scrollOffset(-speed,500)
                postDelayed(resetCallback, 2000)
            }
        }
        return true
    }
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming in Haskell

Programming in Haskell

Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99

Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具