Xamarin Forms Custom Renderer iOS UICollectionView双向滚动/水平滚动–垂直滚动

时间:2018-06-19 12:17:39

标签: xamarin xamarin.ios uicollectionview horizontal-scrolling custom-renderer

Xamarin Forms Custom Renderer iOS UICollectionView双向滚动/水平滚动–垂直滚动

意图:实现可水平和垂直滚动的交互式Grid。

问题: 网格在两个方向上都滚动很大。用手指滚动后,您必须等待几秒钟才能看到应用程序的反应。 我们在本机swift项目中以相同的方式实现了UICollectionView,并且滚动流畅。所以我认为问题在于实现方式而不是渲染过程。

通过仪器,我可以发现CoreFoundation(RunLoops)花费了最多的时间(11s)。所以我想这是一个线程问题。

为什么我需要自定义UICollectionViewLayout以实现水平滚动?因为我还需要修改单个单元格的宽度和其他自定义的功能。 因此,我看到的双向滚动的唯一方法是自定义UICollectionViewLayout。

实施:

Xamarin.Forms项目:

  1. 在Xamarin表单中创建自定义控件(MyGrid),该控件从View类扩展到

     public class MyGrid : View
     {
        public MyGrid() : base()
        {
        }
     }
    
  2. 在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项目:

  1. 在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
            }
        }
    }
    } 
    
  2. 实施本机控件(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();
            }
        }
    
  3. 为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;
    }
    } 
    
  4. 为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;
    }
    } 
    
  5. 为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);
    }
    } 
    

1 个答案:

答案 0 :(得分:0)

如果这是线程问题,那么您会发现以下内容很有教育意义:Fixing a jerky ListView