I have a standard xamarin forms project (both iOS and Android) using shell for navigation. I have implemented themes using merged dictionaries. This is the method I use to apply the theme:
public class MyShellRenderer : ShellRenderer
{
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(Xamarin.Forms.ShellItem shellItem)
{
return new MyShellBottomNavViewAppearanceTracker(this);
}
}
internal class MyShellBottomNavViewAppearanceTracker : IShellBottomNavViewAppearanceTracker
{
private MyShellRenderer myShellRenderer;
public MyShellBottomNavViewAppearanceTracker(MyShellRenderer myShellRenderer)
{
this.myShellRenderer = myShellRenderer;
}
Task SetAppTheme(PreferredTheme preferredTheme)
{
if (Application.Current is null)
return Task.CompletedTask;
BaseTheme defaultTheme;
if (Application.Current.RequestedTheme == OSAppTheme.Dark)
defaultTheme = new DarkTheme();
else
defaultTheme = new LightTheme();
return _mainThread.InvokeOnMainThreadAsync(() =>
{
BaseTheme theme = preferredTheme switch
{
PreferredTheme.Dark => new DarkTheme(),
PreferredTheme.Light => new LightTheme(),
PreferredTheme.Default => defaultTheme
};
CurrentTheme = theme;
ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries != null)
{
mergedDictionaries.Clear();
mergedDictionaries.Add(theme);
}
OnPreferenceChanged(preferredTheme);
});
}
This works for iOS but on Android the ItemText color is not changing. This is a problem when the background color of the dark mode theme is the same as (or close to) the color of the deselected Tab. Icons render as expected.
I have tried implementing a custom render on android like so:
public void ResetAppearance(BottomNavigationView bottomView)
{
//Get and set background color from theme
Xamarin.Forms.Color xfcolor = (Xamarin.Forms.Color)App.Current.Resources["PageBackgroundColor"];
Android.Graphics.Color acolor = xfcolor.ToAndroid();
bottomView.SetBackgroundColor(acolor);
//Get and set state colors for menu items text
Xamarin.Forms.Color xfSelectedColor = (Xamarin.Forms.Color)App.Current.Resources["ColorBlue"];
Android.Graphics.Color aSelectedColor = xfSelectedColor.ToAndroid();
Xamarin.Forms.Color xfDeselectedColor = (Xamarin.Forms.Color)App.Current.Resources["ColorGray"];
Android.Graphics.Color aDeselectedColor = xfDeselectedColor.ToAndroid();
int[][] states = new int[][]
{
new int[] {-Android.Resource.Attribute.Checked }, // unchecked
new int[] { Android.Resource.Attribute.Checked } // pressed
};
int[] colors = new int[]
{
aSelectedColor,
aDeselectedColor
};
ColorStateList colorStateList = new ColorStateList(states, colors);
bottomView.ItemTextColor = colorStateList;
...
This partly works in that the entire text is getting the aSelectedColor applied.
Is this a bug in the SDK or am I simply just doing something wrong?
CodePudding user response:
Found a solution (Thanks to James Montemagno!!!)
Inspiration found here
So fore some odd reason using mergedDictionaries does not work for Android.
Instead I did the following rewrite based on the Hanselman.Forms sample:
Task SetAppTheme(PreferredTheme preferredTheme)
{
if (Application.Current is null)
return Task.CompletedTask;
BaseTheme defaultTheme;
if (Application.Current.RequestedTheme == OSAppTheme.Dark)
defaultTheme = new DarkTheme();
else
defaultTheme = new LightTheme();
return _mainThread.InvokeOnMainThreadAsync(() =>
{
BaseTheme theme = preferredTheme switch
{
PreferredTheme.Dark => new DarkTheme(),
PreferredTheme.Light => new LightTheme(),
PreferredTheme.Default => defaultTheme
};
CurrentTheme = theme;
//Previous code
/*ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries != null)
{
mergedDictionaries.Clear();
mergedDictionaries.Add(theme);
}*/
//New code
var applicationResourceDictionary = Application.Current.Resources;
ManuallyCopyThemes(theme, applicationResourceDictionary);
OnPreferenceChanged(preferredTheme);
});
}
void ManuallyCopyThemes(ResourceDictionary fromResource, ResourceDictionary toResource)
{
foreach (var item in fromResource.Keys)
{
if (toResource.ContainsKey(item))
toResource[item] = fromResource[item];
else
toResource.Add(item, fromResource[item]);
}
}