【问题标题】:Doing a task after a view became invisible在视图变得不可见后执行任务
【发布时间】:2021-04-02 17:36:08
【问题描述】:

我正在使用 Android MediaProjection Api 编写一个屏幕截图应用程序,其中一个覆盖按钮显示在所有内容的顶部,用户可以单击它以在任何地方捕获屏幕截图。由于 MediaProjection 记录屏幕内容,因此覆盖按钮本身位于捕获的屏幕截图中。为了在截屏时隐藏按钮,我尝试将视图visibility 设置为INVISIBLE,截屏并将其还原为VISIBLE,但由于更改可见性是Android 中的异步操作,有时覆盖按钮仍然存在出现在录制的镜头中

我改成下面的 sn-p 并且它在我的实验中起作用:

floatingButton?.setOnClickListener { view ->
    view.visibility = View.INVISIBLE
    view.postDelayed(100) {
        takeShot()
        view.post {view.visibility = View.VISIBLE}
    }
}

但这基本上是说我感觉很好,在 100 毫秒内,按钮将不可见。这不是一个好的解决方案,对于视频,100 毫秒内的内容可能与用户当时实际看到的内容大不相同。

Android 不提供 onVisibiltyChangedListener 之类的东西,那么在确保视图可见性发生变化后,我该如何执行任务


编辑 1

这是takeShot() 方法:

private fun takeShot() {
    val image = imageReader.acquireLatestImage()
    val bitmap = image?.run {
         val planes = image.planes
         val buffer: ByteBuffer = planes[0].buffer
         val pixelStride = planes[0].pixelStride
         val rowStride = planes[0].rowStride
         val rowPadding = rowStride - pixelStride * width
         val bitmap = Bitmap.createBitmap(
                width + rowPadding / pixelStride,
                height,
                Bitmap.Config.ARGB_8888
         )
         bitmap.copyPixelsFromBuffer(buffer)
         image.close()

         bitmap
    }
    bitmap?.let{
        serviceScope.launch {
            gallery.store(it)
        }
    }
}

代码在前台服务中,当用户接受媒体投影时,我创建ImageReaderVirtualDisplay

imageReader = ImageReader.newInstance(size.width, size.height, PixelFormat.RGBA_8888, 2)
virtualDisplay = mediaProjection.createVirtualDisplay(
            "screen-mirror",
            size.width,
            size.height,
            Resources.getSystem().displayMetrics.densityDpi,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // TODO: DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC ??
            imageReader.surface, null, null
        )

mediaProjection.registerCallback(object : MediaProjection.Callback() {
            override fun onStop() {
                virtualDisplay.release()
                mediaProjection.unregisterCallback(this)
            }
        }, null)

我试过没有暂停和协程的东西,结果是一样的,所以它们很可能与问题无关。

【问题讨论】:

  • 试试ViewTreeObserver
  • 我尝试使用 AndroidX View.doOnPreDraw 但没有成功。你能解释一下吗?
  • stackoverflow.com/a/32778292/4168607 。我猜这应该可行。
  • 我使用了该方法并通过设置日志,我看到当覆盖按钮不可见时,实际上调用了捕获方法 (imageReader.acquireLatestImage()),但它有时仍会出现在屏幕截图中! acquireLatestImage() 似乎没有必要给你“最新图像”。时间是不确定的。
  • 你能把takeShot()方法加上 question 吗?还要在其他设备上检查它。

标签: android android-view visibility android-mediaprojection


【解决方案1】:

似乎我的问题与MediaProjection 有关,这将是一个单独的问题,但这个问题本身是相关的。

我最终使用了这个(几乎是为doOnPreDraw() 复制粘贴 core-ktx 代码)。请注意:

  1. 这不适用于 View.INVISIBLE,因为 INVISIBLE 不会触发“布局”

  2. 我不赞同这一点,因为它是全球性的,这意味着与“someView”视图层次结构相关的每个可见性更改都将调用onGlobalLayout 方法(因此您的操作/可运行)。

我保存接受的答案以获得更好的解决方案。

// usage
// someView.doOnVisibilityChange(become = View.GONE) {
//     someView is GONE, do stuff here
// }
inline fun View.doOnVisibilityChange(become: Int, crossinline action: (view: View) -> Unit) {
    OneShotVisibilityChangeListener(this) { action(this) }
    visibility = newVisibility
}

class OneShotVisibilityChangeListener(
    private val view: View,
    private val runnable: Runnable
) : ViewTreeObserver.OnGlobalLayoutListener, View.OnAttachStateChangeListener {

    private var viewTreeObserver: ViewTreeObserver

    init {
        viewTreeObserver = view.viewTreeObserver
        viewTreeObserver.addOnGlobalLayoutListener(this)
        view.addOnAttachStateChangeListener(this)
    }

    override fun onGlobalLayout() {
        removeListener()
        runnable.run()
    }

    private fun removeListener() {
        if (viewTreeObserver.isAlive) {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
        } else {
            view.viewTreeObserver.removeOnGlobalLayoutListener(this)
        }
        view.removeOnAttachStateChangeListener(this)
    }

    override fun onViewAttachedToWindow(v: View) {
        viewTreeObserver = v.viewTreeObserver
    }

    override fun onViewDetachedFromWindow(v: View) {
        removeListener()
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    相关资源
    最近更新 更多