I've created a simple app with the bottom navigation bar and few fragments where one fragment (ArFragment
) shows a camera preview:
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bottomBar = findViewById<BottomNavigationView>(R.id.bottom_navigation)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
bottomBar.setupWithNavController(navHostFragment.navController)
}
}
activity_main.xml:
<LinearLayout
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/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@ id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@ id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:menu="@menu/bottom_navigation_menu" />
</LinearLayout>
ArFragment.kt:
class ArFragment : Fragment() {
private lateinit var cameraExecutor: ExecutorService
private lateinit var binding: FragmentArBinding
private val cameraPermissionResultReceiver =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
startCamera()
} else {
binding.viewFinder.visibility = View.GONE
binding.errorTextView.visibility = View.VISIBLE
binding.frameLayout.invalidate()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentArBinding.inflate(inflater, container, false)
// Request camera permissions
when {
allPermissionsGranted() -> startCamera()
shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
showRationaleDialog()
}
else -> cameraPermissionResultReceiver.launch(Manifest.permission.CAMERA)
}
cameraExecutor = Executors.newSingleThreadExecutor()
return binding.root
}
private fun showRationaleDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.rationale_title_camera)
.setMessage(R.string.rationale_body_camera)
.setPositiveButton(R.string.action_ok) { _, _ ->
cameraPermissionResultReceiver.launch(Manifest.permission.CAMERA)
}
.create()
.show()
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
private fun startCamera() {
context?.let { ctx ->
val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx)
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview
)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(ctx))
}
}
private fun allPermissionsGranted() = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
companion object {
private const val TAG = "ArFragment"
}
}
fragment_ar.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@ id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.ArFragment">
<androidx.camera.view.PreviewView
android:id="@ id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@ id/errorTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_margin="24dp"
android:visibility="gone"
android:text="@string/error_no_camera_permissions" />
</FrameLayout>
The camera preview works perfectly but I can't make it to fit all screen space, please, don't confuse with a fullscreen mode. I want to make status, tab and navigation bars transparent for ArFragment
(and only for ArFragment
). In short, to the camera preview became like a root layer background. A something similar to our iOS version:
P.S. I've tried solutions from similar questions. To use android:fitsSystemWindows="true"
and so on. It works, it makes them transparent but I need to the camera preview could fill all available screen space. At this moment I think that the camera output must be rendered as a root layout background but I am not sure if it's a right way. And even if it's a right way, I don't know how to implement it.
CodePudding user response:
I'm doing something similar to this. Not exactly the same, but hopefully this is close enough to working instructions to get you there.
You need to make the fragment view fill the whole parent, so it is behind the navigation bar. But you also need to change it to be above the navigation bar when you're on a fragment that isn't the camera view, or else some of your content will be overlayed with the navigation bar and bottom system bar.
I would use a ConstraintLayout to achieve this.
You need to switch from fragment
to androidx.fragment.app.FragmentContainerView
so you'll be able to modify layout params to switch between the two states.
Also, make the bottom navigation view 0dp elevation because if it's transparent, its elevation shadow will look super weird.
The bottom navigation view will provide the color that will also go behind the bottom system bar
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@ id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@ id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:elevation="0dp"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@ id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:elevation="0dp"
app:menu="@menu/bottom_navigation_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
Your theme should have the following properties. This makes the system navigation bar invisible (or at least translucent prior on pre-Android Q devices). On pre-Android L devices, I think it doesn't do anything and you're just out of luck for making it transparent.
<item name="android:navigationBarColor" tools:targetApi="l">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds" tools:targetApi="l">true</item>
<item name="android:enforceNavigationBarContrast" tools:targetApi="q">false</item>
In your Activity's onCreate
add this line, which is also necessary for the translucent/transparent bottom navigation bar.
WindowCompat.setDecorFitsSystemWindows(window, false)
And you need to replace findNavController(nav_host_fragment)
with this since we switched from a fragment
element to a FragmentContainerView
:
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
Finally, create a function in the Activity that you'll call whenever switching away or to the camera fragment. (You can use navController.addOnDestinationChangedListener
to call this.)
private fun setFragmentBehindNavigation(behind: Boolean) {
// swap where the bottom edge of fragment is aligned
binding.navHostFragmentActivityMain.apply {
layoutParams = (layoutParams as ConstraintLayout.LayoutParams).apply {
if (behind) {
bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
bottomToTop = ConstraintLayout.LayoutParams.UNSET
} else {
bottomToBottom = ConstraintLayout.LayoutParams.UNSET
bottomToTop = R.id.nav_view
}
}
}
// swap between opaque and translucent bottom nav color
binding.navView.setBackgroundColor(
resources.getColor(
if (fits) R.color.someTranslucentColor else R.color.someOpaqueColor
)
)
}
We still haven't handled the problem of your views going behind the top status bar when you aren't showing the camera preview. If you're using a Toolbar on all your other fragments, I think it will be fine because Toolbars know how to use fitsSystemWindows
(not all types of view can handle it correctly). If you're not, one solution is to insert a plain old View between the top of the parent constraint layout and the top of the fragment container using constraints. Then at runtime, you set this view to the height of the top status bar. Set it between GONE and VISIBLE in the setFragmentBehindNavigation
function above.
The hard part is making it the same height as the system bar. I've run out of time I can devote to this answer (I thought it would be quick, but it's already been over an hour!) so I can't add any more instructions for that, but you can read this article about how to do it. It's very complicated!
CodePudding user response:
use getActionbar.hide()
to avoid showing actionbar on screen and if your action bar is hided the camera will expanded to take whole screen