Xamarin Forms Custom Renderer iOS UICollectionView双向滚动/水平滚动–垂直滚动
意图:实现可水平和垂直滚动的交互式Grid。
问题: 网格在两个方向上都滚动很大。用手指滚动后,您必须等待几秒钟才能看到应用程序的反应。 我们在本机swift项目中以相同的方式实现了UICollectionView,并且滚动流畅。所以我认为问题在于实现方式而不是渲染过程。
通过仪器,我可以发现CoreFoundation(RunLoops)花费了最多的时间(11s)。所以我想这是一个线程问题。
为什么我需要自定义UICollectionViewLayout以实现水平滚动?因为我还需要修改单个单元格的宽度和其他自定义的功能。 因此,我看到的双向滚动的唯一方法是自定义UICollectionViewLayout。
实施:
Xamarin.Forms项目:
在Xamarin表单中创建自定义控件(MyGrid),该控件从View类扩展到
public class MyGrid : View
{
public MyGrid() : base()
{
}
}
在Xamarin Forms ContentPage中使用自定义控件(MyGrid):
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GridDemo"
x:Class="GridDemo.GridDemoPage">
<ContentPage.Content>
<local:MyGrid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="White"/>
</ContentPage.Content>
</ContentPage>
Xamarin.iOS项目:
在iOS项目中为MyGrid类实现自定义渲染器:
[assembly: ExportRenderer(typeof(MyGrid), typeof(MyGridRenderer))]
namespace GridDemo.iOS
{
public class MyGridRenderer : ViewRenderer<MyGrid, IOSGrid>
{
public MyGridRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<MyGrid> e)
{
base.OnElementChanged(e);
if (Control == null)
{
MyGrid myGrid = (MyGrid)e.NewElement;
List<List<string>> values = myGrid.Source;
var list = new List<CustomIOSGridCell>();
var column = 0;
var row = 0;
var maxColumnLength = new int[myGrid.ColumnCount];
for (int i = 0; i < myGrid.RowCount; i++)
{
for (int j = 0; j < myGrid.ColumnCount; j++)
{
var array = values[i];
var stringLength = array.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur);
if (stringLength.Length > maxColumnLength[j])
{
maxColumnLength[j] = stringLength.Length;
}
list.Add(new CustomIOSGridCell(values[i][j));
}
}
var grid = new IOSGrid(this.Bounds, new IOSGridLayout(myGrid.ColumnCount, myGrid.RowCount, maxColumnLength));
grid.AddValues(list, myGrid.ColumnCount, myGrid.RowCount);
SetNativeControl(grid);
}
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}
if (e.NewElement != null)
{
// Configure the control and subscribe to event handlers
}
}
}
}
实施本机控件(iOSGrid),将其与自定义Xamarin表单控件(MyGrid)对应的控件:
public class IOSGrid : UICollectionView
{
List<CustomIOSGridCell> values = new List<CustomIOSGridCell>();
public IOSGrid(CGRect frame, IOSGridLayout collectionViewLayout) : base(frame, collectionViewLayout)
{
this.RegisterClassForCell(typeof(CustomCollectionViewCell), CustomCollectionViewCell.CellID);
BackgroundColor = UIColor.Blue;
}
public void AddValues(List<CustomIOSGridCell> values, int columncount, int rowCount)
{
this.values.AddRange(values);
this.Source = new CustomCollectionSource(this.values, rowCount, columncount);
this.ReloadData();
}
}
为IOSGrid(UICollectionView)实现自定义UICollectionViewLayout以提供水平和垂直滚动
public class IOSGridLayout : UICollectionViewLayout
{
private Timer timer;
enum Direction { up, down ,leftRight, none }
int columnsCount = 0;
int rowCount = 0;
CoreGraphics.CGSize[] itemsSize = null;
CoreGraphics.CGSize contentSize = CoreGraphics.CGSize.Empty;
int[] maxLength;
public CGRect currentRect = CGRect.Empty;
CGPoint currentCorner = new CGPoint(-1, -1);
UICollectionViewLayoutAttributes[,] itemAttributes;
public IOSGridLayout(int columnsCount, int rowCount, int[] maxLength)
{
this.columnsCount = columnsCount;
this.rowCount = rowCount;
this.maxLength = maxLength;
itemAttributes = new UICollectionViewLayoutAttributes[rowCount, columnsCount];
}
public override void PrepareLayout()
{
if (CollectionView == null) return;
var collectionView = CollectionView;
if (collectionView.NumberOfSections() == 0) return;
if (itemAttributes.Length != collectionView.NumberOfSections())
{
generateItemAttributes(collectionView);
return;
}
for (int section = 0; section < collectionView.NumberOfSections(); section++)
{
for (int item = 0; item < collectionView.NumberOfItemsInSection(section); item++)
{
if (section != 0 && item != 0)
{
continue;
}
var attributes = LayoutAttributesForItem(NSIndexPath.FromItemSection(item, section)); }
}
}
public override CGSize CollectionViewContentSize
{
get { return contentSize; }
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem(NSIndexPath indexPath)
{
return itemAttributes[indexPath.Section, indexPath.Row];
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
{
var attributes = new List<UICollectionViewLayoutAttributes>();
// calculate actual shown attributes
return attributes.ToArray();
}
public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds)
{
return true;
}
private void generateItemAttributes(UICollectionView collectionView)
{
if (itemsSize?.Length != columnsCount)
{
CalculateItemSizes();
}
var column = 0;
nfloat xOffset = 0;
nfloat yOffset = 0;
nfloat contentWidth = 0;
itemAttributes = new UICollectionViewLayoutAttributes[rowCount, columnsCount];
var se = collectionView.NumberOfSections();
for (int section = 0; section < rowCount; section++)
{
var sectionAttributes = new UICollectionViewLayoutAttributes[columnsCount];
for (int index = 0; index < columnsCount; index++)
{
var itemSize = itemsSize[index];
var indexPath = NSIndexPath.FromItemSection(index, section);
var attributes = UIKit.UICollectionViewLayoutAttributes.CreateForCell(indexPath);
attributes.Frame = new CGRect(xOffset, yOffset, itemSize.Width, itemSize.Height).Integral();
if (section == 0)
{
var frame = attributes.Frame;
frame.Y = collectionView.ContentOffset.Y;
attributes.Frame = frame;
}
if (index == 0)
{
var frame = attributes.Frame;
frame.X = collectionView.ContentOffset.X;
attributes.Frame = frame;
}
sectionAttributes[index]=attributes;
xOffset += itemSize.Width;
column += 1;
if (column == columnsCount)
{
if (xOffset > contentWidth)
{
contentWidth = xOffset;
}
column = 0;
xOffset = 0;
yOffset += itemSize.Height;
}
}
for (int i = 0; i < sectionAttributes.Length; i++)
{
itemAttributes[section, i] = sectionAttributes[i];
}
}
var attr = itemAttributes[rowCount-1,columnsCount-1];
if (attr != null)
{
contentSize = new CGSize(contentWidth, attr.Frame.GetMaxY());
}
}
private void CalculateItemSizes()
{
itemsSize = new CGSize[columnsCount];
for (int index = 0; index < columnsCount; index++)
{
itemsSize[index] = SizeForItemWithColumnIndex(index);
}
}
private CGSize SizeForItemWithColumnIndex(int index)
{
// CollectionView.CellForItem()
string text = "";
for (int i = 0; i < maxLength[index]; i++)
{
text += "M";
}
NSString ma = new NSString(text);
var size = ma.StringSize(UIFont.SystemFontOfSize(14));
size.Height = 35;
return size;
}
}
为IOSGrid(UICollectionView)实现自定义UICollectionViewSource:
public class CustomCollectionSource : UICollectionViewSource
{
private readonly List<CustomIOSGridCell> values = new List<CustomIOSGridCell>();
private readonly int rowCount = 0;
private readonly int columnCount = 0;
public CustomCollectionSource(List<CustomIOSGridCell> values, int rowCount, int columnCount)
{
this.values = values;
this.rowCount = rowCount;
this.columnCount = columnCount;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return rowCount;
}
public override nint NumberOfSections(UICollectionView collectionView)
{
return columnCount;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (CustomCollectionViewCell)collectionView.DequeueReusableCell(CustomCollectionViewCell.CellID, indexPath);
cell.UpdateCell(values[indexPath.Row].Text);
return cell;
}
}
为IOSGrid(UICollectionView)实现自定义UICollectionViewCell:
public class CustomCollectionViewCell : UICollectionViewCell
{
public UILabel mainLabel;
public static NSString CellID = new NSString("customCollectionCell");
[Export("initWithFrame:")]
public CustomCollectionViewCell(CGRect frame) : base(frame)
{
// Default
ContentView.Layer.BorderColor = UIColor.Blue.CGColor;
ContentView.Layer.BorderWidth = 1.0f;
ContentView.Layer.BackgroundColor = UIColor.White.CGColor;
mainLabel = new UILabel();
ContentView.AddSubview( mainLabel );
}
public void UpdateCell(string text)
{
mainLabel.Text = text;
mainLabel.Frame = new CGRect(5, 5, ContentView.Bounds.Width, 26);
}
}