Xamarin.Forms自定义GridView
在开发中,我们经常用到以格子的形式来展示我们的数据,在很多平台的控件中我们叫做GridView,
在Xamarin.Forms中没有原生的GridView,这里简单介绍一种利用Xamarin.Forms中的Grid来实现
GridView的方法。
原理就是对Grid动态添加RowDefinition和ColumnDefinition。
代码如下:
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Diagnostics; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using System.Windows.Input; 9 using Xamarin.Forms; 10 using Xamarin.Forms.Xaml; 11 12 namespace XFPractice.CustomView 13 { 14 [XamlCompilation(XamlCompilationOptions.Compile)] 15 public partial class GridView : Grid 16 { 17 18 public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(GridView)); 19 public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command),typeof(ICommand), typeof(GridView)); 20 public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate),typeof(DataTemplate),typeof(GridView),null); 21 22 public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(GridView), default(IEnumerable<object>), propertyChanged: OnItemsSourcePropertyChanged); 23 24 25 private int _maxColumns = 2; 26 private float _tileHeight = 100; 27 28 public GridView() 29 { 30 InitializeComponent(); 31 for (var i = 0; i < MaxColumns; i++) 32 { 33 ColumnDefinitions.Add(new ColumnDefinition()); 34 } 35 } 36 37 38 39 public static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) 40 { 41 ((GridView)bindable).UpdateTiles(); 42 } 43 44 45 46 public DataTemplate ItemTemplate 47 { 48 get { return (DataTemplate)GetValue(ItemTemplateProperty); } 49 set { SetValue(ItemTemplateProperty, value); } 50 } 51 52 public IEnumerable ItemsSource 53 { 54 get { return (IEnumerable)GetValue(ItemsSourceProperty); } 55 set {SetValue(ItemsSourceProperty, value);} 56 } 57 58 public int MaxColumns 59 { 60 get { return _maxColumns; } 61 set { _maxColumns = value; } 62 } 63 64 public float TileHeight 65 { 66 get { return _tileHeight; } 67 set { _tileHeight = value; } 68 } 69 70 public object CommandParameter 71 { 72 get { return GetValue(CommandParameterProperty); } 73 set { SetValue(CommandParameterProperty, value); } 74 } 75 76 public ICommand Command 77 { 78 get { return (ICommand)GetValue(CommandProperty); } 79 set { SetValue(CommandProperty, value); } 80 } 81 82 public async Task BuildTiles(IEnumerable tiles) 83 { 84 // Wipe out the previous row definitions if they‘re there. 85 if (RowDefinitions.Any()) 86 { 87 RowDefinitions.Clear(); 88 } 89 Children.Clear(); 90 if (tiles == null) 91 { 92 return; 93 } 94 var enumerable = tiles as IList ?? tiles.Cast<object>().ToArray(); 95 var numberOfRows = Math.Ceiling(enumerable.Count / (float)MaxColumns); 96 HeightRequest = TileHeight * numberOfRows; 97 InvalidateLayout(); 98 for (var i = 0; i < numberOfRows; i++) 99 { 100 RowDefinitions.Add(new RowDefinition { Height = TileHeight }); 101 } 102 int ItemCount = enumerable.Count; 103 int size = (int)numberOfRows * MaxColumns; 104 105 for (var index = 0; index < size; index++) 106 { 107 var column = index % MaxColumns; 108 var row = (int)Math.Floor(index / (float)MaxColumns); 109 if (index < ItemCount) 110 { 111 var tile = await BuildTile(enumerable[index]); 112 Children.Add(tile, column, row); 113 } 114 else 115 { 116 var tile = await BuildEmptyTile(); 117 Children.Add(tile, column,row); 118 } 119 } 120 } 121 122 public async void UpdateTiles() 123 { 124 await BuildTiles(ItemsSource); 125 } 126 127 private async Task<Xamarin.Forms.View> BuildEmptyTile() 128 { 129 return await Task.Run(() => 130 { 131 var content = ItemTemplate?.CreateContent(); 132 if (!(content is Xamarin.Forms.View) && !(content is ViewCell)) throw new Exception(content.GetType().ToString()); 133 var buildTile = (content is Xamarin.Forms.View) ? content as Xamarin.Forms.View : ((ViewCell)content).View; 134 return buildTile; 135 }); 136 137 } 138 139 private async Task<Xamarin.Forms.View> BuildTile(object item) 140 { 141 return await Task.Run(() => 142 { 143 var content = ItemTemplate?.CreateContent(); 144 if (!(content is Xamarin.Forms.View) && !(content is ViewCell)) throw new Exception(content.GetType().ToString()); 145 var buildTile = (content is Xamarin.Forms.View) ? content as Xamarin.Forms.View : ((ViewCell)content).View; 146 buildTile.BindingContext = item; 147 var tapGestureRecognizer = new TapGestureRecognizer 148 { 149 Command = Command, 150 CommandParameter = item 151 }; 152 buildTile.GestureRecognizers.Add(tapGestureRecognizer); 153 return buildTile; 154 }); 155 156 } 157 158 } 159 }
使用如下:
<controls:GridView x:Name="mGridView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" TileHeight="48" MaxColumns ="5" ItemsSource="{Binding UserInfoList,Mode=TwoWay}"> <controls:GridView.ItemTemplate> <DataTemplate> <Grid Padding="8" HeightRequest="48" WidthRequest="48" HorizontalOptions="Center"> <Image Source="{Binding HeadImage}" HeightRequest="32" WidthRequest="32" Aspect="AspectFill"/> </Grid> </DataTemplate> </controls:GridView.ItemTemplate> </controls:GridView>
这段代码有一个问题,ItemsSource绑定ListSource后,当ListSource发生增加减少不会触发GridView的刷新,
之后将ListSource重新new一个对象后,才会触发propertyChanged从而刷新GridView。