如何检测 ScrollViewer 是否超出其可用范围

How can I detect a ScrollViewer is being stretched past its available extent

检测 ScrollViewer 何时位于其顶部或底部很容易,但我想检测用户何时进一步拉动 ScrollViewer,超过其限制,并且顶部或底部出现一些空白间距。你猜对了,我想实现类似于 "pull to refresh".

的东西

VerticalOffset 没有改变,ViewChanging 或 ViewChanged 事件没有触发,我看不到任何变换对象在子元素上发生变化。我只知道是 ScrollContentPresenter 里面的 ItemsPresenter 好像往下移动了。

GitHub 上的 Microsoft UWP 示例中有一个很好的下拉刷新实现示例。

The link to sample.

其他很棒的样本是 listed in Samples folder。请注意 XAML 示例名称以 "Xaml" 开头,因此更容易找到它们。 :)

我有一个非常适用于 Scaling/Zooming 的解决方案(pull out go somewhere idiom)但我不确定如何将它应用到 translate idiom(也许是因为我只是厌倦了那一刻)。

我把它写下来,以防有人能找到一种巧妙的方法来为翻译(拉动刷新)问题找到类似的解决方案。

 // setup code. I do this when I load content into the scroll viewer initially

 myScrollView.MinZoomFactor = desiredMinZoom * 0.4f;
 myScrollView.ZoomSnapPoints.Clear();
 myScrollView.ZoomSnapPoints.Add((float) desiredMinZoom );

// ...

    private void myScrollView_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
    {
        if (e.NextView.ZoomFactor < myScrollView.ZoomSnapPoints.First())
            myScrollView.ZoomSnapPointsType = SnapPointsType.Mandatory;
        else
            myScrollView.ZoomSnapPointsType = SnapPointsType.Optional;

        if ( e.NextView.ZoomFactor == myScrollView.MinZoomFactor )
        {
            VisualStateManager.GoToState(this, "GoSomewhere", true);
            bookView.ZoomToFactor( myScrollView.MinZoomFactor );
        }

    }

此代码的行为就好像在 MinZoom 因子下的任何东西实际上都在拉伸 canvas(因为它只是在您放手时快速返回),但是一旦缩放回到"green zone".

它完全符合人们的预期。

这是一个没有实现任何自定义 类 并且几乎没有代码隐藏的解决方案(如果您真的不喜欢代码隐藏,您可以将它压缩成一个函数)。

它所做的事情几乎与之前发布的 ZoomFactor 解决方案所实现的完全相同。

此代码有细微之处和局限性。

最重要的 是当内部显示的面板小于滚动视图本身时,滚动查看器根本不会移动(没有拉伸交互)。这是一个问题,因为它稍微打破了拉刷新模式,但可以通过填充堆栈面板来解决。

另一个重点是边框大小。 reader 可以测试当 threshold 值不考虑边界时会发生什么。这也是为什么我把那些丑陋的边框放在第一位的原因,以便可以看到这个问题。

最后一个限制是下拉边距的大小。它的高度受实际捕捉点距离的限制,这与堆栈面板中的元素有关。那里需要做一些尝试,但与此处设置的工作示例保持一致,应该没什么大不了的。

综上所述,这是功能齐全的下拉刷新代码:

<RelativePanel Background="Gray">
    <ScrollViewer Name="outerScroll"
                  VerticalSnapPointsAlignment="Far"
                  VerticalSnapPointsType="Mandatory"
                  Width="500"
                  Height="500"
                  BorderBrush="Black"
                  BorderThickness="3"
                  RelativePanel.AlignHorizontalCenterWithPanel="True"
                  RelativePanel.AlignVerticalCenterWithPanel="True">


            <StackPanel Grid.Row="1"
                        x:Name="innerPanel"
                        BorderBrush="Yellow"
                        BorderThickness="4"
                    Width="400"
                      Margin="0,90,0,0"  
                    Background="Blue">

                <TextBlock Height="100" Margin="10,0,10,0" Style="{StaticResource SubheaderTextBlockStyle}">Item 1</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0" Style="{StaticResource SubheaderTextBlockStyle}">Item 2</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0" Style="{StaticResource SubheaderTextBlockStyle}">Item 3</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 4</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 5</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 6</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 8</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 9</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 10</TextBlock>
                <TextBlock Height="100" Margin="10,0,10,0"  Style="{StaticResource SubheaderTextBlockStyle}">Item 11</TextBlock>
        </StackPanel>
    </ScrollViewer>
</RelativePanel>

包含的元素需要实现IScrollSnapPointsInfo接口。 StackPanel 就是这样做的。

public sealed partial class BlankPage1 : Page
{
    double thresholdValue = 0;
    public BlankPage1()
    {
        this.InitializeComponent();

        outerScroll.ViewChanging += OuterScroll_ViewChanging;

        thresholdValue = innerPanel.Margin.Top + innerPanel.BorderThickness.Top + outerScroll.BorderThickness.Top;

        outerScroll.SizeChanged += (s, e) => { outerScroll.ScrollToVerticalOffset(thresholdValue); };
    }

    private void OuterScroll_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
    {
        if ( e.NextView.VerticalOffset < thresholdValue)
        {
            outerScroll.VerticalSnapPointsType = SnapPointsType.Mandatory;    
        }
        else
            outerScroll.VerticalSnapPointsType = SnapPointsType.None;

        if (e.NextView.VerticalOffset == 0 && !e.IsInertial )
        {
            // Pull to refresh event
            innerPanel.Background = new SolidColorBrush(Colors.Blue);
        }
        else
        {
            // base state
            innerPanel.Background = new SolidColorBrush(Colors.Red);
        }
    }
}