如何*动态*改变 iOS/Android 的每种颜色?例如。在 Droid 代码中,覆盖“Styles.xml”中的“colorAccent”
Posted
技术标签:
【中文标题】如何*动态*改变 iOS/Android 的每种颜色?例如。在 Droid 代码中,覆盖“Styles.xml”中的“colorAccent”【英文标题】:How *dynamically* change EVERY color iOS/Android? E.g. in Droid code, override "colorAccent" in "Styles.xml" 【发布时间】:2019-11-29 18:26:39 【问题描述】:我正在向 Xamarin Forms 应用程序添加颜色主题。 主题被指定为动态变化,具体取决于用户站在/靠近哪个客户的位置。我们有“浅色”和“深色”主题的客户,有多种调色板;因此需要能够更改所有显示项目的所有颜色。动态的。
到目前为止,我的动态主题适用于几乎所有类型的视图。
在为 android 上的 ios 样式(垂直滚轮)Xamarin.Forms.Picker
着色时,我坚持两个细节:
问题一:如何改变背景颜色?问题二:如何改变“取消”按钮的文字颜色?
这个基于 Android NumberPicker
的 XF 视图似乎不能开箱即用地完全自定义颜色。截至最新的 XF,4.3.0.991211。
--我试过的:-----
A.在Style.xml
资源文件中静态地设置 Android 主题颜色。成功。但它是静态的。
B.下载Xamarin Forms Light/Dark Theme Sample。
在 Android 上构建和运行。转到其“选择主题”选择器。选择浅色主题。再次转到选择器 - 白色背景,到目前为止,非常好。选择深色主题。再次转到选择器 - 选择器仍然是黑白的。这里没有解决方案。
C.在网上找到了如何动态更改AlertDialog
s“确定”/“取消”按钮文本颜色,使用 AlertDialog“构建器”,并获取资源 ID。成功实现了这一点。不确定如何将相同的技术应用于 Picker。
问题 3:如何发现 XF Picker 使用的 Android Control 中使用了哪些资源 ID?
D.克隆 Xamarin Forms 源代码。将 Droid PickerRenderer 复制到我的代码中。大多数代码无法运行,因为它依赖于internal
类/函数。让我的继承自内置的,并注释掉任何不需要/不会编译的代码。
自定义OnElementChanged
,因为这是设置TextColor
的位置。
在自定义渲染器源代码中,IPickerRenderer.OnClick()
构建一个包含NumberPicker
的AlertDialog
。我没有看到任何设置背景颜色或“取消”按钮文本颜色的逻辑。但至少我们知道 Android 类。 请参阅上面的问题 3。
E.至少,having a small number of predefined themes 可能是一个解决方案。或Android theme.ApplyStyle - 但这可以是动态 风格吗?我还没有测试过这些中的任何一个是否成功地改变了我坚持的细节。 [我尝试后会报告。]
F。 重新启动应用程序可以更改主题:any-color themes。这将是我最后的手段;首先看看我是否可以在 C# 中完全控制颜色,而无需重新启动。
注意:在 SO 和 Xamarin.Forums 上有很多帖子一般讨论 Xamarin Forms 中的主题。我已经过了这一点,所以我不会在这里列出这些,除了提到Clint St.Laurent's theming post,它(快速浏览一下)看起来与我所做的相似。 我只剩下少量的小细节——在我的测试中,Xamarin Forms 未能将主题颜色应用到这些细节上。
G.其他看似相关的 SO 帖子 - 但不执行问题 1 和 2 中列出的确切任务:
Picker implemented as an AlertDialog。显示自定义 PickerRenderer。但没有显示如何在 Q 1 & 2 中设置颜色。
另一个Picker implemented as an AlertDialog。同样。
How to really change primary and accent color in android lollipop - 我在E
和F
中提到了这个问题的答案。如果我无法通过自定义 PickerRenderer 中的 C# 代码实现结果,我会调查这些。
Xamarin 论坛帖子:
How to change the colorAccent at runtime?。给出的唯一答案不会改变主题的colorAccent
。它只是改变了应用栏的颜色。
澄清:有很多文档、论坛和博客文章描述了 Xamarin Forms 主题的一般机制。不是在寻找答案。我只是想解决少量缺失的细节,从最新的稳定 Xamarin Forms 4.3.0.991211 开始。 希望 Xamarin Forms 成功地为所有类型控件的 100% 详细信息设置主题的那一天会到来 - 届时此问答将过时。
【问题讨论】:
【参考方案1】:问题中D.
的实现。在代码中以 cmets 形式给出的学分。
此自定义 Android PickerRenderer 动态设置 3 种颜色:文本(蓝色)、文本“轮”后面的背景(浅橙色)、按钮(深红色)。
澄清:static Android 主题具有深色背景。目标是动态地从暗色主题变为亮色主题。为了使成功设置的颜色非常明显,使用了强烈着色的颜色。
我尚未着色的文本之间的白色“分隔线”。我已经在 Xamarin 或 Android 文档中看到了可绘制分隔线的内容。掌握它并设置它的颜色应该很简单。 编辑:也许 titleDivider
在 Change picker control pop-up background and text colors 或 Vahid's answer 中的 Tiago Flores 代码 sn-p。或selectionDividerField
中的Tapa Save's answer。
注意:我没有添加自定义 XF 属性来控制按钮颜色;硬编码为Color.Accent
。
using Android.App;
using Android.Util;
using Android.Views;
using Android.Widget;
using System;
using System.ComponentModel;
using System.Linq;
using Orientation = Android.Widget.Orientation;
using Android.Content;
using Android.Text;
using Android.Text.Style;
using Java.Lang;
// PickerEditText
using Xamarin.Forms.Platform.Android;
using AColor = Android.Graphics.Color;
using Picker = Xamarin.Forms.Picker;
[assembly: Xamarin.Forms.ExportRenderer(typeof(Picker), typeof(LCPickerRenderer))]
namespace Xamarin.Forms.Platform.Android
// Based on Xamarin.Forms source code, modified.
public class LCPickerRenderer : PickerRenderer, IPickerRenderer //ViewRenderer<Picker, EditText>, IPickerRenderer
AlertDialog _dialog;
NumberPicker _picker;
public LCPickerRenderer(Context context) : base(context)
//AutoPackage = false;
[Obsolete("This constructor is obsolete as of version 2.5. Please use PickerRenderer(Context) instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public LCPickerRenderer()
//AutoPackage = false;
IElementController ElementController => Element as IElementController;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
if (e.OldElement != null)
//((INotifyCollectionChanged)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
base.OnElementChanged(e);
if (e.NewElement != null)
// --- custom work ---
if (Control != null)
SetTextColor();
SetBackgroundColor();
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Picker.BackgroundColorProperty.PropertyName)
SetBackgroundColor();
else if (e.PropertyName == Picker.TextColorProperty.PropertyName)
SetTextColor();
//if (e.PropertyName == Picker.TitleProperty.PropertyName || e.PropertyName == Picker.TitleColorProperty.PropertyName)
// UpdatePicker();
//else if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
// UpdatePicker();
//else if (e.PropertyName == Picker.CharacterSpacingProperty.PropertyName)
// UpdateCharacterSpacing();
//else if (e.PropertyName == Picker.TextColorProperty.PropertyName)
// UpdateTextColor();
//else if (e.PropertyName == Picker.FontAttributesProperty.PropertyName || e.PropertyName == Picker.FontFamilyProperty.PropertyName || e.PropertyName == Picker.FontSizeProperty.PropertyName)
// UpdateFont();
void IPickerRenderer.OnClick()
Picker model = Element;
if (_dialog != null)
return;
var xfTextColor = Element.TextColor;
_picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
_picker.MaxValue = model.Items.Count - 1;
_picker.MinValue = 0;
_picker.SetDisplayedValues(model.Items.ToArray());
_picker.WrapSelectorWheel = false;
_picker.DescendantFocusability = DescendantFocusability.BlockDescendants;
_picker.Value = model.SelectedIndex;
var layout = new LinearLayout(Context) Orientation = Orientation.Vertical ;
layout.AddView(_picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
if (!Element.IsSet(Picker.TitleColorProperty))
builder.SetTitle(model.Title ?? "");
else
var title = new SpannableString(model.Title ?? "");
title.SetSpan(new ForegroundColorSpan(model.TitleColor.ToAndroid()), 0, title.Length(), SpanTypes.ExclusiveExclusive);
builder.SetTitle(title);
// TODO: get texts (and whether to show) from XF Element.
builder.SetNegativeButton(global::Android.Resource.String.Cancel, (s, a) =>
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
_dialog = null;
);
builder.SetPositiveButton(global::Android.Resource.String.Ok, (s, a) =>
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, _picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
_dialog = null;
);
_dialog = builder.Create();
_dialog.DismissEvent += (sender, args) =>
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
_dialog?.Dispose();
_dialog = null;
;
_dialog.Show();
// TODO: Control this via a custom XF property.
var buttonColor = Color.Accent.ToAndroid();
// From https://***.com/a/56555965/199364.
var btnCancel = _dialog.GetButton((int)DialogInterface.ButtonNegative);
// "?" in case no such button was attached.
btnCancel?.SetTextColor(buttonColor);
var btnOK = _dialog.GetButton((int)DialogInterface.ButtonPositive);
btnOK?.SetTextColor(buttonColor);
SetTextColor();
SetBackgroundColor();
private void SetTextColor()
Control?.SetTextColor(Element.TextColor.ToAndroid());
if (_picker != null)
SetTextColor(_picker, Element.TextColor.ToAndroid());
private void SetBackgroundColor()
Control?.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
_picker?.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
_dialog?.Window.SetBackgroundDrawable(new global::Android.Graphics.Drawables.ColorDrawable(Element.BackgroundColor.ToAndroid()));
// From https://***.com/a/26657169/199364
// TODO: When support API 29, this needs to be modified re https://***.com/a/56883356/199364 ?
public static bool SetTextColor(NumberPicker numberPicker, AColor color)
var sdkInt = global::Android.OS.Build.VERSION.SdkInt;
//var limitSdk = global::Android.OS.Build.VERSION_CODES.Q;
int count = numberPicker.ChildCount;
for (int i = 0; i < count; i++)
var child = numberPicker.GetChildAt(i);
if (child.GetType() == typeof(EditText))
try
var selectorWheelPaintField = numberPicker.Class
.GetDeclaredField("mSelectorWheelPaint");
selectorWheelPaintField.Accessible = true;
EditText editText = (EditText)child;
editText.SetTextColor(color);
var paint = (global::Android.Graphics.Paint)selectorWheelPaintField.Get(numberPicker);
paint.Color = color;
numberPicker.Invalidate();
return true;
catch (NoSuchFieldException e)
Log.Warn("setNumberPickerTextColor", e);
catch (IllegalAccessException e)
Log.Warn("setNumberPickerTextColor", e);
catch (IllegalArgumentException e)
Log.Warn("setNumberPickerTextColor", e);
return false;
奖励:为 Android AlertDialog 设置样式:
大多数对话框都可以使用 AlertDialog.Builder 在 Android 上构建(在您的 Android 项目代码中)。这可以在制作自定义渲染器的情况下完成。在这段代码中,我使用了来自我的 XF 应用程序资源的各种命名颜色。 Google 技术在 Xamarin Form 中进行颜色主题化,了解如何将此类资源设置为不同的颜色值:
// Extracted from a larger class. Some of these not used in the code here.
using System;
using System.Collections.Generic;
using System.IO;
using Android.Content;
using Android.Content.PM;
using Android.Graphics;
using Android.Provider;
using Android.Views;
using Android.Widget;
using Android.Support.V4.Content;
using Android.Support.V4.App;
using Android.Support.V7.App;
// For Extension: color.ToAndroid.
using Xamarin.Forms.Platform.Android;
public static partial class UI
// If cancelHandler is null, no Cancel button will be added.
// If noHandler is not null, then "OK" is renamed "Yes".
public static void ShowAlertDialog(string title, string message,
EventHandler okHandler, EventHandler cancelHandler,
EventHandler noHandler = null, bool textIsYes = false)
bool hasCancel = (cancelHandler != null);
bool hasNo = (noHandler != null);
string okOrYes = PS.LocalizedString((textIsYes || hasNo ? "Yes" : "OK"));
// When both cancel and no, is "Cancel".
// When neither cancel or no, defaults to "Cancel", which does nothing.
// The only time this is "No", is if there is a No button, but no Cancel button.
string cancelOrNo = PS.LocalizedString((hasNo && !hasCancel ? "No" : "Cancel"));
EventHandler cancelOrNoHandler = (hasCancel ? cancelHandler : noHandler);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.it);
var titleOb = new Android.Text.SpannableString(title);
// TODO: Add custom XF property to set this.
var titleColor = (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MaxForegroundColor"];
titleOb.SetSpan(new Android.Text.Style.ForegroundColorSpan(titleColor.ToAndroid()), 0, title.Length, Android.Text.SpanTypes.ExclusiveExclusive);
builder.SetTitle(titleOb);
builder.SetMessage(message);
if (hasCancel || hasNo)
builder.SetNegativeButton(cancelOrNo,
(s, e) =>
cancelOrNoHandler?.Invoke(null, EventArgs.Empty);
);
// Only used when BOTH Cancel and No buttons.
if (hasNo && hasCancel)
builder.SetNeutralButton(PS.LocalizedString("No"),
(s, e) => noHandler.Invoke(null, EventArgs.Empty); );
builder.SetPositiveButton(okOrYes, (s, e) =>
if (okHandler != null)
okHandler.Invoke(null, EventArgs.Empty);
);
AlertDialog dialog = builder.Show();
SetAlertStyle(dialog);
/// <summary>
/// This doesn't set Title Color. Set that earlier, while building the dialog.
/// </summary>
/// <param name="dialog"></param>
private static void SetAlertStyle(AlertDialog dialog)
// Use these if succeed in changing both title and background colors.
var foreColor = (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["ForegroundColor"];
var accentAgainstBack = (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["AccentSaturated25Fore33"];
var backColor = (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["BackgroundColor"];
SetBackgroundColor(dialog, backColor);
//// DIDN'T WORK: No effect on title color. Set earlier, while building the dialog.
//int alertViewId = Android.App.Application.Context.Resources.GetIdentifier("alertTitle", "id", "android");
//SetItemTextColor(dialog, "alertTitle", accentAgainstBack);
SetItemTextColor(dialog, "message", foreColor);
SetItemTextColor(dialog, "button1", (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["AccentSaturated25Fore33"]);
SetItemTextColor(dialog, "button2", (Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["ForegroundColor"]);
private static void SetItemTextColor(AlertDialog dialog, string itemResourceName, Xamarin.Forms.Color color)
int textId = Android.App.Application.Context.Resources.GetIdentifier(itemResourceName, "id", "android");
TextView text = dialog.FindViewById<TextView>(textId);
text?.SetTextColor(color.ToAndroid());
private static void SetBackgroundColor(AlertDialog dialog, Xamarin.Forms.Color xfColor)
dialog?.Window.SetBackgroundDrawable(new global::Android.Graphics.Drawables.ColorDrawable(xfColor.ToAndroid()));
【讨论】:
以上是关于如何*动态*改变 iOS/Android 的每种颜色?例如。在 Droid 代码中,覆盖“Styles.xml”中的“colorAccent”的主要内容,如果未能解决你的问题,请参考以下文章
React Native之code-push的热更新(ios android)
React Native之code-push的热更新(ios android)