Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
206 views
in Technique[技术] by (71.8m points)

android - Doing a task after a view became invisible

I'm writing a screenshot app using Android MediaProjection Api in which an overlay button is shown on top of everything and user can click it to capture a screenshot anywhere. Since MediaProjection records screen content, overlay button itself is in captured screenshots. To hide the button when capturing screenshot, I tried to set view visibility to INVISIBLE, take screenshot and revert it back to VISIBLE but since changing visibility is an async operation in Android, sometimes overlay button is still present in recorded shots.

I Changed to below snippet and it worked in my experiments:

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

But it's basically saying I feeling good that in 100ms, button would be invisible. It's not a good solution and in the case of videos, in 100ms content could be very different from what user actually saw at that moment.

Android doesn't provide a onVisibiltyChangedListener kind of thing, so how could I perform a task after ensuring that a view visibility has changed?


Edit 1

Here's the takeShot() method:

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)
        }
    }
}

The codes are inside of a foreground service and when user accepts media projection, I create ImageReader and VirtualDisplay:

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)

I've tried without suspension and coroutine stuff and result was the same, so they most likely are irrelevant to problem.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Seems my problem is related to MediaProjection and that would be a separate question, but this question itself is relevant.

I ended up using this (almost copy-pasting core-ktx code for doOnPreDraw()). Pay attention that:

  1. This doesn't work for View.INVISIBLE, because INVISIBLE doesn't trigger a "layout"

  2. I don't endorse this, since it's GLOBAL, meaning that every visibility change related to "someView" view hierarchy, will call the onGlobalLayout method (and therefore your action/runnable).

I save the accepted answer for a better solution.

// 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()
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...