Android CollectionView Header/Footer (non-sticky) (#6948)
authorE.Z. Hart <hartez@users.noreply.github.com>
Tue, 30 Jul 2019 18:54:43 +0000 (12:54 -0600)
committerGitHub <noreply@github.com>
Tue, 30 Jul 2019 18:54:43 +0000 (12:54 -0600)
25 files changed:
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterString.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterTemplate.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterView.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGallery.cs
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
Xamarin.Forms.Core/Items/ItemsView.cs
Xamarin.Forms.Platform.Android/CollectionView/EmptyViewAdapter.cs
Xamarin.Forms.Platform.Android/CollectionView/GridLayoutSpanSizeLookup.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/CollectionView/ItemContentView.cs
Xamarin.Forms.Platform.Android/CollectionView/ItemViewType.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/CollectionView/ItemsViewAdapter.cs
Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs
Xamarin.Forms.Platform.Android/CollectionView/SimpleViewHolder.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/CollectionView/SpacingItemDecoration.cs
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterString.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterString.cs
new file mode 100644 (file)
index 0000000..aa184f0
--- /dev/null
@@ -0,0 +1,41 @@
+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
+       }
+
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterTemplate.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterTemplate.cs
new file mode 100644 (file)
index 0000000..55f3305
--- /dev/null
@@ -0,0 +1,41 @@
+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
+
+       }
+}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterView.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewHeaderFooterView.cs
new file mode 100644 (file)
index 0000000..63870f7
--- /dev/null
@@ -0,0 +1,40 @@
+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
+       }
+}
index 45e0f6e..cc60cfd 100644 (file)
@@ -9,6 +9,9 @@
     <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>
index 2ed6eae..d27f48f 100644 (file)
@@ -3,6 +3,7 @@ using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGaller
 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
 {
@@ -27,6 +28,7 @@ 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),
                                }
                        };
                }
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs
new file mode 100644 (file)
index 0000000..12e848c
--- /dev/null
@@ -0,0 +1,28 @@
+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),
+                                       }
+                               }
+                       };
+               }
+       }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml
new file mode 100644 (file)
index 0000000..04aa455
--- /dev/null
@@ -0,0 +1,36 @@
+<?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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGrid.xaml.cs
new file mode 100644 (file)
index 0000000..5c47695
--- /dev/null
@@ -0,0 +1,18 @@
+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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml
new file mode 100644 (file)
index 0000000..e02de9d
--- /dev/null
@@ -0,0 +1,15 @@
+<?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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterString.xaml.cs
new file mode 100644 (file)
index 0000000..c0ec09a
--- /dev/null
@@ -0,0 +1,18 @@
+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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml
new file mode 100644 (file)
index 0000000..66f18fa
--- /dev/null
@@ -0,0 +1,52 @@
+<?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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterTemplate.xaml.cs
new file mode 100644 (file)
index 0000000..b466c5a
--- /dev/null
@@ -0,0 +1,61 @@
+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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml
new file mode 100644 (file)
index 0000000..48ae005
--- /dev/null
@@ -0,0 +1,35 @@
+<?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
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterView.xaml.cs
new file mode 100644 (file)
index 0000000..3b20151
--- /dev/null
@@ -0,0 +1,18 @@
+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
index 84c40ae..794a662 100644 (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" />
index 8335530..c93b8f1 100644 (file)
@@ -72,7 +72,6 @@ namespace Xamarin.Forms
                        set => SetValue(HorizontalScrollBarVisibilityProperty, value);
                }
 
-
                public static readonly BindableProperty VerticalScrollBarVisibilityProperty = BindableProperty.Create(
                        nameof(VerticalScrollBarVisibility),
                        typeof(ScrollBarVisibility),
@@ -94,6 +93,42 @@ namespace Xamarin.Forms
                        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);
index 881c098..352d91f 100644 (file)
@@ -8,7 +8,7 @@ using Object = Java.Lang.Object;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       public class EmptyViewAdapter : RecyclerView.Adapter
+       public partial class EmptyViewAdapter : RecyclerView.Adapter
        {
                int _itemViewType;
                object _emptyView;
@@ -70,7 +70,7 @@ namespace Xamarin.Forms.Platform.Android
                                templatedItemViewHolder.Bind(EmptyView, ItemsView);
                        }
 
-                       if (!(holder is EmptyViewHolder emptyViewHolder))
+                       if (!(holder is SimpleViewHolder))
                        {
                                return;
                        }
@@ -87,13 +87,11 @@ namespace Xamarin.Forms.Platform.Android
                                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);
@@ -104,25 +102,5 @@ namespace Xamarin.Forms.Platform.Android
                {
                        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
diff --git a/Xamarin.Forms.Platform.Android/CollectionView/GridLayoutSpanSizeLookup.cs b/Xamarin.Forms.Platform.Android/CollectionView/GridLayoutSpanSizeLookup.cs
new file mode 100644 (file)
index 0000000..346f0d4
--- /dev/null
@@ -0,0 +1,28 @@
+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
index 86b9267..7821991 100644 (file)
@@ -17,7 +17,6 @@ namespace Xamarin.Forms.Platform.Android
 
                internal void RealizeContent(View view)
                {
-                       
                        Content = CreateRenderer(view, Context);
                        AddView(Content.View);
                        Content.Element.MeasureInvalidated += ElementMeasureInvalidated;
diff --git a/Xamarin.Forms.Platform.Android/CollectionView/ItemViewType.cs b/Xamarin.Forms.Platform.Android/CollectionView/ItemViewType.cs
new file mode 100644 (file)
index 0000000..947480e
--- /dev/null
@@ -0,0 +1,10 @@
+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
index 375766d..64e02b8 100644 (file)
@@ -10,20 +10,29 @@ namespace Xamarin.Forms.Platform.Android
 {
        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);
 
@@ -33,6 +42,26 @@ namespace Xamarin.Forms.Platform.Android
                        }
                }
 
+               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)
@@ -45,25 +74,51 @@ namespace Xamarin.Forms.Platform.Android
 
                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;
@@ -73,7 +128,17 @@ namespace Xamarin.Forms.Platform.Android
                {
                        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);
@@ -83,19 +148,27 @@ namespace Xamarin.Forms.Platform.Android
                        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)
@@ -105,6 +178,7 @@ namespace Xamarin.Forms.Platform.Android
                                if (disposing)
                                {
                                        ItemsSource?.Dispose();
+                                       ItemsView.PropertyChanged -= ItemsViewPropertyChanged;
                                }
 
                                _disposed = true;
@@ -119,11 +193,53 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                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
index dcf144b..cf0f986 100644 (file)
@@ -179,11 +179,16 @@ namespace Xamarin.Forms.Platform.Android
 
                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)
diff --git a/Xamarin.Forms.Platform.Android/CollectionView/SimpleViewHolder.cs b/Xamarin.Forms.Platform.Android/CollectionView/SimpleViewHolder.cs
new file mode 100644 (file)
index 0000000..b5d03b2
--- /dev/null
@@ -0,0 +1,47 @@
+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
index a970e9e..63df04e 100644 (file)
@@ -12,7 +12,6 @@ namespace Xamarin.Forms.Platform.Android
                double _adjustedVerticalSpacing = -1;
                double _horizontalSpacing;
                double _adjustedHorizontalSpacing = -1;
-               int _span = 1;
 
                public SpacingItemDecoration(IItemsLayout itemsLayout)
                {
@@ -27,7 +26,6 @@ namespace Xamarin.Forms.Platform.Android
                                        _orientation = gridItemsLayout.Orientation;
                                        _horizontalSpacing = gridItemsLayout.HorizontalItemSpacing;
                                        _verticalSpacing = gridItemsLayout.VerticalItemSpacing;
-                                       _span = gridItemsLayout.Span;
                                        break;
                                case ListItemsLayout listItemsLayout:
                                        _orientation = listItemsLayout.Orientation;
@@ -47,8 +45,6 @@ namespace Xamarin.Forms.Platform.Android
                {
                        base.GetItemOffsets(outRect, view, parent, state);
 
-                       var position = parent.GetChildAdapterPosition(view);
-
                        if (_adjustedVerticalSpacing == -1)
                        {
                                _adjustedVerticalSpacing = parent.Context.ToPixels(_verticalSpacing);
@@ -59,23 +55,37 @@ namespace Xamarin.Forms.Platform.Android
                                _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
index 1c6de5e..46523d9 100644 (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" />