Handle DataTemplateSelector on iOS/Android CollectionView (#5429)
authorE.Z. Hart <hartez@users.noreply.github.com>
Wed, 27 Mar 2019 23:03:35 +0000 (17:03 -0600)
committerShane Neuville <shane94@hotmail.com>
Wed, 27 Mar 2019 23:03:35 +0000 (17:03 -0600)
* DataTemplateSelector working on Android for ItemTemplate and EmptyTemplate

* Demonstrate DataTemplateSelector working with EmptyViewTemplate

* Handle DataTemplateSelector on iOS CollectionView

* Add UI test
Fixes #4826

* Temporarily patching EditorRenderer to get tests running

* Add test for binding errors;
Fix binding errors on Android;

* Fix binding errors for iOS

* Add flag setting to allow UI test to run

* Fix rebase errors

23 files changed:
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml.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/CollectionViewGalleryTestItem.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateGallery.cs
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DemoFilteredItemSource.cs
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGalleryFilterInfo.cs [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewTemplateGallery.xaml.cs
Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/ItemsSourceGenerator.cs
Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
Xamarin.Forms.Core.UITests.Shared/Tests/CollectionViewUITests.cs
Xamarin.Forms.Platform.Android/CollectionView/CarouselViewRenderer.cs
Xamarin.Forms.Platform.Android/CollectionView/EmptyViewAdapter.cs
Xamarin.Forms.Platform.Android/CollectionView/ItemContentView.cs
Xamarin.Forms.Platform.Android/CollectionView/ItemsViewAdapter.cs
Xamarin.Forms.Platform.Android/CollectionView/ItemsViewRenderer.cs
Xamarin.Forms.Platform.Android/CollectionView/SelectableItemsViewAdapter.cs
Xamarin.Forms.Platform.Android/CollectionView/SizedItemContentView.cs
Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs
Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs
Xamarin.Forms.Platform.iOS/Renderers/EditorRenderer.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml
new file mode 100644 (file)
index 0000000..de02641
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<controls:TestContentPage
+    xmlns:controls="clr-namespace:Xamarin.Forms.Controls"
+    xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="Xamarin.Forms.Controls.Issues.CollectionViewBindingErrors">
+        <StackLayout>
+            <Label Text="The label below should read 'Binding Errors: 0'; if the number of binding errors is greater than zero, this test has failed."></Label>
+
+            <Label x:Name="BindingErrorCount" Text="Binding Errors: 0"></Label>
+
+            <CollectionView x:Name="CollectionView" ItemsSource="{Binding ItemsList}">
+                <CollectionView.ItemTemplate>
+                    <DataTemplate>
+                        <StackLayout>
+                            <Image Source="{Binding Image}" WidthRequest="100" HeightRequest="100"/>
+                            <Label Text="{Binding Caption}"></Label>
+                        </StackLayout>
+                    </DataTemplate>
+                </CollectionView.ItemTemplate>
+            </CollectionView>
+        </StackLayout>
+</controls:TestContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/CollectionViewBindingErrors.xaml.cs
new file mode 100644 (file)
index 0000000..0fd32ec
--- /dev/null
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+       [Category(UITestCategories.CollectionView)]
+#endif
+#if APP
+       [XamlCompilation(XamlCompilationOptions.Compile)]
+#endif
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.None, 68000, "Binding errors when CollectionView ItemsSource is set with a binding",
+               PlatformAffected.Android)]
+       public partial class CollectionViewBindingErrors : TestContentPage
+       {
+               public CollectionViewBindingErrors()
+               {
+#if APP
+                       Device.SetFlags(new List<string> { CollectionView.CollectionViewExperimental });
+
+                       InitializeComponent();
+
+                       var listener = new CountBindingErrors(BindingErrorCount);
+                       Log.Listeners.Add(listener);
+                       Disappearing += (obj, args) => { Log.Listeners.Remove(listener); };
+
+                       BindingContext = new BindingErrorsViewModel();
+#endif
+               }
+
+               protected override void Init()
+               {
+
+               }
+
+#if UITEST
+               [Test]
+               public void CollectionViewBindingErrorsShouldBeZero()
+               {
+                       RunningApp.WaitForElement("Binding Errors: 0");
+               }
+#endif
+               }
+
+       [Preserve(AllMembers = true)]
+       public class CollectionViewGalleryTestItem
+       {
+               public DateTime Date { get; set; }
+               public string Caption { get; set; }
+               public string Image { get; set; }
+               public int Index { get; set; }
+
+               public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
+               {
+                       Date = date;
+                       Caption = caption;
+                       Image = image;
+                       Index = index;
+               }
+
+               public override string ToString()
+               {
+                       return $"Item: {Index}";
+               }
+       }
+
+       [Preserve(AllMembers = true)]
+       internal class CountBindingErrors : LogListener
+       {
+               private readonly Label _errorCount;
+               int _count;
+
+               public CountBindingErrors(Label errorCount)
+               {
+                       _errorCount = errorCount;
+               }
+
+               public override void Warning(string category, string message)
+               {
+                       if (category == "Binding")
+                       {
+                               _count += 1;
+                       }
+
+                       _errorCount.Text = $"Binding Errors: {_count}";
+               }
+       }
+
+       [Preserve(AllMembers = true)]
+       internal class BindingErrorsViewModel
+       {
+               readonly string[] _imageOptions = {
+                       "cover1.jpg",
+                       "oasis.jpg",
+                       "photo.jpg",
+                       "Vegetables.jpg",
+                       "Fruits.jpg",
+                       "FlowerBuds.jpg",
+                       "Legumes.jpg"
+               };
+
+               List<CollectionViewGalleryTestItem> GenerateList()
+               {
+                       var items = new List<CollectionViewGalleryTestItem>();
+                       var images = _imageOptions;
+
+                       for (int n = 0; n < 100; n++)
+                       {
+                               items.Add(new CollectionViewGalleryTestItem(DateTime.Now.AddDays(n),
+                                       $"Item: {n}", images[n % images.Length], n));
+                       }
+
+                       return items;
+               }
+
+               List<CollectionViewGalleryTestItem> _items;
+
+               public List<CollectionViewGalleryTestItem> ItemsList
+               {
+                       get
+                       {
+                               if (_items == null)
+                               {
+                                       _items = GenerateList();
+                               }
+
+                               return _items;
+                       }
+               }
+       }
+}
\ No newline at end of file
index 0adefd1..3af4c9b 100644 (file)
     </Compile>
     <Compile Include="$(MSBuildThisFileDirectory)Issue4919.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue5461.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)CollectionViewBindingErrors.xaml.cs">
+      <DependentUpon>CollectionViewBindingErrors.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="$(MSBuildThisFileDirectory)Issue2102.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue1588.xaml.cs">
       <DependentUpon>Issue1588.xaml</DependentUpon>
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="$(MSBuildThisFileDirectory)CollectionViewBindingErrors.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </EmbeddedResource>
+  </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGalleryTestItem.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/CollectionViewGalleryTestItem.cs
new file mode 100644 (file)
index 0000000..20df727
--- /dev/null
@@ -0,0 +1,27 @@
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
+{
+       [Preserve(AllMembers = true)]
+       public class CollectionViewGalleryTestItem
+       {
+               public DateTime Date { get; set; }
+               public string Caption { get; set; }
+               public string Image { get; set; }
+               public int Index { get; set; }
+
+               public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
+               {
+                       Date = date;
+                       Caption = caption;
+                       Image = image;
+                       Index = index;
+               }
+
+               public override string ToString()
+               {
+                       return $"Item: {Index}";
+               }
+       }
+}
\ No newline at end of file
index 270e262..8070cbd 100644 (file)
 
                                                GalleryBuilder.NavButton("ItemSizing Strategy", () => 
                                                        new VariableSizeTemplateGridGallery (ItemsLayoutOrientation.Horizontal), Navigation),
-                                               
+
+                        GalleryBuilder.NavButton("DataTemplateSelector", () =>
+                            new DataTemplateSelectorGallery(), Navigation),
                                        }
-                               }
+                }
                        };
                }
        }
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml
new file mode 100644 (file)
index 0000000..b048254
--- /dev/null
@@ -0,0 +1,68 @@
+<?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:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries;assembly=Xamarin.Forms.Controls"
+             x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.DataTemplateSelectorGallery">
+    
+    <ContentPage.Resources>
+        <ResourceDictionary>
+            <DataTemplate x:Key="DefaultTemplate">
+                <Grid HeightRequest="50">
+                    <Grid.RowDefinitions>
+                        <RowDefinition></RowDefinition>
+                        <RowDefinition></RowDefinition>
+                    </Grid.RowDefinitions>
+
+                    <Image Source="coffee.png" AutomationId="weekday"/>
+
+                    <Label Grid.Row="1" Text="{Binding Date, StringFormat='{}{0:dddd}'}"></Label>
+                </Grid>
+            </DataTemplate>
+            <DataTemplate x:Key="WeekendTemplate">
+                <Grid HeightRequest="50">
+                    <Grid.RowDefinitions>
+                        <RowDefinition></RowDefinition>
+                        <RowDefinition></RowDefinition>
+                    </Grid.RowDefinitions>
+
+                    <Image Source="oasis.jpg" AutomationId="weekend"/>
+
+                    <Label Grid.Row="1" Text="It's the weekend! Woot!"></Label>
+                </Grid>
+            </DataTemplate>
+            
+            <DataTemplate x:Key="EmptyTemplate">
+                <StackLayout>
+                    <Label Text="{Binding ., StringFormat='({0}) does not match any day of the week.'}"></Label>
+                </StackLayout>
+            </DataTemplate>
+
+            <DataTemplate x:Key="SymbolsTemplate">
+                <StackLayout BackgroundColor="Red">
+                    <Label Text="{Binding ., StringFormat='({0}) _definitely_ does not match any day of the week.'}"></Label>
+                </StackLayout>
+            </DataTemplate>
+
+            <local:WeekendSelector x:Key="WeekendSelector"
+                    DefaultTemplate="{StaticResource DefaultTemplate}"
+                    FridayTemplate="{StaticResource WeekendTemplate}" />
+
+            <local:SearchTermSelector x:Key="SearchTermSelector"
+                    DefaultTemplate="{StaticResource EmptyTemplate}"
+                    SymbolsTemplate="{StaticResource SymbolsTemplate}" />
+
+        </ResourceDictionary>
+    </ContentPage.Resources>
+
+    <ContentPage.Content>
+
+        <StackLayout>
+
+            <SearchBar x:Name="SearchBar" Placeholder="Day of Week Filter" />
+
+            <CollectionView x:Name="CollectionView" ItemTemplate="{StaticResource WeekendSelector}" 
+                            EmptyViewTemplate="{StaticResource SearchTermSelector}"/>
+
+        </StackLayout>
+    </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/DataTemplateSelectorGallery.xaml.cs
new file mode 100644 (file)
index 0000000..15f93be
--- /dev/null
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
+{
+    [XamlCompilation(XamlCompilationOptions.Compile)]
+    public partial class DataTemplateSelectorGallery : ContentPage
+    {
+               DemoFilteredItemSource _demoFilteredItemSource;
+
+        public DataTemplateSelectorGallery()
+        {
+            InitializeComponent();
+
+                       _demoFilteredItemSource = new DemoFilteredItemSource(filter: ItemMatches);
+
+                       CollectionView.ItemsSource = _demoFilteredItemSource.Items;
+
+                       SearchBar.SearchCommand = new Command(() =>
+                       {
+                               _demoFilteredItemSource.FilterItems(SearchBar.Text);
+                               CollectionView.EmptyView = SearchBar.Text;
+                       });
+               }
+
+               private bool ItemMatches(string filter, CollectionViewGalleryTestItem item)
+               {
+                       if (String.IsNullOrEmpty(filter))
+                       {
+                               return true;
+                       }
+
+                       return item.Date.DayOfWeek.ToString().ToLower().Contains(filter.ToLower());
+               }
+       }
+
+    public class WeekendSelector : DataTemplateSelector
+    {
+        public DataTemplate FridayTemplate { get; set; }
+        public DataTemplate DefaultTemplate { get; set; }
+
+        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
+        {
+                       var dow = ((CollectionViewGalleryTestItem)item).Date.DayOfWeek;
+
+                       return dow == DayOfWeek.Saturday || dow == DayOfWeek.Sunday
+                ? FridayTemplate 
+                               : DefaultTemplate;
+        }
+    }
+
+       public class SearchTermSelector : DataTemplateSelector
+       {
+               public DataTemplate DefaultTemplate { get; set; }
+               public DataTemplate SymbolsTemplate { get; set; }
+
+               protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
+               {
+                       var search = ((string)item);
+
+                       return search.Any(c => !char.IsLetter(c))
+                               ? SymbolsTemplate
+                               : DefaultTemplate;
+               }
+       }
+}
\ No newline at end of file
index 5c280c7..1ea1e44 100644 (file)
@@ -8,10 +8,11 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
        internal class DemoFilteredItemSource
        {
                readonly List<CollectionViewGalleryTestItem> _source;
+               private readonly Func<string, CollectionViewGalleryTestItem, bool> _filter;
 
                public ObservableCollection<CollectionViewGalleryTestItem> Items { get; }
 
-               public DemoFilteredItemSource(int count = 50)
+               public DemoFilteredItemSource(int count = 50, Func<string, CollectionViewGalleryTestItem, bool> filter = null)
                {
                        _source = new List<CollectionViewGalleryTestItem>();
                        
@@ -32,11 +33,18 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
                                        $"{images[n % images.Length]}, {n}", images[n % images.Length], n));
                        }
                        Items = new ObservableCollection<CollectionViewGalleryTestItem>(_source);
+
+                       _filter = filter ?? ItemMatches;
+               }
+
+               private bool ItemMatches(string filter, CollectionViewGalleryTestItem item)
+               {
+                       return item.Caption.ToLower().Contains(filter.ToLower());
                }
 
                public void FilterItems(string filter)
                {
-                       var filteredItems = _source.Where(item => item.Caption.ToLower().Contains(filter.ToLower())).ToList();
+                       var filteredItems = _source.Where(item => _filter(filter, item)).ToList();
 
                        foreach (CollectionViewGalleryTestItem collectionViewGalleryTestItem in _source)
                        {
diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGalleryFilterInfo.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/EmptyViewGalleries/EmptyViewGalleryFilterInfo.cs
new file mode 100644 (file)
index 0000000..f540324
--- /dev/null
@@ -0,0 +1,29 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewGalleries
+{
+       [Preserve(AllMembers = true)]
+       public class EmptyViewGalleryFilterInfo : INotifyPropertyChanged
+       {
+               string _filter;
+
+               public string Filter
+               {
+                       get => _filter;
+                       set
+                       {
+                               _filter = value; 
+                               OnPropertyChanged();
+                       }
+               }
+
+               public event PropertyChangedEventHandler PropertyChanged;
+
+               void OnPropertyChanged([CallerMemberName] string propertyName = null)
+               {
+                       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+               }
+       }
+}
\ No newline at end of file
index 442b7b5..41a6dce 100644 (file)
@@ -1,7 +1,4 @@
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
-using Xamarin.Forms.Internals;
-using Xamarin.Forms.Xaml;
+using Xamarin.Forms.Xaml;
 
 namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewGalleries
 {
@@ -27,27 +24,4 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.EmptyViewG
                        });
                }
        }
-
-       [Preserve(AllMembers = true)]
-       public class EmptyViewGalleryFilterInfo : INotifyPropertyChanged
-       {
-               string _filter;
-
-               public string Filter
-               {
-                       get => _filter;
-                       set
-                       {
-                               _filter = value; 
-                               OnPropertyChanged();
-                       }
-               }
-
-               public event PropertyChangedEventHandler PropertyChanged;
-
-               void OnPropertyChanged([CallerMemberName] string propertyName = null)
-               {
-                       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
-               }
-       }
 }
\ No newline at end of file
index a5d3e86..2d970c7 100644 (file)
@@ -1,33 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
 {
-       [Preserve(AllMembers = true)]
-       public class CollectionViewGalleryTestItem
-       {
-               public DateTime Date { get; set; }
-               public string Caption { get; set; }
-               public string Image { get; set; }
-               public int Index { get; set; }
-
-               public CollectionViewGalleryTestItem(DateTime date, string caption, string image, int index)
-               {
-                       Date = date;
-                       Caption = caption;
-                       Image = image;
-                       Index = index;
-               }
-
-               public override string ToString()
-               {
-                       return $"Item: {Index}";
-               }
-       }
-
-       internal enum ItemsSourceType
+    internal enum ItemsSourceType
        {
                List,
                ObservableCollection,
index 28e2750..93ad41c 100644 (file)
@@ -57,6 +57,9 @@
        <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\MapWithItemsSourceGallery.xaml">
index 2edb46c..1a9a279 100644 (file)
@@ -280,7 +280,7 @@ namespace Xamarin.Forms.Core.UITests
                        }
                }
 
-        [TestCase("EmptyView", "EmptyView (load simulation)", "photo")]
+               [TestCase("EmptyView", "EmptyView (load simulation)", "photo")]
         public void VisitAndCheckItem(string collectionTestName, string subgallery, string item)
         {
             VisitInitialGallery(collectionTestName);
@@ -290,5 +290,18 @@ namespace Xamarin.Forms.Core.UITests
 
             App.WaitForElement(t => t.Marked(item));
         }
+               
+        [TestCase("DataTemplate Galleries", "DataTemplateSelector")]
+        void VisitAndCheckForItems(string collectionTestName, string subGallery)
+        {
+            VisitInitialGallery(collectionTestName);
+            
+            App.WaitForElement(t => t.Marked(subGallery));
+            App.Tap(t => t.Marked(subGallery));
+
+            App.WaitForElement("weekend");
+            App.WaitForElement("weekday");
+        }
+        
     }
 }
\ No newline at end of file
index c35e904..a4ce410 100644 (file)
@@ -24,7 +24,7 @@ namespace Xamarin.Forms.Platform.Android
                        // So we give it an alternate delegate for creating the views
 
                        ItemsViewAdapter = new ItemsViewAdapter(ItemsView, 
-                               (renderer, context) => new SizedItemContentView(renderer, context, () => Width, () => Height));
+                               (view, context) => new SizedItemContentView(context, () => Width, () => Height));
 
                        SwapAdapter(ItemsViewAdapter, false);
                }
index a464ad1..ff62486 100644 (file)
@@ -3,6 +3,8 @@ using Android.Content;
 using Android.Support.V7.Widget;
 using Android.Views;
 using Android.Widget;
+using Java.Lang;
+using Object = Java.Lang.Object;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -10,12 +12,24 @@ namespace Xamarin.Forms.Platform.Android
        {
                public object EmptyView { get; set; }
                public DataTemplate EmptyViewTemplate { get; set; }
+               protected readonly ItemsView ItemsView;
 
                public override int ItemCount => 1;
 
-               public EmptyViewAdapter()
+               public EmptyViewAdapter(ItemsView itemsView)
                {
                        CollectionView.VerifyCollectionViewFlagEnabled(nameof(EmptyViewAdapter));
+                       ItemsView = itemsView;
+               }
+
+               public override void OnViewRecycled(Object holder)
+               {
+                       if (holder is TemplatedItemViewHolder templatedItemViewHolder)
+                       {
+                               templatedItemViewHolder.Recycle(ItemsView);
+                       }
+
+                       base.OnViewRecycled(holder);
                }
 
                public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
@@ -25,20 +39,25 @@ namespace Xamarin.Forms.Platform.Android
                                return;
                        }
 
+                       if (holder is TemplatedItemViewHolder templatedItemViewHolder)
+                       {
+                               // Use EmptyView as the binding context for the template
+                               templatedItemViewHolder.Bind(EmptyView, ItemsView);
+                       }
+
                        if (!(holder is EmptyViewHolder emptyViewHolder))
                        {
                                return;
                        }
-
-                       // Use EmptyView as the binding context for the template
-                       BindableObject.SetInheritedBindingContext(emptyViewHolder.View, EmptyView);
                }
 
                public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
                {
                        var context = parent.Context;
 
-                       if (EmptyViewTemplate == null)
+                       var template = EmptyViewTemplate;
+
+                       if (template == null)
                        {
                                if (!(EmptyView is View formsView))
                                {
@@ -47,29 +66,13 @@ namespace Xamarin.Forms.Platform.Android
                                }
 
                                // EmptyView is a Forms View; display that
-                               var itemContentControl = new SizedItemContentView(CreateRenderer(formsView, context), context,
-                                       () => parent.Width, () => parent.Height);
+                               var itemContentControl = new SizedItemContentView(context, () => parent.Width, () => parent.Height);
+                               itemContentControl.RealizeContent(formsView);
                                return new EmptyViewHolder(itemContentControl, formsView);
                        }
 
-                       // We have a template, so create a view from it
-                       var templateElement = EmptyViewTemplate.CreateContent() as View;
-                       var templatedItemContentControl = new SizedItemContentView(CreateRenderer(templateElement, context), 
-                               context, () => parent.Width, () => parent.Height);
-                       return new EmptyViewHolder(templatedItemContentControl, templateElement);
-               }
-
-               IVisualElementRenderer CreateRenderer(View view, Context context)
-               {
-                       if (view == null)
-                       {
-                               throw new ArgumentNullException(nameof(view));
-                       }
-
-                       var renderer = Platform.CreateRenderer(view, context);
-                       Platform.SetRenderer(view, renderer);
-
-                       return renderer;
+                       var itemContentView = new SizedItemContentView(parent.Context, () => parent.Width, () => parent.Height);
+                       return new TemplatedItemViewHolder(itemContentView, template);
                }
 
                static TextView CreateTextView(string text, Context context)
index 2f95d0c..a6f6d9e 100644 (file)
@@ -1,3 +1,4 @@
+using System;
 using Android.Content;
 using Android.Views;
 
@@ -5,21 +6,31 @@ namespace Xamarin.Forms.Platform.Android
 {
        internal class ItemContentView : ViewGroup
        {
-               protected readonly IVisualElementRenderer Content;
+               protected IVisualElementRenderer Content;
 
-               public ItemContentView(IVisualElementRenderer content, Context context) : base(context)
+               public ItemContentView(Context context) : base(context)
                {
-                       Content = content;
-                       AddContent();
                }
 
-               void AddContent()
+               internal void RealizeContent(View view)
                {
+                       Content = CreateRenderer(view, Context);
                        AddView(Content.View);
                }
 
+               internal void Recycle()
+               {
+                       RemoveView(Content.View);
+                       Content = null;
+               }
+
                protected override void OnLayout(bool changed, int l, int t, int r, int b)
                {
+                       if (Content == null)
+                       {
+                               return;
+                       }
+
                        var size = Context.FromPixels(r - l, b - t);
 
                        Content.Element.Layout(new Rectangle(Point.Zero, size));
@@ -29,6 +40,12 @@ namespace Xamarin.Forms.Platform.Android
 
                protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
                {
+                       if (Content == null)
+                       {
+                               SetMeasuredDimension(0, 0);
+                               return;
+                       }
+
                        int pixelWidth = MeasureSpec.GetSize(widthMeasureSpec);
                        int pixelHeight = MeasureSpec.GetSize(heightMeasureSpec);
 
@@ -53,5 +70,23 @@ namespace Xamarin.Forms.Platform.Android
 
                        SetMeasuredDimension(pixelWidth, pixelHeight);
                }
+
+               static IVisualElementRenderer CreateRenderer(View view, Context context)
+               {
+                       if (view == null)
+                       {
+                               throw new ArgumentNullException(nameof(view));
+                       }
+
+                       if (context == null)
+                       {
+                               throw new ArgumentNullException(nameof(context));
+                       }
+
+                       var renderer = Platform.CreateRenderer(view, context);
+                       Platform.SetRenderer(view, renderer);
+
+                       return renderer;
+               }
        }
 }
\ No newline at end of file
index 85e5da8..ece007b 100644 (file)
@@ -8,26 +8,26 @@ using ViewGroup = Android.Views.ViewGroup;
 
 namespace Xamarin.Forms.Platform.Android
 {
-       // TODO hartez 2018/07/25 14:43:04 Experiment with an ItemSource property change as _adapter.notifyDataSetChanged
+       // TODO hartez 2018/07/25 14:43:04 Experiment with an ItemSource property change as _adapter.notifyDataSetChanged       
 
        public class ItemsViewAdapter : RecyclerView.Adapter
        {
                protected readonly ItemsView ItemsView;
-               readonly Func<IVisualElementRenderer, Context, AView> _createView;
+               readonly Func<View, Context, ItemContentView> _createItemContentView;
                internal readonly IItemsViewSource ItemsSource;
                bool _disposed;
 
-               internal ItemsViewAdapter(ItemsView itemsView, Func<IVisualElementRenderer, Context, AView> createView = null)
+               internal ItemsViewAdapter(ItemsView itemsView, Func<View, Context, ItemContentView> createItemContentView = null)
                {
                        CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewAdapter));
 
                        ItemsView = itemsView;
-                       _createView = createView;
+                       _createItemContentView = createItemContentView;
                        ItemsSource = ItemsSourceFactory.Create(itemsView.ItemsSource, this);
 
-                       if (_createView == null)
+                       if (_createItemContentView == null)
                        {
-                               _createView = (renderer, context) => new ItemContentView(renderer, context);
+                               _createItemContentView = (view, context) => new ItemContentView(context);
                        }
                }
 
@@ -35,7 +35,7 @@ namespace Xamarin.Forms.Platform.Android
                {
                        if (holder is TemplatedItemViewHolder templatedItemViewHolder)
                        {
-                               ItemsView.RemoveLogicalChild(templatedItemViewHolder.View);
+                               templatedItemViewHolder.Recycle(ItemsView);
                        }
 
                        base.OnViewRecycled(holder);
@@ -49,7 +49,7 @@ namespace Xamarin.Forms.Platform.Android
                                        textViewHolder.TextView.Text = ItemsSource[position].ToString();
                                        break;
                                case TemplatedItemViewHolder templatedItemViewHolder:
-                                       BindableObject.SetInheritedBindingContext(templatedItemViewHolder.View, ItemsSource[position]);
+                                       templatedItemViewHolder.Bind(ItemsSource[position], ItemsView);
                                        break;
                        }
                }
@@ -67,12 +67,20 @@ namespace Xamarin.Forms.Platform.Android
                                return new TextViewHolder(view);
                        }
 
-                       // Realize the content, create a renderer out of it, and use that
-                       var templateElement = (View)template.CreateContent();
-                       ItemsView.AddLogicalChild(templateElement);
-                       var itemContentControl = _createView(CreateRenderer(templateElement, context), context);
+                       var itemContentView = new ItemContentView(parent.Context);
+                       return new TemplatedItemViewHolder(itemContentView, template);
+               }
+
+               public override int ItemCount => ItemsSource.Count;
 
-                       return new TemplatedItemViewHolder(itemContentControl, templateElement);
+               public override int GetItemViewType(int position)
+               {
+                       // TODO hartez We might be able to turn this to our own purposes
+                       // We might be able to have the CollectionView signal the adapter if the ItemTemplate property changes
+                       // And as long as it's null, we return a value to that effect here
+                       // Then we don't have to check _itemsView.ItemTemplate == null in OnCreateViewHolder, we can just use
+                       // the viewType parameter.
+                       return 42;
                }
 
                protected override void Dispose(bool disposing)
@@ -90,36 +98,6 @@ namespace Xamarin.Forms.Platform.Android
                        }
                }
 
-               static IVisualElementRenderer CreateRenderer(View view, Context context)
-               {
-                       if (view == null)
-                       {
-                               throw new ArgumentNullException(nameof(view));
-                       }
-
-                       if (context == null)
-                       {
-                               throw new ArgumentNullException(nameof(context));
-                       }
-
-                       var renderer = Platform.CreateRenderer(view, context);
-                       Platform.SetRenderer(view, renderer);
-
-                       return renderer;
-               }
-
-               public override int ItemCount => ItemsSource.Count;
-
-               public override int GetItemViewType(int position)
-               {
-                       // TODO hartez We might be able to turn this to our own purposes
-                       // We might be able to have the CollectionView signal the adapter if the ItemTemplate property changes
-                       // And as long as it's null, we return a value to that effect here
-                       // Then we don't have to check _itemsView.ItemTemplate == null in OnCreateViewHolder, we can just use
-                       // the viewType parameter.
-                       return 42;
-               }
-
                public virtual int GetPositionForItem(object item)
                {
                        for (int n = 0; n < ItemsSource.Count; n++)
index 21b47eb..6041930 100644 (file)
@@ -31,6 +31,7 @@ namespace Xamarin.Forms.Platform.Android
 
                EmptyViewAdapter _emptyViewAdapter;
                DataChangeObserver _dataChangeViewObserver;
+               bool _watchingForEmpty;
 
                public ItemsViewRenderer(Context context) : base(context)
                {
@@ -214,7 +215,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        // Stop watching the old adapter to see if it's empty (if we _are_ watching)
                        Unwatch(ItemsViewAdapter ?? GetAdapter());
-                       
+
                        UpdateAdapter();
 
                        UpdateEmptyView();
@@ -228,22 +229,30 @@ namespace Xamarin.Forms.Platform.Android
 
                void Unwatch(Adapter adapter)
                {
-                       if (adapter != null && _dataChangeViewObserver != null)
+                       if (_watchingForEmpty && adapter != null && _dataChangeViewObserver != null)
                        {
                                adapter.UnregisterAdapterDataObserver(_dataChangeViewObserver);
                        }
+
+                       _watchingForEmpty = false;
                }
 
                // TODO hartez 2018/10/24 19:25:14 I don't like these method names; too generic         
                // TODO hartez 2018/11/05 22:37:42 Also, thinking all the EmptyView stuff should be moved to a helper   
                void Watch(Adapter adapter)
                {
+                       if (_watchingForEmpty)
+                       {
+                               return;
+                       }
+
                        if (_dataChangeViewObserver == null)
                        {
                                _dataChangeViewObserver = new DataChangeObserver(UpdateEmptyViewVisibility);
                        }
 
                        adapter.RegisterAdapterDataObserver(_dataChangeViewObserver);
+                       _watchingForEmpty = true;
                }
 
                protected virtual void SetUpNewElement(ItemsView newElement)
@@ -369,7 +378,7 @@ namespace Xamarin.Forms.Platform.Android
 
                protected virtual void UpdateEmptyView()
                {
-                       if (ItemsViewAdapter == null)
+                       if (ItemsViewAdapter == null || ItemsView == null)
                        {
                                return;
                        }
@@ -381,7 +390,7 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                if (_emptyViewAdapter == null)
                                {
-                                       _emptyViewAdapter = new EmptyViewAdapter();
+                                       _emptyViewAdapter = new EmptyViewAdapter(ItemsView);
                                }
 
                                _emptyViewAdapter.EmptyView = emptyView;
index 390a170..1491cab 100644 (file)
@@ -11,8 +11,8 @@ namespace Xamarin.Forms.Platform.Android
                protected readonly SelectableItemsView SelectableItemsView;
                List<SelectableViewHolder> _currentViewHolders = new List<SelectableViewHolder>();
 
-               internal SelectableItemsViewAdapter(SelectableItemsView selectableItemsView, 
-                       Func<IVisualElementRenderer, Context, global::Android.Views.View> createView = null) : base(selectableItemsView, createView)
+               internal SelectableItemsViewAdapter(SelectableItemsView selectableItemsView,
+                       Func<View, Context, ItemContentView> createView = null) : base(selectableItemsView, createView)
                {
                        SelectableItemsView = selectableItemsView;
                }
index 1964b42..c756b40 100644 (file)
@@ -8,8 +8,8 @@ namespace Xamarin.Forms.Platform.Android
                readonly Func<int> _width;
                readonly Func<int> _height;
 
-               public SizedItemContentView(IVisualElementRenderer content, Context context, Func<int> width, Func<int> height) 
-                       : base(content, context)
+               public SizedItemContentView(Context context, Func<int> width, Func<int> height) 
+                       : base(context)
                {
                        _width = width;
                        _height = height;
@@ -17,6 +17,12 @@ namespace Xamarin.Forms.Platform.Android
 
                protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
                {
+                       if (Content == null)
+                       {
+                               SetMeasuredDimension(0, 0);
+                               return;
+                       }
+
                        var targetWidth = _width();
                        var targetHeight = _height();
 
index 3a6eb37..19dbf89 100644 (file)
@@ -1,21 +1,54 @@
+using System;
+using Android.Content;
+using Xamarin.Forms.Internals;
+
 namespace Xamarin.Forms.Platform.Android
 {
        internal class TemplatedItemViewHolder : SelectableViewHolder
        {
-               public View View { get; }
+               private readonly ItemContentView _itemContentView;
+               private readonly DataTemplate _template;
+
+               public View View { get; private set; }
 
-               public TemplatedItemViewHolder(global::Android.Views.View itemView, View rootElement) : base(itemView)
+               public TemplatedItemViewHolder(ItemContentView itemContentView, DataTemplate template) : base(itemContentView)
                {
-                       View = rootElement;
+                       _itemContentView = itemContentView;
+                       _template = template;
                }
 
                protected override void OnSelectedChanged()
                {
                        base.OnSelectedChanged();
 
+                       if (View == null)
+                       {
+                               return;
+                       }
+
                        VisualStateManager.GoToState(View, IsSelected 
                                ? VisualStateManager.CommonStates.Selected 
                                : VisualStateManager.CommonStates.Normal);
                }
+
+               public void Recycle(ItemsView itemsView)
+               {
+                       itemsView.RemoveLogicalChild(View);
+                       _itemContentView.Recycle();
+               }
+
+               public void Bind(object itemBindingContext, ItemsView itemsView)
+               {
+                       var template = _template.SelectDataTemplate(itemBindingContext, itemsView);
+
+                       View = (View)template.CreateContent();
+                       _itemContentView.RealizeContent(View);
+
+                       // Set the binding context before we add it as a child of the ItemsView; otherwise, it will
+                       // inherit the ItemsView's binding context
+                       View.BindingContext = itemBindingContext;
+
+                       itemsView.AddLogicalChild(View);
+               }
        }
 }
\ No newline at end of file
index 5747628..cdced0a 100644 (file)
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using Foundation;
 using UIKit;
+using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Platform.iOS
 {
@@ -227,12 +228,22 @@ namespace Xamarin.Forms.Platform.iOS
 
                void ApplyTemplateAndDataContext(TemplatedCell cell, NSIndexPath indexPath)
                {
-                       // We need to create a renderer, which means we need a template
-                       var view = _itemsView.ItemTemplate.CreateContent() as View;
-                       _itemsView.AddLogicalChild(view);
+                       var template = _itemsView.ItemTemplate;
+                       var item = _itemsSource[indexPath.Row];
+
+                       // Run this through the extension method in case it's really a DataTemplateSelector
+                       template = template.SelectDataTemplate(item, _itemsView);
+
+                       // Create the content and renderer for the view and 
+                       var view = template.CreateContent() as View;
                        var renderer = CreateRenderer(view);
-                       BindableObject.SetInheritedBindingContext(view, _itemsSource[indexPath.Row]);
                        cell.SetRenderer(renderer);
+
+                       // Bind the view to the data item
+                       view.BindingContext = _itemsSource[indexPath.Row];
+
+                       // And make sure it's a "child" of the ItemsView
+                       _itemsView.AddLogicalChild(view);
                }
 
                internal void RemoveLogicalChild(UICollectionViewCell cell)
@@ -357,6 +368,9 @@ namespace Xamarin.Forms.Platform.iOS
                {
                        if (emptyViewTemplate != null)
                        {
+                               // Run this through the extension method in case it's really a DataTemplateSelector
+                               emptyViewTemplate = emptyViewTemplate.SelectDataTemplate(emptyView, _itemsView);
+
                                // We have a template; turn it into a Forms view 
                                var templateElement = emptyViewTemplate.CreateContent() as View;
                                var renderer = CreateRenderer(templateElement);
index 5433328..df83735 100644 (file)
@@ -72,7 +72,9 @@ namespace Xamarin.Forms.Platform.iOS
                void CreatePlaceholderLabel()
                {
                        if (Control == null)
+                       {
                                return;
+                       }
 
                        Control.AddSubview(_placeholderLabel);