Flyout过渡问题中的ListView

时间:2014-08-07 13:32:46

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

我在ListView内使用分组的Flyout,并在弹出窗口打开时出现组头的奇怪UI问题。它发生的时间只有几分之一,但大多数用户仍然值得注意。

header animation bug

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的默认样式中找到相应的故事板或转换,但是无法进行。

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

3 个答案:

答案 0 :(得分:3)

摆脱奇怪的动画错误的一种方法是让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。我为Opacity创建了一个ListView动画,只是为了让整个过渡运行得更顺畅。每次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等属性。此外,它还会订阅ItemClick的{​​{1}}事件,因此无论何时单击某个项目,它都会关闭ListView 1}}并填充Flyout

SelectedItem

xaml变得如此简单。

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();
    }
}

注意 <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

我还提供了一个功能齐全的样本here

答案 1 :(得分:1)

我有同样的问题,我找到了解决方法。 即使没有解决方法,我发现表演也很糟糕。完全加载到我的设备上需要大约1秒钟(Lumia 920)。

我认为负责人是ItemsStackPanel,这是ItemsPanel的默认ListView。当我使用另一个面板时,问题不会发生。但是,当我关闭并重新打开弹出框时,scrollviewer偏移不会重置,因此我必须手动完成。

所以我使用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<ScrollViewer>();只是帮助我必须找到VisualTreeHelper.GetChild的子控件。

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

<ListView Visibility="Collapsed" />

我订阅了Flyout OpenedClosed个活动:

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

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

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将其停用。

答案 2 :(得分:0)

事实证明,奇怪的动画来自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>