我制作了一个自定义视频播放器,它的工作大多很好。然而,有一个bug,我想摆脱。我需要你帮我找出我做错了什么。对于更多的上下文,我将postDelay Runnable
设置为在3秒后隐藏视图,这正在工作。然而,如果用户继续与视频视图及其选项交互,我希望能够取消该可运行. 以下是代码,到目前为止,我所尝试的是删除挂起的回调和消息,其中,没有按预期响应,因为视图在我之前设置的3秒后不断消失:
NB:我分享了整个类,让您更清楚地了解我的实现
package consumer.app.co.za.rain.clean.ui.view.custom.doityourselfvideoplayer
import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.SeekBar
import consumer.app.co.za.rain.R
import consumer.app.co.za.rain.databinding.MiniScreenVideoPlayerLayoutBinding
@SuppressLint("ClickableViewAccessibility")
class VideoPlayerMiniScreen @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
/** Initialize global/class variables */
private val binding = MiniScreenVideoPlayerLayoutBinding.inflate(
LayoutInflater.from(context),
this,
true
)
private var seekBarTracking = false
private val handler = Handler(Looper.getMainLooper())
private val updateProgressRunnable = object : Runnable {
override fun run() {
if (!seekBarTracking && binding.vvDoItYourself.isPlaying) {
val currentPosition = binding.vvDoItYourself.currentPosition
binding.seekBarDoItYourself.progress = currentPosition
}
handler.postDelayed(this, 16)
}
}
init {
with(binding) {
/** Set up video player widgets */
videoOptionsDoItYourself.apply {
visibility = VISIBLE
z = 2F
}
ivPlayPause.apply {
alpha = 1F
visibility = GONE
z = 2F
}
val hideViewRunnable = Runnable {
hideView(videoOptionsDoItYourself)
}
/** Set up video player */
vvDoItYourself.run {
z = 1F
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
/** Remove the pending post of setViewToDisappear callback */
cancelSetViewToDisappear(hideViewRunnable)
Log.d("DIY", "Touched Down")
if (ivPlayPause.visibility == VISIBLE) togglePlayPause()
else {
Log.d("DIY", "Released")
showView(ivPlayPause)
showView(videoOptionsDoItYourself)
}
}
MotionEvent.ACTION_UP -> {
setViewToDisappear(ivPlayPause)
setViewToDisappear(videoOptionsDoItYourself)
}
}
true
}
setOnPreparedListener { mediaPlayer ->
seekBarDoItYourself.max = mediaPlayer.duration
/** Start in a stale state */
if (mediaPlayer.isPlaying) mediaPlayer.stop()
mediaPlayer.setOnSeekCompleteListener {
val currentPosition = mediaPlayer.currentPosition
seekBarDoItYourself.progress = currentPosition
}
/** Update Progress Bar in real-time */
handler.post(updateProgressRunnable)
}
setOnCompletionListener { mediaPlayer ->
ivPlayPause.setImageResource(R.drawable.baseline_play_arrow_24)
showView(ivPlayPause)
seekBarDoItYourself.progress = 0
mediaPlayer.seekTo(0)
handler.removeCallbacks(updateProgressRunnable)
}
}
ivPlayPause.setOnClickListener {
togglePlayPause()
}
seekBarDoItYourself.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar?,
progress: Int,
fromUser: Boolean
) {
// Update the seek bar's progress as the video plays
if (!seekBarTracking) {
if (vvDoItYourself.isPlaying) {
val currentPosition = vvDoItYourself.currentPosition
seekBarDoItYourself.progress = currentPosition
}
}
// Update the video's position to the seek bar's progress
if (fromUser) {
vvDoItYourself.seekTo(progress)
seekBarDoItYourself.progress = progress
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
seekBarTracking = true
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
seekBarTracking = false
seekBar?.let {
val progress = it.progress
vvDoItYourself.seekTo(progress)
}
}
})
}
}
fun setVideoURI(uri: Uri) {
binding.vvDoItYourself.setVideoURI(uri)
}
private fun togglePlayPause() {
with(binding) {
if (!vvDoItYourself.isPlaying) {
ivPlayPause.setImageResource(R.drawable.baseline_pause_24)
vvDoItYourself.start()
} else {
ivPlayPause.setImageResource(R.drawable.baseline_play_arrow_24)
vvDoItYourself.pause()
}
}
}
/**
* Set to be called when controls are hidden
*/
private fun showView(view: View) {
view.run {
visibility = VISIBLE
alpha = 0F
animate().alpha(1F).setDuration(500).withEndAction {
}.start()
}
}
private fun hideView(view: View) {
view.run {
alpha = 1F
animate().alpha(0F).setDuration(500).withEndAction {
view.visibility = GONE
}.start()
}
}
fun setViewToDisappear(view: View) {
val hideViewRunnable = Runnable {
hideView(view)
}
handler.postDelayed(hideViewRunnable, 3000)
}
fun cancelSetViewToDisappear(runnable: Runnable) {
handler.removeCallbacksAndMessages(runnable)
}
}
如果你想取消挂起的Runnable
,你必须将特定的Runnable
对象传递给remove回调调用。现在,当你发布Runnable时,你没有保留对它的引用:
fun setViewToDisappear(view: View) {
// you're only storing this in a variable local to the function
val hideViewRunnable = Runnable { hideView(view) }
handler.postDelayed(hideViewRunnable, 3000)
}
你试图取消的是一个不同的Runnable(恰好有相同的名称),这是init
块的本地(并由触摸侦听器闭包捕获-即你在lambda中引用它,因此该块有对它的引用)
// in the init block
// this is a local variable
val hideViewRunnable = Runnable { hideView(videoOptionsDoItYourself) }
...
// in the touch listener defined in the init block
cancelSetViewToDisappear(hideViewRunnable)
所以你传递了一个不同的Runnable
实例给你的cancel函数。
您有两个选择-可能最简单的是使hideViewRunnable
成为顶级变量,因此setViewToDisappear
可以引用它,而不必创建一个新的实例。这样你就可以发布和取消相同的Runnable
。
Handler
post和remove函数,它们接受令牌参数——可以是任何东西,它只是一个共享对象,允许您通过使用相同的令牌对象将内容分组在一起,并且您可以轻松地取消使用相同令牌的任何内容。与重用相同的Runnable的想法相同,除了您使用共享令牌对象来标识它们