1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > android 音乐歌词接口 Android自定义View--仿QQ音乐歌词

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

时间:2024-03-27 01:32:48

相关推荐

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

0.前言

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

image

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

{

val result = mutableListOf()

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 = pile("^(\\[(.*?)\\])(.*?)$")

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

puteCurrentVelocity(1000)

speed = velocityTracker.yVelocity.toInt()

}

MotionEvent.ACTION_UP ->

{

velocityTracker.clear()

velocityTracker.recycle()

scrollOffset(-speed,500)

postDelayed(resetCallback, 2000)

}

}

return true

}

3.项目地址

image

关注我的公众号

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。