【问题标题】:ListView in Flyout transition issueFlyout 转换问题中的 ListView
【发布时间】:2014-08-07 13:32:45
【问题描述】:

我在Flyout 中使用分组ListView,当弹出窗口打开时,组标题出现奇怪的UI 问题。它只发生了几分之一秒,但大多数用户仍然注意到。

XAML(摘自完整的复制示例http://ge.tt/1DWlXbq1/v/0?c):

<Page.Resources>
    <DataTemplate x:Key="GroupHeaderTemplate">
        <ContentControl Content="{Binding Key}"
                        FontWeight="Bold"
                        FontSize="{ThemeResource TextStyleLargeFontSize}"
                        Foreground="{ThemeResource PhoneAccentBrush}"
                        Margin="0 20" />
    </DataTemplate>
    <CollectionViewSource x:Key="ItemsViewSource"
                          IsSourceGrouped="True"
                          Source="{Binding Items}" />
</Page.Resources>

<Page.BottomAppBar>
    <CommandBar>
        <AppBarButton Icon="Caption">
            <AppBarButton.Flyout>
                <Flyout>
                    <ListView ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
                              Margin="20 0">
                        <ListView.GroupStyle>
                            <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
                        </ListView.GroupStyle>
                    </ListView>
                </Flyout>
            </AppBarButton.Flyout>
        </AppBarButton>
    </CommandBar>
</Page.BottomAppBar>

我不能使用内置的ListPickerFlyout,因为它不支持分组。

我试图在ListView/Flyout 的默认样式中找到相应的故事板或过渡,但没能找到。

我想修复该动画或完全禁用它。任何帮助表示赞赏。

【问题讨论】:

  • 你说你确实浏览了所有的动画,或者你只是找不到那个特定的东西?
  • @ChrisW。我找不到那个特定的东西。

标签: xaml windows-phone-8 winrt-xaml windows-phone-8.1 win-universal-app


【解决方案1】:

摆脱奇怪动画错误的一种方法是让Flyout控件的动画先运行,然后在动画完成后显示ListView

为此,您需要订阅Flyout 控件中的以下事件。此外,您还需要为 ListView 命名并将其 Opacity 设置为 0 以开始。

   <Flyout Opened="Flyout_Opened" Closed="Flyout_Closed">
       <ListView x:Name="MyListView" Opacity="0" ItemsSource="{Binding Source={StaticResource ItemsViewSource}}" Margin="20 0">

然后在后面的代码中,您会在短暂延迟后显示ListView。我为ListView 创建了一个小Opacity 动画,只是为了让整个过渡运行更顺畅。每次Flyout 关闭时,我们都会将ListView 重置为不可见。

private async void Flyout_Opened(object sender, object e)
{
    // a short delay to allow the Flyout in animation to take place
    await Task.Delay(400);

    // animate in the ListView
    var animation = new DoubleAnimation
    {
        Duration = TimeSpan.FromMilliseconds(200),
        To = 1
    };
    Storyboard.SetTarget(animation, this.MyListView);
    Storyboard.SetTargetProperty(animation, "Opacity");

    var storyboard = new Storyboard();
    storyboard.Children.Add(animation);
    storyboard.Begin();
}

private void Flyout_Closed(object sender, object e)
{
    this.MyListView.Opacity = 0;
}

但是,在提供了可能的解决方案后,我认为使用Flyout 控件来更改视觉样式并不是正确的方法。

Flyout 控件不是为处理大量数据而设计的。它不支持虚拟化(我认为)。例如,如果您将项目数从 30 增加到 300,则在您点击按钮后将需要相当多的时间来加载。

更新(包括工作示例)

我在想也许我可以创建一个控件来处理所有这些,因为最终您确实希望能够检索您在列表中单击的项目并关闭弹出窗口。

不幸的是ListPickerFlyout 被密封,所以我选择创建一个继承自Flyout 的控件。

这很简单。基本上,该控件公开了ItemsSourceSelectedItem 等属性。它还订阅了ListViewItemClick 事件,因此每当单击某个项目时,它都会关闭Flyout 并填充SelectedItem

public class ListViewFlyout : Flyout
{
    private ListView _listView;

    public object ItemsSource
    {
        get { return (object)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(object), typeof(ListViewFlyout), new PropertyMetadata(null));

    public DataTemplate HeaderTemplate
    {
        get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
        set { SetValue(HeaderTemplateProperty, value); }
    }

    public static readonly DependencyProperty HeaderTemplateProperty =
        DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ListViewFlyout), new PropertyMetadata(null));

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ListViewFlyout), new PropertyMetadata(null));

    public object SelectedItem
    {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(ListViewFlyout), new PropertyMetadata(null));

    public ListViewFlyout()
    {
        // initialization
        this.Placement = FlyoutPlacementMode.Full;
        _listView = new ListView
        {
            Opacity = 0,
            IsItemClickEnabled = true
        };

        this.Opened += ListViewFlyout_Opened;
        this.Closed += ListViewFlyout_Closed;
    }

    private async void ListViewFlyout_Opened(object sender, object e)
    {
        await Task.Delay(400);

        if (!_listView.Items.Any())
        {
            // assign the listView as the Content of this 'custom control'
            _listView.ItemsSource = this.ItemsSource;
            _listView.ItemTemplate = this.ItemTemplate;
            _listView.GroupStyle.Add(new GroupStyle { HeaderTemplate = this.HeaderTemplate });
            this.Content = _listView;

            // whenever an item is clicked, we close the Layout and assign the SelectedItem
            _listView.ItemClick += ListView_ItemClick;
        }

        // animate in the list
        var animation = new DoubleAnimation
        {
            Duration = TimeSpan.FromMilliseconds(200),
            To = 1
        };
        Storyboard.SetTarget(animation, _listView);
        Storyboard.SetTargetProperty(animation, "Opacity");
        var storyboard = new Storyboard();
        storyboard.Children.Add(animation);
        storyboard.Begin();
    }

    private void ListViewFlyout_Closed(object sender, object e)
    {
        _listView.Opacity = 0;
    }

    private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
    {
        this.SelectedItem = e.ClickedItem;
        this.Hide();

        // to be removed
        await Task.Delay(1000);
        var dialog = new MessageDialog(e.ClickedItem.ToString() + " was clicked 1 sec ago!");
        await dialog.ShowAsync();
    }
}

xaml 就这么简单。

    <AppBarButton Icon="Caption">
        <AppBarButton.Flyout>
            <local:ListViewFlyout ItemsSource="{Binding Source={StaticResource ItemsViewSource}}" ItemTemplate="{StaticResource ListViewItemTemplate}" HeaderTemplate="{StaticResource GroupHeaderTemplate}" FlyoutPresenterStyle="{StaticResource FlyoutPresenterStyle}" />
        </AppBarButton.Flyout>
    </AppBarButton>

注意FlyoutPresenterStyle 样式我也为弹出窗口创建了Title

我还包含了一个功能齐全的示例here

【讨论】:

  • 我知道如何更改项目的外观并寻找一种方法来显示具有分组功能的选择器。这里唯一的问题是标准ListPickerFlyout 不支持。
  • 谢谢。我明白了。但是,当我第一次在模拟器上打开弹出窗口时,动画错误仍然存​​在。我认为时间应该进一步调整。
  • 嘿@altso,你是在真实设备上测试过这个,还是在没有调试器的情况下运行模拟器?因为当附加调试器时,运行应用程序时会严重影响性能。我在 520、920 和 1020 上测试过,一切正常。
【解决方案2】:

我有同样的问题,我找到了解决方法。 我发现性能很差,即使没有解决方法。在我的设备(Lumia 920)上完全加载大约需要 1 秒。

我相信责任是ItemsStackPanel,这是ListView 的默认ItemsPanel。当我使用另一个面板时,问题不会发生。但是,当我关闭并重新打开浮出控件时,滚动查看器偏移量不会重置,因此我必须手动执行。

所以我使用VirtalizingStackPanel 来保持虚拟化。

<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <VirtualizingStackPanel />
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

而当 ListView 加载时,我们找到 ListView ScrollViewer,并滚动到顶部。

private void ListView_Loaded(object sender, RoutedEventArgs e)
    {
        var listView = (ListView)sender;            
        var scrollviewer = listView.FindFirstChild<ScrollViewer>();
        scrollviewer.ScrollToVerticalOffset(0);
    }

listView.FindFirstChild&lt;ScrollViewer&gt;(); 只是一个帮手,我必须找到带有VisualTreeHelper.GetChild 的子控件。

我发现这个解决方案在性能方面要好一些: 我将 ListView Visibility 默认设置为 Collapsed :

<ListView Visibility="Collapsed" />

我订阅了 Flyout OpenedClosed 事件:

<Flyout Placement="Full"
        Opened="Flyout_Opened"
        Closed="Flyout_Closed" />

然后,当弹出窗口打开时,我会在 400 毫秒后更改可见性。

private async void Flyout_Opened(object sender, object e)
    {
        await Task.Delay(400);
        var listView = (ListView)((Flyout)sender).Content;
        listView.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }

    private void Flyout_Closed(object sender, object e)
    {
        var listView = (ListView)((Flyout)sender).Content;
        listView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    }

此外,默认情况下,弹出窗口有一个ScrollViewer,这会破坏虚拟化。 您需要将其从FlyoutPresenter 控制模板中删除,或使用ScrollViewer.VerticalScrollMode 禁用它。

【讨论】:

  • 看来设置ScrollViewer.VerticalScrollModeFlyoutPresenter 就足以启用虚拟化了。感谢您的提示。
【解决方案3】:

事实证明,奇怪的动画来自ItemsStackPanel。因此,如果(且仅当)不需要虚拟化,可以将StackPanel 指定为ItemsPanel

<Flyout>
    <ListView ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
              Margin="20 0">
        <ListView.GroupStyle>
            <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
        </ListView.GroupStyle>
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
    </ListView>
</Flyout>

【讨论】:

  • 还有一点需要注意的是,您也会丢失粘性标题。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-16
  • 2011-11-03
  • 2016-08-08
  • 1970-01-01
  • 2020-01-19
  • 1970-01-01
  • 2014-01-21
相关资源
最近更新 更多