Since Android released the new Splash Screen API with Android 12, a lot of apps had issues with duplicate splash screens, lack of customization, etc.
Right now, it is possible to set the background color and icon in the middle of it, but is it possible to customize it a bit more? Since right now we are limited to use single-colored background and non-resizable logo icon which doesn't look quite good.
What I'm trying to achieve is a custom splash screen, with an image drawable as background (or layer-list with 2 items - one background image and one centered logo), as it could be used before Android 12.
Did someone succeed to achieve this type of behavior?
There is a workaround to set windowIsTranslucent attribute to true and show only the second splash (the right one), but it introduces bad UX since it seems like the app is not responding for a few seconds.
Short answer is No, but here in my answer you can find more info:
Android: How to set a drawable as a windowSplashScreenBackground parameter in the new SplashScreen API?
I did something like this.
First remove default drawable
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">#color/...</item>
<item name="windowSplashScreenAnimatedIcon">#android:color/transparent</item>
<item name="postSplashScreenTheme">#style/AppTheme</item>
</style>
Then inflate your custom splash view
class MainActivity : AppCompatActivity(), SplashScreen.OnExitAnimationListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val installSplashScreen = installSplashScreen()
installSplashScreen.setOnExitAnimationListener(this)
setContentView(R.layout.activity_main)
}
override fun onSplashScreenExit(splashScreenViewProvider: SplashScreenViewProvider) {
val view = splashScreenViewProvider.view
if (view is ViewGroup) {
val binding = ActivityMainSplashBinding.inflate(layoutInflater,view, true)
// Do what you want with your inflated view
animate(view) {
// Remove splash
splashScreenViewProvider.remove()
}
}
}
private fun animate(view: View, doOnFinish: () -> Unit) {
view.animate()
.withEndAction(doOnFinish)
.start()
}
}
At the end remember to call splashScreenViewProvider.remove() to remove splash.
Related
I want to make so my status bar is transparent, but also without icons on it. I managed to make it so that bar disappeared, but then it left a line that isn't filled with the background. I want to change that so i can actually see the background without any icons being in the way.
Also, I'm testing it on Xiaomi Redmi Note 8T
Code (with the result seen on the 1st picture)
MainActivity.kt
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
class MainActivity : AppCompatActivity() {
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) hideSystemUI()
}
private fun hideSystemUI() {SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or 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)
}
override fun onCreate(savedInstanceState: Bundle?) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Both themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.TestingSystemModes" parent="Theme.Design.NoActionBar">
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
</style>
</resources>
Here is how it looks with that line:
I want it to look like in the picture below, but without that status bar's icons:
EDIT:
I think, it's phone that makes it like that ( in the center there is a camera ) and probably the black line is made to blend in the camera. That's why, whatever i did it didn't disappear. Still, thanks a lot to everyone who tried to help me.
The problem:
Phone's can now have a "cut-out" (for notched phones etc. etc.), and when you hide the status bar properly, the phone thinks "ok, I need to make the former status bar just a black cut-out so it doesn't look silly."
The solution:
There's actually four things you need to do:
Tell Android you don't want status bars in the app (hide them). There is now a correct, backwards compatible way of doing this which I will detail below.
Tell Android to draw the app behind the cut-out area.
Tell Android that your layout should NOT fit the system window (if you don't do this, it will STILL put your layout within the space where the status bar used to be)
Manually adjust the bottom insets, as when you tell Android your layout should not fit the system window (step 3), unfortunately that's going to include the Navigation Bar, so if you don't adjust for this then your layout will be under the nav bar which may be undesirable.
The Code:
The below is a neat little extension function that will cover steps 1 and 3... (hide the status bars, set decorFitsSystemWindows to false). This allows, in your activity to simply put window.setInImmersiveMode()
fun Window.setInImmersiveMode() {
val windowInsetsController = ViewCompat.getWindowInsetsController(decorView) ?: return
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.statusBars())
WindowCompat.setDecorFitsSystemWindows(this, false)
}
You need to change your themes.xml to tell Android to draw behind these cutouts, with this line (api 27 and above only, hence the tools:targetApi part):
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
And finally, there's a slightly more complex bit of code to handle setting up the bottom insets for your layout - again I've made it into an extension function so you can call view.setupInsets():
fun View.setupInsets() {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.layoutParams = (view.layoutParams as FrameLayout.LayoutParams).apply {
bottomMargin = insets.bottom
}
WindowInsetsCompat.CONSUMED
}
}
Set these properties in your theme. Thats all
<item name="android:windowTranslucentStatus">true</item>
you can use below lines to make it transparent or can set fix color getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
getWindow().setStatusBarColor(ContextCompat.getColor(ContactUsActivity.this,R.color.white));// set status background white
I am trying to set safe inset in the activity as below:
#RequiresApi(Build.VERSION_CODES.Q)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val cutout = window.decorView.rootWindowInsets?.displayCutout
cutout?.safeInsetTop
cutout?.safeInsetBottom
cutout?.safeInsetLeft
cutout?.safeInsetRight
}
However I seem not to see any effect, meaning am implementing safe inset wrongly. I have not been able to come across a proper documentation on how to implement safe inset. Any help on how to do it on android is highly appreciated.
You can set the cutout mode by setting a style in your project & there by survive the notch hell,
<style name="Theme.DeviceCompatability" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
</style>
for more information, read here
TLDR; I am tinting the icons of my Menu's MenuItems by wrapping them in another Drawable and tinting that, and this works. Except for the case where my original Drawable is inside of a LayerDrawable. Then it does not work. It is as if my code has no effect whatsoever whatever I try to do. Code is below, along with screenshots, for details, read the rest.
UPDATE: If I remove the android:visible="false" in the XML, it works. The same happens though if I in the code do menuItem.isVisible = false in onCreateOptionsMenu. When it becomes visible again, it is visually stuck in this black color with some sort of transparency blended into it. I do however not want to show this button unless it is relevant, so making it invisible is what I want.
The reason I am manually tinting is because I use Cyanea Theme Engine to dynamically theme my app during runtime, and Cyanea deals with this throughout the app's life cycle, but for some reason MenuItems do not get their colors applied, so I have to do this manually, which I have successfully done. But I can't seem to have an icon with a badge that can be tinted, no matter the kind of solution I choose to go for, which currently is this one at medium.com.
I am trying to stick to Drawable APIs so that I don't need to do something more custom, but I am stuck. Defining the Drawable in XML does not work, and creating it programatically does not work. It runs and has the correct icon and correct badge count, but not the correct color, in both cases.
I thought it might have to do with icon.mutate() not being called properly. I also thought that maybe setting the badge count causes it, overriding the new tint with the original one, but it does not seem to affect the icon at all one way or another. At the bottom are three screenshots where I have tested three different cases with two icons. Note the slight difference in color between the left and the right one when both are the default color black.
Related issue that required this manual tinting to begin with:
Through previous googling I found a solution to a problem where Cyanea Theme Engine does not apply its theme to MenuItem icons, meaning their drawables do not get the proper color. The solution I found was similar to this but it was written in Java, and I've tweaked mine over time, especially when trying to solve this current problem.
NOTE: I am open to suggestions on solving the original problem of creating a MenuItem icon that can have a dynamic notification count in a badge, if it works with this dynamic tinting of the icon itself at the same time, BUT I still want to learn why this does not work as it stands now.
object MenuItemExtensions {
fun tintMenuIcon(item: MenuItem, color: Int) {
when (val drawable = item.icon.mutate()) {
is LayerDrawable -> {
val id = R.id.badge_bottom_layer
val normal = drawable.findDrawableByLayerId(id)
val wrapped = wrapIcon(normal, color)
drawable.setDrawableByLayerId(id, wrapped)
item.icon = drawable
}
else -> {
item.icon = wrapIcon(normalDrawable = drawable, color = color)
}
}
}
private fun wrapIcon(normalDrawable: Drawable, color: Int): Drawable {
val wrapDrawable = DrawableCompat.wrap(normalDrawable)
DrawableCompat.setTint(wrapDrawable, color)
return wrapDrawable
}
}
I call this function like this:
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.friend_list_menu, menu)
menu.findItem(R.id.friend_list_add_friend_button)?.let {
// This works
applyMenuItemColor(menuItem = it)
it.setOnMenuItemClickListener {
val directions = FriendListFragmentDirections.navGraphActionFriendsToAddFriend()
findNavController().navigate(directions)
true
}
addFriendButton = it
}
menu.findItem(R.id.friend_list_requests_button)?.let {
// This is the original version, the one that works for normal Drawables
applyMenuItemColor(menuItem = it)
// This is an attempt at creating the same structure but entirely programmatically.
buildBadgeIcon(menuItem = it)
// Both yield the same results, which fails for LayerDrawable,
// and they're not both called at the same time
it.setOnMenuItemClickListener {
Timber.d { "Clicked friend requests button" }
true
}
friendRequestsButton = it
}
}
private fun applyMenuItemColor(menuItem: MenuItem) {
// This color works as properly, as the icon next to it has been colored properly,
// which is WHITE in the current settings theme. Changing this to Color.RED changes nothing,
// the icon remains black (incorrect).
MenuItemExtensions.tintMenuIcon(menuItem, Cyanea.instance.menuIconColor)
}
private fun buildBadgeIcon(menuItem: MenuItem) {
val context = requireContext()
val bottomIcon: Drawable = requireNotNull(
ContextCompat.getDrawable(context, R.drawable.ic_notifications_black_24dp)?.let {
DrawableCompat.wrap(DrawableCompat.wrap(it)).apply {
setTint(Cyanea.instance.menuIconColor)
}
}
)
val topIcon: Drawable = requireNotNull(ContextCompat.getDrawable(context, R.color.transparent))
val layerDrawable = LayerDrawable(arrayOf(bottomIcon, topIcon))
menuItem.icon = layerDrawable
}
The icon is a default imported icon through Android Studio. The layer list:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/badge_bottom_layer"
android:drawable="#drawable/ic_notifications_black_24dp"
android:gravity="center" />
<item
android:id="#+id/badge_top_layer"
android:drawable="#color/transparent" />
</layer-list>
Menu XML:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/friend_list_add_friend_button"
android:icon="#drawable/ic_person_add_black_24dp"
android:title="#string/add_friend"
android:orderInCategory="101"
app:showAsAction="ifRoom" />
<item
android:id="#+id/friend_list_requests_button"
android:icon="#drawable/badge_background"
android:enabled="true"
android:title="#string/friend_requests"
android:orderInCategory="100"
app:showAsAction="ifRoom" />
</menu>
Neither icon colored with Cyanea:
Both icons colored with Cyanea:
Both icons colored with Cyanea and BadgeCount layer is turned on:
I'm using NativeScript with Angular, and I can't seem to change the StatusBar color in my project or set it to transparent (any of these would be fine). Instead, it is not totally transparent, but is translucent black, so the background scrolls behind it but it is darkened by the StatusBar. I would like to set it to transparent or to change the color (to the same one as the page background)
What I've tried:
Changing the "ns_primaryDark" and "ns_primary" colors in App_Resources/Android/src/main/res/values/colors.xml (works on launch screen if i set the TranslucentStatus" to false;
Setting <item name="android:windowTranslucentStatus"></item> in <style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> in App_Resources/Android/src/main/res/values/styles.xml doesn't make any difference, despite working fine on <style name="LaunchScreenThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> (the launch screen);
Setting <item name="android:windowLightStatusBar">true</item> changes the text color to black and works on both the launch screen and the main app;
Using the code below in any component's constructor doesn't change anything in my app, but it worked on the other project i tried (details below), setting the color to black:
let window = app.android.startActivity.getWindow();
window.setStatusBarColor(new Color("black").android);
One thing i also tried was doing these steps on another project i had downloaded (https://github.com/NativeScript/nativescript-ui-samples/tree/master/chart this one, to be exact) and it worked, so i thought the template i used in my application might be "overlaying" any settings for the StatusBar.
I then tried to make a new project with the same template as mine and i figured out that it didn't work either. Is the template my problem? If so, is there any way to get around it?
The template i used is this one: https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-tab-navigation-ng (also works with "tns create my-app-name --template tns-template-tab-navigation-ng").
Big thanks in advance.
Add the following code to your main.ts, that should do the job.
import * as application from "tns-core-modules/application";
declare var android;
application.android.on(application.AndroidApplication.activityCreatedEvent, (event) => {
const activity = event.activity;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
activity.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
activity.getWindow().clearFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
activity.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
activity.getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
} else {
activity.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
const parent = activity.findViewById(android.R.id.content);
for (let i = 0; i < parent.getChildCount(); i++) {
const childView = parent.getChildAt(i);
if (childView instanceof android.view.ViewGroup) {
childView.setFitsSystemWindows(true);
childView.setClipToPadding(true);
}
}
});
It's pretty much as in the library. If you like to use the library, you will have to include it in your app grad and access the StatusBarUtil class.
You can use this library to transparent your status bar Library
compile 'com.jaeger.statusbarutil:library:1.4.0'
To make any specific activity to transparent you can simply use this
StatusBarUtil.setTransparent(Activity activity)
It will look like this
I have been struggling with this for a few days now, i have tried lots of the articles on SO and none seem to match my requirements, i have set the Toolbar.xml and set the theme as below
<style name="ToolbarStyle" parent="Theme.AppCompat.Light">
<!-- Customize color of navigation drawer icon and back arrow -->
<item name="colorControlNormal">#color/white</item>
</style>
This works on a per app basis, but i need to alternate between white and black arrows depending on the content page i am on, i have tried custom renderers as well but this doesn't work dynamically as well
Any pointers massively appreciated, even if i can clarify, is the Xamarin forms navigation a Toolbar or an ActionBar?
Cheers
Anthony
is the Xamarin forms navigation a Toolbar or an ActionBar?
It is a Toolbar and not an ActionBar
Android.Support.V7.Widget.Toolbar
You can set the color via the ToolBar.NavigationIcon.SetColorFilter(...), but you have to find it first as Xamarin is not applying it via SetSupportActionBar...
Red backbutton PlatformEffect example:
public class RedBackButtonColorNameEffect : PlatformEffect
{
protected override void OnAttached()
{
void ViewGroups(ViewGroup viewGroup)
{
for (int i = 0; i < viewGroup.ChildCount; i++)
{
Android.Views.View v = viewGroup.GetChildAt(i);
switch (v)
{
case Toolbar tb:
tb.NavigationIcon?.SetColorFilter(viewGroup.Resources.GetColor(Android.Resource.Color.HoloRedDark), PorterDuff.Mode.SrcAtop);
return;
case ViewGroup vg:
ViewGroups(vg);
break;
}
}
}
var rootView = (Control.Context as AppCompatActivity).FindViewById(Android.Resource.Id.Content).RootView as ViewGroup;
ViewGroups(rootView);
}
protected override void OnDetached()
{
}
}
Usage:
someControl.Effects.Add(Effect.Resolve("Effects.RedBackButtonColorNameEffect"));
(remember set your ResolutionGroupName & ExportEffect attributes...)
Note: Xamarin does not apply a value for the Control instance within a PlatformEffect if you attach the effect at the page level or a container level and thus this will not work. You could store the Activity context somewhere (static var) from the MainActivity in order to workaround that issue and thus have the proper Context to search within, your choice..
Note!: PlatformEffects are whack in so many ways, especially since they never get garage collected and thus are a memory leak, they are nice for a quick example/prototype, but use a custom renderer for production code).