Skip to content

Commit f5b6b7d

Browse files
committed
重构歌词相关代码:驯服 ScrollView
1 parent e514cfc commit f5b6b7d

21 files changed

+156
-129
lines changed

app/src/main/java/remix/myplayer/helper/LyricsHelper.kt

+30
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
11
package remix.myplayer.helper
22

3+
import android.content.Context
4+
import remix.myplayer.R
35
import remix.myplayer.lyrics.LyricsLine
6+
import remix.myplayer.theme.Theme
47
import remix.myplayer.ui.widget.desktop.DesktopLyricsView
8+
import remix.myplayer.util.SPUtil
59

610
object LyricsHelper {
11+
fun showLocalLyricsTip(context: Context, action: () -> Unit) {
12+
if (!SPUtil.getValue(
13+
context,
14+
SPUtil.LYRICS_KEY.NAME,
15+
SPUtil.LYRICS_KEY.LOCAL_LYRICS_TIP_SHOWN,
16+
false
17+
)
18+
) {
19+
Theme.getBaseDialog(context)
20+
.positiveText(R.string.confirm)
21+
.onPositive { _, _ ->
22+
SPUtil.putValue(
23+
context,
24+
SPUtil.LYRICS_KEY.NAME,
25+
SPUtil.LYRICS_KEY.LOCAL_LYRICS_TIP_SHOWN,
26+
true
27+
)
28+
action.invoke()
29+
}
30+
.content(R.string.local_lyrics_tip)
31+
.show()
32+
} else {
33+
action.invoke()
34+
}
35+
}
36+
737
fun getDesktopLyricsContent(
838
lyrics: List<LyricsLine>, offset: Int, progress: Int, duration: Int
939
): DesktopLyricsView.Content {

app/src/main/java/remix/myplayer/lyrics/LrcParser.kt

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ object LrcParser {
7373
// [xxx]
7474
if (it.endsWith(']')) {
7575
val tag = it.substring(1, it.lastIndex)
76-
println(tag)
7776
// [offset:+/-xxx]
7877
if (tag.startsWith("offset:")) {
7978
try {

app/src/main/java/remix/myplayer/lyrics/LyricsLine.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package remix.myplayer.lyrics
22

3+
import kotlinx.serialization.Serializable
34
import remix.myplayer.App
45
import remix.myplayer.R
56

6-
abstract class LyricsLine {
7+
@Serializable
8+
sealed class LyricsLine {
79
/**
810
* 这行歌词的开始时间
911
*/

app/src/main/java/remix/myplayer/lyrics/LyricsSearcher.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ object LyricsSearcher {
8787
when (song) {
8888
is Song.Local -> "local"
8989
is Song.Remote -> "remote"
90-
}, if (song is Song.Local) song.id else song.data, song.title, song.artist, song.album
90+
},
91+
if (song is Song.Local) song.id.toString() else song.data,
92+
song.title,
93+
song.artist,
94+
song.album
9195
)
9296
)
9397
// 要作为文件名,安全起见保证输出长度不超过 127 字节,SHA-384 输出 96 字节

app/src/main/java/remix/myplayer/misc/menu/AudioPopupListener.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import remix.myplayer.bean.mp3.Song
1313
import remix.myplayer.db.room.DatabaseRepository
1414
import remix.myplayer.helper.DeleteHelper
1515
import remix.myplayer.helper.EQHelper
16+
import remix.myplayer.helper.LyricsHelper
1617
import remix.myplayer.helper.MusicServiceRemote
1718
import remix.myplayer.helper.MusicServiceRemote.getCurrentSong
1819
import remix.myplayer.lyrics.provider.EmbeddedProvider
1920
import remix.myplayer.lyrics.provider.IgnoredProvider
2021
import remix.myplayer.lyrics.provider.StubProvider
2122
import remix.myplayer.service.Command
2223
import remix.myplayer.theme.Theme.getBaseDialog
23-
import remix.myplayer.ui.ViewCommon
2424
import remix.myplayer.ui.activity.PlayerActivity
2525
import remix.myplayer.ui.dialog.AddtoPlayListDialog
2626
import remix.myplayer.ui.dialog.TimerDialog
@@ -46,7 +46,7 @@ class AudioPopupListener(activity: PlayerActivity, private val song: Song) :
4646
val activity = ref.get() ?: return true
4747
when (item.itemId) {
4848
R.id.menu_lyric -> {
49-
ViewCommon.showLocalLyricTip(activity) {
49+
LyricsHelper.showLocalLyricsTip(activity) {
5050
onClickLyric(activity)
5151
}
5252
return true
@@ -169,9 +169,10 @@ class AudioPopupListener(activity: PlayerActivity, private val song: Song) :
169169

170170
7 -> MusicServiceRemote.service?.updateLyrics(IgnoredProvider) // 忽略
171171
8 -> TODO() // 调整字体大小
172-
9 -> activity.showLyricOffsetView()// 调整时间轴
172+
9 -> activity.showLyricOffsetView() // 调整时间轴
173173
}
174174
}.show()
175+
// TODO: update LyricsFragment
175176
}
176177

177178
companion object {

app/src/main/java/remix/myplayer/ui/ViewCommon.kt

-31
This file was deleted.

app/src/main/java/remix/myplayer/ui/activity/PlayerActivity.kt

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import android.widget.ImageView
2626
import android.widget.LinearLayout
2727
import android.widget.SeekBar
2828
import androidx.activity.result.contract.ActivityResultContracts
29-
import androidx.annotation.RequiresApi
3029
import androidx.appcompat.widget.PopupMenu
3130
import androidx.fragment.app.FragmentManager
3231
import androidx.palette.graphics.Palette

app/src/main/java/remix/myplayer/ui/activity/SettingActivity.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import remix.myplayer.glide.UriFetcher.DOWNLOAD_LASTFM
4646
import remix.myplayer.helper.EQHelper
4747
import remix.myplayer.helper.LanguageHelper
4848
import remix.myplayer.helper.LanguageHelper.AUTO
49+
import remix.myplayer.helper.LyricsHelper
4950
import remix.myplayer.helper.M3UHelper.exportPlayListToFile
5051
import remix.myplayer.helper.M3UHelper.importLocalPlayList
5152
import remix.myplayer.helper.M3UHelper.importM3UFile
@@ -70,7 +71,6 @@ import remix.myplayer.theme.Theme
7071
import remix.myplayer.theme.Theme.getBaseDialog
7172
import remix.myplayer.theme.ThemeStore
7273
import remix.myplayer.theme.TintHelper
73-
import remix.myplayer.ui.ViewCommon
7474
import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_LIBRARY
7575
import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_RECREATE
7676
import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_REFRESH_ADAPTER
@@ -900,7 +900,7 @@ class SettingActivity : ToolbarActivity(), ColorChooserDialog.ColorCallback,
900900
* 歌词搜索优先级
901901
*/
902902
private fun configLyricPriority() {
903-
ViewCommon.showLocalLyricTip(this) {
903+
LyricsHelper.showLocalLyricsTip(this) {
904904
LyricsOrderDialog().show(supportFragmentManager, "configLyricPriority")
905905
}
906906
}

app/src/main/java/remix/myplayer/ui/fragment/LyricsFragment.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,13 @@ class LyricsFragment : BaseMusicFragment<FragmentLrcBinding>(), View.OnClickList
118118
override fun onMediaStoreChanged() {
119119
super.onMediaStoreChanged()
120120
updateLyrics()
121-
TODO("when is it called?")
121+
// TODO("when is it called?")
122122
}
123123

124124
override fun onMetaChanged() {
125125
super.onMetaChanged()
126126
updateLyrics()
127-
TODO("when is it called?")
127+
// TODO("when is it called?")
128128
}
129129

130130
@UiThread

app/src/main/java/remix/myplayer/ui/widget/LyricsView.kt

+65-34
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package remix.myplayer.ui.widget
22

33
import android.annotation.SuppressLint
44
import android.content.Context
5+
import android.os.Build
56
import android.util.AttributeSet
67
import android.view.LayoutInflater
78
import android.view.MotionEvent
@@ -11,19 +12,24 @@ import android.widget.LinearLayout
1112
import android.widget.TextView
1213
import androidx.annotation.ColorInt
1314
import androidx.annotation.UiThread
14-
import remix.myplayer.R
15+
import androidx.core.view.updatePadding
16+
import remix.myplayer.databinding.LayoutLyricsLineBinding
1517
import remix.myplayer.databinding.LayoutLyricsViewBinding
1618
import remix.myplayer.lyrics.LyricsLine
1719
import remix.myplayer.lyrics.PerWordLyricsLine
1820
import remix.myplayer.theme.ThemeStore
21+
import timber.log.Timber
1922
import kotlin.math.roundToInt
2023
import kotlin.time.Duration.Companion.milliseconds
2124

2225
class LyricsView @JvmOverloads constructor(
2326
context: Context, attrs: AttributeSet? = null
2427
) : FrameLayout(context, attrs), View.OnTouchListener {
2528
companion object {
29+
private const val TAG = "LyricsView"
30+
2631
private val DEACTIVATE_DELAY = 5000.milliseconds
32+
private val AUTO_SCROLL_DELAY = 200.milliseconds
2733

2834
private val normalTextColor
2935
@ColorInt get() = ThemeStore.textColorSecondary
@@ -41,40 +47,29 @@ class LyricsView @JvmOverloads constructor(
4147

4248
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
4349
super.onSizeChanged(w, h, oldw, oldh)
50+
Timber.tag(TAG).v("onSizeChanged, h=$h")
4451
if (h != oldh) {
4552
// 给 container 上下加空白,确保第一行和最后一行歌词可以滚动到 view 中间
46-
binding.innerContainer.setPadding(0, h / 2, 0, h / 2)
53+
val padding = (h + 1) / 2
54+
handler.post {
55+
binding.innerContainer.setPadding(0, padding, 0, padding)
56+
}
4757
}
4858
}
4959

50-
private fun newLayoutForLine(line: LyricsLine): LinearLayout {
51-
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
52-
val padding = resources.getDimensionPixelSize(R.dimen.lyrics_view_lrc_block_vertical_padding)
53-
val textColor = normalTextColor
54-
55-
val layout = LinearLayout(context)
56-
layout.layoutParams = params
57-
layout.setPadding(0, padding, 0, padding)
58-
layout.orientation = LinearLayout.VERTICAL
60+
private fun addLayoutForLine(line: LyricsLine) {
61+
val layout =
62+
LayoutLyricsLineBinding.inflate(LayoutInflater.from(context), binding.innerContainer, true)
5963
if (line.content.isNotBlank()) {
60-
val view = TextView(context)
61-
view.layoutParams = params
62-
view.text = if (line is PerWordLyricsLine) {
63-
line.getSpannedString(0f, textColor)
64+
layout.content.text = if (line is PerWordLyricsLine) {
65+
line.getSpannedString(0f, normalTextColor)
6466
} else {
6567
line.content
6668
}
67-
view.setTextColor(textColor)
68-
layout.addView(view)
6969
}
70-
if (line.translation?.isNotBlank() == true) {
71-
val view = TextView(context)
72-
view.layoutParams = params
73-
view.text = line.translation
74-
view.setTextColor(textColor)
75-
layout.addView(view)
70+
if (!line.translation.isNullOrBlank()) {
71+
layout.translation.text = line.translation
7672
}
77-
return layout
7873
}
7974

8075
/**
@@ -89,7 +84,7 @@ class LyricsView @JvmOverloads constructor(
8984
binding.innerContainer.removeAllViews()
9085
isClickable = lyrics.isNotEmpty()
9186
value.forEach {
92-
binding.innerContainer.addView(newLayoutForLine(it))
87+
addLayoutForLine(it)
9388
}
9489
rawProgressAndDuration = null
9590
lastHighlightLine = null
@@ -107,7 +102,7 @@ class LyricsView @JvmOverloads constructor(
107102
}
108103
field = value
109104
if (isActive) {
110-
showTimeIndicator()
105+
updateTimeIndicator()
111106
}
112107
rawProgressAndDuration?.run {
113108
updateProgress(first, second)
@@ -201,7 +196,8 @@ class LyricsView @JvmOverloads constructor(
201196
set(value) {
202197
field = value
203198
if (value) {
204-
showTimeIndicator()
199+
updateTimeIndicator()
200+
binding.timeIndicator.visibility = View.VISIBLE
205201
handler.removeCallbacks(deactivateRunnable)
206202
handler.postDelayed(deactivateRunnable, DEACTIVATE_DELAY.inWholeMilliseconds)
207203
} else {
@@ -215,34 +211,69 @@ class LyricsView @JvmOverloads constructor(
215211
private val deactivateRunnable = Runnable {
216212
isActive = false
217213
}
214+
private val scrollToNearestLineRunnable = Runnable {
215+
scrollToLine(getNearestLine())
216+
}
218217

219218
@SuppressLint("SetTextI18n")
220-
private fun showTimeIndicator() {
219+
private fun updateTimeIndicator() {
221220
(lyrics[getNearestLine()].time - offset).coerceAtLeast(0).let {
222221
binding.time.text =
223222
"%02d:%02d.%02d".format(it / 1000 / 60, it / 1000 % 60, (it % 1000 / 10f).roundToInt())
223+
// TODO: don't set every time
224224
binding.playButton.setOnClickListener { _ ->
225225
onSeekToListener?.onSeekTo(it)
226226
}
227227
}
228-
binding.timeIndicator.visibility = View.VISIBLE
229228
}
230229

230+
private var isTouching: Boolean = false
231+
231232
override fun onTouch(v: View, event: MotionEvent): Boolean {
233+
check(v == binding.outerContainer)
234+
232235
isActive = true
236+
237+
when (event.action) {
238+
MotionEvent.ACTION_DOWN -> {
239+
isTouching = true
240+
handler.removeCallbacks(scrollToNearestLineRunnable)
241+
}
242+
243+
MotionEvent.ACTION_UP -> {
244+
isTouching = false
245+
handler.postDelayed(scrollToNearestLineRunnable, AUTO_SCROLL_DELAY.inWholeMilliseconds)
246+
}
247+
}
248+
233249
return false
234250
}
235251

252+
private fun onScrollChange() {
253+
updateTimeIndicator()
254+
255+
handler.removeCallbacks(scrollToNearestLineRunnable)
256+
if (!isTouching) {
257+
handler.postDelayed(scrollToNearestLineRunnable, AUTO_SCROLL_DELAY.inWholeMilliseconds)
258+
}
259+
}
260+
236261
// 在单独函数以忽略警告
237262
@SuppressLint("ClickableViewAccessibility")
238-
private fun setupOnTouchListener() {
263+
private fun init() {
239264
binding.outerContainer.setOnTouchListener(this)
265+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
266+
binding.outerContainer.setOnScrollChangeListener { _, _, _, _, _ ->
267+
onScrollChange()
268+
}
269+
} else {
270+
binding.outerContainer.viewTreeObserver.addOnScrollChangedListener {
271+
onScrollChange()
272+
}
273+
}
240274
}
241275

242276
init {
243-
binding.outerContainer.onFlingEndListener = ResponsiveScrollView.OnFlingEndListener {
244-
scrollToLine(getNearestLine())
245-
}
246-
setupOnTouchListener()
277+
init()
247278
}
248279
}

0 commit comments

Comments
 (0)