Xamarin.Forms - Změna barvy vybrané položky v UWP

Xamarin XAML

5 years ago

Xamarin.Forms obsahuje ovladací prvek ListView, který nabízí bohaté funkce včetně výběru položky seznamu. V případě UWP je barva výběru udávána aktuálním nastavením barveného motivu operačního systému. Obvyklým požadavkem však je lépe přizpůsobit aplikaci vlastnímu brandingu. V tomto článku se podíváme, jak na to.

Nahlédnutí do výchozího stylu

Pokud chceme zjistit jak je implementován výchozí vzhled zabudovaných ovladacích prvků, nejlepší místo pro to jsou výchozí styly v samotném SDK. Můžeme buď najít trochu zastaralou verzi v [on-line dokumentaci](https://msdn.microsoft.com/en-us/library/windows/apps/xaml/f5fba473-bc71-40ec-8df1-ea977c6938ed), nebo aktuální přímo na našem hard disku. V prohlížeči souborů přejděte na cestu:

C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP

V této složce najdeme podsložku pro každou nainstalovanou verzi UWP SDK. Vybereme tu, na kterou náš projekt cílí a přejděte do složky Generic kde otevřeme generic.xaml ve našem oblíbeném textovém editoru. V tomto poměrně velkém souboru vyhledáme TargetType="ListViewItem", díky čemuž bychom měli najít výchozí styl prvku ListViewItem , který poskytuje obálku pro každou položku v našem ListView. V tomto stylu zjistíme, že mnoho různých resource založených na třídě Brush, mimojiné také vlastnost SelectedBackground . Ta je nastavena takto:

SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"

Odkud vlastně ListViewItemBackgroundSelected pochází? Na to tu máme druhý soubor ve složce Generic - themeresources.xaml. Otevřeme jej:

<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="SystemControlHighlightListAccentLowBrush" />

Takže pouze další odkaz! Pokračujeme v hledání:

<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{ThemeResource SystemAccentColor}" Opacity="0.6" />

Zjistili jsme tedy, že vybraná položka je skutečně zobrazena barvou motivu systému s viditelností 0.6. Jak můžeme toto výchozí chování v Xamarin.Forms změnit?

Změna výchozí barvy

Resource pro celou aplikaci

Jednodušší, ale výrazně méně ideální cestou je přepsání resource na úrovni aplikace. Poskytneme-li vlastní SystemControlHighlightListAccentLowBrush, nahradí výchzozí náš brush všechny výskyty kde se resource používá - tedy včetně jiných ovladacích prvků. Pokud je to to co chceme, můžeme v UWP projektu otevřít soubor App.xaml a přidat resource do resource dictionary Application:

<Application ...>
    <Application.Resources>
         <SolidColorBrush Color="DodgerBlue" x:Key="SystemControlHighlightListAccentLowBrush" />
    </Application.Resources>
</Application>

Protože má brush stejný klíč jako ten nastavený systémem ale je výše v hierarchii, přepíše automaticky výchozí hodnotu v naší aplikaci. Všechny ListView a další prvky v naší aplikaci budou nyní namísto barvy motivu používat výraznou barvu DodgerBlue.

Custom renderer

Méně invazivní cestou je použití Xamarin custom rendereru na vytvoření nové verze ListView s custom rendererem na UWP. Nejprve vytvoříme novout třídu SelectionColorListView ve sdíleném nebo .NET Standard projektu:

public class SelectionColorListView : ListView
{
    public static readonly BindableProperty SelectionColorProperty =
        BindableProperty.Create(nameof(SelectionColor), typeof(Color), typeof(SelectionColorListView), Color.LawnGreen);

    public Color SelectionColor
    {
        get => (Color) GetValue(SelectionColorProperty);
        set => SetValue(SelectionColorProperty, value);
    }
}

Nyní napíšeme vlastní custom renderer který při prvním načtení zjistí barvu pro výběr a nahradí resource právě pro tento control.

[assembly: ExportRenderer(typeof(SelectionColorListView), typeof(SelectionColorListViewRenderer))]

namespace XFUwpListViewColors.UWP
{
    public class SelectionColorListViewRenderer : ListViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement != null)
            {
                UpdateSelectionColor();
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == nameof(SelectionColorListView.SelectionColor))
            {
                UpdateSelectionColor();
            }
        }

        private void UpdateSelectionColor()
        {
            if (Control != null && Element is SelectionColorListView listView)
            {
                var nativeColor = XamarinColorToNative(listView.SelectionColor);
                Control.Resources["SystemControlHighlightListAccentLowBrush"] = new SolidColorBrush(nativeColor);
            }
        }

        private Windows.UI.Color XamarinColorToNative(Xamarin.Forms.Color color)
        {
            var alpha = (byte)(color.A * 255);
            var red = (byte)(color.R * 255);
            var green = (byte)(color.G * 255);
            var blue = (byte)(color.B * 255);
            return Windows.UI.Color.FromArgb(alpha, red, green, blue);
        }
    }
}

Nyní můžeme použít náš vlastní ListView v XAMLu:

<local:SelectionColorListView SelectionMode="Single" SelectionColor="LimeGreen" />

Kde local: musí být deklarovaný XAML jmený prostor v elementu ContentPage:

 xmlns:local="clr-namespace:NamespaceOfYourCustomListView"

Poznamenejme že aktualizace již vybrané barvy za běhu bohužel není možné, protože ListView si po prvním použití brush zapamatuje a bude nadále používat resource který nalezl poprvé.

Další vylepšení

Pro sjednocení přes různé platformy bychom mohli implementovat custom rederer na Androidu a iOS. Není to však povinné, protože Xamarin.Forms jinak bude prostě používat výchozí ListViewRenderer a ignorovat vlastnost SelectionColor. Navíc bychom také mohli přizpůsobit i ostatní barvy ListViewItem - barvu při přejetí myši nad položkou, barvu při stisknutí, atd. Toho dosáhneme přesně stejným postupem jako výše.

Budoucnost

Na konferenci Build 2018 Microsoft představil připravovaný nástroj, který automaticky vygeneruje pro vaši aplikaci barevné styly odpovídající zadanému brandingu. To naše životy výrazně zjednodušší a přestylování celé aplikace už půjde levou zadní.

Zdrojový kód

Zdrojový kód tohoto článku je dostupný na mém GitHubu.

Shrnutí

Úprava výchozích stylů UWP aplikace v Xamarin.Forms je trochu komplikovanější než v klasických UWP projektech. Pokud nechcete měnit vzhled všech ovladacích prvků, použijte custom renderer a změny aplikujte pouze tam.