From d16d9185cd7e45f46ad6a301a860fb40ccf4adc9 Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Tue, 16 Dec 2025 20:05:52 +0530 Subject: [PATCH 1/6] Implemented Material 3 support for CheckBox --- .../Platform/Android/CheckBoxExtensions.cs | 26 ++- .../Resources/values/colors-material3.xml | 178 ++++++++++++++++++ .../Resources/values/styles-material3.xml | 2 +- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 src/Core/src/Platform/Android/Resources/values/colors-material3.xml diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs index a7e9c8717586..fd9be328b34d 100644 --- a/src/Core/src/Platform/Android/CheckBoxExtensions.cs +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -9,6 +9,8 @@ namespace Microsoft.Maui.Platform { public static class CheckBoxExtensions { + static ColorStateList? _defaultButtonTintList; + public static void UpdateBackground(this AppCompatCheckBox platformCheckBox, ICheckBox check) { var paint = check.Background; @@ -26,10 +28,28 @@ public static void UpdateIsChecked(this AppCompatCheckBox platformCheckBox, IChe public static void UpdateForeground(this AppCompatCheckBox platformCheckBox, ICheckBox check) { - var mode = PorterDuff.Mode.SrcIn; + // For Material 3, preserve the default Material 3 theme colors + if (RuntimeFeature.IsMaterial3Enabled) + { + _defaultButtonTintList ??= platformCheckBox.ButtonTintList; - CompoundButtonCompat.SetButtonTintList(platformCheckBox, platformCheckBox.GetColorStateList(check)); - CompoundButtonCompat.SetButtonTintMode(platformCheckBox, mode); + if (check.Foreground is SolidPaint solid) + { + // Apply custom color only when enabled + CompoundButtonCompat.SetButtonTintList(platformCheckBox, ColorStateList.ValueOf(solid.Color.ToPlatform())); + } + else + { + // Restore Material 3 default theme colors + CompoundButtonCompat.SetButtonTintList(platformCheckBox, _defaultButtonTintList); + } + } + else + { + var mode = PorterDuff.Mode.SrcIn; + CompoundButtonCompat.SetButtonTintList(platformCheckBox, platformCheckBox.GetColorStateList(check)); + CompoundButtonCompat.SetButtonTintMode(platformCheckBox, mode); + } } internal static ColorStateList GetColorStateList(this AppCompatCheckBox platformCheckBox, ICheckBox check) diff --git a/src/Core/src/Platform/Android/Resources/values/colors-material3.xml b/src/Core/src/Platform/Android/Resources/values/colors-material3.xml new file mode 100644 index 000000000000..25f5820d05f8 --- /dev/null +++ b/src/Core/src/Platform/Android/Resources/values/colors-material3.xml @@ -0,0 +1,178 @@ + + + + + + #6750A4 + #FFFFFF + #EADDFF + #21005D + + + #625B71 + #FFFFFF + #E8DEF8 + #1D192B + + + #7D5260 + #FFFFFF + #FFD8E4 + #31111D + + + #BA1A1A + #FFDAD6 + #FFFFFF + #410002 + + + #FFFBFE + #1C1B1F + + + #FFFBFE + #1C1B1F + #E7E0EC + #49454F + + + #F3EDF7 + #ECE6F0 + #E6E0E9 + #F7F2FA + #FFFFFF + #FFFBFE + #DDD8E1 + + + #79747E + #CAC4D0 + + + #313033 + #F4EFF4 + #D0BCFF + + + #6750A4 + #000000 + + + #000000 + + + #1A6750A4 + #1F6750A4 + #296750A4 + + #141C1B1F + #1F1C1B1F + #291C1B1F + + + #EADDFF + #21005D + #D0BCFF + #4F378B + + #E8DEF8 + #1D192B + #CCC2DC + #4A4458 + + #FFD8E4 + #31111D + #EFB8C8 + #633B48 + + + + + #D0BCFF + #371E73 + #4F378B + #EADDFF + + + #CCC2DC + #332D41 + #4A4458 + #E8DEF8 + + + #EFB8C8 + #492532 + #633B48 + #FFD8E4 + + + #FFB4AB + #93000A + #690005 + #FFDAD6 + + + #10090D + #E6E1E5 + + + #10090D + #E6E1E5 + #49454F + #CAC4D0 + + + #211F26 + #2B2930 + #36343B + #1D1B20 + #0B0F14 + #3B383E + #141218 + + + #938F99 + #49454F + + + #E6E1E5 + #322F35 + #6750A4 + + + #D0BCFF + #000000 + + + #000000 + + + #1AD0BCFF + #1FD0BCFF + #29D0BCFF + + #14E6E1E5 + #1FE6E1E5 + #29E6E1E5 + + + #EADDFF + #21005D + #D0BCFF + #4F378B + + #E8DEF8 + #1D192B + #CCC2DC + #4A4458 + + #FFD8E4 + #31111D + #EFB8C8 + #633B48 + + + @color/md_theme_light_surface + @color/md_theme_dark_surface + diff --git a/src/Core/src/Platform/Android/Resources/values/styles-material3.xml b/src/Core/src/Platform/Android/Resources/values/styles-material3.xml index 454bfe1ecd4e..338d6b773258 100644 --- a/src/Core/src/Platform/Android/Resources/values/styles-material3.xml +++ b/src/Core/src/Platform/Android/Resources/values/styles-material3.xml @@ -22,4 +22,4 @@ ?attr/colorSurface ?attr/actionBarSize - \ No newline at end of file + From 5c7222703313a126ed27c8a51d0f66cd554907ee Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Wed, 17 Dec 2025 13:32:14 +0530 Subject: [PATCH 2/6] Modified the logic --- .../Platform/Android/CheckBoxExtensions.cs | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs index fd9be328b34d..a8a78473dc97 100644 --- a/src/Core/src/Platform/Android/CheckBoxExtensions.cs +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -28,47 +28,40 @@ public static void UpdateIsChecked(this AppCompatCheckBox platformCheckBox, IChe public static void UpdateForeground(this AppCompatCheckBox platformCheckBox, ICheckBox check) { - // For Material 3, preserve the default Material 3 theme colors - if (RuntimeFeature.IsMaterial3Enabled) - { - _defaultButtonTintList ??= platformCheckBox.ButtonTintList; - - if (check.Foreground is SolidPaint solid) - { - // Apply custom color only when enabled - CompoundButtonCompat.SetButtonTintList(platformCheckBox, ColorStateList.ValueOf(solid.Color.ToPlatform())); - } - else - { - // Restore Material 3 default theme colors - CompoundButtonCompat.SetButtonTintList(platformCheckBox, _defaultButtonTintList); - } - } - else - { - var mode = PorterDuff.Mode.SrcIn; - CompoundButtonCompat.SetButtonTintList(platformCheckBox, platformCheckBox.GetColorStateList(check)); - CompoundButtonCompat.SetButtonTintMode(platformCheckBox, mode); - } + var mode = PorterDuff.Mode.SrcIn; + CompoundButtonCompat.SetButtonTintList(platformCheckBox, platformCheckBox.GetColorStateList(check)); + CompoundButtonCompat.SetButtonTintMode(platformCheckBox, mode); } internal static ColorStateList GetColorStateList(this AppCompatCheckBox platformCheckBox, ICheckBox check) { - AColor tintColor; - // For the moment, we're only supporting solid color Paint for the Android Checkbox if (check.Foreground is SolidPaint solid) { var color = solid.Color; - tintColor = color.ToPlatform(); + AColor tintColor = color.ToPlatform(); + return ColorStateListExtensions.CreateCheckBox(tintColor); } else { + // No custom foreground color + if (RuntimeFeature.IsMaterial3Enabled) + { + // Save the default button tint list from the theme on first call + _defaultButtonTintList ??= platformCheckBox.ButtonTintList; + + // Material 3: Use the default theme's buttonTint + if (_defaultButtonTintList is not null) + { + return _defaultButtonTintList; + } + } + + // Material 2: Use accent color Graphics.Color accent = platformCheckBox.Context?.GetAccentColor() ?? Graphics.Color.FromArgb("#ff33b5e5"); - tintColor = accent.ToPlatform(); + AColor tintColor = accent.ToPlatform(); + return ColorStateListExtensions.CreateCheckBox(tintColor); } - - return ColorStateListExtensions.CreateCheckBox(tintColor); } } -} \ No newline at end of file +} From 7ab492bc28eed1a68adc4526639b7d9bd7fdfda4 Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Wed, 17 Dec 2025 13:36:28 +0530 Subject: [PATCH 3/6] Remove unwanted changes --- src/Core/src/Platform/Android/CheckBoxExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs index a8a78473dc97..44309bbc0288 100644 --- a/src/Core/src/Platform/Android/CheckBoxExtensions.cs +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -29,6 +29,7 @@ public static void UpdateIsChecked(this AppCompatCheckBox platformCheckBox, IChe public static void UpdateForeground(this AppCompatCheckBox platformCheckBox, ICheckBox check) { var mode = PorterDuff.Mode.SrcIn; + CompoundButtonCompat.SetButtonTintList(platformCheckBox, platformCheckBox.GetColorStateList(check)); CompoundButtonCompat.SetButtonTintMode(platformCheckBox, mode); } @@ -44,10 +45,10 @@ internal static ColorStateList GetColorStateList(this AppCompatCheckBox platform } else { - // No custom foreground color + if (RuntimeFeature.IsMaterial3Enabled) { - // Save the default button tint list from the theme on first call + // Save the default button tint list _defaultButtonTintList ??= platformCheckBox.ButtonTintList; // Material 3: Use the default theme's buttonTint From e1b1c1797b34e6ad50ac8446ac22f9f8843cd240 Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Fri, 26 Dec 2025 18:47:32 +0530 Subject: [PATCH 4/6] Removed the unwanted code --- .../Resources/values/colors-material3.xml | 178 ------------------ .../Resources/values/styles-material3.xml | 2 +- 2 files changed, 1 insertion(+), 179 deletions(-) delete mode 100644 src/Core/src/Platform/Android/Resources/values/colors-material3.xml diff --git a/src/Core/src/Platform/Android/Resources/values/colors-material3.xml b/src/Core/src/Platform/Android/Resources/values/colors-material3.xml deleted file mode 100644 index 25f5820d05f8..000000000000 --- a/src/Core/src/Platform/Android/Resources/values/colors-material3.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - #6750A4 - #FFFFFF - #EADDFF - #21005D - - - #625B71 - #FFFFFF - #E8DEF8 - #1D192B - - - #7D5260 - #FFFFFF - #FFD8E4 - #31111D - - - #BA1A1A - #FFDAD6 - #FFFFFF - #410002 - - - #FFFBFE - #1C1B1F - - - #FFFBFE - #1C1B1F - #E7E0EC - #49454F - - - #F3EDF7 - #ECE6F0 - #E6E0E9 - #F7F2FA - #FFFFFF - #FFFBFE - #DDD8E1 - - - #79747E - #CAC4D0 - - - #313033 - #F4EFF4 - #D0BCFF - - - #6750A4 - #000000 - - - #000000 - - - #1A6750A4 - #1F6750A4 - #296750A4 - - #141C1B1F - #1F1C1B1F - #291C1B1F - - - #EADDFF - #21005D - #D0BCFF - #4F378B - - #E8DEF8 - #1D192B - #CCC2DC - #4A4458 - - #FFD8E4 - #31111D - #EFB8C8 - #633B48 - - - - - #D0BCFF - #371E73 - #4F378B - #EADDFF - - - #CCC2DC - #332D41 - #4A4458 - #E8DEF8 - - - #EFB8C8 - #492532 - #633B48 - #FFD8E4 - - - #FFB4AB - #93000A - #690005 - #FFDAD6 - - - #10090D - #E6E1E5 - - - #10090D - #E6E1E5 - #49454F - #CAC4D0 - - - #211F26 - #2B2930 - #36343B - #1D1B20 - #0B0F14 - #3B383E - #141218 - - - #938F99 - #49454F - - - #E6E1E5 - #322F35 - #6750A4 - - - #D0BCFF - #000000 - - - #000000 - - - #1AD0BCFF - #1FD0BCFF - #29D0BCFF - - #14E6E1E5 - #1FE6E1E5 - #29E6E1E5 - - - #EADDFF - #21005D - #D0BCFF - #4F378B - - #E8DEF8 - #1D192B - #CCC2DC - #4A4458 - - #FFD8E4 - #31111D - #EFB8C8 - #633B48 - - - @color/md_theme_light_surface - @color/md_theme_dark_surface - diff --git a/src/Core/src/Platform/Android/Resources/values/styles-material3.xml b/src/Core/src/Platform/Android/Resources/values/styles-material3.xml index 338d6b773258..454bfe1ecd4e 100644 --- a/src/Core/src/Platform/Android/Resources/values/styles-material3.xml +++ b/src/Core/src/Platform/Android/Resources/values/styles-material3.xml @@ -22,4 +22,4 @@ ?attr/colorSurface ?attr/actionBarSize - + \ No newline at end of file From f9131f11e7379bf1324674d4ede7442d5144b942 Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Fri, 2 Jan 2026 10:15:39 +0530 Subject: [PATCH 5/6] Updated the code --- src/Core/src/Platform/Android/CheckBoxExtensions.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs index 44309bbc0288..ceced4d47a69 100644 --- a/src/Core/src/Platform/Android/CheckBoxExtensions.cs +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -36,6 +36,9 @@ public static void UpdateForeground(this AppCompatCheckBox platformCheckBox, ICh internal static ColorStateList GetColorStateList(this AppCompatCheckBox platformCheckBox, ICheckBox check) { + // Cache the default button tint list if not already cached + _defaultButtonTintList ??= platformCheckBox.ButtonTintList; + // For the moment, we're only supporting solid color Paint for the Android Checkbox if (check.Foreground is SolidPaint solid) { @@ -45,12 +48,8 @@ internal static ColorStateList GetColorStateList(this AppCompatCheckBox platform } else { - if (RuntimeFeature.IsMaterial3Enabled) { - // Save the default button tint list - _defaultButtonTintList ??= platformCheckBox.ButtonTintList; - // Material 3: Use the default theme's buttonTint if (_defaultButtonTintList is not null) { From 6433d7b68ca4d37519aa61044c1ecd6036ec894f Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Tue, 6 Jan 2026 21:44:41 +0100 Subject: [PATCH 6/6] Fix Material3 checkbox static caching issues Address review feedback on PR #33339: 1. Replace static ColorStateList cache with per-instance ConditionalWeakTable - Fixes per-Activity theming (different Activities can have different themes) - Fixes theme switching at runtime (dark mode, Material2/3 toggle) - Provides thread safety (no shared mutable state) 2. Improve code structure - Remove unnecessary else block for cleaner flow - Add descriptive comments explaining the caching strategy The original static cache would capture the first checkbox's theme tint and reuse it for all subsequent checkboxes, causing incorrect colors when themes differ across Activities or change at runtime. --- .../Platform/Android/CheckBoxExtensions.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs index ceced4d47a69..55fad63e537c 100644 --- a/src/Core/src/Platform/Android/CheckBoxExtensions.cs +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -1,4 +1,5 @@ -using Android.Content.Res; +using System.Runtime.CompilerServices; +using Android.Content.Res; using Android.Graphics; using AndroidX.AppCompat.Widget; using AndroidX.Core.Widget; @@ -9,7 +10,11 @@ namespace Microsoft.Maui.Platform { public static class CheckBoxExtensions { - static ColorStateList? _defaultButtonTintList; + // Store the original theme button tint per checkbox instance to support: + // - Per-Activity theming (different Activities can have different themes) + // - Theme switching at runtime (dark mode, Material2/3 toggle) + // - Thread safety (no shared mutable state) + static readonly ConditionalWeakTable _defaultButtonTintCache = new(); public static void UpdateBackground(this AppCompatCheckBox platformCheckBox, ICheckBox check) { @@ -36,8 +41,17 @@ public static void UpdateForeground(this AppCompatCheckBox platformCheckBox, ICh internal static ColorStateList GetColorStateList(this AppCompatCheckBox platformCheckBox, ICheckBox check) { - // Cache the default button tint list if not already cached - _defaultButtonTintList ??= platformCheckBox.ButtonTintList; + // Cache the original theme button tint for this checkbox instance before we modify it. + // This must happen before SetButtonTintList is called, as that will overwrite ButtonTintList. + if (!_defaultButtonTintCache.TryGetValue(platformCheckBox, out var defaultButtonTintList)) + { + var currentTint = platformCheckBox.ButtonTintList; + if (currentTint is not null) + { + _defaultButtonTintCache.Add(platformCheckBox, currentTint); + defaultButtonTintList = currentTint; + } + } // For the moment, we're only supporting solid color Paint for the Android Checkbox if (check.Foreground is SolidPaint solid) @@ -46,22 +60,20 @@ internal static ColorStateList GetColorStateList(this AppCompatCheckBox platform AColor tintColor = color.ToPlatform(); return ColorStateListExtensions.CreateCheckBox(tintColor); } - else + + if (RuntimeFeature.IsMaterial3Enabled) { - if (RuntimeFeature.IsMaterial3Enabled) + // Material 3: Use the original theme's buttonTint + if (defaultButtonTintList is not null) { - // Material 3: Use the default theme's buttonTint - if (_defaultButtonTintList is not null) - { - return _defaultButtonTintList; - } + return defaultButtonTintList; } - - // Material 2: Use accent color - Graphics.Color accent = platformCheckBox.Context?.GetAccentColor() ?? Graphics.Color.FromArgb("#ff33b5e5"); - AColor tintColor = accent.ToPlatform(); - return ColorStateListExtensions.CreateCheckBox(tintColor); } + + // Material 2: Use accent color + Graphics.Color accent = platformCheckBox.Context?.GetAccentColor() ?? Graphics.Color.FromArgb("#ff33b5e5"); + AColor tintColor2 = accent.ToPlatform(); + return ColorStateListExtensions.CreateCheckBox(tintColor2); } } }