NET MAUI:自定义日期选择器,使其在单击图标时打开



我想显示一个日历图标,然后当用户点击/单击日历图标时,日期选择器就会打开。有没有一种方法可以自定义日期选择器来实现此功能?我需要自定义处理程序吗?

我试着在日期选择器上调用Focus((,但它没有打开。

<HorizontalStackLayout>
<DatePicker x:Name="MyDatePicker" MinimumDate="01/01/2022"
MaximumDate="12/31/2022"
Date="06/21/2022" />
<ImageButton Source="calendar.svg" Clicked="ImageButton_Clicked"></ImageButton>
</HorizontalStackLayout>
private void ImageButton_Clicked(object sender, EventArgs e)
{
MyDatePicker.Focus();
}

注意:图标来自:https://feathericons.com/?query=calendar

利用毛伊岛。FreakyControls和自定义行为为Android带来了这个解决方案。它确实需要一个自定义处理程序才能成功实现。

MauiProgram.cs

public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCustomHandlers();
});
return builder.Build();
}

共享/扩展/扩展.cs

#if ANDROID
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using static Microsoft.Maui.ApplicationModel.Platform;
using NativeImage = Android.Graphics.Bitmap;
#endif
public static void ExecuteCommandIfAvailable(this ICommand command, object parameter = null)
{
if (command?.CanExecute(parameter) == true)
{
command.Execute(parameter);
}
}
public static void AddCustomHandlers(this IMauiHandlersCollection handlers)
{
#if ANDROID
handlers.AddHandler(typeof(Maui.CustomControls.CustomDatePicker), typeof(CustomDatePickerHandler));
#endif
}
#if ANDROID
public static async Task<NativeImage> ToNativeImageSourceAsync(this ImageSource source)
{
var handler = GetHandler(source);
var returnValue = (NativeImage)null;
returnValue = await handler.LoadImageAsync(source, CurrentActivity);
return returnValue;
}
private static IImageSourceHandler GetHandler(this ImageSource source)
{
//Image source handler to return 
IImageSourceHandler returnValue = null;
//check the specific source type and return the correct image source handler 
switch (source)
{
case UriImageSource:
returnValue = new ImageLoaderSourceHandler();
break;
case FileImageSource:
returnValue = new FileImageSourceHandler();
break;
case StreamImageSource:
returnValue = new StreamImagesourceHandler();
break;
case FontImageSource:
returnValue = new FontImageSourceHandler();
break;
}
return returnValue;
}
#endif

Android/NativeControls/Helpers/DrawableHandlerCallback.cs

public class DrawableHandlerCallback : IDrawableClickListener
{
private readonly IDrawableImageView view;
private readonly Action showPicker;
public DrawableHandlerCallback(IDrawableImageView view, Action showPicker)
{
this.view = view;
this.showPicker = showPicker;
}
public void OnClick(DrawablePosition target)
{
switch (target)
{
case DrawablePosition.Left:
case DrawablePosition.Right:
view.ImageTappedHandler.Invoke(this.showPicker, null);
view.ImageCommand?.ExecuteCommandIfAvailable(view.ImageCommandParameter);
break;
}
}
}

Android/NativeControls/Helpers/DrawablePosition.cs

public enum DrawablePosition
{
Top,
Bottom,
Left,
Right
};

Android/NativeControls/Helpers/IDrawableClickListener.cs

public interface IDrawableClickListener
{
public void OnClick(DrawablePosition target);
}

Android/NativeControls/CustomMauiDatePicker.cs

public class CustomMauiDatePicker : MauiDatePicker
{
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
int actionX, actionY;
private IDrawableClickListener clickListener;
public CustomMauiDatePicker(Context context) : base(context)
{
}
public CustomMauiDatePicker(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public CustomMauiDatePicker(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
}
public override void SetCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
Drawable right, Drawable bottom)
{
if (left != null)
{
drawableLeft = left;
}
if (right != null)
{
drawableRight = right;
}
if (top != null)
{
drawableTop = top;
}
if (bottom != null)
{
drawableBottom = bottom;
}
base.SetCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
}
public override bool OnTouchEvent(MotionEvent e)
{
Rect bounds;
if (e.Action == MotionEventActions.Down)
{
actionX = (int)e.GetX();
actionY = (int)e.GetY();
if (drawableBottom != null
&& drawableBottom.Bounds.Contains(actionX, actionY))
{
clickListener.OnClick(DrawablePosition.Bottom);
return base.OnTouchEvent(e);
}
if (drawableTop != null
&& drawableTop.Bounds.Contains(actionX, actionY))
{
clickListener.OnClick(DrawablePosition.Top);
return base.OnTouchEvent(e);
}
// this works for left since container shares 0,0 origin with bounds
if (drawableLeft != null)
{
bounds = null;
bounds = drawableLeft.Bounds;
int x, y;
int extraTapArea = (int)(13 * Resources.DisplayMetrics.Density + 0.5);
x = actionX;
y = actionY;
if (!bounds.Contains(actionX, actionY))
{
// Gives the +20 area for tapping. /
x = (int)(actionX - extraTapArea);
y = (int)(actionY - extraTapArea);
if (x <= 0)
x = actionX;
if (y <= 0)
y = actionY;
// Creates square from the smallest value /
if (x < y)
{
y = x;
}
}
if (bounds.Contains(x, y) && clickListener != null)
{
clickListener.OnClick(DrawablePosition.Left);
e.Action = (MotionEventActions.Cancel);
return false;
}
}
if (drawableRight != null)
{
bounds = null;
bounds = drawableRight.Bounds;
int x, y;
int extraTapArea = 13;
//
//  IF USER CLICKS JUST OUT SIDE THE RECTANGLE OF THE DRAWABLE
//  THAN ADD X AND SUBTRACT THE Y WITH SOME VALUE SO THAT AFTER
//  CALCULATING X AND Y CO-ORDINATE LIES INTO THE DRAWBABLE
//  BOUND. - this process help to increase the tappable area of
//  the rectangle.
// 
x = (int)(actionX + extraTapArea);
y = (int)(actionY - extraTapArea);
//Since this is right drawable subtract the value of x from the width 
// of view. so that width - tappedarea will result in x co-ordinate in drawable bound. 
//
x = Width - x;
//x can be negative if user taps at x co-ordinate just near the width.
// e.g views width = 300 and user taps 290. Then as per previous calculation
// 290 + 13 = 303. So subtract X from getWidth() will result in negative value.
// So to avoid this add the value previous added when x goes negative.
//
if (x <= 0)
{
x += extraTapArea;
}
// If result after calculating for extra tappable area is negative.
// assign the original value so that after subtracting
// extratapping area value doesn't go into negative value.
//
if (y <= 0)
y = actionY;
//If drawble bounds contains the x and y points then move ahead./
if (bounds.Contains(x, y) && clickListener != null)
{
clickListener
.OnClick(DrawablePosition.Right);
e.Action = (MotionEventActions.Cancel);
return false;
}
return base.OnTouchEvent(e);
}
}
return base.OnTouchEvent(e);
}
protected override void JavaFinalize()
{
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
base.JavaFinalize();
}
public void SetDrawableClickListener(IDrawableClickListener listener)
{
this.clickListener = listener;
}
}

平台/Android/CustomDatePickerHandler.Android.cs

public partial class CustomDatePickerHandler
{
DatePickerDialog? _dialog;
protected override MauiDatePicker CreatePlatformView()
{
var mauiDatePicker = new CustomMauiDatePicker(Context)
{
ShowPicker = ShowPickerDialog,
HidePicker = HidePickerDialog
};
var date = VirtualView?.Date;
if (date != null)
_dialog = CreateDatePickerDialog(date.Value.Year, date.Value.Month, date.Value.Day);
var colorStateList = ColorStateList.ValueOf(Android.Graphics.Color.Transparent);
ViewCompat.SetBackgroundTintList(mauiDatePicker, colorStateList);
return mauiDatePicker;
}
protected override void DisconnectHandler(MauiDatePicker platformView)
{
base.DisconnectHandler(platformView);
if (_dialog != null)
{
_dialog.Hide();
_dialog.Dispose();
_dialog = null;
}
}
internal DatePickerDialog? DatePickerDialog { get { return _dialog; } }
internal async Task HandleAndAlignImageSourceAsync(Maui.CustomControls.CustomDatePicker entry)
{
var imageBitmap = await entry.ImageSource?.ToNativeImageSourceAsync();
if (imageBitmap != null)
{
var bitmapDrawable = new BitmapDrawable(Platform.CurrentActivity?.Resources,
Bitmap.CreateScaledBitmap(imageBitmap, entry.ImageWidth * 2, entry.ImageHeight * 2, true));
var customDatePicker = PlatformView as CustomMauiDatePicker;
customDatePicker.SetDrawableClickListener(new DrawableHandlerCallback(entry, customDatePicker.ShowPicker));
switch (entry.ImageAlignment)
{
case ImageAlignment.Left:
customDatePicker.SetCompoundDrawablesWithIntrinsicBounds(bitmapDrawable, null, null, null);
break;
case ImageAlignment.Right:
customDatePicker.SetCompoundDrawablesWithIntrinsicBounds(null, null, bitmapDrawable, null);
break;
}
}
PlatformView.CompoundDrawablePadding = entry.ImagePadding;
}

void ShowPickerDialog()
{
if (VirtualView == null)
return;
if (_dialog != null && _dialog.IsShowing)
return;
var date = VirtualView.Date;
ShowPickerDialog(date.Year, date.Month - 1, date.Day);
}
void ShowPickerDialog(int year, int month, int day)
{
if (_dialog == null)
_dialog = CreateDatePickerDialog(year, month, day);
else
{
EventHandler? setDateLater = null;
setDateLater = (sender, e) => { _dialog!.UpdateDate(year, month, day); _dialog.ShowEvent -= setDateLater; };
_dialog.ShowEvent += setDateLater;
}
_dialog.Show();
}
void HidePickerDialog()
{
_dialog?.Hide();
}
}

共享/控制/IDrawableImageView.cs

public interface IDrawableImageView
{
public EventHandler ImageTappedHandler
{
get;
}
public object ImageCommandParameter
{
get;
}
public ICommand ImageCommand
{
get;
}
public int ImagePadding
{
get;
}
public int ImageWidth
{
get;
}
public int ImageHeight
{
get;
}
public ImageSource ImageSource
{
get;
}
public ImageAlignment ImageAlignment
{
get;
}
}

共享/CustomDatePicker/CustomDatePicker.cs

public class CustomDatePicker : DatePicker, IDrawableImageView
{
private EventHandler imageTapped;
public EventHandler ImageTappedHandler
{
get { return imageTapped; }
}
public event EventHandler ImageTapped
{
add
{
imageTapped += value;
}
remove
{
imageTapped -= value;
}
}
public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(
nameof(Image),
typeof(ImageSource),
typeof(CustomDatePicker),
default(ImageSource));
public static readonly BindableProperty ImageHeightProperty = BindableProperty.Create(
nameof(ImageHeight),
typeof(int),
typeof(CustomDatePicker),
25);
public static readonly BindableProperty ImageWidthProperty = BindableProperty.Create(
nameof(ImageWidth),
typeof(int),
typeof(CustomDatePicker),
25);
public static readonly BindableProperty ImageAlignmentProperty = BindableProperty.Create(
nameof(ImageAlignment),
typeof(ImageAlignment),
typeof(CustomDatePicker),
ImageAlignment.Right);
public static readonly BindableProperty ImagePaddingProperty = BindableProperty.Create(
nameof(ImagePadding),
typeof(int),
typeof(CustomDatePicker),
5);
public static readonly BindableProperty ImageCommandProperty = BindableProperty.Create(
nameof(ImagePadding),
typeof(ICommand),
typeof(CustomDatePicker),
default(ICommand));
public static readonly BindableProperty ImageCommandParameterProperty = BindableProperty.Create(
nameof(ImageCommandParameter),
typeof(object),
typeof(CustomDatePicker),
default(object));
/// <summary>
/// Command parameter for your Image tap command 
/// </summary>
public object ImageCommandParameter
{
get => GetValue(ImageCommandParameterProperty);
set => SetValue(ImageCommandParameterProperty, value);
}
/// <summary>
/// <see cref="ImageCommand"/> of type <see cref="ICommand"/> that you can use to bind with your Image that you added to your control's ViewPort
/// </summary>
public ICommand ImageCommand
{
get => (ICommand)GetValue(ImageCommandProperty);
set => SetValue(ImageCommandProperty, value);
}
/// <summary>
/// Padding of the Image as <see cref="int"/> that you added to the ViewPort
/// </summary>
public int ImagePadding
{
get => (int)GetValue(ImagePaddingProperty);
set => SetValue(ImagePaddingProperty, value);
}
/// <summary>
/// Width of the Image in your ViewPort
/// </summary>
public int ImageWidth
{
get => (int)GetValue(ImageWidthProperty);
set => SetValue(ImageWidthProperty, value);
}
/// <summary>
/// Height of the Image in your ViewPort
/// </summary>
public int ImageHeight
{
get => (int)GetValue(ImageHeightProperty);
set => SetValue(ImageHeightProperty, value);
}
/// <summary>
/// An <see cref="ImageSource"/> that you want to add to your ViewPort
/// </summary>
public ImageSource ImageSource
{
get => (ImageSource)GetValue(ImageSourceProperty);
set => SetValue(ImageSourceProperty, value);
}
/// <summary>
/// <see cref="ImageAlignment"/> for your Image's ViewPort, By default set to Right.
/// </summary>
public ImageAlignment ImageAlignment
{
get => (ImageAlignment)GetValue(ImageAlignmentProperty);
set => SetValue(ImageAlignmentProperty, value);
}
}

共享/CustomDatePickerHandler/CustomDatePickerHandler.cs

#if ANDROID
public partial class CustomDatePickerHandler : DatePickerHandler
{
public CustomDatePickerHandler()
{
Mapper.AppendToMapping("CustomDatePickerCustomization", MapDatePicker);
}
private void MapDatePicker(IDatePickerHandler datePickerHandler, IDatePicker datePicker)
{
if (datePicker is Maui.CustomControls.CustomDatePicker customDatePicker &&
datePickerHandler is CustomDatePickerHandler customDatePickerHandler)
{
if (customDatePicker.ImageSource != default(ImageSource))
{
customDatePickerHandler.HandleAndAlignImageSourceAsync(customDatePicker).RunConcurrently();
}
}
}
}
#endif

共享/枚举/ImageAlignment.cs

public enum ImageAlignment
{
/// <summary>
/// Aligns your control to the left view port of the view. 
/// </summary>
Left,
/// <summary>
/// Aligns your control to the right view port of the view.
/// </summary>
Right
}

共享/扩展/任务扩展.cs

public static class TaskExtensions
{
public static void RunConcurrently(this Task task)
{
if (task == null)
throw new ArgumentNullException("task", "task is null.");
if (task.Status == TaskStatus.Created)
task.Start();
}
}

自定义日期选取器页面.xaml

<StackLayout Padding="10,60,10,0">
<Label Text="Please enter a date."
FontSize="12" />
<custom:CustomDatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2022"
Date="06/21/2022"
ImageSource="calendar"
ImageAlignment="Right"
ImageHeight="40"
ImageWidth="40"
ImagePadding="10"
FontSize="Large">
<custom:CustomDatePicker.Behaviors>
<local:CustomDatePickerBehavior />
</custom:CustomDatePicker.Behaviors>
</custom:CustomDatePicker>
</StackLayout>

行为/CustomDatePickerBehavior.cs

public class CustomDatePickerBehavior : Behavior<CustomDatePicker>
{
protected override void OnAttachedTo(CustomDatePicker picker)
{
picker.ImageTapped += Picker_ImageTapped;
base.OnAttachedTo(picker);
}
protected override void OnDetachingFrom(CustomDatePicker picker)
{
picker.ImageTapped -= Picker_ImageTapped;
base.OnDetachingFrom(picker);
}
private void Picker_ImageTapped(object sender, EventArgs e)
{
Action showPicker = (Action)sender;
showPicker.Invoke();
}
}

最新更新