在视图变得不可见后执行任务

Posted

技术标签:

【中文标题】在视图变得不可见后执行任务【英文标题】: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 但没有成功。你能解释一下吗? ***.com/a/32778292/4168607 。我猜这应该可行。 我使用了该方法并通过设置日志,我看到当覆盖按钮不可见时,实际上调用了捕获方法 (imageReader.acquireLatestImage()),但它有时仍会出现在屏幕截图中! acquireLatestImage() 似乎没有必要给你“最新图像”。时间是不确定的。 你能把takeShot()方法加上 question 吗?还要在其他设备上检查它。 【参考方案1】:

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

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

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

    我不赞同这一点,因为它是全球性的,这意味着与“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()
    

【讨论】:

以上是关于在视图变得不可见后执行任务的主要内容,如果未能解决你的问题,请参考以下文章

当 viewController 的视图变得可见时,视图的框架设置不正确

当tableview单元格变得不可见时如何获取UITextField(UITableViewCell的子视图)文本值

我怎样才能捕捉到视图变得可见

从 iphone 上的 push segue 导航返回时,顶部导航栏变得可见

滚动时的 BaseAdapter 列表视图位置

CALayer 在视图可见后添加时不渲染