在以前写UWP程序的时候,了解到在ListView或者ListBox这类的列表空间中,有一个叫做ItemsPannel的属性,它是所有列表中子元素实际的容器,如果要让列表进行横向排列,只需要在Xaml中如下编辑即可

    //UWP中用XAML大致实现如下
···
<ListView.ItemsPannel>
<StackPannel Orientation="Horizental"/>
</ListView.ItemsPannel>
···

这种让列表元素横向排列实际是一个很常见的场景,但是在Xamarin.Forms中,并没有提供直接的实现方法,如果想要这种效果,有两种解决办法

  • Renderer:利用Renderer在各平台实现,适用于对性能有较高要求的场景,比如大量数据展示
  • 自定义布局:实现比较简单,但是适用于数据量比较小的场景

    实际在使用的时候,利用自定义布局会比较简单,并且横向的列表展示并不适合大量数据的场景。

怎么实现呢?

Xamarin.Forms的列表控件是直接利用Renderer实现的,没有提供类似ItemsPannel之类的属性,所以考虑直接自己实现一个列表控件。有以下几个点:

  • 列表控件要支持滚动:所以在控件最外层需要一个ScrollView
  • 实现类似ItemsPannel的效果:所以需要实现一个ItemsPannel属性,类型是StackLayout,并且它应该是ScrollView的Content
  • ItemsControl控件的基类型是View,便于使用,直接让它继承自ContentView,这样就可以直接设置其Content为ScrollView

至此,先来给出这部分的代码,我们直接在构造函数中完成绝大多数操作

    ···
private ScrollView _scrollView;
private StackLayout itemsPanel = null;
public StackLayout ItemsPanel
{
get { return this.itemsPanel; }
set { this.itemsPanel = value; }
}
public ItemsControl()
{
this._scrollView = new ScrollView();
this._scrollView.Orientation = Orientation; this.itemsPanel = new StackLayout() { Orientation = StackOrientation.Horizontal };//子元素水平排布的关键 this.Content = this._scrollView;
this._scrollView.Content = this.itemsPanel;
}
···

子元素的容器是ItemsPannel,它实际是一个水平排布的StackLayout。想要在列表控件添加子元素,实际就是对该StackLayout的Children添加子元素。

考虑到列表控件中子元素的添加,就必须实现一个属性ItemsSource,是集合类型,并且为了支持数据绑定等,还需要让他是一个依赖属性,针对ItemsSource属性值自身的改变或者其集合中元素的添加删除等,都需要监听,并且将具体变化表现在ItemsControl中。实现该属性如下:

    ···
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(ItemsControl), defaultBindingMode: BindingMode.OneWay, defaultValue: null, propertyChanged: OnItemsSourceChanged); public IEnumerable ItemsSource
{
get { return (IEnumerable)this.GetValue(ItemsSourceProperty); }
set { this.SetValue(ItemsSourceProperty, value); }
}
···
Static vid OnItemsSourceChanged(BindableObject sender,object oldValue,object newValue)
{
···
}
···

当为ItemsSource属性赋值之后,OnItemsSourceChanged方法被调用,在该方法中,需要干这么几件事儿:

  • 为ItemsSource中的每一个元素,根据ItemTemplate创建相应的View,设置View的数据绑定上想问BindingContext为该元素,并且将此View添加到ItemsPannel中(ItemsPannel实际是StackLayout,他的子元素必须继承自View或者是View)
  • 检测ItemsSource的数据源是否实现了接口INotifyCollectionChanged,如果实现了,需要订阅其CollectionChanged事件,注册一个方法,便于在集合元素变动后调用我们注册的方法,来通知ItemsControl控件,把具体的变动表现在UI层面(通常就是元素的添加和删除)

OnItemsSourceChanged方法实现如下:

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ItemsControl), defaultValue: default(DataTemplate));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)this.GetValue(ItemTemplateProperty); }
set { this.SetValue(ItemTemplateProperty, value); }
} static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = bindable as ItemsControl;
if (control == null)
{
return;
}
//检测是否实现该接口,如果实现,就订阅该事件
var oldCollection = oldValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.OnCollectionChanged;
} if (newValue == null)
{
return;
} control.ItemsPanel.Children.Clear(); //遍历数据源中每个元素,为它创建View,并设置其BindingContext
foreach (var item in (IEnumerable)newValue)
{
object content;
content = control.ItemTemplate.CreateContent();
View view;
var cell = content as ViewCell;
if (cell != null)
{
view = cell.View;
}
else
{
view = (View)content;
} //元素点击相关事件
view.GestureRecognizers.Add(control._tapGestureRecognizer);
view.BindingContext = item;
control.ItemsPanel.Children.Add(view);
} var newCollection = newValue as INotifyCollectionChanged;
if (newCollection != null)
{
newCollection.CollectionChanged += control.OnCollectionChanged;
}
control.SelectedItem = control.ItemsPanel.Children[control.SelectedIndex].BindingContext; //更新布局
control.UpdateChildrenLayout();
control.InvalidateLayout(); }

CollectionChanged实现方法如下:

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
this.ItemsPanel.Children.RemoveAt(e.OldStartingIndex);
this.UpdateChildrenLayout();
this.InvalidateLayout();
} if (e.NewItems == null)
{
return;
}
foreach (var item in e.NewItems)
{
var content = this.ItemTemplate.CreateContent(); View view;
var cell = content as ViewCell;
if (cell != null)
{
view = cell.View;
}
else
{
view = (View)content;
}
if (!view.GestureRecognizers.Contains(this._tapGestureRecognizer))
{
view.GestureRecognizers.Add(this._tapGestureRecognizer);
}
view.BindingContext = item;
this.ItemsPanel.Children.Insert(e.NewItems.IndexOf(item), view);
} this.UpdateChildrenLayout();
this.InvalidateLayout(); }

到目前为止,已经实现ItemsControl控件大部分的内容了,还需要实现的有

  • SelectedItem,SelectedIndex:当前列表选定项
  • ItemSelected:列表中元素被选定时触发

怎么判断元素被选定呢?

当一个元素被点击后,认为它被选中了,所以需要监听列表中每一个元素的点击事件。

列表中每一个View被点击后,触发OnTapped事件,事件的发送者是该View本身

        //只定义一个TapGestureRecognizer,不需要为每一个元素都创建,只需要为每一个元素的GestureRecognizers集合添加该实例即可。
TapGestureRecognizer _tapGestureRecognizer; //在构造函数中创建一个Tap事件的GestureRecognizer,并且订阅其Tapped事件
public ItemsControl()
{
_tapGestureRecognizer = new TapGestureRecognizer();
_tapGestureRecognizer.Tapped += OnTapped;
}
···
private void OnTapped(object sender, EventArgs e)
{
var view = (BindableObject)sender;
this.SelectedItem = view.BindingContext; }
···
static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
···
if (!view.GestureRecognizers.Contains(this._tapGestureRecognizer))
{
view.GestureRecognizers.Add(this._tapGestureRecognizer);
}
···
}
···

一个基本的ItemsControl列表控件就完成了,至此,它的已经具备Xamarin.Forms提供的ListView的大致功能。不过还是有几点

  • 它不支持虚拟化技术,所以在列表数据量比较大的时候,会有明显的卡顿

具体代码和Demo看我的Github:

ItemsControl源码

Xamarin自定义布局系列——ListView的一个自定义实现ItemsControl(横向列表)的更多相关文章

  1. 【Android基础】listview控件的使用(4)-----自定义布局的listview的使用

    前面我介绍了listview控件的不同用法,但是这些用法在实际的开发项目中是不足以满足需求的,因为前面的几种用法只能简单的显示文本信息,而且布局都比较单一,很难做出复杂的结果,在实际的开发项目中,90 ...

  2. 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)

    [SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...

  3. Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动

    在上一篇文章<Android自定义组件系列[3]--自定义ViewGroup实现侧滑>中实现了仿Facebook和人人网的侧滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布局示 ...

  4. Xamarin自定义布局系列——瀑布流布局

    Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...

  5. Xamarin自定义布局系列——支持无限滚动的自动轮播视图CarouselView

    背景简述 自动轮播视图(CarouselView)在现在App中的地位不言而喻,绝大多数的App中都有类似的视图,无论是WebApp还是Native App.在安卓.iOS以及Windows(UWP) ...

  6. Xamarin自定义布局系列——PivotPage,多页面切换控件

    PivotPage ---- 多页面切换控件 PivotPage是一个多页面切换控件,类似安卓中的ViewPager和UWP中的Pivot枢轴控件. 起初打算直接通过ScrollView+StackL ...

  7. Android自定义组件系列【3】——自定义ViewGroup实现侧滑

    有关自定义ViewGroup的文章已经很多了,我为什么写这篇文章,对于初学者或者对自定义组件比较生疏的朋友虽然可以拿来主义的用了,但是要一步一步的实现和了解其中的过程和原理才能真真脱离别人的代码,举一 ...

  8. Android自定义组件系列【1】——自定义View及ViewGroup

    View类是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用来充当View的容器,将其中的View作为自己孩子,并对其进行管理,当然孩子也可以是ViewGr ...

  9. 在JS中,一个自定义函数如何调用另一个自定义函数中的变量

    function aa1511() { var chengshi="马鞍山"; var shengfen="安徽省"; return shengfen+&quo ...

随机推荐

  1. 动软模板系列二(Model层模板)

    动软模板其实和CodeSmith的模板差不多 实现的原理是一样的,但是CodeSmith貌似只支持表生成,而且不够“国人化”,所以打算研究下动软的模板,如果熟练掌握后想必以后开发项目效率可以提高很多了 ...

  2. Spring MVC DispatcherServlet绑定多种URL

    需要学习的内容: http://my.oschina.net/shishuifox/blog/215617 当前的处理方式,在web.xml中配置: <servlet> <servl ...

  3. Spring Boot启动过程(一)

    之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE. 首先,普通的入口,这没什么好说的,我就随便贴贴代码了: SpringAp ...

  4. 搭建spring工程配置数据源连接池

    Spring作为一个优秀的开源框架,越来越为大家所熟知,前段时间用搭了个spring工程来管理数据库连接池,没有借助Eclipse纯手工搭建,网上此类文章不多,这里给大家分享一下,也作为一个手记. 工 ...

  5. Java内存回收优化及配置

    原文链接:http://eol.cqu.edu.cn/eol/jpk/course/preview/jpkmaterials_folder_txtrtfview.jsp?resId=23156& ...

  6. Awesome Chrome 插件集锦

    子曾曰:"工欲善其事,必先利其器.居是邦也."--语出<论语·卫灵公>:其后一百多年,荀子也在其<劝学>中倡言道:"吾尝终日而思矣,不如须臾之所学 ...

  7. 龙珠超的新OP【限界突破×サバイバー】

    这首歌真的很燃 下载>>   限界突破×サバイバー 中文歌词 演唱:冰川清志 兴奋了!就去宇宙吧 最先端的“着迷”怎么样! 握在手中 突然想要大笑 糊里糊涂也习惯了吗! I can't g ...

  8. 设置npm安装模块目录<nodejs>

    nodejs安装模块命令: npm install <input_name> # 本地安装 npm install <input_name> -g # 全局安装 1.npm i ...

  9. [商业_法务] 2、注册公司起名很费劲,用C++怒写个随机名字生成器

    前言 博主最近在注册公司,由于之前听说过注册公司的名字很难通过,于是便直接找代理去帮忙跑趟,为确保万无一失,还自己绞尽脑汁想了几个很奇葩的名字(噬菌体.云木.灌木.杏仁...). 但是不幸的是那些奇葩 ...

  10. phpcms的安装以及简单使用

    先来说一下phpcms的安装 首先从网上下个phpcms的压缩包,解压 解压后就是个这样的文件夹 这里要注意,下载的时候要放在平时存动态网页的那个地址,www目录下,如图 点开phpcms文件夹,里面 ...