@@ -2,6 +2,7 @@ package remix.myplayer.ui.widget
2
2
3
3
import android.annotation.SuppressLint
4
4
import android.content.Context
5
+ import android.os.Build
5
6
import android.util.AttributeSet
6
7
import android.view.LayoutInflater
7
8
import android.view.MotionEvent
@@ -11,19 +12,24 @@ import android.widget.LinearLayout
11
12
import android.widget.TextView
12
13
import androidx.annotation.ColorInt
13
14
import androidx.annotation.UiThread
14
- import remix.myplayer.R
15
+ import androidx.core.view.updatePadding
16
+ import remix.myplayer.databinding.LayoutLyricsLineBinding
15
17
import remix.myplayer.databinding.LayoutLyricsViewBinding
16
18
import remix.myplayer.lyrics.LyricsLine
17
19
import remix.myplayer.lyrics.PerWordLyricsLine
18
20
import remix.myplayer.theme.ThemeStore
21
+ import timber.log.Timber
19
22
import kotlin.math.roundToInt
20
23
import kotlin.time.Duration.Companion.milliseconds
21
24
22
25
class LyricsView @JvmOverloads constructor(
23
26
context : Context , attrs : AttributeSet ? = null
24
27
) : FrameLayout(context, attrs), View.OnTouchListener {
25
28
companion object {
29
+ private const val TAG = " LyricsView"
30
+
26
31
private val DEACTIVATE_DELAY = 5000 .milliseconds
32
+ private val AUTO_SCROLL_DELAY = 200 .milliseconds
27
33
28
34
private val normalTextColor
29
35
@ColorInt get() = ThemeStore .textColorSecondary
@@ -41,40 +47,29 @@ class LyricsView @JvmOverloads constructor(
41
47
42
48
override fun onSizeChanged (w : Int , h : Int , oldw : Int , oldh : Int ) {
43
49
super .onSizeChanged(w, h, oldw, oldh)
50
+ Timber .tag(TAG ).v(" onSizeChanged, h=$h " )
44
51
if (h != oldh) {
45
52
// 给 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
+ }
47
57
}
48
58
}
49
59
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 )
59
63
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)
64
66
} else {
65
67
line.content
66
68
}
67
- view.setTextColor(textColor)
68
- layout.addView(view)
69
69
}
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
76
72
}
77
- return layout
78
73
}
79
74
80
75
/* *
@@ -89,7 +84,7 @@ class LyricsView @JvmOverloads constructor(
89
84
binding.innerContainer.removeAllViews()
90
85
isClickable = lyrics.isNotEmpty()
91
86
value.forEach {
92
- binding.innerContainer.addView(newLayoutForLine(it) )
87
+ addLayoutForLine(it )
93
88
}
94
89
rawProgressAndDuration = null
95
90
lastHighlightLine = null
@@ -107,7 +102,7 @@ class LyricsView @JvmOverloads constructor(
107
102
}
108
103
field = value
109
104
if (isActive) {
110
- showTimeIndicator ()
105
+ updateTimeIndicator ()
111
106
}
112
107
rawProgressAndDuration?.run {
113
108
updateProgress(first, second)
@@ -201,7 +196,8 @@ class LyricsView @JvmOverloads constructor(
201
196
set(value) {
202
197
field = value
203
198
if (value) {
204
- showTimeIndicator()
199
+ updateTimeIndicator()
200
+ binding.timeIndicator.visibility = View .VISIBLE
205
201
handler.removeCallbacks(deactivateRunnable)
206
202
handler.postDelayed(deactivateRunnable, DEACTIVATE_DELAY .inWholeMilliseconds)
207
203
} else {
@@ -215,34 +211,69 @@ class LyricsView @JvmOverloads constructor(
215
211
private val deactivateRunnable = Runnable {
216
212
isActive = false
217
213
}
214
+ private val scrollToNearestLineRunnable = Runnable {
215
+ scrollToLine(getNearestLine())
216
+ }
218
217
219
218
@SuppressLint(" SetTextI18n" )
220
- private fun showTimeIndicator () {
219
+ private fun updateTimeIndicator () {
221
220
(lyrics[getNearestLine()].time - offset).coerceAtLeast(0 ).let {
222
221
binding.time.text =
223
222
" %02d:%02d.%02d" .format(it / 1000 / 60 , it / 1000 % 60 , (it % 1000 / 10f ).roundToInt())
223
+ // TODO: don't set every time
224
224
binding.playButton.setOnClickListener { _ ->
225
225
onSeekToListener?.onSeekTo(it)
226
226
}
227
227
}
228
- binding.timeIndicator.visibility = View .VISIBLE
229
228
}
230
229
230
+ private var isTouching: Boolean = false
231
+
231
232
override fun onTouch (v : View , event : MotionEvent ): Boolean {
233
+ check(v == binding.outerContainer)
234
+
232
235
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
+
233
249
return false
234
250
}
235
251
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
+
236
261
// 在单独函数以忽略警告
237
262
@SuppressLint(" ClickableViewAccessibility" )
238
- private fun setupOnTouchListener () {
263
+ private fun init () {
239
264
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
+ }
240
274
}
241
275
242
276
init {
243
- binding.outerContainer.onFlingEndListener = ResponsiveScrollView .OnFlingEndListener {
244
- scrollToLine(getNearestLine())
245
- }
246
- setupOnTouchListener()
277
+ init ()
247
278
}
248
279
}
0 commit comments