Assuming related to How to setup night mode drawables as expected
I'm trying to implement the new AppCompat DayNight theme in my Android app while providing a theme switch in the settings.
Scenario:
As said theme switch was used, the "settings" activity recreated itself as a result of a call to AppCompatDelegate#setDefaultNightMode and AppCompatDelegate#applyDayNight (the latter is available to AppCompatActivity through its getDelegate() method).
The text color was inverted, the background changed and the back arrow's color changed as well. So far so good.
As the user finished changing the app's theme, he would close the activity and continue using the app. What does he expect to happen? Exactly, the main activity should have changed its theme as well. How do we do this? Activity#recreate in Activity#onActivityResult.
The call to AppCompatDelegate#setDefaultNightMode triggers a change in the configuration, which, afaik, is being applied either by AppCompatDelegate#applyDayNight or as soon as another Activity resolves its theme & configuration in Activity#onCreate. The first applies for the settings activity, the second is what is happening now.
Now to the critical part: the app's resources are dispatched a configuration change, and are now being checked against their validity (resource qualification), and if that fails, they will be cleared from the cache so that new resources (in this case, drawables) are created in order to fully reflect the new theme & configuration.
Now as I've said, the back arrow in the settings activity did change its color, but this was not my own drawable, it was the default one that you get with getSupportActionBar.setDisplayHomeAsUpEnabled(true).
In the main activity however, I did use my own drawables, for menu icons and other ui elements for example. These just didn't change. I'm using vector drawables, which get their color from the fillColor attribute.
TL;DR
To make the DayNight theme work for me, I have defined theme attributes for certain colors used throughout my app, also for icons (drawables). These attributes are populated by the one unified theme I'm using, and are referring to colors from values-night or values-notnight depending on the current configuration. This is a redundant indirection, but makes for a better style, at least in my opinion, when used together with default theme attributes such as textColorPrimary etc..
But: as it turns out, it doesn't work. Going the attribute-way, those attributes, which are in the end colors, loose their quality of being resource qualified (night or notnight) and therefore pass the "validity test" performed on configuration change, even if that's not what's supposed to happen. Thus, after I have changed the night mode, I end up with the old, wrong drawables.
To fix this I could e.g. simply
replace android:fillColor="?attr/myCustomAttributeColorXEitherNightOrNot" with android:fillColor="#color/colorXEitherNightOrNot".
But is that really what this is supposed to be like? Am I missing some important attributes-principle here, or is it a bug? I'd like to hear some thoughts on this.
Its pity but suppose that using on bitwise operations with Configuration still only normal way to provide night theme. See example: https://gist.github.com/slightfoot/c508cdc8828a478572e0
Related
I have a logo on my splash screen and two files:
logo_night.png and logo_day.png
How can i change this logo that will change depending on the theme of the app using these two files?
The usual way is to use a night resource qualifier, so if you're using a dark theme it pulls the named resource from the equivalent night folder if it exists there.
You have three basic options with this I think:
just call them both logo.png, but put one version in drawable and the other in drawable-night. That way, whenever you refer to #drawable/logo it'll pull the correct version depending on the theme. If you have multiple density versions, in drawable-hdpi etc, you'll need them in drawable-night-hdpi etc. (the qualifier order matters)
if you're making your splash in the usual way, where you create a splash drawable with a background layer and your logo drawable on another layer, you could just make a night variant of this instead (in drawable-night). Each version can explicitly refer to logo_day or logo_night and you can just keep those PNGs in the normal drawable folder
or you can make a night version of your splash theme, and explicitly set the background to the dark version of your splash drawable instead. Both your splash drawables can stay in the normal drawable folder
So basically, something's gotta get a version in a night-qualified resource folder, so it can provide an alternative resource for dark mode. It's up to you which resource in that hierarchy you want to make the night version of (or you can do it for all of them if you want).
Also bear in mind that your splash theme (if you're doing it that way) follows the system's dark mode setting. If you're implementing a toggle in your app, and you set it to dark mode, the splash will still show up as the light variant if that's what the device is set to. That theme is set before your app starts and gets to work out if it should be in dark or light mode
Since you are using png format. You can call .getTheme() on context object to get current theme, make an if statement to decide which png to show.
For example in activity you can get theme by just calling getTheme() because this.getTheme() is implied.
Other possibility is to switch to vectors where you can define different colors for different themes they will change automatically when user changes theme. This is probably better and more scalable solution. You can find much more details here.
There are lots of docs and tutorials on creating or customising an Android theme style via XML, but have not been able to find a way to create it in code. Any ideas on how to create the style in code rather than xml?
This is the example XML, need to create this programmatically in code:
<resources>
<style name="AppTheme" parent="android:Theme.Material">
<item name="android:colorPrimary">#color/primary</item>
<item name="android:colorPrimaryDark">#color/primary_dark</item>
<item name="android:colorAccent">#color/accent</item>
</style>
</resources>
TL;DR: Just no, you can't. That's how Android works and will never be changed.
There is a plain and simple reason why programmatically creating Android themes will never be possible. That is, when an app is starting, Android creates a dummy window that shows the app's android:windowBackground. At this time, the app process is still being initialized, and when app execution starts and Activity.onCreate method of the activity being launched returns, the dummy window is replaced with the app window, which shows that activity.
Therefore, the fact is, since android:windowBackground is set by themes but Android has to access it before the app is even started, themes have to be resources, so that they can be accessed from any process (including the system process, of course.)
Moreover, themes are resources, and as such they're completely immutable. That's how Android works. It cannot just be changed all of a sudden, and it's very unlikely it will ever. An additional reason why resources will never be able to be dynamically modified is due to the direct implication such that the APK itself would need to be modified as well — and that's not the way APKs are meant, either.
One may argue “everything must be done programmatically, under the hood.” Well, that's correct. However, the android.content.res.Resources.Theme class is final for a reason, which is to make sure that nothing be overridden, so that its behavior is guaranteed to reflect what resources say, which is fundamental for the system process accessing the android:windowBackground of the activity theme to be coherent with the way the app will behave once started. The same holds when it comes to the android.content.Context.obtainStyledResources method, which is final too. Indeed, if the app could override that method, it would be able to return values that do not match resources, which is a problem as that would happen only once the app process is started, when the original, real android:windowBackground had already been shown.
short answer: Its not possible as programmatically
create a theme & set as application theme ( even if we achieved to create a Theme object) without a theme resource id.
Details:
when you call setTheme the function ineffect a method of ContextWrapper,
which at the end calls AssetManager with resource id pointer, AssetManager class holds the method for applying application theme, which is JNI call
native static final void applyThemeStyle(long theme, int res, boolean force);
As above we can only pass a resource id to apply the themestyle. But possible options are
Though its limited to Window class feature constants. We can use
setFeatureDrawable & feature constants to set some drawables
like, FEATURE_ACTION_BAR, FEATURE_CONTEXT_MENU etc..
Using setTheme function from activity, we can set the theme from style resource, that will solve the problem mentioned in comments by AjaySharma & Nathan
I have device A and device B.
I can easily detect if the app is running on device A or on device B.
Now what I need is to use on theme (styles) for device A and other on device B.
How can I do this?
In your Activity.onCreate(), you can call setTheme() to set the theme you would like to use. Note this must be done before you call setContentView() or otherwise create your UI.
Keep in mind that when the user launches your app, the system will show a preview of it while this happen. This previous is based on creating a window that matches the theme declared in your manifest. You want this to match as closely as possible the themes you are going to set in your onCreate() to make the transition to your app as smooth as possible.
If you want your theme to vary based on some device configuration -- such as platform version or screen size -- you can do this all through resources. Just declare different versions of your theme for the different configurations you want. The file layout would be something like:
values/
styles.xml # Required default theme
values-v11/
styles.xml # Theme when running on Android 3.0 or higher
values-xlarge/
styles.xml # Theme when running on an xlarge screen
The -v11 allows you to have a version of the theme that uses a new theme when running on newer platforms while reverting to something compatible on older versions. For example in the values-v11 style your theme's parent could be the new #android:style/Theme.Holo, while the basic one would inherit from the older #android:style/Theme.
Also Android 3.0 gives you a way to change your theme at runtime, by asking that your activity being restarted like when a configuration change happens: http://developer.android.com/reference/android/app/Activity.html#recreate()
After calling that, the new instance of the Activity that gets created can call setTheme() with a different value (based for example on information in the saved instance state or a shared preference) than the theme that was previously being used.
I noticed the UI color (eg Button background/text color) all changes from device to device, based on the current theme that is being used in a device.
What is the best practice to apply custom UI colors for Android app, so that I have same color scheme for my app in all Android devices. I can set text/background color on a UI item. I'm wondering if there is a single place where I can define all the colors which will override the current theme applied on the phone.
thx.
Yes, there is a single place where you can define these values for your app. See Styles and Themes in the Android docs for how it works.
A style is just a mapping of values to predefined names. If you find yourself repeating a number of common attributes in your layouts, you can factor that out into a style. For example, you might have a special button style that defines a specific background and text color.
A theme is a sort of meta-style. It can be applied to an Activity or even a whole application through your AndroidManifest.xml. Among other things it defines the default styles for widgets and values that control other parts of the look and feel for your UI.
When you're trying to blend in with the system in an otherwise custom UI for your app, you can query the current theme for values. Just like you use the # reference syntax #android:drawable/foo when referring to a system resource, you can use the syntax ?android:attr/foo when you want to use the value stored in the system theme attribute foo.
In your case, if you want to change the primary text color across your app, apply a custom theme that sets the attribute textColorPrimary. If you just want to be sure that an element of your app is using the primary text color as defined by the device your app is running on, you can set android:textColor="?android:attr/textColorPrimary". The same principles apply elsewhere as well.
If you want to see what attributes are used in the system, they are defined as part of the Android framework in this file: frameworks/base/core/res/res/values/attrs.xml. Look at the children of the XML element <declare-styleable name="Theme"> at the top. To see examples of what the system sets these to, see themes.xml in the same directory. Finally, not all of these attributes are public - non-public attributes cannot be set by an app, they're implementation details of the Android framework. See public.xml for the complete list of which attributes are available for use in apps.
Best practice is to apply a custom theme to your application, and override as much of the default properties as you need.
Almost everything can be changed, except
The Menu
Some properties of AlertDialog (these can be changed using a custom dialog)
OS provided views such as the Quick Search Bar (QSB)
If you like the look of the default SDK resources then you can find these in sdk_folder/platforms/android-9/data/res/ (replace 9 with the SDK version you want the resources from) - copy the ones you want into your App and reference those.
You can take a look at the theme the SDK uses:
themes.xml
styles.xml
I want to have a "change theme" feature in my app. If I call setTheme() in onCreate(), there is one problem.
The moment after I launch my app, a plain white background appears for a second (because I have set light theme in the manifest). After that, the complete layout of my Activity is displayed - it is either with white or black background, according to user's theme preference.
Is there any way I can change whether white or black background appears after launch?
Make sure you call setTheme() in onCreate() BEFORE calling setContentView(). Then if you want to dynamically change the theme again later, you should simply restart your activity.
If you are adding a theme to the entire program than you could start by doing:
In your manifest you add to your application tag that you are using a theme.
<application android:theme="#style/mythemename">
Then look at Theme XML to make sure that you have what you need declared in the appropriate places.
If it is just for a particular action you could add the activity tag
<activity android:theme="#android:style/Theme.propertyname">
You can also, if you want your theme to just change the background color, follow the same pattern with either the activity or application tag (what ever one you are using) and set the item name "colorbackground" to what you want.
You can also use Theme XML and remake what you want in your current theme and call that your custom theme using the method above.
I hope this helps and if not please let me know so I might be able to help better in the future.
Another way would be to have a kind of splash screen which will check for a preference variable for example and then decide whether to use the light or the dark theme. This way you could also use XML layouts.
EDIT: Yet another way is to have the all the layout defining stuff in the onCreate() method and then just trigger the onStart() method when ready.