Make it easier to create custom renderers for alternate CV layouts on Android (#6990)

Demo of StaggeredGridLayoutManager on Android
This commit is contained in:
E.Z. Hart 2019-07-30 04:27:36 -06:00 коммит произвёл Rui Marinho
Родитель 880f368dda
Коммит 83a244801e
13 изменённых файлов: 280 добавлений и 27 удалений

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

@ -0,0 +1,105 @@
using System;
using Android.Content;
using Android.Graphics;
using Android.Support.V7.Widget;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.Android;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries;
using Xamarin.Forms.Platform.Android;
using AView = Android.Views.View;
[assembly: ExportRenderer(typeof(StaggeredCollectionView), typeof(StaggeredCollectionViewRenderer))]
namespace Xamarin.Forms.ControlGallery.Android
{
public class StaggeredCollectionViewRenderer : CollectionViewRenderer
{
public StaggeredCollectionViewRenderer(Context context) : base(context) { }
protected override LayoutManager SelectLayoutManager(IItemsLayout layoutSpecification)
{
if (layoutSpecification is StaggeredGridItemsLayout staggeredGridLayout)
{
var manager = new StaggeredGridLayoutManager(staggeredGridLayout.Span,
staggeredGridLayout.Orientation == ItemsLayoutOrientation.Horizontal
? LinearLayoutManager.Horizontal
: LinearLayoutManager.Vertical);
manager.GapStrategy = StaggeredGridLayoutManager.GapHandlingNone;
return manager;
}
return base.SelectLayoutManager(layoutSpecification);
}
protected override ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayout)
{
return new SpacingItemDecoration(itemsLayout as StaggeredGridItemsLayout);
}
}
public class SpacingItemDecoration : RecyclerView.ItemDecoration
{
ItemsLayoutOrientation _orientation;
double _verticalSpacing;
double _adjustedVerticalSpacing = -1;
double _horizontalSpacing;
double _adjustedHorizontalSpacing = -1;
int _span = 1;
public SpacingItemDecoration(StaggeredGridItemsLayout itemsLayout)
{
if (itemsLayout == null)
{
throw new ArgumentNullException(nameof(itemsLayout));
}
switch (itemsLayout)
{
case StaggeredGridItemsLayout gridItemsLayout:
_orientation = gridItemsLayout.Orientation;
_horizontalSpacing = gridItemsLayout.HorizontalItemSpacing;
_verticalSpacing = gridItemsLayout.VerticalItemSpacing;
_span = gridItemsLayout.Span;
break;
}
}
public override void GetItemOffsets(Rect outRect, AView view, RecyclerView parent, RecyclerView.State state)
{
base.GetItemOffsets(outRect, view, parent, state);
var position = parent.GetChildAdapterPosition(view);
if (_adjustedVerticalSpacing == -1)
{
_adjustedVerticalSpacing = parent.Context.ToPixels(_verticalSpacing);
}
if (_adjustedHorizontalSpacing == -1)
{
_adjustedHorizontalSpacing = parent.Context.ToPixels(_horizontalSpacing);
}
var spanIndex = 0;
var layoutParameters = view.LayoutParameters as StaggeredGridLayoutManager.LayoutParams;
if (layoutParameters != null)
{
spanIndex = layoutParameters.SpanIndex;
}
if (_orientation == ItemsLayoutOrientation.Vertical)
{
outRect.Left = spanIndex == 0 ? 0 : (int)_adjustedHorizontalSpacing;
outRect.Top = position < _span ? 0 : (int)_adjustedVerticalSpacing;
}
else
{
outRect.Left = position < _span ? 0 : (int)_adjustedHorizontalSpacing;
outRect.Top = spanIndex == 0 ? 0 : (int)_adjustedVerticalSpacing;
}
}
}
}

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

@ -109,6 +109,7 @@
<Compile Include="CustomRenderers.cs" /> <Compile Include="CustomRenderers.cs" />
<Compile Include="ColorPicker.cs" /> <Compile Include="ColorPicker.cs" />
<Compile Include="SampleNativeControl.cs" /> <Compile Include="SampleNativeControl.cs" />
<Compile Include="StaggeredCollectionViewRenderer.cs" />
<Compile Include="TestCloudService.cs" /> <Compile Include="TestCloudService.cs" />
<Compile Include="_1909CustomRenderer.cs" /> <Compile Include="_1909CustomRenderer.cs" />
<Compile Include="_2489CustomRenderer.cs" /> <Compile Include="_2489CustomRenderer.cs" />

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

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ScrollModeGalleries;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries
{
internal class AlternateLayoutGallery : ContentPage
{
public AlternateLayoutGallery()
{
var descriptionLabel =
new Label { Text = "Alternate Layout Galleries", Margin = new Thickness(2, 2, 2, 2) };
Title = "Alternate Layout Galleries";
Content = new ScrollView
{
Content = new StackLayout
{
Children =
{
descriptionLabel,
GalleryBuilder.NavButton("Staggered Grid [Android only]", () =>
new StaggeredLayout(), Navigation),
GalleryBuilder.NavButton("ScrollTo Item (Staggered Grid, [Android only])", () =>
new ScrollToCodeGallery(new StaggeredGridItemsLayout(3, ItemsLayoutOrientation.Vertical),
ScrollToMode.Element, ExampleTemplates.RandomSizeTemplate, () => new StaggeredCollectionView()), Navigation),
GalleryBuilder.NavButton("Scroll Mode (Staggered Grid, [Android only])", () =>
new ScrollModeTestGallery(new StaggeredGridItemsLayout(3, ItemsLayoutOrientation.Vertical),
ExampleTemplates.RandomSizeTemplate, () => new StaggeredCollectionView()), Navigation)
}
}
};
}
}
}

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

@ -0,0 +1,16 @@
<?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"
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries.StaggeredLayout">
<ContentPage.Content>
<local:StaggeredCollectionView x:Name="CV">
<local:StaggeredCollectionView.ItemsLayout>
<local:StaggeredGridItemsLayout Span="3" Orientation="Vertical" HorizontalItemSpacing="5" VerticalItemSpacing="5"></local:StaggeredGridItemsLayout>
</local:StaggeredCollectionView.ItemsLayout>
</local:StaggeredCollectionView>
</ContentPage.Content>
</ContentPage>

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

@ -0,0 +1,31 @@
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StaggeredLayout : ContentPage
{
readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource();
public StaggeredLayout()
{
InitializeComponent();
CV.ItemTemplate = ExampleTemplates.RandomSizeTemplate();
CV.ItemsSource = _demoFilteredItemSource.Items;
}
}
public class StaggeredCollectionView : CollectionView { }
public class StaggeredGridItemsLayout : GridItemsLayout
{
public StaggeredGridItemsLayout([Parameter("Orientation")] ItemsLayoutOrientation orientation) : base(orientation)
{
}
public StaggeredGridItemsLayout(int span, [Parameter("Orientation")] ItemsLayoutOrientation orientation) : base(span, orientation)
{
}
}
}

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

@ -2,6 +2,7 @@
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGalleries; using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.GroupingGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries; using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.SelectionGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ScrollModeGalleries; using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ScrollModeGalleries;
using Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.AlternateLayoutGalleries;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
{ {
@ -25,6 +26,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
GalleryBuilder.NavButton("Propagation Galleries", () => new PropagationGallery(), Navigation), GalleryBuilder.NavButton("Propagation Galleries", () => new PropagationGallery(), Navigation),
GalleryBuilder.NavButton("Grouping Galleries", () => new GroupingGallery(), Navigation), GalleryBuilder.NavButton("Grouping Galleries", () => new GroupingGallery(), Navigation),
GalleryBuilder.NavButton("Scroll Mode Galleries", () => new ScrollModeGallery(), Navigation), GalleryBuilder.NavButton("Scroll Mode Galleries", () => new ScrollModeGallery(), Navigation),
GalleryBuilder.NavButton("Alternate Layout Galleries", () => new AlternateLayoutGallery(), Navigation),
} }
}; };
} }

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

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
@ -302,6 +303,29 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
}); });
} }
public static DataTemplate RandomSizeTemplate()
{
var indexHeightConverter = new IndexRequestRandomConverter(50, 150);
var indexWidthConverter = new IndexRequestRandomConverter(50, 150);
var colorConverter = new IndexColorConverter();
return new DataTemplate(() =>
{
var layout = new Frame();
layout.SetBinding(VisualElement.HeightRequestProperty, new Binding("Index", converter: indexHeightConverter));
layout.SetBinding(VisualElement.WidthRequestProperty, new Binding("Index", converter: indexWidthConverter));
layout.SetBinding(VisualElement.BackgroundColorProperty, new Binding("Index", converter: colorConverter));
var label = new Label { FontSize = 30 };
label.SetBinding(Label.TextProperty, new Binding("Index"));
layout.Content = label;
return layout;
});
}
public static DataTemplate DynamicTextTemplate() public static DataTemplate DynamicTextTemplate()
{ {
return new DataTemplate(() => return new DataTemplate(() =>
@ -456,6 +480,34 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
} }
class IndexRequestRandomConverter : IValueConverter
{
readonly int _lowValue;
readonly int _highValue;
readonly Random _random;
readonly Dictionary<int, int> _dictionary = new Dictionary<int, int>();
public IndexRequestRandomConverter(int lowValue, int highValue)
{
_lowValue = lowValue;
_highValue = highValue;
_random = new Random(DateTime.UtcNow.Millisecond);
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var index = (int)value;
if (!_dictionary.ContainsKey(index))
{
_dictionary[index] = _random.Next(_lowValue, _highValue);
}
return _dictionary[index];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
class IndexColorConverter : IValueConverter class IndexColorConverter : IValueConverter
{ {
Color[] _colors = new Color[] { Color.Red, Color.Green, Color.Blue, Color.Orange, Color.BlanchedAlmond }; Color[] _colors = new Color[] { Color.Red, Color.Green, Color.Blue, Color.Orange, Color.BlanchedAlmond };

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

@ -22,8 +22,6 @@
<Button x:Name="AddItemToEnd" FontSize="10" AutomationId="AddItemToEnd" Text="Add Item To End" Grid.Row="4" <Button x:Name="AddItemToEnd" FontSize="10" AutomationId="AddItemToEnd" Text="Add Item To End" Grid.Row="4"
HeightRequest="40" Clicked="AddItemToEnd_Clicked" /> HeightRequest="40" Clicked="AddItemToEnd_Clicked" />
<CollectionView x:Name="CollectionView" Grid.Row="5" />
</Grid> </Grid>
</ContentPage.Content> </ContentPage.Content>
</ContentPage> </ContentPage>

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

@ -13,23 +13,31 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.ScrollMode
public partial class ScrollModeTestGallery : ContentPage public partial class ScrollModeTestGallery : ContentPage
{ {
readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(20); readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(20);
CollectionView _collectionView;
public ScrollModeTestGallery() public ScrollModeTestGallery(IItemsLayout itemsLayout = null, Func<DataTemplate> dataTemplate = null, Func<CollectionView> createCollectionView = null)
{ {
InitializeComponent(); InitializeComponent();
var scrollModeSelector = new EnumSelector<ItemsUpdatingScrollMode>(() => CollectionView.ItemsUpdatingScrollMode, _collectionView = createCollectionView == null ? new CollectionView() : createCollectionView();
mode => CollectionView.ItemsUpdatingScrollMode = mode, "SelectScrollMode"); _collectionView.ItemsLayout = itemsLayout ?? ListItemsLayout.Vertical;
var scrollModeSelector = new EnumSelector<ItemsUpdatingScrollMode>(() => _collectionView.ItemsUpdatingScrollMode,
mode => _collectionView.ItemsUpdatingScrollMode = mode, "SelectScrollMode");
Grid.Children.Add(scrollModeSelector); Grid.Children.Add(scrollModeSelector);
CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate();
CollectionView.ItemsSource = _demoFilteredItemSource.Items; Grid.Children.Add(_collectionView);
Grid.SetRow(_collectionView, 5);
_collectionView.ItemTemplate = dataTemplate == null ? ExampleTemplates.PhotoTemplate() : dataTemplate();
_collectionView.ItemsSource = _demoFilteredItemSource.Items;
} }
void ScrollToMiddle_Clicked(object sender, EventArgs e) void ScrollToMiddle_Clicked(object sender, EventArgs e)
{ {
CollectionView.ScrollTo(_demoFilteredItemSource.Items.Count / 2, position: ScrollToPosition.Start, animate: false); _collectionView.ScrollTo(_demoFilteredItemSource.Items.Count / 2, position: ScrollToPosition.Start, animate: false);
} }
void AddItemAbove_Clicked(object sender, EventArgs e) void AddItemAbove_Clicked(object sender, EventArgs e)

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

@ -4,8 +4,10 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
{ {
internal class ScrollToCodeGallery : ContentPage internal class ScrollToCodeGallery : ContentPage
{ {
public ScrollToCodeGallery(IItemsLayout itemsLayout, ScrollToMode mode = ScrollToMode.Position, Func<DataTemplate> dataTemplate = null) public ScrollToCodeGallery(IItemsLayout itemsLayout, ScrollToMode mode = ScrollToMode.Position, Func<DataTemplate> dataTemplate = null, Func<CollectionView> createCollectionView = null)
{ {
createCollectionView = createCollectionView ?? (() => new CollectionView());
Title = $"ScrollTo (Code, {itemsLayout})"; Title = $"ScrollTo (Code, {itemsLayout})";
var layout = new Grid var layout = new Grid
@ -20,11 +22,10 @@ namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries
var itemTemplate = dataTemplate == null ? ExampleTemplates.ScrollToIndexTemplate() : dataTemplate(); var itemTemplate = dataTemplate == null ? ExampleTemplates.ScrollToIndexTemplate() : dataTemplate();
var collectionView = new CollectionView var collectionView = createCollectionView();
{
ItemsLayout = itemsLayout, collectionView.ItemsLayout = itemsLayout;
ItemTemplate = itemTemplate, collectionView.ItemTemplate = itemTemplate;
};
var generator = new ItemsSourceGenerator(collectionView, initialItems: 50); var generator = new ItemsSourceGenerator(collectionView, initialItems: 50);
layout.Children.Add(generator); layout.Children.Add(generator);

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

@ -315,7 +315,7 @@ namespace Xamarin.Forms.Platform.Android
AddOnScrollListener(_recyclerViewScrollListener); AddOnScrollListener(_recyclerViewScrollListener);
} }
void UpdateVerticalScrollBarVisibility() protected virtual void UpdateVerticalScrollBarVisibility()
{ {
if (_defaultVerticalScrollVisibility == ScrollBarVisibility.Default) if (_defaultVerticalScrollVisibility == ScrollBarVisibility.Default)
_defaultVerticalScrollVisibility = VerticalScrollBarEnabled ? ScrollBarVisibility.Always : ScrollBarVisibility.Never; _defaultVerticalScrollVisibility = VerticalScrollBarEnabled ? ScrollBarVisibility.Always : ScrollBarVisibility.Never;
@ -328,7 +328,7 @@ namespace Xamarin.Forms.Platform.Android
VerticalScrollBarEnabled = newVerticalScrollVisibility == ScrollBarVisibility.Always; VerticalScrollBarEnabled = newVerticalScrollVisibility == ScrollBarVisibility.Always;
} }
void UpdateHorizontalScrollBarVisibility() protected virtual void UpdateHorizontalScrollBarVisibility()
{ {
if (_defaultHorizontalScrollVisibility == ScrollBarVisibility.Default) if (_defaultHorizontalScrollVisibility == ScrollBarVisibility.Default)
_defaultHorizontalScrollVisibility = _defaultHorizontalScrollVisibility =
@ -520,7 +520,7 @@ namespace Xamarin.Forms.Platform.Android
} }
} }
protected virtual int DeterminePosition(ScrollToRequestEventArgs args) protected virtual int DetermineTargetPosition(ScrollToRequestEventArgs args)
{ {
if (args.Mode == ScrollToMode.Position) if (args.Mode == ScrollToMode.Position)
{ {
@ -543,10 +543,15 @@ namespace Xamarin.Forms.Platform.Android
RemoveItemDecoration(_itemDecoration); RemoveItemDecoration(_itemDecoration);
} }
_itemDecoration = new SpacingItemDecoration(_layout); _itemDecoration = CreateSpacingDecoration(_layout);
AddItemDecoration(_itemDecoration); AddItemDecoration(_itemDecoration);
} }
protected virtual ItemDecoration CreateSpacingDecoration(IItemsLayout itemsLayout)
{
return new SpacingItemDecoration(itemsLayout);
}
void ScrollToRequested(object sender, ScrollToRequestEventArgs args) void ScrollToRequested(object sender, ScrollToRequestEventArgs args)
{ {
ScrollTo(args); ScrollTo(args);
@ -554,7 +559,7 @@ namespace Xamarin.Forms.Platform.Android
protected virtual void ScrollTo(ScrollToRequestEventArgs args) protected virtual void ScrollTo(ScrollToRequestEventArgs args)
{ {
var position = DeterminePosition(args); var position = DetermineTargetPosition(args);
if (args.IsAnimated) if (args.IsAnimated)
{ {

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

@ -29,7 +29,6 @@ namespace Xamarin.Forms.Platform.Android
if (!_maintainingScrollOffsets) if (!_maintainingScrollOffsets)
{ {
_maintainingScrollOffsets = true; _maintainingScrollOffsets = true;
//_recyclerView.ScrollChange += ScrollChange;
_recyclerView.AddOnScrollListener(this); _recyclerView.AddOnScrollListener(this);
} }

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

@ -49,11 +49,6 @@ namespace Xamarin.Forms.Platform.Android
var position = parent.GetChildAdapterPosition(view); var position = parent.GetChildAdapterPosition(view);
if (position == 0)
{
return;
}
if (_adjustedVerticalSpacing == -1) if (_adjustedVerticalSpacing == -1)
{ {
_adjustedVerticalSpacing = parent.Context.ToPixels(_verticalSpacing); _adjustedVerticalSpacing = parent.Context.ToPixels(_verticalSpacing);
@ -61,7 +56,7 @@ namespace Xamarin.Forms.Platform.Android
if (_adjustedHorizontalSpacing == -1) if (_adjustedHorizontalSpacing == -1)
{ {
_adjustedHorizontalSpacing = parent.Context.ToPixels(_horizontalSpacing); _adjustedHorizontalSpacing = parent.Context.ToPixels(_horizontalSpacing);
} }
var firstInRow = false; var firstInRow = false;