Xamarin Forms:如何检测滚动视图(或扩展器)结束或到达顶部?

Xamarin Forms: How to detect the scrollview(or expander) end or top reached?

我需要在滚动视图到达末尾或顶部时加载更多数据。我在这里找到了相同的thread,但是在那上面,我不明白什么是MyScrollView

private void OnScrolled(object sender, ScrolledEventArgs e)
{
    MyScrollView scrollView = sender as MyScrollView;
    double scrollingSpace = scrollView.ContentSize.Height - scrollView.Height;

    if (scrollingSpace <= e.ScrollY)
    {
       //reached end
    }
}

我在 ListView 中使用 ItemAppearing 事件实施了相同的操作。但是目前,我在 UI 上使用 Expander 来显示数据。扩展器到达终点和起点是否有相同类型的事件?扩展器位于滚动视图内部,这就是我开始研究滚动视图结束事件的原因。

如何在滚动视图结束或开始时触发某种事件?

更新

我的 scrollview 内容如下,它包含 BindableLayoutExpander

<ScrollView
    x:Name="MyScrollView"
    Scrolled="OnScrolled">
    <StackLayout BindableLayout.ItemsSource="{Binding AllItems,Mode=TwoWay}">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <Expander>
                </Expander>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
</ScrollView>

MyScrollView 可能是一个使用ScrollView 的自定义视图。这是我使用 Listview 的示例代码。您也可以将其应用于您的自定义滚动视图。

CustomListview.cs

public class CustomListView : ListView
{
    public CustomListView()
    {
    }
}

iOS

[assembly: ExportRenderer(typeof(CustomListView), typeof(CustomListViewRenderer))]
namespace YOUR_PROJECT_NAME.iOS.Renderers
{
    public class CustomListViewRenderer : ListViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);
            if (Element != null)
            {
                Control.AlwaysBounceVertical = Element.IsPullToRefreshEnabled;
                Control.Bounces = Element.IsPullToRefreshEnabled;
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == nameof(Element.IsPullToRefreshEnabled))
            {
                Control.AlwaysBounceVertical = Element.IsPullToRefreshEnabled;
                Control.Bounces = Element.IsPullToRefreshEnabled;
            }
        }
    }
}

Android

No custom renderer is needed

Xaml

<Grid RowSpacing="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0"
              x:Name="DashboardBarGrid"
              BackgroundColor="#263A43"
              RowSpacing="0">

            <Grid.RowDefinitions>
                <RowDefinition Height="50" />
            </Grid.RowDefinitions>

            <Grid Grid.Row="0"
                  HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
                <Label Text="Title" TextColor="White"/>
            </Grid>

        </Grid>
         <controls:CustomListView Grid.Row="1" ItemsSource="{Binding Items}" Scrolled="listView_Scrolled" IsPullToRefreshEnabled="False"/>
     </Grid>

Page.cs

...
private Animation _lastRowAnimation;
private readonly double _initialLastRowHeight;
private readonly RowDefinition _lastRowDefinition;
private bool _isScrolled;
private bool _isFirstRun;
public List<string> Items { get; set; } = new List<string>();
...

public MyPage()
{
   InitializeComponent();
   _lastRowDefinition = DashboardBarGrid.RowDefinitions[0];
   _initialLastRowHeight = _lastRowDefinition.Height.Value;
   _isFirstRun = true;
            
   Items.Add("A");
   Items.Add("B");
   Items.Add("C");
   ...
   Items.Add("Z");

   BindingContext = this;
}

...
private void listView_Scrolled(object sender, ScrolledEventArgs e)
{
   var listView = (ListView)sender;
   if (e.ScrollY > 50) _isFirstRun = false;
   if (_isScrolled && e.ScrollY == 0 && _lastRowDefinition?.Height.Value < _initialLastRowHeight){
      listView.IsEnabled = false;
      _lastRowAnimation = new Animation((d) => _lastRowDefinition.Height = new GridLength(d.Clamp(50, double.MaxValue)), _lastRowDefinition.Height.Value, _initialLastRowHeight, Easing.Linear, () => _lastRowAnimation = null);
      _lastRowAnimation.Commit(this, "Animation", 16, 500, Easing.Linear, (v, c) =>
      {
         _isScrolled = false;
         listView.IsEnabled = true;
      });
   }


   // Hide the rows
   else if (!_isScrolled && _lastRowDefinition?.Height.Value >= 
   _initialLastRowHeight && !_isFirstRun)
   {
      listView.IsEnabled = false;
      _lastRowAnimation = new Animation((d) => _lastRowDefinition.Height = new GridLength(d.Clamp(0, double.MaxValue)), _initialLastRowHeight, 0, Easing.Linear, () => _lastRowAnimation = null);
      _lastRowAnimation.Commit(this, "Animation", 16, 500, Easing.Linear, (v, c) =>    {
         _isScrolled = true;
         listView.IsEnabled = true;
      });
   }
}

您的原始代码在正确的轨道上。前段时间我花了一些时间才让它工作,但这是我使用的代码。比那个MyScrollView简单多了class.

private void TheScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
    if (TheScrollView.ScrollY >= TheScrollView.ContentSize.Height - TheScrollView.Height)
    {
        //All the way to the bottom.
    }
    else if (TheScrollView.ScrollX <= 0)
    {
        //All the way to the top.
    }
    else
    {
        //Neither all the way to the top nor to the right.
    }
}

就像我在问题中提到的那样,我在 ListView 中使用 ItemAppearing 事件实现了相同的功能。但在这种情况下,我的数据是另一个列表中的项目列表。这就是为什么我被迫使用 Expander 组件来显示 UI.

上的数据

同时,当Expander底部到达时,我需要加载更多的项目。但是 Expander 不提供相关事件,因为它不是可滚动控件。所以我试图找出 ScrollView 组件的事件。但也没有运气。

最后,我决定将数据从一个列表中的另一个列表更改为单个列表。我已经使用一些 for 循环转换了它,现在数据适合 ListView。我已经使用 ItemAppearing 事件实现了加载更多功能。

谢谢