Per the android docs, we can make an activity go full-screen by adding the following block of code in the onCreate method (inside setContent{...}, specifically, if you are using Compose)
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
However, this seems to only hide the information displayed on the statusbar, replacing it with just a black stripe.
Now, my question is - How can we modify the color of this stripe so that the UI of the app seems to extend to the entire display?
I am not sure where to start but I've heard accompanist MAY have something related to this, but I thought it better to post this question here, so that if anyone already knows a way around, they may share since it will be helpful to the community.
Other than that, solutions that do not involve accompanist are also welcome, and may be even preferred.
For reference, here's the output as of now
Notice the black bar at the top? That's the target.
copy and paste it in onCreate method after result you can understand
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window: Window = window
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
val decorView: View = window.getDecorView()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
window.setStatusBarColor(Color.TRANSPARENT)
}
Related
I'm trying to learn android studio, android, and kotlin. Mostly this has been straightforward but with a few glitches. One such glitch is the following for which I could use some help:
I'm trying to create a phone view in immersive mode. According to the documentation (https://developer.android.com/training/system-ui/immersive) the following function should do the trick.
private fun hideSystemBars() {
val windowInsetsController =
ViewCompat.getWindowInsetsController(window.decorView) ?: return
// Configure the behavior of the hidden system bars
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
}// end hideSystemBars function
I set this in the main Activity onCreate function as follows
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
requestWindowFeature(Window.FEATURE_NO_TITLE)
supportActionBar?.hide()
setContentView(binding.root)
hideSystemBars()
}// end onCreate function
Unfortunately I seem to missing something basic as this always fails because
ViewCompat.getWindowInsetsController(window.decorView) always returns null (I ran several tests to verify this)
I've verified that window.decorView is not null and looks okay. As a precaution I replaced the hideSystemBars() function call with window.decorView.doOnLayout {hideSystemBars()} in case the layout wasn't complete but this doesn't help. In fact, from some addition tests I ran use of the window.decorView.doOnLayout {} doesn't seem to work either no matter what function is used in the brackets (another glitch I don't understand)
I think I may be missing something basic (and possibly trivial) but cannot seem to discover how to make this work. Any help will be appreciated.
Maybe you should check your API Level on the device. The following solution works for me.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView) ?: return
// Configure the behavior of the hidden system bars
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
}else{
val flags =
(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
window!!.decorView.setSystemUiVisibility(flags)
}
I'm trying to update my App to Android 11. Many Screens of my App were Designed with App Content behind the StatusBar. I Updated my gradle to Android 11 and started updating the Window code to get the No Limit behavior also for Android 11 Devices.
I achived my desired result for pre Android 11 Devices with the folowing Code in my Activitiys onCreate method:
Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
I tried to get the same no limit behavior for Android 11 by using w.setDecorFitsSystemWindows(false);
I tried using it instead of using the flags, using it with flags and passing true and false, setting it before and after setting the flags but i always see a white status and system navigation bar instead of my Apps content behind them.
What i tried:
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false); //also tried with true
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
//or
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false); //also tried with true
}
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
//or
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false); //also tried with true
}
My App still is in Java Code, i tried window?.setDecorFitsSystemWindows(false) in another app which uses Kotlin code and it worked without any troubles.
Does anyone have an idea what i'm missing or doing wrong here?
My App still is in Java Code, i tried window?.setDecorFitsSystemWindows(false) in another app which uses Kotlin code and it worked without any troubles.
Kotlin internally uses Java, so it does not matter whether you code in Java or Kotlin in terms of that insets API.
Probably, one of the view groups, which you use in your view hierarchy, consumes insets and does not propagate them to the child views. This was my case - my app is using DebugDrawer and window.setDecorFitsSystemWindows(false); didn't have any effect on the layout in my case. There is even the solution for that case. Maybe you could use it too, if you have a similar problem. It could be helpful to check the view hierarchy in the new Layout Inspector of Android Studio. Pay attention to the views that have attribute fitsSystemWindows = true. Also, that answer could be helpful.
For android 11 along with flags we also need to add below styles in the app theme
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
I have a webview-based Android app full-screen, running in COSU mode. Each time I pick a color using an HTML5 <input type="color"/>, the hidden action bars are shown again, and I can't hide them, unless I close the app and open it again.
Is there any way of avoiding the bars showing up each time I use the color picker? The only solution I came up to is using the javascript interface to call a function/method in Android to hide the bars each time the color picker is used, but seems too tricky/elaborate. There should be a better way... Or not?
Any ideas?
Thanks in advance
EDITED:
How I hide bars? I call this function I defined inside the Activity:
private fun hideBars() {
actionBar?.hide()
this.supportActionBar?.hide()
}
Also, use this one to set the activity to full screen
private fun setFullScreen() {
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
I've been waiting a few days for a response, but looks like it is not a very common problem, or there is no cleaner solution than using the javascript interface.
Finally, I opted to call to a function using the javascript interface when the <input type="color"/> is clicked. This is the function I used:
class WebAppInterface {
private val context : Context
...
constructor(context : Context) {
this.context = context
...
}
#JavascriptInterface
fun resetScreen() {
val activity = context as MainActivity
activity.runOnUiThread(Runnable {
fun run() {
activity.hideBars()
activity.setFullScreen()
}
});
}
}
Note I had to call the hiding bar functions using Activity.runOnUiThread(). Otherwise, Android kindly answered I was not running the code in the UI thread.
I mark this as the answer, but if someone in the future finds a cleaner way to solve this problem, I will review it. Otherwise, I hope this helps somebody
I have an Activity with a RecyclerView in a data binding layout. RecyclerView takes up the whole screen, and looking at making the UX go full screen, drawn under the status and nav bars.
I'm calling setSystemUiVisibility in activity's onCreate as below.
window.decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
Now the RecyclerView is drawn under the system bars, so I want to make sure it has enough padding so the items don't overlap with the system UI.
I found 2 ways of doing this, via a BindingAdapter.
Option 1
var statusBar = 0
var resourceId = view.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
statusBar = view.resources.getDimensionPixelSize(resourceId)
}
var navBar = 0
resourceId = view.resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
navBar = view.resources.getDimensionPixelSize(resourceId)
}
view.setPadding(0, statusBar, 0, navBar)
Option 2
var insets = view.rootWindowInsets.stableInsets
view.setPadding(0, insets.top, 0, insets.bottom)
I prefer the first, because it (with limited testing on emulators seems to) work on API 21, 28 and 29.
Option 2 only works on API 29, and also seems to get null on view.rootWindowInsets if/when the view is not attached. (So I guess I have to add a listener and wait for it to be attached before doing this)
So my question is, is there a down side to Option 1? Can I use it over the new API in 29? Is there any scenarios that Option 1 would not work?
(I think Option 1 might not work well on tablets where both nav and systems bars are on the bottom, so extra padding will be applied to the wrong side.)
A little bit late to the party, but here is the way that I've been doing, someone might need it.
For Android M and above, you can call View#rootWindowInsets directly, otherwise rely on Java's Reflection to access the private field mStableInsets
fun getStableInsets(view: View): Rect {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val windowInsets = view.rootWindowInsets
if (windowInsets != null) {
Rect(windowInsets.stableInsetLeft, windowInsets.stableInsetTop,
windowInsets.stableInsetRight, windowInsets.stableInsetBottom)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
} else {
val attachInfoField = View::class.java.getDeclaredField("mAttachInfo")
attachInfoField.isAccessible = true
val attachInfo = attachInfoField.get(view);
if (attachInfo != null) {
val stableInsetsField = attachInfo.javaClass.getDeclaredField("mStableInsets")
stableInsetsField.isAccessible = true
Rect(stableInsetsField.get(attachInfo) as Rect)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
}
}
Update:
stableInsetBottom .etc. are now deprecated with message
Use {#link #getInsetsIgnoringVisibility(int)} with {#link Type#systemBars()}
* instead.
Unfortunately systemBars() was graylisted in API 29 and is blacklisted in API 30 plus using this seems to work on API 30 emulator, however (some) real devices even running API 29 throws.
Below is logcat from Galaxy S20 FE
Accessing hidden method Landroid/view/WindowInsets$Type;->systemBars()I (blacklist, linking, denied)
2021-01-17 01:45:18.348 23013-23013/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: test.app.package, PID: 23013
java.lang.NoSuchMethodError: No static method systemBars()I in class Landroid/view/WindowInsets$Type; or its super classes (declaration of 'android.view.WindowInsets$Type' appears in /system/framework/framework.jar!classes3.dex)
No answer for this it seems. Please put an answer if you find anything not covered below.
Using Option 1 I noticed on devices that do OEM specific gesture navigation atleast, when those gesture modes are active, above will still return full navigation bar height even though no visible navigation bar is present. So above will still pad the UI when it shouldn't.
Option 2 keeps returning null for insets until the view is attached so if you're doing this on a BindingAdapter, it won't work. It needs to be called after the view is attached.
My current solution is as below.
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.doOnAttach {
var bottom = it.rootWindowInsets?.stableInsetBottom?: 0
var top = it.rootWindowInsets?.stableInsetTop?: 0
view.setPadding(0, top, 0, bottom)
}
}
else {
// use option1, old devices don't have custom OEM specific gesture navigation.
// or.. just don't support versions below Android M ¯\_(ツ)_/¯
}
Caveats
Some OEMs (well atleast OnePlus) decided not to restart some activities, especially ones that are paused, when the navigation mode changed. So if the user decides to switch away from your app, change the navigation mode and return, your app may still overlap navigation bar until the activity is restarted.
The status bar and the screen moves up completely when the keyboard popsUp in xamarin forms android and the EditText field is in the bottom of the screen. I tried using
WindowSoftInputMode = SoftInput.AdjustPan
and
WindowSoftInputMode = SoftInput.AdjustResize
But unfortunately both are not working,i also clubbed both
From a blog post i read putting
Xamarin.Forms.Application.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
and
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
Window.DecorView.SystemUiVisibility = 0;
var statusBarHeightInfo = typeof(FormsAppCompatActivity).GetField("_statusBarHeight", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
statusBarHeightInfo.SetValue(this, 0);
Window.SetStatusBarColor(new Android.Graphics.Color(0,0,0, 255)); // Change color as required.
}
after launch application is an alternative, but unfortunately this also failed. Any other option available?
Its a bug in Xamarin. I used following code in mainActivity
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
Window.DecorView.SystemUiVisibility = 0;
var statusBarHeightInfo = typeof(FormsAppCompatActivity).GetField("_statusBarHeight", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
statusBarHeightInfo.SetValue(this, 50);
}
And used
Xamarin.Forms.Application.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
The problem was it wont work if you forcefully hide the title bar
Forms.SetTitleBarVisibility(AndroidTitleBarVisibility.Never);
I commented out this code and the issue was solved.
But due to resize property i faced lot of issue since i have designed screens with Grid and star value which caused many unwanted issues.
So i am not going to use this method sadly. :(