在移动设备上忽略网格上的滑动
Swipe on grid is ignored on mobile
当用户从 ComboBox 中选择条目时,我以编程方式填充 ListView。
我想通过允许用户滑动 left/right 作为更改内容的替代方式来提高应用程序的可用性。
更确切地说,当用户执行轻拂或短扫时,列表视图会填充一组不同的项目。我不需要任何操作已经开始的视觉提示(即像 Edge 在使用手势来回导航时所做的那样)
下面是简化的 XAML 结构。
<Grid
x:Name="MainGrid"
ManipulationMode="TranslateX, TranslateInertia"
Loaded="MainGrid_Loaded">
<ComboBox x:Name="CBMonthPicker" />
<ListView
x:Name="LV"
ItemsSource="{Binding CalItems, ElementName=page}"
DataContext="MainPage"
IsItemClickEnabled="True"
ItemClick="OnItemClick"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
</ListView>
</Grid>
这是我用来检测滑动的代码,它在桌面上有效(如果我用鼠标模拟滑动),但在移动设备上不起作用(Grid_ManipulationDelta 在移动设备上未触发):
private void MainGrid_Loaded(object sender, RoutedEventArgs e)
{
MainGrid.ManipulationDelta += Grid_ManipulationDelta;
}
private void Grid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var swiped = e.Cumulative.Translation.X;
var swipeTriggerThreshhold = 300;
//if the gesture was not above a set threshold we ignore it
if (!e.IsInertial || swiped * swiped < swipeTriggerThreshhold * swipeTriggerThreshhold )
return;
//If we got so far, we registered a swipe
e.Complete(); //end manipulation so we only handle it once
if (swiped < 0) { //we swiped left
changeDisplayedMonth(-1);
}
else{ //we swiped right
changeDisplayedMonth(1);
}
}
我的猜测是 ListView 或 ListViewItem(s) 以某种方式隐藏了对 Grid 的操作。
我已经尝试使用 ListView 和 ListViewItem 来处理操作,但从未为它们中的任何一个触发 ManipulationDelta,即使我将 ManipulationMode 设置为 TranslateX、TranslateInertia。
SO 上有很多关于滑动 ListViewItem 的问题(如 Outlook 中的特色)。这个问题与那个无关。
因为内容是根据用户输入动态加载的,所以我不想使用 Pivot。即使我可以,这也意味着我的界面会发生重大变化,而我又不想这样做。
您的猜测是正确的 - ListView 有一个 ScrollViewer,它在非常低的水平上工作,提高了性能,但是它首先处理操作,然后 'swallows' 它们。好像前段时间问过a similar question.
有一种方法可以手动处理这种情况 - Rob Caplan's blog post describes it a little, it's quite old, but should bring you some light. You may also take a look at this answer,其中针对 Windows-Runtime 进行了描述,这在 [=20] 上也应该类似=]UWP.
另请注意,如果您将 ListView 放在 Pivot 或带有水平 ScrollViewer,你可以把ListView的ScrollViewer的属性IsHorizontalScrollChainingEnabled改成true ,这会将水平滚动传递给父级。这是一个示例 XAML 代码:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible">
<StackPanel Orientation="Horizontal">
<ListView Width="200" Height="100" ScrollViewer.IsHorizontalScrollChainingEnabled="True">
<x:String>First Item</x:String>
<x:String>Item</x:String>
<x:String>Item</x:String>
<x:String>Item</x:String>
<x:String>Last Item</x:String>
</ListView>
<Rectangle Width="200" Height="100" Fill="Red"/>
<Rectangle Width="200" Height="100" Fill="Green"/>
<Rectangle Width="200" Height="100" Fill="Blue"/>
</StackPanel>
</ScrollViewer>
</Grid>
在对 Pivot 进行了大量尝试后,结果完全不尽如人意,我发现了 FlipView,它可以通过一些黑客攻击来实现我想要的。
诀窍是添加两个虚拟 FlipViewItems,一个在包含我们的 ListView 的之前,一个在它之后。下一步是捕获 FlipView 的 SelectionChanged 事件,如果我们的内容之前的 FlipViewItem 已被选中,则触发一个动作,就像向后滑动一样,对于我们内容之后的 FlipViewItem 也是如此。
最后一步是强制选择回到我们的内容 ListViewItem。
以这种方式破解 FlipView 的一个小烦恼是,当强制选择回到我们中间的 FlipViewItem(承载我们内容的那个)时,动画与最终用户期望的相反。这很容易通过在 FlipView 上设置 属性 UseTouchAnimationsForAllNavigation="False" 来解决。
奖励:为了表明在重新填充 ListView 时确实发生了某些事情,我添加了一个进度环。为此,将 FlipView 和 FlipViewItems 以及 ListView 的背景 属性 设置为透明很重要。 ListViewItems 有一个指定的背景来覆盖它。这是实现 ProgressRing 的完全不优雅的解决方案,但是它有效并且很快就想出了 :)。
更新后的 XAML 代码如下所示:
<Grid x:Name="MainGrid" >
<ComboBox x:Name="CBMonthPicker" Grid.Row="0"/>
<ProgressRing Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" />
<FlipView x:Name="MainView" Grid.Row="1"
Loaded="MainView_Loaded"
UseTouchAnimationsForAllNavigation="False"
Background="Transparent">
<FlipViewItem Background="Transparent">
</FlipViewItem>
<FlipViewItem Background="Transparent">
<ListView x:Name="LV"
ItemsSource="{Binding CalItems, ElementName=page}" DataContext="MainPage"
IsItemClickEnabled="True" ItemClick="OnItemClick"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
</ListView>
</FlipViewItem>
<FlipViewItem Background="Transparent">
</FlipViewItem>
</Grid>
后面的代码是这样的:
private void MainView_Loaded(object sender, RoutedEventArgs e)
{
MainView.SelectedIndex = 1;
MainView.SelectionChanged += MainView_SelectionChanged;
}
private void MainView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainView.SelectedIndex == 0)
{
//perform the action for Back Swipe Gesture
changeDisplayedMonth(-1);
//force selection back to our content FlipViewItem
MainView.SelectedIndex = 1;
}
if (MainView.SelectedIndex == 2)
{
//perform the action for Forward Swipe Gesture
changeDisplayedMonth(1);
//force selection back to our content FlipViewItem
MainView.SelectedIndex = 1;
}
}
当用户从 ComboBox 中选择条目时,我以编程方式填充 ListView。
我想通过允许用户滑动 left/right 作为更改内容的替代方式来提高应用程序的可用性。
更确切地说,当用户执行轻拂或短扫时,列表视图会填充一组不同的项目。我不需要任何操作已经开始的视觉提示(即像 Edge 在使用手势来回导航时所做的那样)
下面是简化的 XAML 结构。
<Grid
x:Name="MainGrid"
ManipulationMode="TranslateX, TranslateInertia"
Loaded="MainGrid_Loaded">
<ComboBox x:Name="CBMonthPicker" />
<ListView
x:Name="LV"
ItemsSource="{Binding CalItems, ElementName=page}"
DataContext="MainPage"
IsItemClickEnabled="True"
ItemClick="OnItemClick"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
</ListView>
</Grid>
这是我用来检测滑动的代码,它在桌面上有效(如果我用鼠标模拟滑动),但在移动设备上不起作用(Grid_ManipulationDelta 在移动设备上未触发):
private void MainGrid_Loaded(object sender, RoutedEventArgs e)
{
MainGrid.ManipulationDelta += Grid_ManipulationDelta;
}
private void Grid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var swiped = e.Cumulative.Translation.X;
var swipeTriggerThreshhold = 300;
//if the gesture was not above a set threshold we ignore it
if (!e.IsInertial || swiped * swiped < swipeTriggerThreshhold * swipeTriggerThreshhold )
return;
//If we got so far, we registered a swipe
e.Complete(); //end manipulation so we only handle it once
if (swiped < 0) { //we swiped left
changeDisplayedMonth(-1);
}
else{ //we swiped right
changeDisplayedMonth(1);
}
}
我的猜测是 ListView 或 ListViewItem(s) 以某种方式隐藏了对 Grid 的操作。
我已经尝试使用 ListView 和 ListViewItem 来处理操作,但从未为它们中的任何一个触发 ManipulationDelta,即使我将 ManipulationMode 设置为 TranslateX、TranslateInertia。
SO 上有很多关于滑动 ListViewItem 的问题(如 Outlook 中的特色)。这个问题与那个无关。
因为内容是根据用户输入动态加载的,所以我不想使用 Pivot。即使我可以,这也意味着我的界面会发生重大变化,而我又不想这样做。
您的猜测是正确的 - ListView 有一个 ScrollViewer,它在非常低的水平上工作,提高了性能,但是它首先处理操作,然后 'swallows' 它们。好像前段时间问过a similar question.
有一种方法可以手动处理这种情况 - Rob Caplan's blog post describes it a little, it's quite old, but should bring you some light. You may also take a look at this answer,其中针对 Windows-Runtime 进行了描述,这在 [=20] 上也应该类似=]UWP.
另请注意,如果您将 ListView 放在 Pivot 或带有水平 ScrollViewer,你可以把ListView的ScrollViewer的属性IsHorizontalScrollChainingEnabled改成true ,这会将水平滚动传递给父级。这是一个示例 XAML 代码:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible">
<StackPanel Orientation="Horizontal">
<ListView Width="200" Height="100" ScrollViewer.IsHorizontalScrollChainingEnabled="True">
<x:String>First Item</x:String>
<x:String>Item</x:String>
<x:String>Item</x:String>
<x:String>Item</x:String>
<x:String>Last Item</x:String>
</ListView>
<Rectangle Width="200" Height="100" Fill="Red"/>
<Rectangle Width="200" Height="100" Fill="Green"/>
<Rectangle Width="200" Height="100" Fill="Blue"/>
</StackPanel>
</ScrollViewer>
</Grid>
在对 Pivot 进行了大量尝试后,结果完全不尽如人意,我发现了 FlipView,它可以通过一些黑客攻击来实现我想要的。
诀窍是添加两个虚拟 FlipViewItems,一个在包含我们的 ListView 的之前,一个在它之后。下一步是捕获 FlipView 的 SelectionChanged 事件,如果我们的内容之前的 FlipViewItem 已被选中,则触发一个动作,就像向后滑动一样,对于我们内容之后的 FlipViewItem 也是如此。 最后一步是强制选择回到我们的内容 ListViewItem。
以这种方式破解 FlipView 的一个小烦恼是,当强制选择回到我们中间的 FlipViewItem(承载我们内容的那个)时,动画与最终用户期望的相反。这很容易通过在 FlipView 上设置 属性 UseTouchAnimationsForAllNavigation="False" 来解决。
奖励:为了表明在重新填充 ListView 时确实发生了某些事情,我添加了一个进度环。为此,将 FlipView 和 FlipViewItems 以及 ListView 的背景 属性 设置为透明很重要。 ListViewItems 有一个指定的背景来覆盖它。这是实现 ProgressRing 的完全不优雅的解决方案,但是它有效并且很快就想出了 :)。
更新后的 XAML 代码如下所示:
<Grid x:Name="MainGrid" >
<ComboBox x:Name="CBMonthPicker" Grid.Row="0"/>
<ProgressRing Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" />
<FlipView x:Name="MainView" Grid.Row="1"
Loaded="MainView_Loaded"
UseTouchAnimationsForAllNavigation="False"
Background="Transparent">
<FlipViewItem Background="Transparent">
</FlipViewItem>
<FlipViewItem Background="Transparent">
<ListView x:Name="LV"
ItemsSource="{Binding CalItems, ElementName=page}" DataContext="MainPage"
IsItemClickEnabled="True" ItemClick="OnItemClick"
ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
</ListView>
</FlipViewItem>
<FlipViewItem Background="Transparent">
</FlipViewItem>
</Grid>
后面的代码是这样的:
private void MainView_Loaded(object sender, RoutedEventArgs e)
{
MainView.SelectedIndex = 1;
MainView.SelectionChanged += MainView_SelectionChanged;
}
private void MainView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainView.SelectedIndex == 0)
{
//perform the action for Back Swipe Gesture
changeDisplayedMonth(-1);
//force selection back to our content FlipViewItem
MainView.SelectedIndex = 1;
}
if (MainView.SelectedIndex == 2)
{
//perform the action for Forward Swipe Gesture
changeDisplayedMonth(1);
//force selection back to our content FlipViewItem
MainView.SelectedIndex = 1;
}
}