Xamarin.Forms에서 개요 항목 만들기

아시다시피 Xamarin.Forms에서 항목은 Google의 머티리얼 디자인으로 스타일이 지정되며 일반 iOS 컨트롤은 iPhone에서 사용됩니다. 코딩 중인 프로젝트에서 Material Design의 outlined style(테두리 및 부동 레이블)이 있는 항목을 만들고 싶었지만 현재 이 스타일로 항목을 렌더링하는 기본 방법이 없습니다.



이번에는 이 동작을 시뮬레이션하기 위해 사용자 지정 컨트롤을 만들 것입니다.

커스텀 렌더러 만들기



머티리얼 디자인에 따라 윤곽이 잡힌 항목을 생성하려면 항목을 사용자 지정하고 두 플랫폼 모두에서 테두리를 제거해야 하므로 항목 컨트롤을 기반으로 하는 사용자 지정 렌더러를 사용할 것입니다. 컨트롤의 이름을 BorderlessEntry로 지정하겠습니다.

namespace XamarinSamples.Views.Controls
{
    public class BorderlessEntry : Entry
    {

    }
}

그런 다음 각 플랫폼에 대한 렌더러를 만들 것입니다.

[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace XamarinSamples.Droid.UI.Renderers
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        public BorderlessEntryRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            //Configure native control (TextBox)
            if(Control != null)
            {
                Control.Background = null;
            }

            // Configure Entry properties
            if(e.NewElement != null)
            {

            }
        }
    }
}



[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace XamarinSamples.iOS.UI.Renderers
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            //Configure Native control (UITextField)
            if (Control != null)
            {
                Control.Layer.BorderWidth = 0;
                Control.BorderStyle = UIKit.UITextBorderStyle.None;
            }
        }
    }
}

Borderless Entry로 사용자 정의 제어 추가



테두리가 없는 항목이 있으면 이 항목을 호스트할 사용자 지정 컨트롤을 만들 차례입니다. 컨트롤을 쉽게 사용할 수 있도록 일부 바인딩 가능한 속성을 추가하여 XAML에서 직접 컨트롤을 구성할 수 있습니다. 이러한 속성은 Text , Placeholder , PlaceholderColorBorderColor 입니다.

namespace XamarinSamples.Views.Controls
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class EntryOutlined : ContentPage
    {
        public EntryOutlined()
        {
            InitializeComponent();
            this.TextBox.PlaceholderColor = PlaceholderColor;
        }

        public static readonly BindableProperty TextProperty =
            BindableProperty.Create(nameof(Text), typeof(string), typeof(EntryOutlined), null);

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly BindableProperty PlaceholderProperty =
            BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(EntryOutlined), null);

        public string Placeholder
        {
            get { return (string)GetValue(PlaceholderProperty); }
            set { SetValue(PlaceholderProperty, value); }
        }

        public static readonly BindableProperty PlaceholderColorProperty =
            BindableProperty.Create(nameof(PlaceholderColor), typeof(Color), typeof(EntryOutlined), Color.Blue);

        public Color PlaceholderColor
        {
            get { return (Color)GetValue(PlaceholderColorProperty); }
            set { SetValue(PlaceholderColorProperty, value); }
        }

        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(EntryOutlined), Color.Blue);

        public Color BorderColor
        {
            get { return (Color)GetValue(BorderColorProperty); }
            set { SetValue(BorderColorProperty, value); }
        }

    }
}



<?xml version="1.0" encoding="utf-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinSamples.Views.Controls.EntryOutlined"
             xmlns:controls="clr-namespace:XamarinSamples.Views.Controls;assembly=XamarinSamples"
             Margin="{OnPlatform Android='3,0,3,5', iOS='3,0,3,5'}"
             x:Name="this">
    <ContentView.Content>
        <StackLayout>
            <Grid>
                <Frame HasShadow="False"
                       x:Name="EntryFrame"
                       BorderColor="{Binding BorderColor, Source={x:Reference this}}"
                       CornerRadius="{OnPlatform Android=3}"
                       Padding="{OnPlatform Android='5,0,5,0', iOS='8,0,8,0'}"
                       Margin="{OnPlatform Android='0,0,0,0', iOS='0,0,0,0'}" />

                <Label x:Name="PlaceHolderLabel"
                       BackgroundColor="White" HorizontalOptions="Start"
                       TextColor="{Binding PlaceholderColor, Source={Reference this}}"
                       Text="{Binding Placeholder,Source={x:Reference this}}"
                       Margin="10,0,0,0"
                       VerticalOptions="Center" />

                <controls:BorderlessEntry
                    HeightRequest="{OnPlatform iOS=40}"
                    x:Name="TextBox" VerticalOptions="FillAndExpand"
                    Text="{Binding Text,Source={x:Reference this},Mode=TwoWay}"
                    Margin="10,0,0,0"
                    />
            </Grid>
        </StackLayout>
    </ContentView.Content>
</ContentView>

이제 우리는 컨트롤을 간단한 페이지에 추가할 것이고 그것은 다음과 같아야 합니다:




자리 표시자를 항목 안팎으로 변환



이제 컨트롤이 준비되었으므로 Entry에 포커스가 있을 때 자리 표시자를 테두리의 맨 위로 이동하고 포커스를 잃고 텍스트가 비어 있을 때 다시 Entry로 이동해야 합니다. 따라서 FocusedUnfocused 이벤트에 대한 처리기를 추가할 것입니다.

async void TextBox_Focused(object sender, FocusEventArgs e)
{
    await TranslateLabelToTitle();
}

async void TextBox_Unfocused(object sender, FocusEventArgs e)
{
    await TranslateLabelToPlaceHolder();
}

async Task TranslateLabelToTitle()
{
    if (string.IsNullOrEmpty(this.Text))
    {
        var placeHolder = this.PlaceHolderLabel;
        var distance = GetPlaceholderDistance(placeHolder);
        await placeHolder.TranslateTo(0, -distance);
    }

}

async Task TranslateLabelToPlaceHolder()
{
    if(string.IsNullOrEmpty(this.Text))
    {
        await this.PlaceHolderLabel.TranslateTo(0, 0);
    }
}

double GetPlaceholderDistance(Label control)
{
    // In Android we need to move the label slightly up so it's centered in the border frame.
    var distance = 0d;
    if(Device.RuntimePlatform == Device.iOS) distance = 0;
    else distance = 5;

    distance = control.Height + distance;
    return distance;
}

이제 이러한 이벤트를 컨트롤에 추가하고 런타임에서 컨트롤을 다시 테스트해 보겠습니다.

<controls:BorderlessEntry
    HeightRequest="{OnPlatform iOS=40}"
    x:Name="TextBox" VerticalOptions="FillAndExpand"
    Text="{Binding Text,Source={x:Reference this},Mode=TwoWay}"
    Margin="10,0,0,0"
    Focused="TextBox_Focused"
    Unfocused="TextBox_Unfocused"
    />

이러한 변경으로 컨트롤은 다음과 같이 작동해야 합니다.




소비자를 위한 텍스트 변경 이벤트 추가



마지막으로 텍스트가 변경될 때 알림을 받으려면 EntryOutlined에 이벤트를 추가하고 내부 항목에 연결합니다.

public event EventHandler<TextChangedEventArgs> TextChanged;

public virtual void OnTextChanged(System.Object sender, Xamarin.Forms.TextChangedEventArgs e)
{
    TextChanged?.Invoke(this, e);
}



<controls:BorderlessEntry
    HeightRequest="{OnPlatform iOS=40}"
    x:Name="TextBox" VerticalOptions="FillAndExpand"
    Text="{Binding Text,Source={x:Reference this},Mode=TwoWay}"
    Margin="10,0,0,0"
    Focused="TextBox_Focused"
    Unfocused="TextBox_Unfocused"
    TextChanged="OnTextChanged" />

마지막으로 페이지의 컨트롤을 다음과 같이 사용할 수 있습니다.

<controls:EntryOutlined
            Placeholder="I'm an entry"
            BorderColor="Blue"
            PlaceholderColor="Red"
            TextChanged="EntryOutlined_OnTextChanged" />

저장소



어떤 사람들은 저에게 저장소를 제공해달라고 요청했습니다. 다음은 게시물의 코드가 포함된 기본 저장소입니다.


jpozo20 / xamarin 샘플


dev.to/jefrypozo에 게시된 게시물에 대한 Xamarin 코드





xamarin 샘플


dev.to/jefrypozo에 게시된 게시물에 대한 Xamarin 코드


View on GitHub


결론



이번에는 Google의 머티리얼 디자인을 기반으로 개요 항목을 만드는 간단하고 쉬운 방법을 보았습니다. 고맙게도 Xamarin.Forms의 사람들은 매우 훌륭한 작업을 수행했으며 원하는 결과를 얻기 위해 몇 가지 간단한 작업만 수행하면 됩니다. 다음 항목에서는 유효성 검사를 위해 항목 아래에 레이블을 추가하는 방법을 보여 드리겠습니다.

계속 지켜봐!

좋은 웹페이지 즐겨찾기