隐藏

xamarin.forms使用stacklayout自定义列表及相关加载状态处理

发布:2021/10/16 18:21:50作者:管理员 来源:本站 浏览次数:988

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();
                        //右边列表source
                        var 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();
            }
        }
        }
    }