In my layout i display a stream widget (the WidgetFPV one) which contains a surface onto which a camera stream is projected via DJI'S MSDK5 sdk. I also have a card mapCard
which hosts a MapBox map fragment that displays a map. I'm now allowing users to click on the map (which is minimized to the bottom-left corner of the screen) in order to maximize it and consequently I minimize the FPV stream. However, I'm noticing that when the map is expanded and thus the stream minimized, the stream then gets hidden behind the expanded map despite me setting the elevation to a higher value than the map.
If i set the mapCard to View.INVISIBLE
then I can see the stream "behind" it. Any ideas what I'm doing wrong? I also tried the following:
bringToFront
on the FPV widget for example and also calling clRoot.invalidate()
cardElevation
and elevation
setters as well on the mapCardsetZ()
and/or setTranslationZ()
All to no avail sadly.
layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clRoot"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.pckg.dji.widgets.ui.fpv.FPVWidget
android:id="@+id/widgetFPV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:keepScreenOn="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<other components of the layout>
<androidx.cardview.widget.CardView
android:id="@+id/mapCard"
android:layout_width="@dimen/dji_widgetMap_width"
android:layout_height="@dimen/dji_widgetMap_height"
android:layout_marginStart="@dimen/core_margin_s"
android:layout_marginBottom="@dimen/core_margin_s"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.pkg.dji.ui.map.Dji5MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_dji_map" />
</androidx.cardview.widget.CardView>
....
</ConstraintLayout>
The code that adjusts the elevation and constraints dynamically is the following:
private fun handleMapExpansionToggle(isMapExpanded: Boolean) {
with(binding) {
val widgetFPVDims = if (!isMapExpanded) MATCH_PARENT to MATCH_PARENT else 400 to 212
val mapCardDims = if (!isMapExpanded) 160 to 86 else MATCH_PARENT to MATCH_PARENT
val displayDensityFactor: Float =
requireContext().resources.displayMetrics.density // used to convert pixels to dp
widgetFPV.reset()
// scale down camera surface
widgetFPV.setup(width = widgetFPVDims.first, height = widgetFPVDims.second)
// scale down/up fpv widget
widgetFPV.layoutParams =
ConstraintLayout.LayoutParams(widgetFPVDims.first, widgetFPVDims.second)
widgetFPV.translationZ = if (!isMapExpanded) 0f else 30f
widgetFPV.updateLayoutParams<ConstraintLayout.LayoutParams> {
startToStart = clRoot.id
bottomToBottom = clRoot.id
if (!isMapExpanded) {
endToEnd = clRoot.id
topToTop = clRoot.id
}
marginStart = if (!isMapExpanded) 0 else 12
bottomMargin = if (!isMapExpanded) 0 else 12
}
// scale up/down map
val mapLayoutParams =
ConstraintLayout.LayoutParams(
(mapCardDims.first * displayDensityFactor).toInt(),
(mapCardDims.second * displayDensityFactor).toInt()
)
mapLayoutParams.setMargins(
if (!isMapExpanded) 12 else 0, 0, 0, if (!isMapExpanded) 12 else 0
)
mapCard.layoutParams = mapLayoutParams
mapCard.elevation = if (!isMapExpanded) 21f else 0f
mapCard.cardElevation = if (!isMapExpanded) 21f else 0f
map.translationZ = if (!isMapExpanded) 21f else 0f
mapCard.translationZ = if (!isMapExpanded) 21f else 0f
mapCard.radius = if (!isMapExpanded) 8f else 0f
mapCard.updateLayoutParams<ConstraintLayout.LayoutParams> {
startToStart = clRoot.id
bottomToBottom = clRoot.id
if (isMapExpanded) {
endToEnd = clRoot.id
topToTop = clRoot.id
}
marginStart = if (!isMapExpanded) 12 else 0
bottomMargin = if (!isMapExpanded) 12 else 0
}
if (isMapExpanded) {
mapCard.setPadding(0, 12, 0, 0)
}
// update telemetry widget constraints
widgetTelemetry.updateLayoutParams<ConstraintLayout.LayoutParams> {
startToEnd = if (isMapExpanded) {
widgetFPV.id
} else {
mapCard.id
}
}
llCamera.isVisible = !isMapExpanded
llPhotoVideo.isVisible = !isMapExpanded
widgetLightsControl.isVisible = !isMapExpanded
imgBtnMapLayers.isVisible = isMapExpanded
}
}
Thanks to @CommonsWare suggestions, using a TextureView
instead fixed the issue right away. Here's what I had to change:
In the original layout i was using a SurfaceView
, so that got swapped for:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/core_dark100">
<TextureView
android:id="@+id/surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</FrameLayout>
Here I wrapped it in a FrameLayout so that I could control "its" color while the video is still loading. Then in the custom ConstraintLayout implementation for my FPV widget, i replaced the SurfaceView's callbacks with the appropriate one for the TextureView:
binding.surface.isOpaque = false // used to display the FrameLayout's bg color
binding.surface.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
isSurfaceReady = true
surface = Surface(surfaceTexture)
surface?.let { doStuff }
}
override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
surface?.let { doStuff }
isSurfaceReady = true
}
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
isSurfaceReady = false
surface?.release()
surface = null
return true
}
override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {
// Called when the SurfaceTexture is updated via updateTexImage()
}
}
That's all i had to do, now I could pass the surface directly to the DJI API.