如何在.Net Maui中创建信用卡视图
#dotnet #csharp #ios #maui

在本文中,我想展示如何创建信用卡两面的观点。与某些银行应用程序类似,我们可能看到带有隐藏CVC的电子信用卡。我们还将开发相似的卡并按类型实现数字卡验证。让我们开始。

要开始,您需要创建一个.NET MAUI项目:

dotnet new maui -n CreditCardSample

遵循步骤,您需要下载标志性字体,以添加信用卡徽标。您可以通过link找到的字体。为桌面选择字体。您需要获取FontAwesome6-Brands.otfFontAwesome6-Regular.otf并将它们复制到Resources>Fonts文件夹。下一步,您应该注册字体。转到MainProgram.cs并添加此代码:

fonts.AddFont("FontAwesome6-Brands.otf", "FA6Brands");
fonts.AddFont("FontAwesome6-Regular.otf", "FA6Regular");

,为方便起见,让我们设置我们将要使用的颜色。在资源文件夹中,您可以找到样式文件夹,也可以找到Colors.xaml

<Color x:Key="AmericanExpress">#3177CB</Color>
<Color x:Key="DinersClub">#1B4F8F</Color>
<Color x:Key="Discover">#E9752F</Color>
<Color x:Key="JCB">#9E2911</Color>
<Color x:Key="MasterCard">#394854</Color>
<Color x:Key="Visa">#2867BA</Color>
<Color x:Key="Default">#75849D</Color>

<Color x:Key="HeaderLabelTextColor">#BCBCBC</Color>
<Color x:Key="InfoLabelTextColor">#FFFFFF</Color>

现在,让我们创建几个助手并创建一个助手文件夹。第一个助手将通过付费系统检查卡号。

internal static class CardValidationHelper
{
    public static Regex AmericanExpress
        = new(@"^3[47][0-9]{13}$");

    public static Regex DinersClub
        = new(@"^3(?:0[0-5]|[68][0-9])[0-9]{11}$");

    public static Regex Discover
        = new(@"^6(?:011|5[0-9]{2})[0-9]{12}$");

    public static Regex JCB
       = new(@"^(?:2131|1800|35\d{3})\d{11}$");

    public static Regex MasterCard
        = new(@"^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$");

    public static Regex Visa
        = new(@"^4[0-9]{12}(?:[0-9]{3})?$");
}

第二个助手允许通过资源名称设置颜色。

internal static class StringExtensions
{
    public static Color ToColorFromResourceKey(this string resourceKey)
    {
        return Application.Current?.Resources
            .MergedDictionaries.First()[resourceKey] as Color;
    }
}

最后,让我们创建一个视图文件夹并创建CreditCardView。现在添加此标记,我将解释此代码。

<?xml version="1.0" encoding="utf-8"?>

<Frame xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       x:Class="CreditCardApp.Views.CreditCardView"
       HasShadow="False"
       BorderColor="White"
       CornerRadius="0">
    <Grid>
        <Frame x:Name="CardFront"
               Grid.Row="0"
               Grid.Column="0"
               HasShadow="True"
               BorderColor="Grey"
               HorizontalOptions="Center"
               VerticalOptions="Start"
               WidthRequest="300"
               HeightRequest="200"
               CornerRadius="8"
               Margin="40,10,40,30"
               Padding="20">
            <Frame.Resources>
                <Style TargetType="Label"
                       x:Key="HeaderLabelStyle">
                    <Setter Property="LineBreakMode"
                            Value="TailTruncation" />
                    <Setter Property="FontSize"
                            Value="12" />
                    <Setter Property="TextColor"
                            Value="{StaticResource HeaderLabelTextColor}" />
                </Style>

                <Style TargetType="Label"
                       x:Key="InfoLabelStyle">
                    <Setter Property="FontSize"
                            Value="20" />
                    <Setter Property="TextColor"
                            Value="{StaticResource InfoLabelTextColor}" />
                </Style>

                <Style TargetType="Label"
                       x:Key="CreditCardImageLabelStyle">
                    <Setter Property="FontSize"
                            Value="48" />
                    <Setter Property="TextColor"
                            Value="#FFFFFF" />
                    <Setter Property="HorizontalOptions"
                            Value="EndAndExpand" />
                </Style>
            </Frame.Resources>
            <Grid ColumnDefinitions="*,*"
                  ColumnSpacing="30"
                  RowDefinitions="Auto,Auto,40,Auto,40"
                  RowSpacing="0">
                <Label x:Name="CreditCardImageLabel"
                       Style="{StaticResource CreditCardImageLabelStyle}"
                       Grid.Row="0"
                       Grid.Column="1" />

                <Label Text="Card Number"
                       Style="{StaticResource HeaderLabelStyle}"
                       Grid.Row="1"
                       Grid.Column="0"
                       Grid.ColumnSpan="2" />

                <Label x:Name="CreditCardNumber"
                       Style="{StaticResource InfoLabelStyle}"
                       Grid.Row="2"
                       Grid.Column="0"
                       Grid.ColumnSpan="2" />

                <Label Text="Expiration"
                       Style="{StaticResource HeaderLabelStyle}"
                       Grid.Row="3"
                       Grid.Column="0" />

                <Label x:Name="ExpirationDateLabel"
                       Style="{StaticResource InfoLabelStyle}"
                       Grid.Row="4"
                       Grid.Column="0" />
            </Grid>
        </Frame>

        <Frame x:Name="CardBack"
               Grid.Row="0"
               HasShadow="True"
               BorderColor="Grey"
               HorizontalOptions="Center"
               VerticalOptions="Start"
               WidthRequest="300"
               HeightRequest="200"
               CornerRadius="8"
               Margin="40,10,40,30">
            <Frame.Resources>
                <Style TargetType="Label"
                       x:Key="InfoLabelStyle">
                    <Setter Property="FontSize"
                            Value="20" />
                    <Setter Property="TextColor"
                            Value="{StaticResource InfoLabelTextColor}" />
                </Style>
            </Frame.Resources>
            <StackLayout>
                <Grid VerticalOptions="EndAndExpand">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <BoxView BackgroundColor="Grey"
                             Grid.Column="1"
                             WidthRequest="100"
                             HeightRequest="40"
                             HorizontalOptions="End"
                             VerticalOptions="End" />
                    <Label x:Name="CardValidationCodeLabel"
                           Style="{StaticResource InfoLabelStyle}"
                           Grid.Column="1"
                           HorizontalOptions="Center"
                           VerticalOptions="Center" />
                </Grid>
            </StackLayout>
        </Frame>
        <Frame x:Name="Streak" Grid.Row="0" BackgroundColor="#5d3623" WidthRequest="300"
               HeightRequest="60" CornerRadius="0" HasShadow="False" VerticalOptions="Start" Margin="40">
        </Frame>
    </Grid>
</Frame>

因为我们想显示卡的两面,所以我们需要使用不同的帧。每个帧都有自己的状态,不依赖于另一帧。此外,它允许在一个方面实现不同的视图。我将视图分为三个部分:前侧,背面和装饰框架。

public partial class CreditCardView
{
    public static readonly BindableProperty CardNumberProperty
        = BindableProperty.Create(nameof(CardNumber), 
            typeof(string), typeof(CreditCardView), 
            propertyChanged: OnCardNumberChanged);

    public string CardNumber
    {
        get => (string)GetValue(CardNumberProperty);
        set => SetValue(CardNumberProperty, value);
    }

    public static readonly BindableProperty ExpirationDateProperty
        = BindableProperty.Create(nameof(ExpirationDate), 
            typeof(DateTime), typeof(CreditCardView), DateTime.Now, 
            propertyChanged: OnExpirationDateChanged);

    public DateTime ExpirationDate
    {
        get => (DateTime)GetValue(ExpirationDateProperty);
        set => SetValue(ExpirationDateProperty, value);
    }

    public static readonly BindableProperty CardValidationCodeProperty
    = BindableProperty.Create(nameof(CardValidationCode), 
        typeof(string), typeof(CreditCardView), "-", 
        propertyChanged: OnCardValidationCodeChanged);

    public string CardValidationCode
    {
        get => (string)GetValue(CardValidationCodeProperty);
        set => SetValue(CardValidationCodeProperty, value);
    }

    private static void OnCardNumberChanged(BindableObject bindable, 
        object oldValue, object newValue)
    {
        if (bindable is not CreditCardView creditCardView)
            return;

        creditCardView.SetCreditCardNumber();
    }

    private static void OnExpirationDateChanged(BindableObject bindable, 
        object oldValue, object newValue)
    {
        if (bindable is not CreditCardView creditCardView)
            return;

        creditCardView.SetExpirationDate();
    }

    private static void OnCardValidationCodeChanged(BindableObject bindable, 
        object oldValue, object newValue)
    {
        if (bindable is not CreditCardView creditCardView)
            return;

        creditCardView.SetCardValidationCode();
    }

    public CreditCardView()
    {
        InitializeComponent();

        CardFront.BackgroundColor = "Default".ToColorFromResourceKey();
        CardBack.BackgroundColor = "Default".ToColorFromResourceKey();

        CreditCardImageLabel.Text = "\uf09d";
        CreditCardImageLabel.FontFamily = "FA6Regular";

        ExpirationDateLabel.Text = "-";

        CardValidationCodeLabel.Text = $"CVC: -";
        CardBack.IsVisible = false;
        CardFront.IsVisible = true;
        Streak.IsVisible = false;

        var tapGestureRecognizer = new TapGestureRecognizer();
        tapGestureRecognizer.Tapped += OnCardTapped;
        this.GestureRecognizers.Add(tapGestureRecognizer);

    }

    private void SetCreditCardNumber()
    {
        if (string.IsNullOrEmpty(CardNumber))
        {
            CardFront.BackgroundColor = (Color)Application.Current?.Resources["Default"];
            CardBack.BackgroundColor = (Color)Application.Current?.Resources["Default"];
            CreditCardImageLabel.Text = "\uf09d";
            CreditCardImageLabel.FontFamily = "FA6Regular";
        }

        if (long.TryParse(CardNumber, out long cardNumberAsLong))
        {
            CreditCardNumber.Text = 
                string.Format("{0:0000  0000  0000  0000}", cardNumberAsLong);
        }
        else
        {
            CreditCardNumber.Text = "-";
        }

        if (CardNumber != null)
        {
            var normalizedCardNumber = CardNumber.Replace("-", string.Empty);

            if (CardValidationHelper.AmericanExpress.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "AmericanExpress".ToColorFromResourceKey();
                CardBack.BackgroundColor = "AmericanExpress".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf1f3";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else if (CardValidationHelper.DinersClub.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "DinersClub".ToColorFromResourceKey();
                CardBack.BackgroundColor = "DinersClub".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf24c";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else if (CardValidationHelper.Discover.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "Discover".ToColorFromResourceKey();
                CardBack.BackgroundColor = "Discover".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf1f2";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else if (CardValidationHelper.JCB.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "JCB".ToColorFromResourceKey();
                CardBack.BackgroundColor = "JCB".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf24b";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else if (CardValidationHelper.MasterCard.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "MasterCard".ToColorFromResourceKey();
                CardBack.BackgroundColor = "MasterCard".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf1f1";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else if (CardValidationHelper.Visa.IsMatch(normalizedCardNumber))
            {
                CardFront.BackgroundColor = "Visa".ToColorFromResourceKey();
                CardBack.BackgroundColor = "Visa".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf1f0";
                CreditCardImageLabel.FontFamily = "FA6Brands";
            }
            else
            {
                CardFront.BackgroundColor = "Default".ToColorFromResourceKey();
                CardBack.BackgroundColor = "Default".ToColorFromResourceKey();
                CreditCardImageLabel.Text = "\uf09d";
                CreditCardImageLabel.FontFamily = "FA6Regular";
            }
        }
    }

    private void SetExpirationDate()
    {
        ExpirationDateLabel.Text = ExpirationDate.ToString("MM/yy", 
            CultureInfo.InvariantCulture);
    }

    private void SetCardValidationCode()
    {
        CardValidationCodeLabel.Text = $"CVC: {CardValidationCode}";
    }


    public static readonly BindableProperty IsFlippedProperty =
        BindableProperty.Create(nameof(IsFlipped), typeof(bool), typeof(CreditCardView), false);

    public bool IsFlipped
    {
        get => (bool)GetValue(IsFlippedProperty);
        set
        {
            SetValue(IsFlippedProperty, value);
            UpdateCardSideVisibility();
        }
    }

    private void UpdateCardSideVisibility()
    {
        if (IsFlipped)
        {
            CardFront.IsVisible = false;
            CardBack.IsVisible = true;
            Streak.IsVisible = true;
        }
        else
        {
            CardFront.IsVisible = true;
            CardBack.IsVisible = false;
            Streak.IsVisible = false;
        }
    }

    private void OnCardTapped(object sender, EventArgs e)
    {
        IsFlipped = !IsFlipped;
    }
}

让我们解释一下此文件中发生的情况。 CardNumberProperty绑定了一个卡号。 ExpirationDateProperty绑定了到期日期。 CardValidationCodeProperty在后侧绑定了安全码。 IsFlippedProperty绑定卡状态。 SetCreditCardNumber为每张卡设置样式。 SetExpirationDate格式到期日期。 SetCardValidationCode将标题加入安全码,更方便。 OnCardNumberChangedOnExpirationDateChangedOnCardValidationCodeChangedOnCardTapped可观察的变化。 UpdateCardSideVisibility开关帧。

最后一步,让我们使用传递数据添加信用卡视图。

<VerticalStackLayout>
            <views:CreditCardView CardNumber="371449635398431"                                  
                                  ExpirationDate="2024-12-01"
                                  CardValidationCode="123"/>
            <views:CreditCardView CardNumber="38520000023237"
                                  ExpirationDate="2025-12-01"
                                  CardValidationCode="456"/>
            <views:CreditCardView CardNumber="6011000990139424"
                                  ExpirationDate="2026-12-01"
                                  CardValidationCode="789"/>
            <views:CreditCardView CardNumber="3566002020360505"
                                  ExpirationDate="2027-12-01"
                                  CardValidationCode="321"/>
            <views:CreditCardView CardNumber="5555555555554444"
                                  ExpirationDate="2028-12-01"
                                  CardValidationCode="654"/>
            <views:CreditCardView CardNumber="4012888888881881"
                                  ExpirationDate="2028-12-01"
                                  CardValidationCode="987"/>
            <views:CreditCardView />
        </VerticalStackLayout>

让我们检查一下。

front

按卡片点击。

back

您甚至可以用另一张卡来点击。

back 2

如果这个主题很有趣,我将在下一篇文章中展示如何在.net Maui中制作动画。现在就这样。在下一篇文章中与您见面!

您可以通过link

找到的源代码

Buy Me A Beer