Внешний вид кнопки (Button) в Xamarin.Forms

Автор:

www.microsoft.com

Кнопка (Button) в Xamarin.Forms наследует или определяет несколько свойств, которые влияют на ее внешний вид:

  • TextColor – цвет текста кнопки
  • BackgroundColor – цвет фона этого текста
  • BorderColor – цвет области, окружающей кнопку
  • FontFamily – шрифты, используемые для текста
  • FontSize – размер текста
  • FontAttributes указывает, является ли текст курсивом или жирным
  • BorderWidth – ширина границы
  • CornerRadius – радиус угла кнопки
  • CharacterSpacing – расстояние между символами текста кнопки
  • TextTransform определяет обводку текста кнопки.

Примечание. Класс Button также имеет свойства Margin и Padding, которые управляют расположением кнопки.

Эффекты шести из этих свойств (за исключением FontFamily и FontAttributes) демонстрируются на странице «Визуальный вид кнопки». Еще одно свойство, Image, обсуждается в разделе «Использование растровых изображений с кнопкой».

Все представления и привязки данных на странице Button Appearance определены в файле XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:local="clr-namespace:ButtonDemos"
            x:Class="ButtonDemos.ButtonAppearancePage"
            Title="Button Appearance">
   <StackLayout>
       <Button x:Name="button"
               Text="Button"
               VerticalOptions="CenterAndExpand"
               HorizontalOptions="Center"
               TextColor="{Binding Source={x:Reference textColorPicker},
                                   Path=SelectedItem.Color}"
               BackgroundColor="{Binding Source={x:Reference backgroundColorPicker},
                                         Path=SelectedItem.Color}"
               BorderColor="{Binding Source={x:Reference borderColorPicker},
                                     Path=SelectedItem.Color}" />
 
       <StackLayout BindingContext="{x:Reference button}"
                    Padding="10">
 
           <Slider x:Name="fontSizeSlider"
                   Maximum="48"
                   Minimum="1"
                   Value="{Binding FontSize}" />
 
           <Label Text="{Binding Source={x:Reference fontSizeSlider},
                                 Path=Value,
                                 StringFormat='FontSize = {0:F0}'}"
                  HorizontalTextAlignment="Center" />
 
           <Slider x:Name="borderWidthSlider"
                   Minimum="-1"
                   Maximum="12"
                   Value="{Binding BorderWidth}" />
 
           <Label Text="{Binding Source={x:Reference borderWidthSlider},
                                 Path=Value,
                                 StringFormat='BorderWidth = {0:F0}'}"
                  HorizontalTextAlignment="Center" />
 
           <Slider x:Name="cornerRadiusSlider"
                   Minimum="-1"
                   Maximum="24"
                   Value="{Binding CornerRadius}" />
 
           <Label Text="{Binding Source={x:Reference cornerRadiusSlider},
                                 Path=Value,
                                 StringFormat='CornerRadius = {0:F0}'}"
                  HorizontalTextAlignment="Center" />
 
           <Grid>
               <Grid.RowDefinitions>
                   <RowDefinition Height="Auto" />
                   <RowDefinition Height="Auto" />
                   <RowDefinition Height="Auto" />
               </Grid.RowDefinitions>
 
               <Grid.ColumnDefinitions>
                   <ColumnDefinition Width="*" />
                   <ColumnDefinition Width="*" />
               </Grid.ColumnDefinitions>
 
               <Grid.Resources>
                   <Style TargetType="Label">
                       <Setter Property="VerticalOptions" Value="Center" />
                   </Style>
               </Grid.Resources>
 
               <Label Text="Text Color:"
                      Grid.Row="0" Grid.Column="0" />
 
               <Picker x:Name="textColorPicker"
                       ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
                       ItemDisplayBinding="{Binding FriendlyName}"
                       SelectedIndex="0"
                       Grid.Row="0" Grid.Column="1" />
 
               <Label Text="Background Color:"
                      Grid.Row="1" Grid.Column="0" />
 
               <Picker x:Name="backgroundColorPicker"
                       ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
                       ItemDisplayBinding="{Binding FriendlyName}"
                       SelectedIndex="0"
                       Grid.Row="1" Grid.Column="1" />
 
               <Label Text="Border Color:"
                      Grid.Row="2" Grid.Column="0" />
 
               <Picker x:Name="borderColorPicker"
                       ItemsSource="{Binding Source={x:Static local:NamedColor.All}}"
                       ItemDisplayBinding="{Binding FriendlyName}"
                       SelectedIndex="0"
                       Grid.Row="2" Grid.Column="1" />
           </Grid>
       </StackLayout>
   </StackLayout>
</ContentPage>

Кнопка в верхней части страницы имеет три свойства Color, привязанные к элементам Picker в нижней части страницы. Элементы Picker представляют собой цвета из класса NamedColor, включенного в проект. Три элемента Slider содержат двусторонние привязки к свойствам FontSize, BorderWidth и CornerRadius кнопки Button.

Эта программа позволяет экспериментировать с комбинациями всех этих свойств:

Чтобы увидеть границу кнопки, вам нужно установить для BorderColor значение, отличное от Default, а для BorderWidth – положительное значение.

На iOS вы заметите, что большая ширина границы мешает отображению текста. Если вы решите использовать границу в кнопке iOS, вы, вероятнее всего, будете использовать свойство Text с пробелами, чтобы сохранить его видимость.

В UWP при выборе CornerRadius, превышающего половину высоты кнопки, возникает исключение.

Визуальные состояния кнопки

У кнопки есть состояние Pressed VisualState, которое можно использовать для инициирования визуального изменения кнопки при нажатии пользователем, при условии, что оно включено.

В следующем примере XAML показано, как определить визуальное состояние для состояния Pressed:

<Button Text="Click me!"
       ...>
   <VisualStateManager.VisualStateGroups>
       <VisualStateGroup x:Name="CommonStates">
           <VisualState x:Name="Normal">
               <VisualState.Setters>
                   <Setter Property="Scale"
                           Value="1" />
               </VisualState.Setters>
           </VisualState>
 
           <VisualState x:Name="Pressed">
               <VisualState.Setters>
                   <Setter Property="Scale"
                           Value="0.8" />
               </VisualState.Setters>
           </VisualState>
 
       </VisualStateGroup>
   </VisualStateManager.VisualStateGroups>
</Button>

Визуальное состояние (VisualState) Pressed определяет, что при нажатии кнопки ее свойство Scale будет изменено со значения по умолчанию 1 на 0,8. Normal VisualState указывает, что, когда кнопка находится в нормальном состоянии, ее свойство Scale будет установлено в 1. Таким образом, общий эффект заключается в том, что, когда кнопка нажата, ее масштаб немного уменьшается, а когда кнопка отпущена, она изменяется до размера по умолчанию.

Создание кнопки переключения

Можно создать подкласс Button так, чтобы он работал как переключатель: нажмите на кнопку один раз, чтобы включить ее, и нажмите еще раз, чтобы выключить.

Следующий класс ToggleButton является производным от Button и определяет новое событие Toggled и логическое свойство IsToggled. Это те же два свойства, которые определены в Xamarin.Forms Switch:

 class ToggleButton : Button
{
   public event EventHandler Toggled;
 
   public static BindableProperty IsToggledProperty =
       BindableProperty.Create("IsToggled", typeof(bool), typeof(ToggleButton), false,
                               propertyChanged: OnIsToggledChanged);
 
   public ToggleButton()
   {
       Clicked += (sender, args) => IsToggled ^= true;
   }
 
   public bool IsToggled
   {
       set { SetValue(IsToggledProperty, value); }
       get { return (bool)GetValue(IsToggledProperty); }
   }
 
   protected override void OnParentSet()
   {
       base.OnParentSet();
       VisualStateManager.GoToState(this, "ToggledOff");
   }
 
   static void OnIsToggledChanged(BindableObject bindable, object oldValue, object newValue)
   {
       ToggleButton toggleButton = (ToggleButton)bindable;
       bool isToggled = (bool)newValue;
 
       // Fire event
       toggleButton.Toggled?.Invoke(toggleButton, new ToggledEventArgs(isToggled));
 
       // Set the visual state
       VisualStateManager.GoToState(toggleButton, isToggled ? "ToggledOn" : "ToggledOff");
   }
}

Конструктор ToggleButton присоединяет обработчик к событию Clicked, чтобы можно было изменить значение свойства IsToggled. Метод OnIsToggledChanged вызывает событие Toggled.

Последняя строка метода OnIsToggledChanged вызывает статический метод VisualStateManager.GoToState с двумя текстовыми строками "ToggledOn" и "ToggledOff".

Поскольку ToggleButton выполняет вызов VisualStateManager.GoToState, самому классу не нужно включать какие-либо дополнительные средства для изменения внешнего вида кнопки в зависимости от ее состояния IsToggled. За это отвечает XAML, в котором размещается ToggleButton.

Демонстрационная страница Toggle Button содержит два экземпляра ToggleButton, включая разметку Visual State Manager, которая устанавливает Text, BackgroundColor и TextColor кнопки в зависимости от визуального состояния:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:local="clr-namespace:ButtonDemos"
            x:Class="ButtonDemos.ToggleButtonDemoPage"
            Title="Toggle Button Demo">
 
   <ContentPage.Resources>
       <Style TargetType="local:ToggleButton">
           <Setter Property="VerticalOptions" Value="CenterAndExpand" />
           <Setter Property="HorizontalOptions" Value="Center" />
       </Style>
   </ContentPage.Resources>
 
   <StackLayout Padding="10, 0">
       <local:ToggleButton Toggled="OnItalicButtonToggled">
           <VisualStateManager.VisualStateGroups>
               <VisualStateGroup Name="ToggleStates">
                   <VisualState Name="ToggledOff">
                       <VisualState.Setters>
                           <Setter Property="Text" Value="Italic Off" />
                           <Setter Property="BackgroundColor" Value="#C0C0C0" />
                           <Setter Property="TextColor" Value="Black" />
                       </VisualState.Setters>
                   </VisualState>
 
                   <VisualState Name="ToggledOn">
                       <VisualState.Setters>
                           <Setter Property="Text" Value=" Italic On " />
                           <Setter Property="BackgroundColor" Value="#404040" />
                           <Setter Property="TextColor" Value="White" />
                       </VisualState.Setters>
                   </VisualState>
               </VisualStateGroup>
           </VisualStateManager.VisualStateGroups>
       </local:ToggleButton>
 
       <local:ToggleButton Toggled="OnBoldButtonToggled">
           <VisualStateManager.VisualStateGroups>
               <VisualStateGroup Name="ToggleStates">
                   <VisualState Name="ToggledOff">
                       <VisualState.Setters>
                           <Setter Property="Text" Value="Bold Off" />
                           <Setter Property="BackgroundColor" Value="#C0C0C0" />
                           <Setter Property="TextColor" Value="Black" />
                       </VisualState.Setters>
                   </VisualState>
 
                   <VisualState Name="ToggledOn">
                       <VisualState.Setters>
                           <Setter Property="Text" Value=" Bold On " />
                           <Setter Property="BackgroundColor" Value="#404040" />
                           <Setter Property="TextColor" Value="White" />
                       </VisualState.Setters>
                   </VisualState>
               </VisualStateGroup>
           </VisualStateManager.VisualStateGroups>
       </local:ToggleButton>
 
       <Label x:Name="label"
              Text="Just a little passage of some sample text that can be formatted in italic or boldface by toggling the two buttons."
              FontSize="Large"
              HorizontalTextAlignment="Center"
              VerticalOptions="CenterAndExpand" />
 
   </StackLayout>
</ContentPage>

Обработчики события Toggled находятся в файле кода. Они отвечают за установку свойства FontAttributes в Label в зависимости от состояния кнопок:

public partial class ToggleButtonDemoPage : ContentPage
{
   public ToggleButtonDemoPage ()
   {
       InitializeComponent ();
   }
 
   void OnItalicButtonToggled(object sender, ToggledEventArgs args)
   {
       if (args.Value)
       {
           label.FontAttributes |= FontAttributes.Italic;
       }
       else
       {
           label.FontAttributes &= ~FontAttributes.Italic;
       }
   }
 
   void OnBoldButtonToggled(object sender, ToggledEventArgs args)
   {
       if (args.Value)
       {
           label.FontAttributes |= FontAttributes.Bold;
       }
       else
       {
           label.FontAttributes &= ~FontAttributes.Bold;
       }
   }
}

Пример программы, работающей на iOS, Android и UWP:

Использование растровых изображений в кнопках

Класс Button определяет свойство ImageSource, которое позволяет выводить растровое изображение на кнопке отдельно или в сочетании с текстом. Вы также можете указать, как будут расположены текст и изображение.

Свойство ImageSource имеет тип ImageSource, что означает, что растровые изображения могут быть загружены из файла, встроенного ресурса, URI или потока.

Примечание. Хотя кнопка может загрузить анимированный GIF, она отобразит только первый кадр GIF.

Каждая платформа, поддерживаемая Xamarin.Forms, позволяет хранить изображения нескольких размеров для различных пиксельных разрешений различных устройств, на которых может работать приложение. Эти несколько растровых изображений именуются или хранятся таким образом, чтобы операционная система могла выбрать наилучшее соответствие разрешению видеодисплея устройства.

Для растрового изображения на кнопке самый подходящий размер от 32 до 64 dip, в зависимости от того, насколько большим вы хотите его сделать. Изображения, используемые в этом примере, имеют размер 48 dip.

Для iOS

Папка Resources содержит три размера этого изображения:

  • 48-пиксельная квадратная растровая карта, сохраненная как /Resources/MonkeyFace.png
  • 96-пиксельная квадратная растровая карта, сохраненная как /Resource/MonkeyFace@2x.png.
  • 144-пиксельная квадратная растровая карта, сохраненная как /Resource/MonkeyFace@3x.png.

Всем трем растровым изображениям было присвоено действие Build Action BundleResource.

Для Android

Все растровые изображения имеют одинаковые имена, но хранятся в разных подпапках папки Resources:

  • 72-пиксельная квадратная растровая карта хранится как /Resources/drawable-hdpi/MonkeyFace.png
  • 96-пиксельная квадратная растровая карта, хранящаяся как /Resources/drawable-xhdpi/MonkeyFace.png
  • Квадратная растровая карта размером 144 пикселя, сохраненная как /Resources/drawable-xxhdpi/MonkeyFace.png
  • Квадратная растровая карта размером 192 пикселя, сохраненная как /Resources/drawable-xxxhdpi/MonkeyFace.png

Им было присвоено действие Build Action AndroidResource.

Для UWP

Растровые изображения могут храниться в любом месте проекта, но обычно они находятся в пользовательской папке или в существующей папке Assets. Пример:

  • 48-пиксельная квадратная растровая карта, хранящаяся как /Assets/MonkeyFace.scale-100.png
  • Квадратная растровая карта размером 96 пикселей, сохраненная как /Assets/MonkeyFace.scale-200.png
  • Квадратная растровая карта размером 192 пикселя, сохраненная как /Assets/MonkeyFace.scale-400.png

Всем им было присвоено действие Build Action of Content.

Вы можете задать расположение свойств Text и ImageSource на кнопке с помощью свойства ContentLayout кнопки Button. Это свойство имеет тип ButtonContentLayout, который является встроенным классом в Button. Конструктор имеет два аргумента:

  • Элемент перечисления ImagePosition: Left, Top, Right или Bottom, указывающий, как растровое изображение отображается относительно текста.
  • Двойное значение расстояния между растровым изображением и текстом.

По умолчанию используются значения Left и 10 единиц. Два неизменяющихся свойства ButtonContentLayout, Position и Spacing, содержат значения этих свойств.

В коде вы можете создать кнопку и установить свойство ContentLayout следующим образом:

Button button = new Button
{
   Text = "button text",
   ImageSource = new FileImageSource
   {
       File = "image filename"
   },
   ContentLayout = new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 20)
};
 

В XAML нужно указать только элемент перечисления, или интервал, или оба в любом порядке, разделенные запятыми:

<Button Text="button text"
       ImageSource="image filename"
       ContentLayout="Right, 20" />

Демонстрационная страница Image Button использует OnPlatform для указания разных имен файлов растровых изображений для iOS, Android и UWP. Если вы хотите использовать одно и то же имя файла для каждой платформы и избежать использования OnPlatform, вам нужно будет хранить растровые изображения UWP в корневом каталоге проекта.

Первая кнопка на демонстрационной странице Image Button устанавливает свойство Image, но не свойство Text:

<Button>
   <Button.ImageSource>
       <OnPlatform x:TypeArguments="ImageSource">
           <On Platform="iOS, Android" Value="MonkeyFace.png" />
           <On Platform="UWP" Value="Assets/MonkeyFace.png" />
       </OnPlatform>
   </Button.ImageSource>
</Button>

Если растровые изображения UWP хранятся в корневом каталоге проекта, эту разметку можно значительно упростить:

<Button ImageSource="MonkeyFace.png" />

Чтобы избежать большого количества повторяющейся разметки в файле ImageButtonDemo.xaml, также определен неявный стиль для установки свойства ImageSource. Этот стиль автоматически применяется к пяти другим элементам кнопки. Вот полный файл XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            x:Class="ButtonDemos.ImageButtonDemoPage">
 
   <FlexLayout Direction="Column"
               JustifyContent="SpaceEvenly"
               AlignItems="Center">
 
       <FlexLayout.Resources>
           <Style TargetType="Button">
               <Setter Property="ImageSource">
                   <OnPlatform x:TypeArguments="ImageSource">
                       <On Platform="iOS, Android" Value="MonkeyFace.png" />
                       <On Platform="UWP" Value="Assets/MonkeyFace.png" />
                   </OnPlatform>
               </Setter>
           </Style>
       </FlexLayout.Resources>
 
       <Button>
           <Button.ImageSource>
               <OnPlatform x:TypeArguments="ImageSource">
                   <On Platform="iOS, Android" Value="MonkeyFace.png" />
                   <On Platform="UWP" Value="Assets/MonkeyFace.png" />
               </OnPlatform>
           </Button.ImageSource>
       </Button>
 
       <Button Text="Default" />
 
       <Button Text="Left - 10"
               ContentLayout="Left, 10" />
 
       <Button Text="Top - 10"
               ContentLayout="Top, 10" />
 
       <Button Text="Right - 20"
               ContentLayout="Right, 20" />
 
       <Button Text="Bottom - 20"
               ContentLayout="Bottom, 20" />
   </FlexLayout>
</ContentPage>

Последние четыре элемента Button используют свойство ContentLayout для указания положения и расстояния между текстом и растровым изображением:

Материалы по теме