I realize I'm probably doing something fundamentally wrong with styles and themes but I'm still a bit of an Android newbie so please excuse my ignorance. I'm trying to change the style of my MediaRouteButton from the default dark to light since I have a light ActionBar. My MediaRouteButton is implemented in the ActionBar as follows:
<item
android:id="#+id/menu_item_cast"
android:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
android:actionViewClass="android.support.v7.app.MediaRouteButton"
android:showAsAction="always"
android:actionButtonStyle="#android:style/Theme.MediaRouter.Light"/>
However, this gives me:
android/res/menu/main.xml:24: error: Error: No resource found that matches the given name (at 'actionButtonStyle' with value '#android:style/Theme.MediaRouter.Light').
If you don't want to change the color of the icon, framework would choose the right one (dark or light) based on the theme of your actionbar, so for an actionbar with light background, it will choose a darker icon and vice versa; here is a sample app with two different themes, Theme.AppCompat.Light and Theme.AppCompat, respectively (everything else is identical):
As you can see, the appropriate one is selected automatically. If you want to use a different color based on your branding requirements, the easiest would be to add the following images to your project (with usual resolutions under mdpi, hdpi, ..):
mr_ic_media_route_disabled_holo_dark.png
mr_ic_media_route_off_holo_dark.png
mr_ic_media_route_on_0_holo_dark.png
mr_ic_media_route_on_1_holo_dark.png
mr_ic_media_route_on_2_holo_dark.png
(if you are using a light actionbar theme, replace "dark" with "light"). Take a look at the assets at Google Cast >
Sample Apps (section Cast Icons) to get a feel for what these images are and build your own ones based on those.
I ended up decompiling android-support-v7-mediarouter.jar to see what was going on. With the code available I was able to extend MediaRouteButton and set the private Drawable through reflection hacking. There has to be a better way right?
public class CustomMediaRouteButton extends MediaRouteButton {
private static final String TAG = "CustomMediaRouteButton";
public CustomMediaRouteButton(Context context){
this(context, null);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mediaRouteButtonStyle);
}
public CustomMediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Drawable d = getResources().getDrawable(R.drawable.mr_ic_media_route_holo_light);
setRemoteIndicatorDrawable(d);
}
private void setRemoteIndicatorDrawable(Drawable d) {
try {
Field field = MediaRouteButton.class.getDeclaredField("mRemoteIndicator");
field.setAccessible(true);
Drawable remoteIndicator = (Drawable)field.get(this);
if (remoteIndicator != null) {
remoteIndicator.setCallback(null);
unscheduleDrawable(remoteIndicator);
}
field.set(this, d);
if (d != null) {
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == 0, false);
}
} catch (Exception e) {
Log.e(TAG, "problem changing drawable:" + e.getMessage());
}
refreshDrawableState();
}
}
You can change it easily now with your custom drawable.
Just call this method on your cast button.
mediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
mediaRouteButton.setRemoteIndicatorDrawable(yourDrawable);
I found a way to change your color of MediaRouteButton by code, and is easy to be done, no need to touch existing code.
The MediaRouteButton will style itself following the theme of context which you passed. You can create a ContextThemeWrapper to wrap the context and then pass it to MediaRouteActionProvider.
Following is an example:
MenuItem item = menu.add(Menu.NONE, R.id.menu_cast, Menu.NONE, "Cast");
MenuItemCompat.setActionProvider(item, new MediaRouteActionProvider(new ContextThemeWrapper(this, R.style.AppTheme)));
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
Here the R.style.AppTheme is a theme which extends from Theme.AppCompat, it is a dark theme so the cast button will always show in light version. You can also pass in light theme to make cast button behave in dark version. Also you can change it dynamically, just invalidate the option menu, it should recreate the action provider using the new theme.
I am using support library 23.1.1 and have not found any problem in this way.
If you want to change the icons used (not just the style), you need to name them the exact same way they are named here. For instance, for the light theme, you need to have a set of icons for every resolution with names: ic_cast_on_light.png, ic_cast_on_0_light.png, ic_cast_on_1_light.png, ic_cast_on_2_light.png, ic_cast_disabled_light.png, ic_cast_off_light.png.
You should be able to change the style by applying the style to your activity, e.g. in AndroidManifest.xml. If you want to change the drawable, I succeeded by adding mr_ic_media_route_holo_light drawable to my project. Just add it to drawables folder and customize it as you need it. Example: https://github.com/android/platform_frameworks_support/blob/master/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
Related
Our app relies on AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) to make us pick up Light and Dark theme colors from values/colors and values-night/colors
But every time we try to use the WebView, it starts by resetting the UiMode and our app gets confused which color values to pick for our themes
Some people discussed the issue in detail here and here
Anyone out there run into a similar issue?
As previous issue was closed I opened the new one: https://issuetracker.google.com/issues/170328697
And I tried to fix it in this way:
class UiModeCareWebView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {
init {
fixUiModeIfNeeded()
}
private fun fixUiModeIfNeeded() {
val configuration = context.resources.configuration
val configurationNighMode = configuration.uiMode and UI_MODE_NIGHT_MASK
val appCompatNightMode = getDefaultNightMode()
val newUiModeConfiguration = when {
configurationNighMode == UI_MODE_NIGHT_NO && appCompatNightMode == MODE_NIGHT_YES -> {
UI_MODE_NIGHT_YES or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
configurationNighMode == UI_MODE_NIGHT_YES && appCompatNightMode == MODE_NIGHT_NO -> {
UI_MODE_NIGHT_NO or (configuration.uiMode and UI_MODE_NIGHT_MASK.inv())
}
else -> null
}
if (newUiModeConfiguration != null) {
val fixedConfiguration = Configuration().apply {
uiMode = newUiModeConfiguration
}
#Suppress("DEPRECATION")
context.resources.updateConfiguration(
fixedConfiguration,
context.resources.displayMetrics
)
}
}
}
Answering my own question, looks like Google fixed the issue https://issuetracker.google.com/issues/37124582
with https://developer.android.com/jetpack/androidx/releases/appcompat#1.1.0-alpha03 Fixed WebView resets DayNight Resources
First of all you need to add android.webkit dependency into your project
dependencies {
implementation "androidx.webkit:webkit:1.3.0"
}
At the time of writing this post the latest stable version of webkit is 1.3.0.
It is worth noting that the Dark Theme support has been added with version 1.2.0, before this version it was impossible to add Dark Theme support to Webview.
Next step would be to check whether Webview and Android framework on user’s device supporting theming:
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
...
}
Please note that WebViewFeature.FORCE_DARK is only supported starting with Webview version 76. Unfortunately before that version there is no straightforward way to support the dark theming.
If you own the contents displayed within the Webview you might want to implement your custom CSS themes and toggle them using #JavascriptInterface from your app.
If WebViewFeature.FORCE_DARK is supported we can choose from three available options:
FORCE_DARK_OFF - Disable dark theme, the content will be rendered in default Light Theme
FORCE_DARK_ON - Enable dark theme, the content will be rendered in Dark Theme
FORCE_DARK_AUTO - Enable dark theme based on the state of parent view
Then we need to apply the setting using WebSettingsCompat
WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
You can read about in more detail in the below blogpost
https://androidexplained.github.io/ui/android/material-design/2020/09/24/dark-mode-webview.html
Recently, my users send me the following screenshot, when Android 9.0 Pie & Android OS's Night Mode is enabled.
As you can see, the stock name is not visible, because black color is applied for stock name. In normal white theme, it supposes to look as following
This is my code use to highlight the text color. In my code, I always assume the background for drop down notification is white. (Which is not correct in case dark mode is enabled)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NotificationTitle" parent="android:TextAppearance.Material.Notification.Title"></style>
</resources>
public SpannableString getFallBelowSpannableString(Context context) {
if (fallBelowSpannableString != null) {
return fallBelowSpannableString;
}
if (fallBelow == null) {
return null;
}
// The attributes you want retrieved
int[] attrs = {android.R.attr.textColor};
TypedArray ta = context.obtainStyledAttributes(R.style.NotificationTitle, attrs);
int textColor;
try {
textColor = ta.getColor(0, context.getResources().getColor(R.color.notification_symbol_name));
} finally {
ta.recycle();
}
final String notificationMessage = context.getString(R.string.falls_below_template, symbol, org.yccheok.jstock.watchlist.Utils.toStockPrice(fallBelow));
fallBelowSpannableString = new SpannableString(notificationMessage);
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(textColor);
fallBelowSpannableString.setSpan(foregroundColorSpan, 0, symbol.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return fallBelowSpannableString;
}
May I know, how to get the notification drop-down background color, and decide best text color when night Mode is enabled?
I think you can use UiModeManager class for getting the current mode. Though it can't be sure if the user in dark mode when he is 'Auto' mode. Let's look at the code -
UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
int modeType = uiModeManager.getNightMode();
You can also mode type from this below code -
int currentNightMode = getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
// Night mode is not active, we're in day time
case Configuration.UI_MODE_NIGHT_YES:
// Night mode is active, we're at night!
case Configuration.UI_MODE_NIGHT_UNDEFINED:
// We don't know what mode we're in, assume notnight
}
After that you can change your text color based on the mode. But I found another alternative way to do your work. Like change your resources style according to the mode, like in your res/values/themes.xml file-
<style name="Theme.AppCompat.DayLight"
parent="Theme.AppCompat.Light" />
And another file res/values-night/themes.xml -
<style name="Theme.AppCompat.Night"
parent="Theme.AppCompat.DayNight" />
You can try different attributes like the only Theme.AppCompat instead of Theme.AppCompat.DayNight. I actually didn't run this code but seems to work with little more changes.
Please have a look on the android documentation, this stackoverflow question and this medium blog.
I know there is a way to set themes by defining in styles.xml and use it like that
setTheme(android.R.style.MyTheme);
However, I want to get themes from another app which I developed as well. I know the resources names and actually I am able to get theme id with this code block;
Resources res = getPackageManager().getResourcesForApplication("com.example.theme");
int resThemeId = res.getIdentifier("my_theme","style","com.example.theme");
When I debug, I see that resThemeId is not zero.
Then, I need the final command to set this theme. Before super.onCreate() function, I try to implement this method but it seems it is not working
setTheme(resThemeId);
But instead of this, if I write below statement, I works fine
setTheme(android.R.style.Theme_Holo_Light);
So, what should I do to use a theme from different package resource?
So, what should I do to use a theme from different package resource?
You shouldn't do this for many reasons. I wrote a simple project that shows that it is indeed possible as long as the package contains the resources your activity uses.
See: https://github.com/jaredrummler/SO-41872033
Basically, you would need to return the package's resources from the activity:
public class MainActivity extends AppCompatActivity {
Resources resources;
#Override protected void onCreate(Bundle savedInstanceState) {
int themeResId = getResources().getIdentifier("AppTheme", "style", "com.example.theme");
if (themeResId != 0) {
setTheme(themeResId);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override public Resources getResources() {
if (resources == null) {
try {
resources = getPackageManager().getResourcesForApplication("com.example.theme");
} catch (PackageManager.NameNotFoundException e) {
resources = super.getResources();
}
}
return resources;
}
}
This is just to show that it is possible. Again, I recommend that you avoid this.
As already mentioned in a comment you can access resources from other applications, but using another applications theme will not work.
Since having proof is always a good thing let's have a look at the source code (I used API 24 sources)
Calling setTheme() on an Activity will invoke initializeTheme() in the ContextThemeWrapper parent class, which will end up calling onApplyThemeResource(..) which in turn will try to load the actual theme data from resources by calling theme.applyStyle(resId, true)
Following the chain through the wrapper Resources.Theme we can see in ResourcesImpl.ThemeImpl the following, where AssetManager is called to load the style into the theme:
void applyStyle(int resId, boolean force) {
synchronized (mKey) {
AssetManager.applyThemeStyle(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
}
This is where you try and fail to load the foreign theme from your other app.
Since most of the methods you would need to use are static calls or package local methods it does not seem that there is any way to achieve what you want, (e.g. applying or creating a new Theme)
Even if you get a hold of the other application's AssetManager by using getAssets() on a context there is no accessible method to create or apply themes.
So the only way to use another app's resources would be to add the resources to yours.
Have you seen this demo: Multiple Theme Material Design
You can check this demo for runtime theme change.
Hope it will helps you.
I try to change the items font size in Xamarin.Picker for my Android app. In my project, I use BindablePicker that inherits from Picker class. Source here.
I spent some time to do research and I found that I should create a PickerRenderer class and render the Picker.
My renderer class:
public class BindablePickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
var picker = e.NewElement;
BindablePicker bp = (BindablePicker)this.Element;
if (this.Control != null)
{
var pickerStyle = new Style(typeof(BindablePicker))
{
Setters =
{
new Setter { Property = VisualElement.BackgroundColorProperty, Value = Color.Red }
}
};
picker.Style = pickerStyle;
}
}
}
For test purposes I set the backgroundColor for Picker and it works fine. However, in my PickerRenderer class I only have access to Control property which is type of Android.Widget.EditText.
The effect :
Question
How can I access to Picker items, and set the font size for them? Is this possible?
Here is my repository with an example project.
https://github.com/k8mil/PickerRendererXamarin
Related links
https://developer.xamarin.com/api/type/Xamarin.Forms.Picker/
Changing the default text color of a Picker control in Xamarin Forms for Windows Phone 8.1
Font size in Picker control in xamarin forms
I has been able to resolve this by add the line:
Control.TextSize = 30;
On OnElementChanged method:
public class BindablePickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
Control.TextSize = 30;
}
}
}
Maybe this can help someone that is looking for change font size of a Bindable picker.
After some research I don't think this is possible for a generic picker.
You would likely have an easier time just rolling your own picker control in Forms code using a clickable label that pops up a list to select from.
I was able to style a date or time picker using styles in the Android styles.xml file, but since Android does not have a built in generic picker widget, I imagine that Forms is rolling it's own picker list as I can't find the dialog widget to theme that changes your list text size.
For a DatePicker, I can just add the following to the main style element in styles.xml:
<item name="android:datePickerDialogTheme">#style/AppCompatDialogStyle</item>
and then add a new style element in style.xml
<style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog">
<item name="android:textSize">60sp</item>
</style>
The above does change the text size for a DatePicker (no custom renderer required). Also the customer picker renderer is a bit of a misnomer... it really is just rendering the edit text field that shows the picked item, and allows opening the picker list on click.
I know this is not a solution, but just an indication as to what I found when checking this out and a suggestion that it is likely easiest to not use the Forms Picker type if you want such customization.
I have a tablet application which I am rebranding so there are multiple themes, based on the type of user.
I'd like to find the name of the theme that is currently being applied, and based on that theme then I can do some back-end functionality changes.
I have to dynamically set some image resources, which is fine as long as I pass in the correct theme resource (the R.style.redtheme), but I'd like to set this dynamically.
TypedArray a = getTheme().obtainStyledAttributes(R.style.redtheme, new int[] {aTabResource.mDrawableAttrId});
To do the styling I'm creating custom attributes and then overriding them in the styles.
If there is no simple way to get the theme, I will just save a preference.
The package manager has access to quite a bit of metadata.
It can be accessed like this:
int theme = 0; //0==not set
try
{
String packageName = getClass().getPackage().getName();
PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, PackageManager.GET_META_DATA);
theme = packageInfo.applicationInfo.theme;
}
catch (Exception e)
{
e.printStackTrace();
}
After this runs, theme will have the style resource.