ListView in progress to fly out

I am using grouped ListView

internally Flyout

and am getting weird UI issue with the group title when the popup opens. This happens for a split second, but is still noticeable by most users.

header animation bug

XAML (excerpt from full reproduction example 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>

      

I can't use the built-in ListPickerFlyout

one as it doesn't support grouping.

I tried to find an appropriate storyboard or default style transition for ListView

/ Flyout

but couldn't.

I would like to fix this animation or disable it altogether. Any help is appreciated.

+3


source to share


3 answers


One way to get rid of the weird animation error is to start the control animation first Flyout

and then show it after the animation ends ListView

.

To do this, you need to subscribe to the following events in the control Flyout

. Also, you need to specify the name of ListView

a and set it Opacity

to 0

start.

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

      

Then in the code behind you show ListView

after a short delay. I created a small animation Opacity

to ListView

, to make the whole transition much smoother. Every time it Flyout

closes, we reset ListView

back to invisible.

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

      

However, having provided a possible solution, I don't think using a control Flyout

to change the visual style is the right approach.

The control is Flyout

not designed to handle large amounts of data. It doesn't support virtualization (I think). For example, if you increase the number of items from 30 to 300, it will only take a few seconds to load once you click the button.

Update (including working sample)



I thought maybe I could create a control that handles the whole thing, because at the end of the day, you want to get the item you clicked in the list, and also close the popup.

Unfortunately, it is ListPickerFlyout

sealed, so I decided to create a control that inherits from Flyout

.

It's pretty straight forward. Basically the control exposes properties like ItemsSource

, SelectedItem

etc. Also it attaches to the event ItemClick

ListView

, so whenever an element is clicked it closes Flyout

and fills 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 becomes as simple as this.

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

      

Note that in style FlyoutPresenterStyle

I created Title

for the popup.

I've also included a fully functional sample here .

+3


source


I have the same problem and found a workaround. I find the performances are pretty bad even without a workaround. It takes about 1 second for a full download on my device (Lumia 920).

I believe it is in charge ItemsStackPanel

which is the default ItemsPanel

for ListView

. When I use another panel, the problem does not occur. However, the offset of the scrollviewer is not reset when I close and reopen the popup, so I have to do it manually.

So, I used VirtalizingStackPanel

to keep virtualization.

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

      

And when the ListView loads, we find the ListView ScrollViewer and scroll to the top.

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

      

listView.FindFirstChild<ScrollViewer>();

is just a helper, I have to find the child controls using VisualTreeHelper.GetChild

.



I find this solution slightly better from a performance point of view: By default, List Visibility is collapsed:

<ListView Visibility="Collapsed" />

      

I subscribe to Flyout events Opened

and Closed

:

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

      

Then when the popup opens, I change the visibility after 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;
    }

      

Also, by default the popup has ScrollViewer

, which will break virtualization. You need to remove it from the control template FlyoutPresenter

or disable it with ScrollViewer.VerticalScrollMode

.

+1


source


As it turns out, the strange animation comes from ItemsStackPanel

. Therefore, if (and only if) virtualization is not required, you can specify StackPanel

as 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>

      

0


source







All Articles