--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.None, 8675310, "CollectionView Header/Footer Strings", PlatformAffected.All)]
+ public class CollectionViewHeaderFooterString : TestNavigationPage
+ {
+ protected override void Init()
+ {
+#if APP
+ FlagTestHelpers.SetCollectionViewTestFlag();
+
+ PushAsync(new GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterString());
+#endif
+ }
+
+#if UITEST && __ANDROID__ // TODO ezhart When this feature is implemented on iOS, update this check
+ [Test]
+ public void CollectionViewHeaderAndFooterUsingStrings()
+ {
+ RunningApp.WaitForElement("Just a string as a header");
+ RunningApp.WaitForElement("This footer is also a string");
+ }
+#endif
+ }
+
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.None, 8675311, "CollectionView Header/Footer Template", PlatformAffected.All)]
+ public class CollectionViewHeaderFooterTemplate : TestNavigationPage
+ {
+ protected override void Init()
+ {
+#if APP
+ FlagTestHelpers.SetCollectionViewTestFlag();
+
+ PushAsync(new GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterTemplate());
+#endif
+ }
+
+#if UITEST && __ANDROID__ // TODO ezhart When this feature is implemented on iOS, update this check
+ [Test]
+ public void CollectionViewHeaderAndFooterUsingTemplates()
+ {
+ RunningApp.WaitForElement("This Is A Header");
+ RunningApp.WaitForElement("This Is A Footer");
+ }
+#endif
+
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.CollectionView)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.None, 8675312, "CollectionView Header/Footer View", PlatformAffected.All)]
+ public class CollectionViewHeaderFooterView : TestNavigationPage
+ {
+ protected override void Init()
+ {
+#if APP
+ FlagTestHelpers.SetCollectionViewTestFlag();
+
+ PushAsync(new GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterView());
+#endif
+ }
+
+#if UITEST && __ANDROID__ // TODO ezhart When this feature is implemented on iOS, update this check
+ [Test]
+ public void CollectionViewHeaderAndFooterUsingViews()
+ {
+ RunningApp.WaitForElement("This Is A Header");
+ RunningApp.WaitForElement("This Is A Footer");
+ }
+#endif
+ }
+}
<Import_RootNamespace>Xamarin.Forms.Controls.Issues</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)CollectionViewHeaderFooterString.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)CollectionViewHeaderFooterTemplate.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)CollectionViewHeaderFooterView.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CollectionViewItemsUpdatingScrollMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5046.xaml.cs">
<DependentUpon>Issue5046.xaml</DependentUpon>
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ScrollModeGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries;
+using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
{
GalleryBuilder.NavButton("Grouping Galleries", () => new GroupingGallery(), Navigation),
GalleryBuilder.NavButton("Scroll Mode Galleries", () => new ScrollModeGallery(), Navigation),
GalleryBuilder.NavButton("Alternate Layout Galleries", () => new AlternateLayoutGallery(), Navigation),
+ GalleryBuilder.NavButton("Header/Footer Galleries", () => new HeaderFooterGallery(), Navigation),
}
};
}
--- /dev/null
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries
+{
+ internal class HeaderFooterGallery : ContentPage
+ {
+ public HeaderFooterGallery()
+ {
+ var descriptionLabel =
+ new Label { Text = "Header/Footer Galleries", Margin = new Thickness(2, 2, 2, 2) };
+
+ Title = "Header/Footer Galleries";
+
+ Content = new ScrollView
+ {
+ Content = new StackLayout
+ {
+ Children =
+ {
+ descriptionLabel,
+ GalleryBuilder.NavButton("Header/Footer (String)", () => new HeaderFooterString(), Navigation),
+ GalleryBuilder.NavButton("Header/Footer (Forms View)", () => new HeaderFooterView(), Navigation),
+ GalleryBuilder.NavButton("Header/Footer (Template)", () => new HeaderFooterTemplate(), Navigation),
+ GalleryBuilder.NavButton("Header/Footer (Grid)", () => new HeaderFooterGrid(), Navigation),
+ }
+ }
+ };
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:d="http://xamarin.com/schemas/2014/forms/design"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterGrid">
+ <ContentPage.Content>
+ <CollectionView x:Name="CollectionView" >
+ <CollectionView.ItemsLayout>
+ <GridItemsLayout Span="3" Orientation="Vertical" HorizontalItemSpacing="4" VerticalItemSpacing="2"></GridItemsLayout>
+ </CollectionView.ItemsLayout>
+
+ <CollectionView.Header>
+
+ <Grid>
+ <Image Source="oasis.jpg" Aspect="AspectFill" HeightRequest="60"></Image>
+ <Label Text="This Is A Header" TextColor="AntiqueWhite" HorizontalTextAlignment="Center"
+ FontAttributes="Bold" FontSize="36" />
+ </Grid>
+
+ </CollectionView.Header>
+
+ <CollectionView.Footer>
+
+ <Grid>
+ <Image Source="cover1.jpg" Aspect="AspectFill" HeightRequest="80"></Image>
+ <Label Text="This Is A Footer" TextColor="AntiqueWhite" HorizontalTextAlignment="Center" Rotation="10"
+ FontAttributes="Bold" FontSize="20" />
+ </Grid>
+
+ </CollectionView.Footer>
+
+ </CollectionView>
+ </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class HeaderFooterGrid : ContentPage
+ {
+ readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(10);
+
+ public HeaderFooterGrid()
+ {
+ InitializeComponent();
+
+ CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate();
+ CollectionView.ItemsSource = _demoFilteredItemSource.Items;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:d="http://xamarin.com/schemas/2014/forms/design"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterString">
+ <ContentPage.Content>
+
+ <CollectionView x:Name="CollectionView" Header="Just a string as a header" Footer="This footer is also a string">
+
+ </CollectionView>
+
+ </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class HeaderFooterString : ContentPage
+ {
+ readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(3);
+
+ public HeaderFooterString()
+ {
+ InitializeComponent();
+
+ CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate();
+ CollectionView.ItemsSource = _demoFilteredItemSource.Items;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:d="http://xamarin.com/schemas/2014/forms/design"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterTemplate">
+ <ContentPage.Content>
+ <CollectionView x:Name="CollectionView" Header="{Binding .}" Footer="{Binding .}" ItemsSource="{Binding Items}">
+
+ <CollectionView.HeaderTemplate>
+ <DataTemplate>
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="50"></RowDefinition>
+ <RowDefinition Height="20"></RowDefinition>
+ </Grid.RowDefinitions>
+ <Image Source="oasis.jpg" Aspect="AspectFill" HeightRequest="80">
+ <Image.GestureRecognizers>
+ <TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding TapCommand}"></TapGestureRecognizer>
+ </Image.GestureRecognizers>
+ </Image>
+ <Label Text="{Binding CurrentTime}" TextColor="AntiqueWhite" HorizontalTextAlignment="Center"
+ FontAttributes="Bold" FontSize="36" InputTransparent="True" />
+ <Label Grid.Row="1" Text="This Is A Header"></Label>
+ </Grid>
+ </DataTemplate>
+ </CollectionView.HeaderTemplate>
+
+ <CollectionView.FooterTemplate>
+ <DataTemplate>
+ <Grid>
+ <Grid.RowDefinitions>
+ <RowDefinition Height="50"></RowDefinition>
+ <RowDefinition Height="20"></RowDefinition>
+ </Grid.RowDefinitions>
+ <Image Source="cover1.jpg" Aspect="AspectFill" HeightRequest="50">
+ <Image.GestureRecognizers>
+ <TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding TapCommand}"></TapGestureRecognizer>
+ </Image.GestureRecognizers>
+ </Image>
+ <Label Text="{Binding CurrentTime}" TextColor="AntiqueWhite" HorizontalTextAlignment="Center"
+ FontAttributes="Bold" FontSize="20" InputTransparent="True" />
+
+ <Label Grid.Row="1" Text="This Is A Footer"></Label>
+ </Grid>
+ </DataTemplate>
+ </CollectionView.FooterTemplate>
+
+ </CollectionView>
+ </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+using Xamarin.Forms.Xaml;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class HeaderFooterTemplate : ContentPage
+ {
+ public HeaderFooterTemplate()
+ {
+ InitializeComponent();
+
+ CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate();
+
+ BindingContext = new HeaderFooterDemoModel();
+ }
+
+ [Preserve(AllMembers = true)]
+ class HeaderFooterDemoModel : INotifyPropertyChanged
+ {
+ readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(3);
+ DateTime _currentTime;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public HeaderFooterDemoModel()
+ {
+ CurrentTime = DateTime.Now;
+ }
+
+ void OnPropertyChanged([CallerMemberName] string property = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+
+ public ObservableCollection<CollectionViewGalleryTestItem> Items => _demoFilteredItemSource.Items;
+
+ public ICommand TapCommand => new Command(()=> { CurrentTime = DateTime.Now; });
+
+ public DateTime CurrentTime
+ {
+ get => _currentTime;
+ set
+ {
+ if (value == _currentTime)
+ {
+ return;
+ }
+
+ _currentTime = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:d="http://xamarin.com/schemas/2014/forms/design"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d"
+ x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries.HeaderFooterView">
+ <ContentPage.Content>
+
+ <CollectionView x:Name="CollectionView">
+
+ <CollectionView.Header>
+
+ <Grid>
+ <Image Source="oasis.jpg" Aspect="AspectFill" HeightRequest="100"></Image>
+ <Label Text="This Is A Header" TextColor="AntiqueWhite" HorizontalTextAlignment="Center"
+ FontAttributes="Bold" FontSize="36" />
+ </Grid>
+
+ </CollectionView.Header>
+
+ <CollectionView.Footer>
+
+ <Grid>
+ <Image Source="cover1.jpg" Aspect="AspectFill" HeightRequest="80"></Image>
+ <Label Text="This Is A Footer" TextColor="AntiqueWhite" HorizontalTextAlignment="Center" Rotation="10"
+ FontAttributes="Bold" FontSize="20" />
+ </Grid>
+
+ </CollectionView.Footer>
+
+ </CollectionView>
+
+ </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
--- /dev/null
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class HeaderFooterView : ContentPage
+ {
+ readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(3);
+
+ public HeaderFooterView()
+ {
+ InitializeComponent();
+
+ CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate();
+ CollectionView.ItemsSource = _demoFilteredItemSource.Items;
+ }
+ }
+}
\ No newline at end of file
<Compile Update="GalleryPages\BindableLayoutGalleryPage.xaml.cs">
<DependentUpon>BindableLayoutGalleryPage.xaml</DependentUpon>
</Compile>
- <Compile Update="GalleryPages\CollectionViewGalleries\EmptyViewGalleries\EmptyViewLoadSimulateGallery.xaml.cs">
- <DependentUpon>EmptyViewLoadSimulateGallery.xaml</DependentUpon>
- </Compile>
- <Compile Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\PreselectedItemsGallery.xaml.cs">
- <DependentUpon>PreselectedItemsGallery.xaml</DependentUpon>
- </Compile>
<Compile Update="GalleryPages\VisualStateManagerGalleries\OnPlatformExample.xaml.cs">
<DependentUpon>OnPlatformExample.xaml</DependentUpon>
</Compile>
<EmbeddedResource Update="GalleryPages\BindableLayoutGalleryPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
- <EmbeddedResource Update="GalleryPages\CollectionViewGalleries\EmptyViewGalleries\EmptyViewSwapGallery.xaml">
- <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
- </EmbeddedResource>
- <EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\MultipleBoundSelection.xaml">
- <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
- </EmbeddedResource>
- <EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\PreselectedItemsGallery.xaml">
- <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
- </EmbeddedResource>
- <EmbeddedResource Update="GalleryPages\CollectionViewGalleries\DataTemplateSelectorGallery.xaml">
- <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
- </EmbeddedResource>
- <EmbeddedResource Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\SelectionChangedCommandParameter.xaml">
- <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
- </EmbeddedResource>
<EmbeddedResource Update="GalleryPages\MapGallery.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</EmbeddedResource>
</ItemGroup>
- <ItemGroup>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\BasicGrouping.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\GroupingNoTemplates.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\GroupingPlusSelection.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\MeasureFirstStrategy.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\SomeEmptyGroups.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\GroupingGalleries\SwitchGrouping.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\ScrollModeGalleries\ScrollModeTestGallery.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- <None Update="GalleryPages\CollectionViewGalleries\SelectionGalleries\SelectionChangedCommandParameter.xaml">
- <Generator>MSBuild:Compile</Generator>
- </None>
- </ItemGroup>
<Target Name="CreateControllGalleryConfig" BeforeTargets="Build">
<CreateItem Include="blank.config">
<Output TaskParameter="Include" ItemName="ConfigFile" />
set => SetValue(HorizontalScrollBarVisibilityProperty, value);
}
-
public static readonly BindableProperty VerticalScrollBarVisibilityProperty = BindableProperty.Create(
nameof(VerticalScrollBarVisibility),
typeof(ScrollBarVisibility),
set => SetValue(RemainingItemsThresholdProperty, value);
}
+ public static readonly BindableProperty HeaderProperty =
+ BindableProperty.Create(nameof(Header), typeof(object), typeof(ItemsView), null);
+
+ public object Header
+ {
+ get => GetValue(HeaderProperty);
+ set => SetValue(HeaderProperty, value);
+ }
+
+ public static readonly BindableProperty HeaderTemplateProperty =
+ BindableProperty.Create(nameof(HeaderTemplate), typeof(DataTemplate), typeof(ItemsView), null);
+
+ public DataTemplate HeaderTemplate
+ {
+ get => (DataTemplate)GetValue(HeaderTemplateProperty);
+ set => SetValue(HeaderTemplateProperty, value);
+ }
+
+ public static readonly BindableProperty FooterProperty =
+ BindableProperty.Create(nameof(Footer), typeof(object), typeof(ItemsView), null);
+
+ public object Footer
+ {
+ get => GetValue(FooterProperty);
+ set => SetValue(FooterProperty, value);
+ }
+
+ public static readonly BindableProperty FooterTemplateProperty =
+ BindableProperty.Create(nameof(FooterTemplate), typeof(DataTemplate), typeof(ItemsView), null);
+
+ public DataTemplate FooterTemplate
+ {
+ get => (DataTemplate)GetValue(FooterTemplateProperty);
+ set => SetValue(FooterTemplateProperty, value);
+ }
+
public void AddLogicalChild(Element element)
{
_logicalChildren.Add(element);
namespace Xamarin.Forms.Platform.Android
{
- public class EmptyViewAdapter : RecyclerView.Adapter
+ public partial class EmptyViewAdapter : RecyclerView.Adapter
{
int _itemViewType;
object _emptyView;
templatedItemViewHolder.Bind(EmptyView, ItemsView);
}
- if (!(holder is EmptyViewHolder emptyViewHolder))
+ if (!(holder is SimpleViewHolder))
{
return;
}
if (!(EmptyView is View formsView))
{
// No template, EmptyView is not a Forms View, so just display EmptyView.ToString
- return new EmptyViewHolder(CreateTextView(EmptyView?.ToString(), context), null);
+ return SimpleViewHolder.FromText(EmptyView?.ToString(), context);
}
// EmptyView is a Forms View; display that
- var itemContentControl = new SizedItemContentView(context, () => parent.Width, () => parent.Height);
- itemContentControl.RealizeContent(formsView);
- return new EmptyViewHolder(itemContentControl, formsView);
+ return SimpleViewHolder.FromFormsView(formsView, context, () => parent.Width, () => parent.Height);
}
var itemContentView = new SizedItemContentView(parent.Context, () => parent.Width, () => parent.Height);
{
return _itemViewType;
}
-
- static TextView CreateTextView(string text, Context context)
- {
- var textView = new TextView(context) { Text = text };
- var layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent,
- ViewGroup.LayoutParams.MatchParent);
- textView.LayoutParameters = layoutParams;
- textView.Gravity = GravityFlags.Center;
- return textView;
- }
-
- internal class EmptyViewHolder : RecyclerView.ViewHolder
- {
- public EmptyViewHolder(global::Android.Views.View itemView, View rootElement) : base(itemView)
- {
- View = rootElement;
- }
-
- public View View { get; }
- }
}
}
\ No newline at end of file
--- /dev/null
+using Android.Support.V7.Widget;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class GridLayoutSpanSizeLookup : GridLayoutManager.SpanSizeLookup
+ {
+ readonly GridItemsLayout _gridItemsLayout;
+ readonly RecyclerView _recyclerView;
+
+ public GridLayoutSpanSizeLookup(GridItemsLayout gridItemsLayout, RecyclerView recyclerView)
+ {
+ _gridItemsLayout = gridItemsLayout;
+ _recyclerView = recyclerView;
+ }
+
+ public override int GetSpanSize(int position)
+ {
+ var itemViewType = _recyclerView.GetAdapter().GetItemViewType(position);
+
+ if (itemViewType == ItemViewType.Header || itemViewType == ItemViewType.Footer)
+ {
+ return _gridItemsLayout.Span;
+ }
+
+ return 1;
+ }
+ }
+}
\ No newline at end of file
internal void RealizeContent(View view)
{
-
Content = CreateRenderer(view, Context);
AddView(Content.View);
Content.Element.MeasureInvalidated += ElementMeasureInvalidated;
--- /dev/null
+namespace Xamarin.Forms.Platform.Android
+{
+ public static class ItemViewType
+ {
+ public const int TextItem = 41;
+ public const int TemplatedItem = 42;
+ public const int Header = 43;
+ public const int Footer = 44;
+ }
+}
\ No newline at end of file
{
public class ItemsViewAdapter : RecyclerView.Adapter
{
- const int TextView = 41;
- const int TemplatedView = 42;
-
protected readonly ItemsView ItemsView;
readonly Func<View, Context, ItemContentView> _createItemContentView;
internal readonly IItemsViewSource ItemsSource;
+
bool _disposed;
ASize _size;
+ bool _usingItemTemplate = false;
+ int _headerOffset = 0;
+ bool _hasFooter;
+
internal ItemsViewAdapter(ItemsView itemsView, Func<View, Context, ItemContentView> createItemContentView = null)
{
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewAdapter));
- ItemsView = itemsView;
+ ItemsView = itemsView ?? throw new ArgumentNullException(nameof(itemsView));
+
+ UpdateUsingItemTemplate();
+ UpdateHeaderOffset();
+ UpdateHasFooter();
+
+ ItemsView.PropertyChanged += ItemsViewPropertyChanged;
+
_createItemContentView = createItemContentView;
ItemsSource = ItemsSourceFactory.Create(itemsView.ItemsSource, this);
}
}
+ private void ItemsViewPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs property)
+ {
+ if (property.Is(ItemsView.HeaderProperty))
+ {
+ UpdateHeaderOffset();
+ }
+ else if (property.Is(ItemsView.ItemTemplateProperty))
+ {
+ UpdateUsingItemTemplate();
+ }
+ else if (property.Is(ItemsView.ItemTemplateProperty))
+ {
+ UpdateUsingItemTemplate();
+ }
+ else if (property.Is(ItemsView.FooterProperty))
+ {
+ UpdateHasFooter();
+ }
+ }
+
public override void OnViewRecycled(Object holder)
{
if (holder is TemplatedItemViewHolder templatedItemViewHolder)
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
+ if (IsHeader(position))
+ {
+ if (holder is TemplatedItemViewHolder templatedItemViewHolder)
+ {
+ BindTemplatedItemViewHolder(templatedItemViewHolder, ItemsView.Header);
+ }
+
+ return;
+ }
+
+ if (IsFooter(position))
+ {
+ if (holder is TemplatedItemViewHolder templatedItemViewHolder)
+ {
+ BindTemplatedItemViewHolder(templatedItemViewHolder, ItemsView.Footer);
+ }
+
+ return;
+ }
+
+ var itemsSourcePosition = position - _headerOffset;
+
switch (holder)
{
case TextViewHolder textViewHolder:
- textViewHolder.TextView.Text = ItemsSource[position].ToString();
+ textViewHolder.TextView.Text = ItemsSource[itemsSourcePosition].ToString();
break;
case TemplatedItemViewHolder templatedItemViewHolder:
- if (ItemsView.ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem)
- {
- templatedItemViewHolder.Bind(ItemsSource[position], ItemsView, SetStaticSize, _size);
- }
- else
- {
- templatedItemViewHolder.Bind(ItemsSource[position], ItemsView);
- }
-
+ BindTemplatedItemViewHolder(templatedItemViewHolder, ItemsSource[itemsSourcePosition]);
break;
}
}
+ void BindTemplatedItemViewHolder(TemplatedItemViewHolder templatedItemViewHolder, object context)
+ {
+ if (ItemsView.ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem)
+ {
+ templatedItemViewHolder.Bind(context, ItemsView, SetStaticSize, _size);
+ }
+ else
+ {
+ templatedItemViewHolder.Bind(context, ItemsView);
+ }
+ }
+
void SetStaticSize(ASize size)
{
_size = size;
{
var context = parent.Context;
- if(viewType == TextView)
+ if (viewType == ItemViewType.Header)
+ {
+ return CreateHeaderFooterViewHolder(ItemsView.Header, ItemsView.HeaderTemplate, context);
+ }
+
+ if (viewType == ItemViewType.Footer)
+ {
+ return CreateHeaderFooterViewHolder(ItemsView.Footer, ItemsView.FooterTemplate, context);
+ }
+
+ if (viewType == ItemViewType.TextItem)
{
var view = new TextView(context);
return new TextViewHolder(view);
return new TemplatedItemViewHolder(itemContentView, ItemsView.ItemTemplate);
}
- public override int ItemCount => ItemsSource.Count;
+ public override int ItemCount => ItemsSource.Count + _headerOffset + (_hasFooter ? 1 : 0);
public override int GetItemViewType(int position)
{
- // Does the ItemsView have a DataTemplate?
- // TODO ezhart We could probably cache this instead of having to GetValue every time
- if (ItemsView.ItemTemplate == null)
+ if (IsHeader(position))
+ {
+ return ItemViewType.Header;
+ }
+
+ if (IsFooter(position))
{
- // No template, just use the Text view
- return TextView;
+ return ItemViewType.Footer;
}
- return TemplatedView;
+ if (_usingItemTemplate)
+ {
+ return ItemViewType.TemplatedItem;
+ }
+
+ // No template, just use the Text view
+ return ItemViewType.TextItem;
}
protected override void Dispose(bool disposing)
if (disposing)
{
ItemsSource?.Dispose();
+ ItemsView.PropertyChanged -= ItemsViewPropertyChanged;
}
_disposed = true;
{
if (ItemsSource[n] == item)
{
- return n;
+ return n + _headerOffset;
}
}
return -1;
}
+
+ void UpdateUsingItemTemplate()
+ {
+ _usingItemTemplate = ItemsView.ItemTemplate != null;
+ }
+
+ void UpdateHeaderOffset()
+ {
+ _headerOffset = ItemsView.Header == null ? 0 : 1;
+ }
+
+ void UpdateHasFooter()
+ {
+ _hasFooter = ItemsView.Footer != null;
+ }
+
+ bool IsHeader(int position)
+ {
+ return _headerOffset > 0 && position == 0;
+ }
+
+ bool IsFooter(int position)
+ {
+ return _hasFooter && position > ItemsSource.Count;
+ }
+
+ RecyclerView.ViewHolder CreateHeaderFooterViewHolder(object content, DataTemplate template, Context context)
+ {
+ if (template != null)
+ {
+ var footerContentView = new ItemContentView(context);
+ return new TemplatedItemViewHolder(footerContentView, template);
+ }
+
+ if (content is View formsView)
+ {
+ return SimpleViewHolder.FromFormsView(formsView, context);
+ }
+
+ // No template, Footer is not a Forms View, so just display Footer.ToString
+ return SimpleViewHolder.FromText(content?.ToString(), context, fill: false);
+ }
}
}
\ No newline at end of file
GridLayoutManager CreateGridLayout(GridItemsLayout gridItemsLayout)
{
- return new GridLayoutManager(Context, gridItemsLayout.Span,
+ var gridLayoutManager = new GridLayoutManager(Context, gridItemsLayout.Span,
gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal
? LinearLayoutManager.Horizontal
: LinearLayoutManager.Vertical,
false);
+
+ // Give the layout a way to determine that headers/footers span multiple rows/columns
+ gridLayoutManager.SetSpanSizeLookup(new GridLayoutSpanSizeLookup(gridItemsLayout, this));
+
+ return gridLayoutManager;
}
void OnElementChanged(ItemsView oldElement, ItemsView newElement)
--- /dev/null
+using System;
+using Android.Content;
+using Android.Support.V7.Widget;
+using Android.Views;
+using Android.Widget;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class SimpleViewHolder : RecyclerView.ViewHolder
+ {
+ public SimpleViewHolder(global::Android.Views.View itemView, View rootElement) : base(itemView)
+ {
+ View = rootElement;
+ }
+
+ public View View { get; }
+
+ public static SimpleViewHolder FromText(string text, Context context, bool fill = true)
+ {
+ var textView = new TextView(context) { Text = text };
+ if (fill)
+ {
+ var layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent,
+ ViewGroup.LayoutParams.MatchParent);
+ textView.LayoutParameters = layoutParams;
+ }
+
+ textView.Gravity = GravityFlags.Center;
+
+ return new SimpleViewHolder(textView, null);
+ }
+
+ public static SimpleViewHolder FromFormsView(View formsView, Context context, Func<int> width, Func<int> height)
+ {
+ var itemContentControl = new SizedItemContentView(context, width, height);
+ itemContentControl.RealizeContent(formsView);
+ return new SimpleViewHolder(itemContentControl, formsView);
+ }
+
+ public static SimpleViewHolder FromFormsView(View formsView, Context context)
+ {
+ var itemContentControl = new ItemContentView(context);
+ itemContentControl.RealizeContent(formsView);
+ return new SimpleViewHolder(itemContentControl, formsView);
+ }
+ }
+}
\ No newline at end of file
double _adjustedVerticalSpacing = -1;
double _horizontalSpacing;
double _adjustedHorizontalSpacing = -1;
- int _span = 1;
public SpacingItemDecoration(IItemsLayout itemsLayout)
{
_orientation = gridItemsLayout.Orientation;
_horizontalSpacing = gridItemsLayout.HorizontalItemSpacing;
_verticalSpacing = gridItemsLayout.VerticalItemSpacing;
- _span = gridItemsLayout.Span;
break;
case ListItemsLayout listItemsLayout:
_orientation = listItemsLayout.Orientation;
{
base.GetItemOffsets(outRect, view, parent, state);
- var position = parent.GetChildAdapterPosition(view);
-
if (_adjustedVerticalSpacing == -1)
{
_adjustedVerticalSpacing = parent.Context.ToPixels(_verticalSpacing);
_adjustedHorizontalSpacing = parent.Context.ToPixels(_horizontalSpacing);
}
- var firstInRow = false;
- var firstInCol = false;
+ var itemViewType = parent.GetChildViewHolder(view).ItemViewType;
+
+ if (itemViewType == ItemViewType.Header)
+ {
+ outRect.Bottom = (int)_adjustedVerticalSpacing;
+ return;
+ }
+
+ if (itemViewType == ItemViewType.Footer)
+ {
+ return;
+ }
+
+ var spanIndex = 0;
+
+ if(view.LayoutParameters is GridLayoutManager.LayoutParams gridLayoutParameters)
+ {
+ spanIndex = gridLayoutParameters.SpanIndex;
+ }
if (_orientation == ItemsLayoutOrientation.Vertical)
{
- firstInRow = position >= _span && position % _span == 0;
- firstInCol = position < _span;
+ outRect.Left = spanIndex == 0 ? 0 : (int)_adjustedHorizontalSpacing;
+ outRect.Bottom = (int)_adjustedVerticalSpacing;
}
if (_orientation == ItemsLayoutOrientation.Horizontal)
{
- firstInCol = position >= _span && position % _span == 0;
- firstInRow = position < _span;
+ outRect.Top = spanIndex == 0 ? 0 : (int)_adjustedVerticalSpacing;
+ outRect.Right = (int)_adjustedHorizontalSpacing;
}
-
- outRect.Top = firstInCol ? 0 : (int)_adjustedVerticalSpacing;
- outRect.Left = firstInRow ? 0 : (int)_adjustedHorizontalSpacing;
}
}
}
\ No newline at end of file
<Compile Include="CollectionView\CenterSnapHelper.cs" />
<Compile Include="CollectionView\DataChangeObserver.cs" />
<Compile Include="CollectionView\EmptySource.cs" />
- <Compile Include="CollectionView\NongreedySnapHelper.cs" />
<Compile Include="CollectionView\RecyclerViewScrollListener.cs" />
+ <Compile Include="CollectionView\GridLayoutSpanSizeLookup.cs" />
+ <Compile Include="CollectionView\NongreedySnapHelper.cs" />
+ <Compile Include="CollectionView\SimpleViewHolder.cs" />
<Compile Include="CollectionView\SingleSnapHelper.cs" />
<Compile Include="CollectionView\EmptyViewAdapter.cs" />
<Compile Include="CollectionView\EndSingleSnapHelper.cs" />
<Compile Include="CollectionView\StartSnapHelper.cs" />
<Compile Include="CollectionView\TemplatedItemViewHolder.cs" />
<Compile Include="CollectionView\TextViewHolder.cs" />
+ <Compile Include="CollectionView\ItemViewType.cs" />
<Compile Include="Elevation.cs" />
<Compile Include="Extensions\DrawableExtensions.cs" />
<Compile Include="Extensions\EntryRendererExtensions.cs" />