xamarin.forms本身有提供ListView控件,个人觉得不够灵活,而且在和ScrollView嵌套使用时,会存在内外两个滚动条问题,不好处理。
我们可以用ScrollView和StackLayout及TapGestureRecognizer做一个列表功能,可自定义每行item个数及其他的自定义动作,下边做一个单行双item列表,scrollview滚动到底部时,显示加载状态并加载数据:
1、xaml部分
//网络异常时重载按钮
<Button x:Name="btnReload" BorderWidth="0" BackgroundColor="White" HorizontalOptions="Center" IsVisible="false"></Button>
//列表主体
<ScrollView x:Name="svList" Orientation="Vertical" VerticalOptions="StartAndExpand"><Frame OutlineColor="Red" BackgroundColor="White" HasShadow="False"><StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">//筛选条件下返回数据为空时显示<StackLayout Orientation="Horizontal"><Label x:Name="lblNoResult"></Label></StackLayout>//列表数据<StackLayout x:Name="searchList" VerticalOptions="FillAndExpand"></StackLayout></StackLayout></Frame></ScrollView>//加载状态提示<Label x:Name="lblLoading" Text="全力帮您加载中,稍等" HorizontalOptions="Center" IsVisible="false"></Label><Label x:Name="lblLoaded" Text="已无更多" HorizontalOptions="Center" IsVisible="false"></Label>
2、xaml.cs
public partial class List : ContentPage{//这边异步锁用来防止数据重复加载,不做描述,有兴趣的请自行查找相关资料static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);public int PageIndex{ get; set; }public int PageSize { get; set; }public List(){InitializeComponent();//分页初始值this.PageIndex = 0;this.PageSize = 10;//列表初始化加载this.Appearing += async (sender, e) =>{ var initResult = await BindSearchResult();};//滚动到底部时加载数据svList.Scrolled += async (sender, e) =>{//1、重载按钮是否为显示状态//2、列表滚动位置计算:内容高度 - 列表高度if (!btnReload.IsVisible && svList.ScrollY >= svList.ContentSize.Height - svList.Height){var result = await BindSearchResult();}};//重载按钮事件,请求异常或者网络异常时需要重新加载数据btnReload.Command = new Command(async () =>{await BindSearchResult();});private async Task<bool> BindSearchResult(){//加载状态显示lblLoading.IsVisible = true;//重载按钮隐藏btnReload.IsVisible = false;//防止短时间内重复加载数据await semaphoreSlim.WaitAsync();try{ //构建请求参数MultipartFormDataContent可以同时传递不同类型的请求参数:StringContent、//StreamContent等。 var mpContent = new MultipartFormDataContent();mpContent.Add(new StringContent(this.PageIndex.ToString(), Encoding.UTF8), "pageIndex");mpContent.Add(new StringContent(this.PageSize.ToString(), Encoding.UTF8), "pageSize");//Utils.PostAsync<T>()自己封装的restapi请求方法,这边不介绍,UrlConfig类为各种api请求的Url集合var postResult = await Utils.PostAsync<SearchResult>(UrlConfig.SearchUrl, mpContent, (message) =>{btnReload.Text = message + "点击重新加载";btnReload.IsVisible = true;lblLoading.IsVisible = false;});if (postResult != null && postResult.success){//判断请求结果记录数if (postResult.data.total > 0){lblNoResult.IsVisible = false;//左边列表source var search_l = postResult.data.data.Where((d, i) => i % 2 == 0).Select(d => new{//这边为左边列表要绑定的业务数据}).ToList();//右边列表sourcevar search_r = postResult.data.data.Where((d, i) => i % 2 != 0).Select(d => new{//这边为右边列表要绑定的业务数据}).ToList();//UI构造,这个例子为双item,所以以右边列表个数作为遍历上限//使用Grid列表item的布局,这边以一张图片和图片名称的Label示例for (var i = 0; i < search_r.Count(); i++){var grid = new Grid();var item_l = new StackLayout();item_l.Children.Add(new Image() { Source = search_l[i].ImagePath, HeightRequest = 120 });item_l.Children.Add(new Label() { Text = search_l[i].ImageName, HorizontalOptions = LayoutOptions.Center });//图片ID编号,由列表进入item详情页时传递过去item_l.Children.Add(new Label() { Text = search_l[i].ID.ToString(), IsVisible = false });var item_r = new StackLayout();item_r.Children.Add(new Image() { Source = search_r[i].ImagePath, HeightRequest = 120 });item_r.Children.Add(new Label() { Text = search_r[i].ImageName, HorizontalOptions = LayoutOptions.Center });//图片ID编号,由列表进入item详情页时传递过去item_r.Children.Add(new Label() { Text = search_r[i].ID.ToString(), IsVisible = false });grid.Children.Add(item_l, 0, 0);grid.Children.Add(item_r, 1, 0);//左边列表item点击事件var tap_l = new TapGestureRecognizer();tap_l.Tapped += (sender, e) =>{//点击进入详情页var id = ((Label)(((StackLayout)sender).Children[2])).Text;var name = ((Label)(((StackLayout)sender).Children[1])).Text;this.Navigation.PushAsync(new Info(int.Parse(id)) { Title = /*自定义详情页标题*/ });};item_l.GestureRecognizers.Add(tap_l);//右边列表item点击事件var tap_r = new TapGestureRecognizer();tap_r.Tapped += (sender, e) =>{//点击进入详情页var id = ((Label)(((StackLayout)sender).Children[2])).Text;var name = ((Label)(((StackLayout)sender).Children[1])).Text;this.Navigation.PushAsync(new Info(int.Parse(id)) { Title = /*自定义详情页标题*/ });};item_r.GestureRecognizers.Add(tap_r);searchList.Children.Add(grid);}//请求个数恰巧为单数时处理,逻辑同上if (search_l.Count() > search_r.Count()){var grid = new Grid();var item = new StackLayout();item.Children.Add(new Image() { Source = search_l[search_l.Count() - 1].ImagePath, HeightRequest = 120 });item.Children.Add(new Label() { Text = search_l[search_l.Count() - 1].ImageName, HorizontalOptions = LayoutOptions.Center });item.Children.Add(new Label() { Text = search_l[search_l.Count() - 1].ID.ToString(), IsVisible = false });var tap = new TapGestureRecognizer();tap.Tapped += (sender, e) =>{//点击进入详情页var id = ((Label)(((StackLayout)sender).Children[2])).Text;var name = ((Label)(((StackLayout)sender).Children[1])).Text;this.Navigation.PushAsync(new Info(int.Parse(id)) { Title = /*自定义详情页标题*/ });};item.GestureRecognizers.Add(tap);grid.Children.Add(item, 0, 0);searchList.Children.Add(grid);}}else{//请求返回的数据为空处理lblNoResult.IsVisible = true;lblNoResult.Text = string.Format("抱歉,没有找到{0}的信息", /*某某某*/);}//关闭加载状态显示lblLoading.IsVisible = false;if (!postResult.data.data.Any()){lblLoaded.IsVisible = true;await lblLoaded.FadeTo(0, 2000);lblLoaded.Opacity = 1;lblLoaded.IsVisible = false;}//当前分页数累加this.PageIndex++;return true;}return false;}finally{//释放锁semaphoreSlim.Release();}}}}