我正在使用AndroidMediaProjection
Api编写一个屏幕截图应用程序,其中所有内容的顶部都显示了一个覆盖按钮,用户可以点击它在任何地方捕捉屏幕截图。由于MediaProjection记录屏幕内容,覆盖按钮本身就在捕获的屏幕截图中。为了在拍摄屏幕截图时隐藏按钮,我尝试将视图visibility
设置为INVISIBLE
,拍摄屏幕截图并将其恢复为VISIBLE
,但由于更改可见性在Android中是一种异步操作,有时覆盖按钮仍然存在于录制的快照中。
我改成了下面的片段,它在我的实验中起到了作用:
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)
}
}
}
代码在前台服务内部,当用户接受媒体投影时,我创建ImageReader
和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)
我试过不停赛和配合比赛,结果是一样的,所以它们很可能和问题无关。
我的问题似乎与MediaProjection
有关,这将是一个单独的问题,但这个问题本身是相关的。
我最终使用了这个(几乎是复制粘贴doOnPreDraw()
的核心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()
}
}