Reverting Window Insets on fragment change - android

I have three fragments. I want to apply a transparent status bar on just one fragment. For that purpose, I am calling the following hide method on the setOnItemSelectedListener method of the bottom navigation bar. Also added an image of what I am getting right now
private fun hideStatusBar() {
window.statusBarColor = ContextCompat.getColor(this#SuperActivity, R.color.transparent)
WindowCompat.setDecorFitsSystemWindows(window, false)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = insets.left
rightMargin = insets.right
bottomMargin = insets.bottom
}
WindowInsetsCompat.CONSUMED
}
}
private fun showStatusBar() {
window.statusBarColor = ContextCompat.getColor(this#SuperActivity, R.color.transparent)
WindowCompat.setDecorFitsSystemWindows(window, true)
}
I am getting the appropriate behavior on fragment calling hide method.
But when I tap on another fragment (the one that needs to show the status bar), I get the following behaviour:

The bottom margin by default is 0 (or the designated value in the root layout "binding.root")
So, you need to reset the bottom margin again; if it's already 0; then you can:
private fun showStatusBar() {
window.statusBarColor = ContextCompat.getColor(this#SuperActivity, R.color.transparent)
WindowCompat.setDecorFitsSystemWindows(window, true)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = 0 // reset the margin
}
WindowInsetsCompat.CONSUMED
}
}
}
Or if it's something else; then you need to convert that from dp to pixels and set it to the bottomMargin
The same thing applies if you have some designated margin values in binding.root; but I think you didn't as the issue only appears at the bottom.
UPDATE:
The method setOnApplyWindowInsetsListener is not called inside showStatusBar method. Because in this, the Window Insets are not changed. Since, we added margin in hideStatusBar method, so this space that you see below navigation bar is from hideStatusBar method.
Although the listener should be triggered, but you can update the root directly:
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = 0
}
But notice that the setDecorFitsSystemWindows can take some time to update, so updateLayoutParams wouldn't have the effect, so, you might need a little delay for that:
Handler(Looper.getMainLooper()).postDelayed( {
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = 0
}
}, 0.1.toLong())

Related

How to disable/skip STATE_EXPANDED on BottomSheetDialog (make STATE_HALF_EXPANDED the maximum)?

This question may seem a little bit odd, but let me explain my problem.
I have a BottomSheetDialogFragment with rounded top corners. When I fully expand my dialog those corners will be filled automatically. I want the height to be more or less fixed, so isFitToContents is set to false.
Preferably I want my expanded state to have a slight margin to the top and still transparent corners, so the user is able to see a bit of the underlying layout. STATE_HALF_EXPANDED with a halfExpandedRatio of something like 0.95 is pretty much it.
But then the user is still able to switch to STATE_EXPANDED with a swipe up, which is weird, because there is almost no difference in height between both states, so this seems unnecessary.
Is there a way to make STATE_HALF_EXPANDED the maximum (disable STATE_EXPANDED) or, as an alternative, can I make STATE_EXPANDED behave as described and skip STATE_HALF_EXPANDED instead?
It seems like a really small thing, but I didn't find a way to achieve this behavior yet.
(I'm using XML layouts if this is relevant.)
This is what I currently apply to the dialog in the onShowListener:
isFitToContents = false
halfExpandedRatio = 0.95f
state = BottomSheetBehavior.STATE_HALF_EXPANDED
skipCollapsed = true
And in onViewCreated I ensure the parent layout's height is MATCH_PARENT, so the area below my inflated layout is not transparent:
val parentLayout = dialog?.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
parentLayout?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT
This is the maximum state that I want
This is the state I want to disable
You can use following function in your bottom sheet, this may helpful for your problem.
override onCreateDialog()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener { dialogInterface: DialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
setupFullHeight(bottomSheetDialog)
}
return dialog
}
copy this function :
private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
val bottomSheet = bottomSheetDialog.findViewById<FrameLayout>(R.id.design_bottom_sheet)
//BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
if (bottomSheet != null) {
val layoutParams = bottomSheet.layoutParams
val windowHeight = windowHeight
if (layoutParams != null) {
layoutParams.height = windowHeight
}
bottomSheet.layoutParams = layoutParams
BottomSheetBehavior.from(bottomSheet).setPeekHeight((windowHeight * 0.8).toInt(), _do)
}
}
EDITED - Missed variable updated.
private val windowHeight: Int
get() = if (activity != null && !requireActivity().isDestroyed && !requireActivity().isFinishing) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = requireActivity().windowManager.currentWindowMetrics
val insets = windowMetrics.windowInsets
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
windowMetrics.bounds.height() - insets.top - insets.bottom
} else {
val displayMetrics = DisplayMetrics()
#Suppress("DEPRECATION")
requireActivity().windowManager
.defaultDisplay.getMetrics(displayMetrics)
displayMetrics.heightPixels
}
} else 0
I found a soulution which uses STATE_EXPANDED as the only available state, since I finally managed to keep the rounded corners clear/transparent in that state.
Originally I was just cutting out the corners using this ShapeAppearance,
<style name="ShapeAppearance.App.LargeComponent" parent="ShapeAppearance.MaterialComponents.LargeComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">32dp</item>
</style>
which I then applied to the BottomSheet.Modal style. Instead I now set the whole background of the BottomSheetDialogFragment transparent, what I couldn't accomplish before. But now it works using the following styles:
<style name="BottomSheetDialogThemeOverlay" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
<item name="bottomSheetStyle">#style/BottomSheetDialogStyle</item>
</style>
<style name="BottomSheetDialogStyle" parent="Widget.MaterialComponents.BottomSheet.Modal">
<item name="backgroundTint">#color/transparent</item>
</style>
Obviously you need to apply them to you AppTheme using this line:
<item name="bottomSheetDialogTheme">#style/BottomSheetDialogThemeOverlay</item>
Then I was able to add a proper drawable with rounded corners as background inside the dialog's layout.
Final behavior setup
To make STATE_EXPANDED the initial and only available state to the dialog I use the following code inside onCreateView:
dialog?.setOnShowListener { dialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
bottomSheetDialog.behavior.apply {
isFitToContents = false
skipCollapsed = true
expandedOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, resources.displayMetrics).toInt()
// Just using Int (pixel value) would be fine here, but I prefer dp
state = BottomSheetBehavior.STATE_EXPANDED
}
}
And additionally I need to override onViewCreated to achieve match_parent in my dialog root layout's height behaving as it should:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val parent = dialog?.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
parent?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT
}
That's all I needed to achieve the behavior described in my question.

View container visibility based on the visibility of views contained in it

I have several views inside another view.
I need to show the container view if at least one view is visible. So, if none of the view's visibility is VISIBLE, then the container should itself hide.
It could be done by using constraintlayout group or any other ways in fragment.
But I am using Data Binding and I needed to handle it in ViewModel with LiveData. So I tried using MediatorLiveData. And it is not working as expected.
Here is how my code looks like:
class MyViewModel: ViewModel() {
val firstViewVisibility: LiveData<Int> = checkVisibility(firstView)
val secondViewVisibility: LiveData<Int> = checkVisibility(secondView)
val thirdViewVisibility: LiveData<Int> = checkVisibility(thirdView)
// and so on
val viewContainerVisibility = MediatorLiveData<Int>.apply {
fun update(visibility: Int) {
value = visibility
}
addSource(firstViewVisibility) {
update(it)
}
addSource(secondViewVisibility) {
update(it)
}
addSource(thirdViewVisibility) {
update(it)
}
// and so on
}
}
CheckVisibility function:
private fun checkVisibility(viewType: String) =
Transformations.map(myLiveData) { value ->
if(some logic involving value returns true) View.VISIBLE
else View.GONE
}
This is not working as the parent view's visibility depends upon the visibility added by last addSource in MediatorLiveData. So, if the last view's visibility is VISIBLE then the parent will be Visible and if it is GONE, the parent will be gone even though other view's visibility are VISIBLE.
Is MediatorLiveData not best fit here? OR I mis-utilized it?
What could be the best solution for my case?
Currently, when you update Visibility of the container, if the latest update of any view out of three is invisible, it set value as invisible even though previously any of three was visible. SO you need to update the Update() method. Something similar like this
val viewContainerVisibility = MediatorLiveData<Int>.apply {
fun update() {
if(firstViewVisibility.value == View.Visible || secondViewVisibility.value == View.Visible || thirdViewVisibility.value == View.Visible)
{View.Visible}
else{
View.GONE //or INVISIBLE as required}
}
addSource(firstViewVisibility) {
update()
}
addSource(secondViewVisibility) {
update()
}
addSource(thirdViewVisibility) {
update()
}
// and so on
}

Showing a BottomSheetScaffold with a BottomNavigation - Compose UI - Android

Using Compose UI, I have a bottom navigation bar and a Bottom Sheet,
so starting a "BottomSheetScaffold" from "Catalogue" screen is causing the "Bottom Nav Bar" to stay visible.
How can I show "BottomSheetScaffold" making it cover the whole Screen (covering the bottom nav. bar),
but keeping in mind to write the "BottomSheetScaffold" in the Compose Screen [Catalogue] itself, NOT on a higher level (Parent Activity Level) since it doesn't seem right
If I didn't misunderstood the question, you can wrap your content with a ModalBottomSheetLayout.
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetContent()
}
) {
Scaffold(...)
}
The result would be something like this:
It's not totally related to your question, but if you want to avoid the half-expanded state, you can do the following:
Declare the function below:
#ExperimentalMaterialApi
suspend fun ModalBottomSheetState.forceExpand() {
try {
animateTo(ModalBottomSheetValue.Expanded)
} catch (e: CancellationException) {
currentCoroutineContext().ensureActive()
forceExpand()
}
}
In your Bottom Sheet declaration, do the foollowing:
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = {
it != ModalBottomSheetValue.HalfExpanded
}
)
Call the following to show/hide the Bottom Sheet:
coroutineScope.launch {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.hide()
} else {
modalBottomSheetState.forceExpand()
}
}
A temporary solution that I use is passing lambda function that will change bottom bar visibility to compose screen

android bottomSheet can not update child size programmatically

it's hard to me to explain this problem, but you can see the below layout code,
First i have the layout look like this:
yeah, this is the call screen using webrtc, when i have the video, put it into main_render, the change the size when i have delegate for video size:
main_render.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
main_render.init(rootEglBase.eglBaseContext, object : RendererCommon.RendererEvents{
override fun onFirstFrameRendered() {
Log.e(TAG, "onFirstFrameRendered")
}
override fun onFrameResolutionChanged(i: Int, i1: Int, i2: Int) {
Log.e(TAG, "onFrameResolutionChanged: $i - $i1")
runOnUiThread {
val newParams = main_render.layoutParams as FrameLayout.LayoutParams
newParams.width = dm.widthPixels
newParams.height = i1 * dm.widthPixels / i
main_render.layoutParams = newParams
main_render.requestLayout()
main_layout.updateViewLayout(main_render, newParams)
main_layout.requestLayout()
}
}
})
But the problem is the size does not changed, i have to press to hide sheet, press again to show sheet then now the size is change ( i have onclick to hide and show collapse sheet)
Can someone help me know this problem, when remove sheet and using main_layout it's work normally, but when using sheet the size can not changed immediately
Try to set state of BottomSheet after update viewlayout if it helps:
val behavior = bottomSheetDialog.behavior
behavior.state = BottomSheetBehavior.STATE_EXPANDED

How to add marquee animation to icon titles on Bottom Navigation View?

I was wondering if there is a way that makes the bottom navigation bar icon titles to scroll like marquee. I have seen some apps like that and tried to find some ways but couldn't find any. so if anyone could help me i would appreciate it.
There is the simplest solution. Works on Material Components versions 1.0.0 - 1.2.0-alpha02.
fun BottomNavigationView.findAllLabels(): Sequence<TextView> {
return sequence {
children.forEach { bottomNavigationChild ->
if (bottomNavigationChild is ViewGroup) {
bottomNavigationChild.children.forEach {
val smallLabel = it.findViewById<TextView>(
com.google.android.material.R.id.smallLabel
)
yield(smallLabel)
val largeLabel = it.findViewById<TextView>(
com.google.android.material.R.id.largeLabel
)
yield(largeLabel)
}
}
}
}
}
// Your BottomNavigationView
bottomNavigationView.findAllLabels().forEach {
it.apply {
ellipsize = TextUtils.TruncateAt.MARQUEE
setSingleLine(true)
// For infinite loop, comment if it's not required
marqueeRepeatLimit = -1
}
}

Categories

Resources