Views
CalendarPage.xaml <?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:system="clr-namespace:System;assembly=netstandard"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="this"
x:Class="CustomCalendarControl.Views.CalendarPage">
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Bottom"
PositionOut="Bottom"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="200"
DurationOut="200"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<ContentPage.Content>
<AbsoluteLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"
AbsoluteLayout.LayoutBounds="1,1,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Transparent">
<!--<DatePicker />-->
<Grid Padding="0,0,0,30" Margin="5,0,5,0" IsClippedToBounds="True" BackgroundColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label LineHeight="1.2" Grid.Row="0" BackgroundColor="Orange" Padding="15,15,5,15" >
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding CurrentYear}" TextColor="White" FontSize="18" >
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowYearListCommand}"/>
</Span.GestureRecognizers>
</Span>
<Span Text="{x:Static system:Environment.NewLine}" />
<Span Text="{Binding SelectedDateInString}" TextColor="White" />
</FormattedString>
</Label.FormattedText>
</Label>
<ListView BackgroundColor="White" x:Name="lstYear" HeightRequest="340" WidthRequest="310" Grid.Row="1" ItemsSource="{Binding YearList}" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="0,10,0,10">
<Label HorizontalTextAlignment="Center" Text="{Binding .}" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer CommandParameter="{Binding .}" Command="{Binding Source={x:Reference this},Path=BindingContext.YearSelectionCommand}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Triggers>
<DataTrigger TargetType="ListView" Binding="{Binding IsCalendarDetailsVisible}" Value="True">
<Setter Property="IsVisible" Value="False" />
</DataTrigger>
<DataTrigger TargetType="ListView" Binding="{Binding IsCalendarDetailsVisible}" Value="False">
<Setter Property="IsVisible" Value="True" />
</DataTrigger>
</ListView.Triggers>
</ListView>
<StackLayout Grid.Row="2" Padding="0" IsVisible="{Binding IsCalendarDetailsVisible}">
<Grid ColumnDefinitions="34,34,34,34,34,34,34" IsVisible="{Binding IsCalendarDetailsVisible}" Margin="30,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" >
<Label FontSize="12" Text="S" Grid.Row="1" TextColor="Red" Grid.Column="0" />
<Label FontSize="12" Text="M" Grid.Row="1" TextColor="Gray" Grid.Column="1"/>
<Label FontSize="12" Text="T" Grid.Row="1" TextColor="Gray" Grid.Column="2"/>
<Label FontSize="12" Text="W" Grid.Row="1" TextColor="Gray" Grid.Column="3"/>
<Label FontSize="12" Text="T" Grid.Row="1" TextColor="Gray" Grid.Column="4"/>
<Label FontSize="12" Text="F" Grid.Row="1" TextColor="Gray" Grid.Column="5"/>
<Label FontSize="12" Text="S" Grid.Row="1" TextColor="Gray" Grid.Column="6"/>
<Label VerticalTextAlignment="Center" Grid.Row="0" Grid.ColumnSpan="2" TextColor="Black" Text="<" FontSize="25" HorizontalTextAlignment="Start" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PreviousMonthCommand}" />
</Label.GestureRecognizers>
</Label>
<Label VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" FontSize="13" FontAttributes="Bold" HorizontalTextAlignment="Start" HorizontalOptions="CenterAndExpand" Text="{Binding CurrentMonthYear}" TextColor="Black" />
<Label VerticalTextAlignment="Center" HorizontalTextAlignment="Center" Grid.Row="0" Grid.Column="5" Grid.ColumnSpan="2" TextColor="Black" Text=">" FontSize="25" >
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NextMonthCommand}" />
</Label.GestureRecognizers>
</Label>
</Grid>
<CollectionView IsVisible="{Binding IsCalendarDetailsVisible}" Margin="15,0,0,0" ItemsSource="{Binding CalendarList}" HeightRequest="260" WidthRequest="295" IsGrouped="True" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" >
<CollectionView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Command="{Binding NextMonthCommand}" />
<SwipeGestureRecognizer Direction="Right" Command="{Binding PreviousMonthCommand}" />
</CollectionView.GestureRecognizers>
<CollectionView.ItemsLayout>
<GridItemsLayout SnapPointsType="MandatorySingle" SnapPointsAlignment="Center" Span="7" Orientation="Horizontal" />
</CollectionView.ItemsLayout>
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Padding="0" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="Red" HasShadow="False" Padding="0" Margin="0" HeightRequest="40" WidthRequest="40">
<Label FontSize="12" TextColor="Black" Text="{Binding DateInNumberStr}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" >
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding CurrentDate}" Value="True">
<Setter Property="TextColor" Value="White" />
</DataTrigger>
</Label.Triggers>
</Label>
<Frame.Triggers>
<DataTrigger TargetType="Frame" Binding="{Binding CurrentDate}" Value="True">
<Setter Property="BackgroundColor" Value="Orange" />
<Setter Property="CornerRadius" Value="19.5" />
</DataTrigger>
<DataTrigger TargetType="Frame" Binding="{Binding CurrentDate}" Value="False">
<Setter Property="BackgroundColor" Value="Transparent" />
</DataTrigger>
</Frame.Triggers>
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference this}, Path=BindingContext.CurrentDateCommand}" CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<Label Margin="0,0,90,0" Grid.Row="3" FontAttributes="Bold" x:Name="lblCancelDate" Text="CANCEL" FontSize="17" TextColor="Orange" HorizontalOptions="End" >
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_ForCancelDate" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
<Label Margin="0,0,30,0" Grid.Row="3" FontAttributes="Bold" TextColor="Orange" Text="OK" x:Name="lblSelectDate" FontSize="17" HorizontalOptions="End" >
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_ForSelectDate" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</Grid>
</AbsoluteLayout>
</ContentPage.Content>
</pages:PopupPage>
CalendarPage.xaml.cs
using CustomCalendarControl.ViewModels;
using Rg.Plugins.Popup.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomCalendarControl.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CalendarPage : Rg.Plugins.Popup.Pages.PopupPage
{
public CalendarPage(DateTime date)
{
InitializeComponent();
this.BindingContext = new CalendarPageViewModel(date);
} private void TapGestureRecognizer_Tapped_ForCancelDate(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(async () =>
{
await PopupNavigation.Instance.PopAsync();
});
} public ICommand SelectedDateCommand { get; set; }
private void TapGestureRecognizer_Tapped_ForSelectDate(object sender, EventArgs e)
{
var btnObject = (Label)sender;
var context = (CalendarPageViewModel)btnObject.BindingContext;
SelectedDateCommand.Execute(context.SelectedDate);
TapGestureRecognizer_Tapped_ForCancelDate(sender, null);
}
}
} ViewModelsCalendarPageViewModel.cs using CustomCalendarControl.Models;
using MvvmHelpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using Xamarin.Forms;
namespace CustomCalendarControl.ViewModels
{
public class CalendarPageViewModel : INotifyPropertyChanged
{
#region Properties & Event
public event PropertyChangedEventHandler PropertyChanged;
int MaxYear;
int MinYear;
public static List<CalendarModel> _staticCalendarList;
public static List<int> dummyYearList;
public ObservableRangeCollection<int> YearList { get; } = new ObservableRangeCollection<int>();
public ObservableRangeCollection<Grouping<CalendarHeader, CalendarModel>> CalendarList { get; } = new ObservableRangeCollection<Grouping<CalendarHeader, CalendarModel>>();
private bool _isCalendarDetailsVisible = true;
public bool IsCalendarDetailsVisible
{
get => _isCalendarDetailsVisible;
set { _isCalendarDetailsVisible = value; OnPropertyChanged(); }
}
private string _currentMonthYear = string.Empty;
public string CurrentMonthYear
{
get => _currentMonthYear;
set { _currentMonthYear = value; OnPropertyChanged(); }
}
private int _currentYear;
public int CurrentYear
{
get => _currentYear;
set { _currentYear = value; OnPropertyChanged(); }
}
private DateTime _currentDate;
public DateTime CurrentDate
{
get => _currentDate;
set
{
_currentDate = value; OnPropertyChanged();
}
}
private DateTime? _selectedDate { get; set; }
public DateTime? SelectedDate
{
get => _selectedDate;
set
{
_selectedDate = value; OnPropertyChanged();
}
}
private string _selectedDateInString;
public string SelectedDateInString
{
get => _selectedDateInString;
set { _selectedDateInString = value; OnPropertyChanged(); }
}
#endregion
#region Constructor
public CalendarPageViewModel(DateTime _selectedDate)
{
AddDates();
BindDates(_selectedDate.Year, _selectedDate.Month, _selectedDate.ToString("MMM"), _selectedDate);
}
public CalendarPageViewModel()
{
}
#endregion
#region Methods
private void AddDates()
{
MaxYear = DateTime.Now.Year + 30;
MinYear = DateTime.Now.Year - 100;
if (dummyYearList == null)
dummyYearList = new List<int>();
if (_staticCalendarList == null)
_staticCalendarList = new List<CalendarModel>();
if (_staticCalendarList.Count == 0)
{
for (int year = MinYear; year <= MaxYear; year++)
{
for (int month = 1; month <= 12; month++)
{
int daysCount = DateTime.DaysInMonth(year, month);
var dates = new List<CalendarModel>();
for (int days = 1; days <= daysCount; days++)
{
DateTime date = new DateTime(year, month, days);
CalendarModel obj = new CalendarModel();
obj.Date = date;
obj.Year = year;
obj.Month = month;
obj.DateInNumber = days;
obj.DateInNumberStr = days + "";
obj.DayName = date.ToString("ddd");
dates.Add(obj);
}
_staticCalendarList.AddRange(dates);
}
dummyYearList.Add(year);
}
}
else
{
if (dummyYearList.Count == 0)
{
for (int year = MinYear; year <= MaxYear; year++)
{
dummyYearList.Add(year);
}
}
var selectedDates = _staticCalendarList.Where(f => f.CurrentDate == true).ToList();
selectedDates.ForEach(f => { f.CurrentDate = false; });
}
YearList.AddRange(dummyYearList.OrderByDescending(f => f).ToList());
}
public void BindDates(int year, int month, string MonthName, DateTime? newDate)
{
CalendarList.Clear();
bool isFoundStartDate = false;
int days = DateTime.DaysInMonth(year, month);
CurrentMonthYear = MonthName + " " + year.ToString();
var dates = _staticCalendarList.Where(f => f.Year == year && f.Month == month).ToList();
if (dates.Count > 0)
{
if (newDate.HasValue)
{
var currentDate = dates.Where(f => f.DateInNumber == newDate?.Day).FirstOrDefault();
currentDate.CurrentDate = true;
SelectedDate = newDate.Value;
CurrentDate = newDate.Value;
CurrentYear = year;
SelectedDateInString = CurrentDate.ToString("ddd, MMMM dd");
}
string startDayName = dates.Where(f => f.DateInNumber == 1).FirstOrDefault().DayName.ToLower();
var list = new List<CalendarModel>();
var header = new CalendarHeader();
header.DayName = "Sun";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Mon";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Tue";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Wed";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Thu";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Fri";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
list.Clear();
header = new CalendarHeader();
header.DayName = "Sat";
if (startDayName != header.DayName.ToLower() && !isFoundStartDate)
{
list.Add(new CalendarModel());
}
else
{
isFoundStartDate = true;
}
list.AddRange(dates.Where(f => f.DayName.ToLower() == header.DayName.ToLower()).ToList());
CalendarList.Add(new Grouping<CalendarHeader, CalendarModel>(header, list));
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyValue = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyValue));
}
#endregion
#region Commands
// used for Traverse through date
public ICommand CurrentDateCommand
{
get
{
return new Command<CalendarModel>((item) =>
{
if (item?.DayName != null)
{
var selectedDates = _staticCalendarList.Where(f => f.CurrentDate == true).ToList();
selectedDates.ForEach(f => { f.CurrentDate = false; });
CurrentDate = item.Date;
CurrentYear = item.Date.Year;
item.CurrentDate = true;
SelectedDateInString = item.Date.ToString("ddd, dd MMMM");
SelectedDate = CurrentDate;
}
});
}
}
//Executed on the Tap of Cancel Button.
public ICommand CancelCommand
{
get
{
return new Command(() =>
{
SelectedDate = null;
});
}
}
//Executed on the Tap of > Sign.
public ICommand NextMonthCommand
{
get
{
return new Command(() =>
{
if (CurrentDate != null)
{
bool isMaxDateReach = false;
if (MaxYear == CurrentDate.Year && CurrentDate.Month == 12)
{
isMaxDateReach = true;
}
if (!isMaxDateReach)
{
CurrentDate = CurrentDate.Date.AddMonths(1);
BindDates(CurrentDate.Year, CurrentDate.Month, CurrentDate.ToString("MMM"), null);
}
}
});
}
}
//Executed on the Tap of < Sign.
public ICommand PreviousMonthCommand
{
get
{
return new Command(() =>
{
if (CurrentDate != null)
{
bool isMinDateReach = false;
if (MinYear == CurrentDate.Year && CurrentDate.Month == 1)
{
isMinDateReach = true;
}
if (!isMinDateReach)
{
CurrentDate = CurrentDate.Date.AddMonths(-1);
BindDates(CurrentDate.Year, CurrentDate.Month, CurrentDate.ToString("MMM"), null);
}
}
});
}
}
public ICommand YearSelectionCommand
{
get
{
return new Command<int>((year) =>
{
if (year > 0 && SelectedDate.HasValue)
{
DateTime newDate;
try
{
newDate = new DateTime(year, (int)SelectedDate?.Month, (int)SelectedDate?.Day);
}
catch (ArgumentOutOfRangeException ex)
{
newDate = new DateTime(year, (int)SelectedDate?.Month, (int)SelectedDate?.Day - 1);
}
BindDates(year, newDate.Month, newDate.ToString("MMM"), newDate);
IsCalendarDetailsVisible = true;
}
});
}
}
public ICommand ShowYearListCommand
{
get
{
return new Command(() =>
{
if (CurrentDate != null)
{
IsCalendarDetailsVisible = !IsCalendarDetailsVisible;
}
});
}
}
#endregion
}
}
Models
CalendarModel.cs