Skip to content

Instantly share code, notes, and snippets.

@rudyryk
Last active November 9, 2023 08:57
Show Gist options
  • Save rudyryk/8cbe067a1363b45351f6 to your computer and use it in GitHub Desktop.
Save rudyryk/8cbe067a1363b45351f6 to your computer and use it in GitHub Desktop.
C# — Xamarin.Forms custom simple badge view + rounded box view via custom renderer
//
// Badge.cs
// Created by Alexey Kinev on 19 Jan 2015.
//
// Licensed under The MIT License (MIT)
// http://opensource.org/licenses/MIT
//
// Copyright (c) 2015 Alexey Kinev <alexey.rudy@gmail.com>
//
using System;
using Xamarin.Forms;
namespace ZeroFiveBit.Forms.Basic
{
/// <summary>
/// Badge.
/// </summary>
public class Badge : AbsoluteLayout
{
/// <summary>
/// The text property.
/// </summary>
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(String), typeof(Badge), "");
/// <summary>
/// The box color property.
/// </summary>
public static readonly BindableProperty BoxColorProperty =
BindableProperty.Create("BoxColor", typeof(Color), typeof(Badge), Color.Default);
/// <summary>
/// The text.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Gets or sets the color of the box.
/// </summary>
public Color BoxColor
{
get { return (Color)GetValue(BoxColorProperty); }
set { SetValue(BoxColorProperty, value); }
}
/// <summary>
/// The box.
/// </summary>
protected RoundedBox Box;
/// <summary>
/// The label.
/// </summary>
protected Label Label;
/// <summary>
/// Initializes a new instance of the <see cref="ZeroFiveBit.Forms.Basic.Badge"/> class.
/// </summary>
public Badge(double size, double fontSize)
{
HeightRequest = size;
WidthRequest = HeightRequest;
// Box
Box = new RoundedBox {
CornerRadius = HeightRequest / 2
};
Box.SetBinding(BackgroundColorProperty, new Binding("BoxColor", source: this));
Children.Add(Box, new Rectangle(0, 0, 1.0, 1.0), AbsoluteLayoutFlags.All);
// Label
Label = new Label {
TextColor = Color.White,
FontSize = fontSize,
XAlign = TextAlignment.Center,
YAlign = TextAlignment.Center
};
Label.SetBinding(Label.TextProperty, new Binding("Text",
BindingMode.OneWay, source: this));
Children.Add(Label, new Rectangle(0, 0, 1.0, 1.0), AbsoluteLayoutFlags.All);
// Auto-width
SetBinding(WidthRequestProperty, new Binding("Text", BindingMode.OneWay,
new BadgeWidthConverter(WidthRequest), source: this));
// Hide if no value
SetBinding(IsVisibleProperty, new Binding("Text", BindingMode.OneWay,
new BadgeVisibleValueConverter(), source: this));
// Default color
BoxColor = Color.Red;
}
}
/// <summary>
/// Badge visible value converter.
/// </summary>
class BadgeVisibleValueConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var text = value as string;
return !String.IsNullOrEmpty(text);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
/// <summary>
/// Badge width converter.
/// </summary>
class BadgeWidthConverter : IValueConverter
{
/// <summary>
/// The width of the base.
/// </summary>
readonly double baseWidth;
/// <summary>
/// The width ratio.
/// </summary>
const double widthRatio = 0.33;
public BadgeWidthConverter(double baseWidth)
{
this.baseWidth = baseWidth;
}
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var text = value as string;
if ((text != null) && (text.Length > 1))
{
// We won't measure text length exactly here!
// May be we should, but it's too tricky. So,
// we'll just approximate new badge width as
// linear function from text legth.
return baseWidth * (1 + widthRatio * (text.Length - 1));
}
return baseWidth;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
//
// RoundedBox.cs
// Created by Alexey Kinev on 19 Jan 2015.
//
// Licensed under The MIT License (MIT)
// http://opensource.org/licenses/MIT
//
// Copyright (c) 2015 Alexey Kinev <alexey.rudy@gmail.com>
//
using System;
using Xamarin.Forms;
namespace ZeroFiveBit.Forms.Basic
{
public class RoundedBox : BoxView
{
/// <summary>
/// The corner radius property.
/// </summary>
public static readonly BindableProperty CornerRadiusProperty =
BindableProperty.Create("CornerRadius", typeof(double), typeof(RoundedBox), 0.0);
/// <summary>
/// Gets or sets the corner radius.
/// </summary>
public double CornerRadius
{
get { return (double)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
}
}
//
// RoundedBoxRenderer_Droid.cs
// Created by Alexey Kinev on 26 Apr 2015.
//
// Licensed under The MIT License (MIT)
// http://opensource.org/licenses/MIT
//
// Copyright (c) 2015 Alexey Kinev <alexey.rudy@gmail.com>
//
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using ZeroFiveBit.Forms.Basic;
using ZeroFiveBit.Forms.Basic.Droid;
[assembly: ExportRenderer(typeof(RoundedBox), typeof(RoundedBoxRenderer))]
namespace ZeroFiveBit.Forms.Basic.Droid
{
public class RoundedBoxRenderer : BoxRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
SetWillNotDraw(false);
Invalidate();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == RoundedBox.CornerRadiusProperty.PropertyName)
{
Invalidate();
}
}
public override void Draw(Canvas canvas)
{
var box = Element as RoundedBox;
var rect = new Rect();
var paint = new Paint() {
Color = box.BackgroundColor.ToAndroid(),
AntiAlias = true,
};
GetDrawingRect(rect);
var radius = (float)(rect.Width() / box.Width * box.CornerRadius);
canvas.DrawRoundRect(new RectF(rect), radius, radius, paint);
}
}
}
//
// RoundedBoxRenderer_iOS.cs
// Created by Alexey Kinev on 19 Jan 2015.
//
// Licensed under The MIT License (MIT)
// http://opensource.org/licenses/MIT
//
// Copyright (c) 2015 Alexey Kinev <alexey.rudy@gmail.com>
//
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using ZeroFiveBit.Forms.Basic;
using ZeroFiveBit.Forms.Basic.iOS;
[assembly: ExportRenderer(typeof(RoundedBox), typeof(RoundedBoxRenderer))]
namespace ZeroFiveBit.Forms.Basic.iOS
{
public class RoundedBoxRenderer : BoxRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
if (Element != null)
{
Layer.MasksToBounds = true;
UpdateCornerRadius(Element as RoundedBox);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == RoundedBox.CornerRadiusProperty.PropertyName)
{
UpdateCornerRadius(Element as RoundedBox);
}
}
void UpdateCornerRadius(RoundedBox box)
{
Layer.CornerRadius = (float)box.CornerRadius;
}
}
}
@rraallvv
Copy link

rraallvv commented Feb 6, 2018

@sportzbee I added the badge to the ToolbarItem for iOS using a custom renderer as explained in this blog post.

By the way, if anyone has been able to do the same for Android, I'm interested, I've googled for a solution without any luck.

@pjavax
Copy link

pjavax commented Feb 23, 2018

How can i use it? I have grid and inside it i have 7 image(Grid.Row="0" Grid.Column="0" and so on) how can i do to show badge each cell? The values that will be shown comes from a method like this(the value to be show is resultado):

decimal realizado = 0;
                        decimal previsto = 0;

                        foreach (var item in indItem)
                        {
                            var val = item.Nome == "Previsto" ? previsto = item.Valor : realizado = item.Valor;
                        }

                        decimal desvio = Math.Abs(previsto - realizado);

                        if (desvio < 1000)
                            resultado = Math.Truncate(desvio).ToString();
                        else
                            while (desvio >= 1000 && siglas.Count > 0)
                            {
                                desvio /= 1000;
                                resultado = Math.Truncate(desvio) + siglas[0];
                                siglas.RemoveAt(0);
                            }

                        if (CorIndicador == "VERDE")
                            image.Source = ImageSource.FromResource("Estapar.AppOperacional.Images.faturamento caixa-28.png");
                        else
                        {
                            var pinta = CorIndicador == "VERMELHO" ? image.Source = ImageSource.FromResource("Estapar.AppOperacional.Images.faturamento caixa-26.png") : image.Source = ImageSource.FromResource("Estapar.AppOperacional.Images.faturamento caixa-27.png");
                        }
                        label.Text = resultado;


and my xaml is like this


<ContentPage.Content>
        <StackLayout Padding="0" Spacing="0" Margin="0">
            <Grid x:Name="grd" RowSpacing="1" ColumnSpacing="1" Padding="0" Margin="0" >
            <Grid.RowDefinitions>
                <RowDefinition Height="1" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
                <Image x:Name="imgDesvioFat" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Source="{local:ImageResource Operacional.Images.faturamento caixa-28.png}" Aspect="AspectFill">
                        <Image.GestureRecognizers>
                            <TapGestureRecognizer Tapped="OnDesvioFaturamentoTapReconizerTapped" NumberOfTapsRequired="1"></TapGestureRecognizer>
                        </Image.GestureRecognizers>
                    </Image>
                    
                    <!--<Label x:Name="lblFaturamento" Text="" Grid.Row="0" Grid.Column="0" TextColor="White" FontSize="9" FontAttributes="Bold" />-->    
                  
                <!--</Grid>-->
                <Image x:Name="imgTckCancelados" Grid.Row="1" Grid.Column="1" Source="{local:ImageResource Operacinal.Images.tickets cancelados-05.png}" Aspect="AspectFill">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer Tapped="OnTckCanceladosTapGestureReconizerTapped" NumberOfTapsRequired="1"></TapGestureRecognizer>
                    </Image.GestureRecognizers>
                </Image>

i shown just two images, but i have 7 images and the code is such. How can i show the badge and the value inside with my code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment