Android CollectionView Header/Footer (non-sticky) (#6948)

This commit is contained in:
E.Z. Hart 2019-07-30 12:54:43 -06:00 коммит произвёл GitHub
Родитель 857ec92860
Коммит dae2fe3b06
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 705 добавлений и 113 удалений

Просмотреть файл

@ -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
}
}

Просмотреть файл

@ -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
}
}

Просмотреть файл

@ -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
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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),
}
};
}

Просмотреть файл

@ -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),
}
}
};
}
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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();
}
}
}
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -38,12 +38,6 @@
<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>
@ -53,21 +47,6 @@
<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>
@ -130,32 +109,6 @@
</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" />

Просмотреть файл

@ -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);

Просмотреть файл

@ -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; }
}
}
}

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;
}
}

Просмотреть файл

@ -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))
{
// No template, just use the Text view
return TextView;
return ItemViewType.Header;
}
return TemplatedView;
if (IsFooter(position))
{
return ItemViewType.Footer;
}
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);
}
}
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -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);
}
}
}

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -71,8 +71,10 @@
<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" />
@ -98,6 +100,7 @@
<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" />