DatePicker

This commit is contained in:
2026-01-16 05:14:04 +00:00
parent 20fcbd78e3
commit 870382097b
2 changed files with 104 additions and 58 deletions

View File

@@ -54,11 +54,11 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
var current = Application.Current; var current = Application.Current;
if (current != null && (int)current.UserAppTheme == 2) // Dark theme if (current != null && (int)current.UserAppTheme == 2) // Dark theme
{ {
platformView.CalendarBackgroundColor = new SKColor(30, 30, 30); platformView.CalendarBackgroundColor = Color.FromRgb(30, 30, 30);
platformView.TextColor = new SKColor(224, 224, 224); platformView.TextColor = Color.FromRgb(224, 224, 224);
platformView.BorderColor = new SKColor(97, 97, 97); platformView.BorderColor = Color.FromRgb(97, 97, 97);
platformView.DisabledDayColor = new SKColor(97, 97, 97); platformView.DisabledDayColor = Color.FromRgb(97, 97, 97);
platformView.BackgroundColor = new SKColor(45, 45, 45); platformView.BackgroundColor = Color.FromRgb(45, 45, 45).ToSKColor();
} }
} }
@@ -68,11 +68,11 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
base.DisconnectHandler(platformView); base.DisconnectHandler(platformView);
} }
private void OnDateSelected(object? sender, EventArgs e) private void OnDateSelected(object? sender, DateChangedEventArgs e)
{ {
if (VirtualView is null || PlatformView is null) return; if (VirtualView is null || PlatformView is null) return;
VirtualView.Date = PlatformView.Date; VirtualView.Date = e.NewDate;
} }
public static void MapDate(DatePickerHandler handler, IDatePicker datePicker) public static void MapDate(DatePickerHandler handler, IDatePicker datePicker)
@@ -104,7 +104,7 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
if (handler.PlatformView is null) return; if (handler.PlatformView is null) return;
if (datePicker.TextColor is not null) if (datePicker.TextColor is not null)
{ {
handler.PlatformView.TextColor = datePicker.TextColor.ToSKColor(); handler.PlatformView.TextColor = datePicker.TextColor;
} }
} }

View File

@@ -3,6 +3,7 @@
using System; using System;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform.Linux; using Microsoft.Maui.Platform.Linux;
using SkiaSharp; using SkiaSharp;
@@ -10,14 +11,15 @@ namespace Microsoft.Maui.Platform;
/// <summary> /// <summary>
/// Skia-rendered date picker control with calendar popup. /// Skia-rendered date picker control with calendar popup.
/// Implements MAUI IDatePicker interface patterns.
/// </summary> /// </summary>
public class SkiaDatePicker : SkiaView public class SkiaDatePicker : SkiaView
{ {
#region BindableProperties #region BindableProperties
public static readonly BindableProperty DateProperty = public static readonly BindableProperty DateProperty =
BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(SkiaDatePicker), DateTime.Today, BindingMode.OneWay, BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(SkiaDatePicker), DateTime.Today, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).OnDatePropertyChanged()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).OnDatePropertyChanged((DateTime)o, (DateTime)n));
public static readonly BindableProperty MinimumDateProperty = public static readonly BindableProperty MinimumDateProperty =
BindableProperty.Create(nameof(MinimumDate), typeof(DateTime), typeof(SkiaDatePicker), new DateTime(1900, 1, 1), BindingMode.TwoWay, BindableProperty.Create(nameof(MinimumDate), typeof(DateTime), typeof(SkiaDatePicker), new DateTime(1900, 1, 1), BindingMode.TwoWay,
@@ -32,39 +34,39 @@ public class SkiaDatePicker : SkiaView
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty TextColorProperty = public static readonly BindableProperty TextColorProperty =
BindableProperty.Create(nameof(TextColor), typeof(SKColor), typeof(SkiaDatePicker), SKColors.Black, BindingMode.TwoWay, BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(SkiaDatePicker), Colors.Black, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty BorderColorProperty = public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(SKColor), typeof(SkiaDatePicker), new SKColor(189, 189, 189), BindingMode.TwoWay, BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(189, 189, 189), BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty CalendarBackgroundColorProperty = public static readonly BindableProperty CalendarBackgroundColorProperty =
BindableProperty.Create(nameof(CalendarBackgroundColor), typeof(SKColor), typeof(SkiaDatePicker), SKColors.White, BindingMode.TwoWay, BindableProperty.Create(nameof(CalendarBackgroundColor), typeof(Color), typeof(SkiaDatePicker), Colors.White, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty SelectedDayColorProperty = public static readonly BindableProperty SelectedDayColorProperty =
BindableProperty.Create(nameof(SelectedDayColor), typeof(SKColor), typeof(SkiaDatePicker), new SKColor(33, 150, 243), BindingMode.TwoWay, BindableProperty.Create(nameof(SelectedDayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(33, 150, 243), BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty TodayColorProperty = public static readonly BindableProperty TodayColorProperty =
BindableProperty.Create(nameof(TodayColor), typeof(SKColor), typeof(SkiaDatePicker), new SKColor(33, 150, 243, 64), BindingMode.TwoWay, BindableProperty.Create(nameof(TodayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgba(33, 150, 243, 64), BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty HeaderColorProperty = public static readonly BindableProperty HeaderColorProperty =
BindableProperty.Create(nameof(HeaderColor), typeof(SKColor), typeof(SkiaDatePicker), new SKColor(33, 150, 243), BindingMode.TwoWay, BindableProperty.Create(nameof(HeaderColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(33, 150, 243), BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty DisabledDayColorProperty = public static readonly BindableProperty DisabledDayColorProperty =
BindableProperty.Create(nameof(DisabledDayColor), typeof(SKColor), typeof(SkiaDatePicker), new SKColor(189, 189, 189), BindingMode.TwoWay, BindableProperty.Create(nameof(DisabledDayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(189, 189, 189), BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
public static readonly BindableProperty FontSizeProperty = public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create(nameof(FontSize), typeof(float), typeof(SkiaDatePicker), 14f, BindingMode.TwoWay, BindableProperty.Create(nameof(FontSize), typeof(double), typeof(SkiaDatePicker), 14.0, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).InvalidateMeasure()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).InvalidateMeasure());
public static readonly BindableProperty CornerRadiusProperty = public static readonly BindableProperty CornerRadiusProperty =
BindableProperty.Create(nameof(CornerRadius), typeof(float), typeof(SkiaDatePicker), 4f, BindingMode.TwoWay, BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(SkiaDatePicker), 4.0, BindingMode.TwoWay,
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate()); propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
#endregion #endregion
@@ -106,57 +108,57 @@ public class SkiaDatePicker : SkiaView
set => SetValue(FormatProperty, value); set => SetValue(FormatProperty, value);
} }
public SKColor TextColor public Color TextColor
{ {
get => (SKColor)GetValue(TextColorProperty); get => (Color)GetValue(TextColorProperty);
set => SetValue(TextColorProperty, value); set => SetValue(TextColorProperty, value);
} }
public SKColor BorderColor public Color BorderColor
{ {
get => (SKColor)GetValue(BorderColorProperty); get => (Color)GetValue(BorderColorProperty);
set => SetValue(BorderColorProperty, value); set => SetValue(BorderColorProperty, value);
} }
public SKColor CalendarBackgroundColor public Color CalendarBackgroundColor
{ {
get => (SKColor)GetValue(CalendarBackgroundColorProperty); get => (Color)GetValue(CalendarBackgroundColorProperty);
set => SetValue(CalendarBackgroundColorProperty, value); set => SetValue(CalendarBackgroundColorProperty, value);
} }
public SKColor SelectedDayColor public Color SelectedDayColor
{ {
get => (SKColor)GetValue(SelectedDayColorProperty); get => (Color)GetValue(SelectedDayColorProperty);
set => SetValue(SelectedDayColorProperty, value); set => SetValue(SelectedDayColorProperty, value);
} }
public SKColor TodayColor public Color TodayColor
{ {
get => (SKColor)GetValue(TodayColorProperty); get => (Color)GetValue(TodayColorProperty);
set => SetValue(TodayColorProperty, value); set => SetValue(TodayColorProperty, value);
} }
public SKColor HeaderColor public Color HeaderColor
{ {
get => (SKColor)GetValue(HeaderColorProperty); get => (Color)GetValue(HeaderColorProperty);
set => SetValue(HeaderColorProperty, value); set => SetValue(HeaderColorProperty, value);
} }
public SKColor DisabledDayColor public Color DisabledDayColor
{ {
get => (SKColor)GetValue(DisabledDayColorProperty); get => (Color)GetValue(DisabledDayColorProperty);
set => SetValue(DisabledDayColorProperty, value); set => SetValue(DisabledDayColorProperty, value);
} }
public float FontSize public double FontSize
{ {
get => (float)GetValue(FontSizeProperty); get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value); set => SetValue(FontSizeProperty, value);
} }
public float CornerRadius public double CornerRadius
{ {
get => (float)GetValue(CornerRadiusProperty); get => (double)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value); set => SetValue(CornerRadiusProperty, value);
} }
@@ -181,7 +183,7 @@ public class SkiaDatePicker : SkiaView
#region Events #region Events
public event EventHandler? DateSelected; public event EventHandler<DateChangedEventArgs>? DateSelected;
#endregion #endregion
@@ -195,6 +197,36 @@ public class SkiaDatePicker : SkiaView
#endregion #endregion
#region Helper Methods
/// <summary>
/// Converts a MAUI Color to SkiaSharp SKColor.
/// </summary>
private static SKColor ToSKColor(Color? color)
{
if (color == null) return SKColors.Transparent;
return new SKColor(
(byte)(color.Red * 255),
(byte)(color.Green * 255),
(byte)(color.Blue * 255),
(byte)(color.Alpha * 255));
}
/// <summary>
/// Converts a MAUI Color to SKColor with modified alpha.
/// </summary>
private static SKColor ToSKColorWithAlpha(Color? color, byte alpha)
{
if (color == null) return SKColors.Transparent;
return new SKColor(
(byte)(color.Red * 255),
(byte)(color.Green * 255),
(byte)(color.Blue * 255),
alpha);
}
#endregion
#region Private Methods #region Private Methods
private SKRect GetCalendarRect(SKRect pickerBounds) private SKRect GetCalendarRect(SKRect pickerBounds)
@@ -225,10 +257,10 @@ public class SkiaDatePicker : SkiaView
return new SKRect(calendarLeft, calendarTop, calendarLeft + CalendarWidth, calendarTop + CalendarHeight); return new SKRect(calendarLeft, calendarTop, calendarLeft + CalendarWidth, calendarTop + CalendarHeight);
} }
private void OnDatePropertyChanged() private void OnDatePropertyChanged(DateTime oldValue, DateTime newValue)
{ {
_displayMonth = new DateTime(Date.Year, Date.Month, 1); _displayMonth = new DateTime(newValue.Year, newValue.Month, 1);
DateSelected?.Invoke(this, EventArgs.Empty); DateSelected?.Invoke(this, new DateChangedEventArgs(oldValue, newValue));
Invalidate(); Invalidate();
} }
@@ -251,27 +283,32 @@ public class SkiaDatePicker : SkiaView
private void DrawPickerButton(SKCanvas canvas, SKRect bounds) private void DrawPickerButton(SKCanvas canvas, SKRect bounds)
{ {
float cornerRadius = (float)CornerRadius;
float fontSize = (float)FontSize;
using var bgPaint = new SKPaint using var bgPaint = new SKPaint
{ {
Color = IsEnabled ? BackgroundColor : new SKColor(245, 245, 245), Color = IsEnabled ? BackgroundColor : new SKColor(245, 245, 245),
Style = SKPaintStyle.Fill, Style = SKPaintStyle.Fill,
IsAntialias = true IsAntialias = true
}; };
canvas.DrawRoundRect(new SKRoundRect(bounds, CornerRadius), bgPaint); canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), bgPaint);
SKColor borderColor = IsFocused ? ToSKColor(SelectedDayColor) : ToSKColor(BorderColor);
using var borderPaint = new SKPaint using var borderPaint = new SKPaint
{ {
Color = IsFocused ? SelectedDayColor : BorderColor, Color = borderColor,
Style = SKPaintStyle.Stroke, Style = SKPaintStyle.Stroke,
StrokeWidth = IsFocused ? 2 : 1, StrokeWidth = IsFocused ? 2 : 1,
IsAntialias = true IsAntialias = true
}; };
canvas.DrawRoundRect(new SKRoundRect(bounds, CornerRadius), borderPaint); canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), borderPaint);
using var font = new SKFont(SKTypeface.Default, FontSize, 1f, 0f); SKColor textColor = ToSKColor(TextColor);
using var font = new SKFont(SKTypeface.Default, fontSize, 1f, 0f);
using var textPaint = new SKPaint(font) using var textPaint = new SKPaint(font)
{ {
Color = IsEnabled ? TextColor : TextColor.WithAlpha(128), Color = IsEnabled ? textColor : textColor.WithAlpha(128),
IsAntialias = true IsAntialias = true
}; };
@@ -285,9 +322,10 @@ public class SkiaDatePicker : SkiaView
private void DrawCalendarIcon(SKCanvas canvas, SKRect bounds) private void DrawCalendarIcon(SKCanvas canvas, SKRect bounds)
{ {
SKColor textColor = ToSKColor(TextColor);
using var paint = new SKPaint using var paint = new SKPaint
{ {
Color = IsEnabled ? TextColor : TextColor.WithAlpha(128), Color = IsEnabled ? textColor : textColor.WithAlpha(128),
Style = SKPaintStyle.Stroke, Style = SKPaintStyle.Stroke,
StrokeWidth = 1.5f, StrokeWidth = 1.5f,
IsAntialias = true IsAntialias = true
@@ -312,6 +350,7 @@ public class SkiaDatePicker : SkiaView
private void DrawCalendar(SKCanvas canvas, SKRect bounds) private void DrawCalendar(SKCanvas canvas, SKRect bounds)
{ {
SKRect calendarRect = GetCalendarRect(bounds); SKRect calendarRect = GetCalendarRect(bounds);
float cornerRadius = (float)CornerRadius;
using var shadowPaint = new SKPaint using var shadowPaint = new SKPaint
{ {
@@ -319,24 +358,24 @@ public class SkiaDatePicker : SkiaView
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 4f), MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 4f),
Style = SKPaintStyle.Fill Style = SKPaintStyle.Fill
}; };
canvas.DrawRoundRect(new SKRoundRect(new SKRect(calendarRect.Left + 2f, calendarRect.Top + 2f, calendarRect.Right + 2f, calendarRect.Bottom + 2f), CornerRadius), shadowPaint); canvas.DrawRoundRect(new SKRoundRect(new SKRect(calendarRect.Left + 2f, calendarRect.Top + 2f, calendarRect.Right + 2f, calendarRect.Bottom + 2f), cornerRadius), shadowPaint);
using var bgPaint = new SKPaint using var bgPaint = new SKPaint
{ {
Color = CalendarBackgroundColor, Color = ToSKColor(CalendarBackgroundColor),
Style = SKPaintStyle.Fill, Style = SKPaintStyle.Fill,
IsAntialias = true IsAntialias = true
}; };
canvas.DrawRoundRect(new SKRoundRect(calendarRect, CornerRadius), bgPaint); canvas.DrawRoundRect(new SKRoundRect(calendarRect, cornerRadius), bgPaint);
using var borderPaint = new SKPaint using var borderPaint = new SKPaint
{ {
Color = BorderColor, Color = ToSKColor(BorderColor),
Style = SKPaintStyle.Stroke, Style = SKPaintStyle.Stroke,
StrokeWidth = 1f, StrokeWidth = 1f,
IsAntialias = true IsAntialias = true
}; };
canvas.DrawRoundRect(new SKRoundRect(calendarRect, CornerRadius), borderPaint); canvas.DrawRoundRect(new SKRoundRect(calendarRect, cornerRadius), borderPaint);
DrawCalendarHeader(canvas, new SKRect(calendarRect.Left, calendarRect.Top, calendarRect.Right, calendarRect.Top + 48f)); DrawCalendarHeader(canvas, new SKRect(calendarRect.Left, calendarRect.Top, calendarRect.Right, calendarRect.Top + 48f));
DrawWeekdayHeaders(canvas, new SKRect(calendarRect.Left, calendarRect.Top + 48f, calendarRect.Right, calendarRect.Top + 48f + 30f)); DrawWeekdayHeaders(canvas, new SKRect(calendarRect.Left, calendarRect.Top + 48f, calendarRect.Right, calendarRect.Top + 48f + 30f));
@@ -345,17 +384,19 @@ public class SkiaDatePicker : SkiaView
private void DrawCalendarHeader(SKCanvas canvas, SKRect bounds) private void DrawCalendarHeader(SKCanvas canvas, SKRect bounds)
{ {
float cornerRadius = (float)CornerRadius;
using var headerPaint = new SKPaint using var headerPaint = new SKPaint
{ {
Color = HeaderColor, Color = ToSKColor(HeaderColor),
Style = SKPaintStyle.Fill Style = SKPaintStyle.Fill
}; };
canvas.Save(); canvas.Save();
canvas.ClipRoundRect(new SKRoundRect(new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Top + CornerRadius * 2f), CornerRadius), SKClipOperation.Intersect, false); canvas.ClipRoundRect(new SKRoundRect(new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Top + cornerRadius * 2f), cornerRadius), SKClipOperation.Intersect, false);
canvas.DrawRect(bounds, headerPaint); canvas.DrawRect(bounds, headerPaint);
canvas.Restore(); canvas.Restore();
canvas.DrawRect(new SKRect(bounds.Left, bounds.Top + CornerRadius, bounds.Right, bounds.Bottom), headerPaint); canvas.DrawRect(new SKRect(bounds.Left, bounds.Top + cornerRadius, bounds.Right, bounds.Bottom), headerPaint);
using var font = new SKFont(SKTypeface.Default, 16f, 1f, 0f); using var font = new SKFont(SKTypeface.Default, 16f, 1f, 0f);
using var textPaint = new SKPaint(font) using var textPaint = new SKPaint(font)
@@ -433,6 +474,11 @@ public class SkiaDatePicker : SkiaView
DateTime today = DateTime.Today; DateTime today = DateTime.Today;
SKRect cellRect = default; SKRect cellRect = default;
SKColor textColor = ToSKColor(TextColor);
SKColor selectedDayColor = ToSKColor(SelectedDayColor);
SKColor todayColor = ToSKColor(TodayColor);
SKColor disabledDayColor = ToSKColor(DisabledDayColor);
for (int day = 1; day <= daysInMonth; day++) for (int day = 1; day <= daysInMonth; day++)
{ {
DateTime dayDate = new DateTime(_displayMonth.Year, _displayMonth.Month, day); DateTime dayDate = new DateTime(_displayMonth.Year, _displayMonth.Month, day);
@@ -452,16 +498,16 @@ public class SkiaDatePicker : SkiaView
if (isSelected) if (isSelected)
{ {
bgPaint.Color = SelectedDayColor; bgPaint.Color = selectedDayColor;
canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint); canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint);
} }
else if (isToday) else if (isToday)
{ {
bgPaint.Color = TodayColor; bgPaint.Color = todayColor;
canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint); canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint);
} }
textPaint.Color = isSelected ? SKColors.White : (isDisabled ? DisabledDayColor : TextColor); textPaint.Color = isSelected ? SKColors.White : (isDisabled ? disabledDayColor : textColor);
string dayText = day.ToString(); string dayText = day.ToString();
SKRect dayTextBounds = default; SKRect dayTextBounds = default;
textPaint.MeasureText(dayText, ref dayTextBounds); textPaint.MeasureText(dayText, ref dayTextBounds);