【问题标题】:UICollectionView repeating and showing items out of orderUICollectionView 重复并无序显示项目
【发布时间】:2018-02-05 19:34:32
【问题描述】:

我已经为 UICollectionView 构建了一个自定义渲染器。

我遇到了一个问题,我想不出可能的修复方法,就在这里。

每当用户滚动 UICollectionView 时,屏幕上要显示的下一个项目会乱序显示并重复。

你可以在 GitHub 上找到我的代码:https://github.com/DanielCauser/XamarinHorizontalList

这个 gif 确切地显示了问题所在,一些列表项出现了不止一次并且乱序。

我认为这是由于竞争条件,操作系统只是将数据加载到它在该帧可用的任何视单元中。

这是我在 Xamarin.Forms 中的视图:

<local:HorizontalViewNative ItemsSource="{Binding Monkeys}"
                            Grid.Row="5"
                            VerticalOptions="Start"
                            ItemHeight="100"
                            ItemWidth="100">
                <local:HorizontalViewNative.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <ContentView>
                                <StackLayout WidthRequest="100"
                                             HeightRequest="100">
                                    <Image Source="{Binding Image}" />

                                    <Label Text="{Binding Name}"
                                               LineBreakMode="MiddleTruncation"
                                               HorizontalTextAlignment="Center"
                                               VerticalTextAlignment="Center"/>
                                </StackLayout>
                            </ContentView>
                        </ViewCell>
                    </DataTemplate>
                </local:HorizontalViewNative.ItemTemplate>
            </local:HorizontalViewNative>

这是我在 Xamarin.Forms 项目中的自定义控件:

public class HorizontalViewNative : View
    {
        public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(HorizontalViewNative), default(IEnumerable<object>), BindingMode.TwoWay, propertyChanged: ItemsSourceChanged);

        public static readonly BindableProperty ItemTemplateProperty =
            BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(HVScrollGridView), default(DataTemplate));

        public static readonly BindableProperty ItemHeightProperty =
            BindableProperty.Create("ItemHeight", typeof(int), typeof(HVScrollGridView), default(int));

        public static readonly BindableProperty ItemWidthProperty =
            BindableProperty.Create("ItemWidth", typeof(int), typeof(HVScrollGridView), default(int));

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public int ItemHeight
        {
            get { return (int)GetValue(ItemHeightProperty); }
            set { SetValue(ItemHeightProperty, value); }
        }

        public int ItemWidth
        {
            get { return (int)GetValue(ItemWidthProperty); }
            set { SetValue(ItemWidthProperty, value); }
        }

        private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var itemsLayout = (HorizontalViewNative)bindable;
        }
    }

这是我在 iOS 项目中的自定义渲染(使用 UICollectionView、UICollectionViewSource 和 UICollectionViewCell)。

[assembly: ExportRenderer(typeof(HorizontalViewNative), typeof(iOSHorizontalViewRenderer))]
namespace XamarinHorizontalList.iOS
{
    public class iOSHorizontalViewRenderer : ViewRenderer<HorizontalViewNative, UICollectionView>
    {
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(HorizontalViewNative.ItemsSource))
            {
                Control.Source = new iOSViewSource(Element as HorizontalViewNative);
                Control.RegisterClassForCell(typeof(iOSViewCell), nameof(iOSViewCell));
            }
        }

        protected override void OnElementChanged(ElementChangedEventArgs<HorizontalViewNative> e)
        {
            base.OnElementChanged(e);

            if (Control == null)
            {
                var layout = new UICollectionViewFlowLayout();
                layout.ScrollDirection = UICollectionViewScrollDirection.Horizontal;

                if (e.NewElement != null)
                {
                    layout.ItemSize = new CGSize(e.NewElement.ItemWidth, e.NewElement.ItemHeight);
                    layout.MinimumInteritemSpacing = 0;
                    layout.MinimumLineSpacing = 0;

                    var rect = new CGRect(0, 0, 100, 100);
                    SetNativeControl(new UICollectionView(rect, layout));
                    Control.BackgroundColor = e.NewElement?.BackgroundColor.ToUIColor();
                }
            }
        }
    }

    public class iOSViewSource : UICollectionViewSource
    {
        private readonly HorizontalViewNative _view;

        private readonly IList _dataSource;

        public iOSViewSource(HorizontalViewNative view)
        {
            _view = view;
            _dataSource = view.ItemsSource?.Cast<object>()?.ToList();
        }

        public override nint NumberOfSections(UICollectionView collectionView)
        {
            return 1;
        }

        public override nint GetItemsCount(UICollectionView collectionView, nint section)
        {
            return _dataSource != null ? _dataSource.Count : 0;
        }

        public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
        {
            iOSViewCell cell = (iOSViewCell)collectionView.DequeueReusableCell(nameof(iOSViewCell), indexPath);
            var dataContext = _dataSource[indexPath.Row];
            Debug.WriteLine(((Monkey)dataContext).Name);
            if (dataContext != null)
            {
                var dataTemplate = _view.ItemTemplate;
                ViewCell viewCell;
                var selector = dataTemplate as DataTemplateSelector;
                if (selector != null)
                {
                    var template = selector.SelectTemplate(_dataSource[indexPath.Row], _view.Parent);
                    viewCell = template.CreateContent() as ViewCell;
                }
                else
                {
                    viewCell = dataTemplate?.CreateContent() as ViewCell;
                }

                cell.UpdateUi(viewCell, dataContext, _view);
            }
            return cell;
        }
    }

    public class iOSViewCell : UICollectionViewCell
    {
        private UIView _view;

        public iOSViewCell(IntPtr p) : base(p)
        {
        }

        public void UpdateUi(ViewCell viewCell, object dataContext, HorizontalViewNative view)
        {
            viewCell.BindingContext = dataContext;
            viewCell.Parent = view;

            var height = (int)((view.ItemHeight + viewCell.View.Margin.Top + viewCell.View.Margin.Bottom));
            var width = (int)((view.ItemWidth + viewCell.View.Margin.Left + viewCell.View.Margin.Right));
            viewCell.View.Layout(new Rectangle(0, 0, width, height));

            if (Platform.GetRenderer(viewCell.View) == null)
            {
                Platform.SetRenderer(viewCell.View, Platform.CreateRenderer(viewCell.View));
            }
            var renderer = Platform.GetRenderer(viewCell.View).NativeView;

            if (_view == null)
            {
                renderer.ContentMode = UIViewContentMode.ScaleAspectFit;
                ContentView.AddSubview(renderer);
            }
            _view = renderer;
        }
    }
}

【问题讨论】:

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


    【解决方案1】:

    在 iOS 上,UICollectionView 将重用单元格,因为您调用了 DequeueReusableCell(nameof(iOSViewCell), indexPath);

    这意味着当集合视图首次加载其内容时,它会要求其数据源为每个可见项提供一个视图。为了简化代码的创建过程,集合视图要求您始终使视图出队,而不是在代码中显式创建它们。详情请见here

    因此,您的 UICollectionView 起初似乎在最初的四个单元格中运行良好。但是在你滚动它之后,单元格的内容会因为这个不正确的陈述而变得混乱:

    if (_view == null)
    {
        renderer.ContentMode = UIViewContentMode.ScaleAspectFit;
        ContentView.AddSubview(renderer);
    }
    

    UICollectionView 尝试重用队列中的Cell 时,属性_view 不会为空,所以ContentView 不会添加新的子视图。然后您的单元格显示旧图像和文本。你可以试试这个来修复它:

    //if (_view == null)
    //{
    //Remove all subViews from contentView when the cell being reused.
    foreach(UIView subView in ContentView.Subviews)
    {
        subView.RemoveFromSuperview();
    }
    renderer.ContentMode = UIViewContentMode.ScaleAspectFit;
    ContentView.AddSubview(renderer);
    //}
    //_view = renderer;
    

    但我要推荐你做的是:既然你尝试使用渲染器,为什么不尝试使用原生方法来创建你的单元格、数据源?

    首先,修改单元格的结构,如下所示:

    UIImageView imgView;
    UILabel label;
    public iOSViewCell(IntPtr p) : base(p)
    {
        imgView = new UIImageView(new CGRect(0, 0, 100, 80));
        ContentView.AddSubview(imgView);
        imgView.ContentMode = UIViewContentMode.ScaleAspectFit;
    
        label = new UILabel(new CGRect(0, 80, 100, 20));
        label.TextAlignment = UITextAlignment.Center;
        label.LineBreakMode = UILineBreakMode.MiddleTruncation;
        ContentView.AddSubview(label);
    }
    

    同时更改UpdateUi()方法:

    public void UpdateUi(object dataContext)
    {
        Monkey monkey = dataContext as Monkey;
        imgView.Image = UIImage.FromBundle(monkey.Image);
        label.Text = monkey.Name;
    }
    

    最后一步是修改GetCell()事件:

    public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
    {
        iOSViewCell cell = (iOSViewCell)collectionView.DequeueReusableCell(nameof(iOSViewCell), indexPath);
        var dataContext = _dataSource[indexPath.Row];
        if (dataContext != null)
        {
            cell.UpdateUi(dataContext);
        }
        return cell;
    }
    

    这样scrollView可以滚动得更流畅。

    【讨论】:

    • 感谢陆陆的回复。我将尝试从 ContentView 中删除子视图,我正在这样做,但是在 ContentView 上,例如 ContentView.RemoveFromSuperview();您对尝试本机控件的建议是有道理的,但是在这个项目中,我希望能够将任何类型的 UI 作为我的列表 ItemTemplate 和任何类型的数据作为我的绑定上下文,这就是我试图保留它的原因尽可能通用。
    • 从 ContentView 中删除子视图并添加我完美工作的新视图!非常感谢!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多