しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

【Win10 Pre】 画面を引っ張ったら表示タイルを追加する UWP アプリをつくる(仮想化なし)

前回 はスクロール中にアプリバーを隠すようにしました

f:id:matatabi_ux:20150720114105g:plain

ただ、今度は起動時の写真表示の遅さが気になってきます・・・VariableSizedWrapGrid は仮想化が効かないため、最初は少数だけ表示して画面を下に引っ張ったら追加の写真を表示するようにしてみます

Windows 10 Insider Preview Build 10240 時点での情報のため、正式リリース後仕様等が変更になっている可能性があります

またも VariableHeightGridView を改修

/// <summary>
/// Variable height tiles shows GridView
/// </summary>
public class VariableHeightGridView : GridView
{
    /// <summary>
    /// Internal ScrollViewer
    /// </summary>
    private ScrollViewer scrollViewer = null;

    /// <summary>
    /// Scrolling interval timer
    /// </summary>
    private DispatcherTimer timer = new DispatcherTimer();

    /// <summary>
    /// Pull refresh wait timer
    /// </summary>
    private DispatcherTimer pullTimer = new DispatcherTimer();

    /// <summary>
    /// 
    /// </summary>
    private Windows.Foundation.Point delta = default(Windows.Foundation.Point);

    /// <summary>
    /// Request incremental item update event
    /// </summary>
    public event EventHandler RequestIncrementalUpdate;

    ~ 中略 ~

    /// <summary>
    /// Constructor
    /// </summary>
    public VariableHeightGridView()
    {
        this.SizeChanged += this.OnSizeChanged;

        this.timer.Interval = TimeSpan.FromSeconds(3);
        this.timer.Tick += this.OnTimerTick;

        this.pullTimer.Interval = TimeSpan.FromMilliseconds(500);
        this.pullTimer.Tick += this.OnPullTimerTick;
    }

    ~ 中略 ~

    /// <summary>
    /// Internal ScrollViewer DirectManipulationStarted event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnDirectManipulationStarted(object sender, object e)
    {
        this.timer.Stop();

        this.IsHaltedScrolling = false;
        this.delta = default(Windows.Foundation.Point);

        this.pullTimer.Start();
    }

    /// <summary>
    /// Pull refresh wait timer Tick event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnPullTimerTick(object sender, object e)
    {
        this.pullTimer.Stop();

        this.delta = this.ItemsPanelRoot.TransformToVisual(this.scrollViewer)
                        .TransformPoint(new Windows.Foundation.Point(0d, 0d));
    }

    /// <summary>
    /// Internal ScrollViewer DirectManipulationCompleted event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnDirectManipulationCompleted(object sender, object e)
    {
        this.IsHaltedScrolling = false;

        this.pullTimer.Stop();
        this.timer.Start();

        if (delta.Y > 10 && this.RequestIncrementalUpdate != null)
        {
            this.RequestIncrementalUpdate.Invoke(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Scrolling interval timer Tick event handler (3 seconds interval)
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnTimerTick(object sender, object e)
    {
        this.timer.Stop();

        this.IsHaltedScrolling = true;
    }

    /// <summary>
    /// Unsubscribe events
    /// </summary>
    protected override void OnDisconnectVisualChildren()
    {
        if (this.timer != null)
        {
            this.timer.Stop();
            this.timer.Tick -= this.OnTimerTick;
        }
        if (this.pullTimer != null)
        {
            this.pullTimer.Stop();
            this.pullTimer.Tick -= this.OnPullTimerTick;
        }
        if (this.scrollViewer != null)
        {
            this.scrollViewer.DirectManipulationStarted -= this.OnDirectManipulationStarted;
            this.scrollViewer.DirectManipulationCompleted -= this.OnDirectManipulationCompleted;
        }

        base.OnDisconnectVisualChildren();
    }

    ~ 中略 ~
}

タッチによるスクロール開始から 500 ms 後に下記の箇所で引っ張った幅を計算しています

    /// <summary>
    /// Pull refresh wait timer Tick event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnPullTimerTick(object sender, object e)
    {
        this.pullTimer.Stop();

        this.delta = this.ItemsPanelRoot.TransformToVisual(this.scrollViewer)
                        .TransformPoint(new Windows.Foundation.Point(0d, 0d));
    }

ScrollViewer と VariableSizedWraoGrid の左上隅の座標差を求めています

スクロールのタッチを離した際に幅が一定数より大きい場合は画面を下に引っ張ったと判定してイベントを発生させます

    /// <summary>
    /// Internal ScrollViewer DirectManipulationCompleted event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event arguments</param>
    private void OnDirectManipulationCompleted(object sender, object e)
    {
        this.IsHaltedScrolling = false;

        this.pullTimer.Stop();
        this.timer.Start();

        if (delta.Y > 10 && this.RequestIncrementalUpdate != null)
        {
            this.RequestIncrementalUpdate.Invoke(this, EventArgs.Empty);
        }
    }

次はイベント発生時に追加データのレンダリングをさせるため ViewModel を修正

/// <summary>
/// MainPage ViewModel
/// </summary>
public class MainPageViewModel : BindableBase
{
    /// <summary>
    /// Photo items original collection 
    /// </summary>
    private IList<PhotoItemViewModel> ItemsSource = new List<PhotoItemViewModel>();

    /// <summary>
    /// Rendered photo item index
    /// </summary>
    private int index = 0;

    /// <summary>
    /// Flag whther bottom bar is opened or not
    /// </summary>
    private bool isBottomBarOpen = true;

    /// <summary>
    /// Flag whther bottom bar is opened or not
    /// </summary>
    public bool IsBottomBarOpen
    {
        get { return this.isBottomBarOpen; }
        set { this.SetProperty<bool>(ref this.isBottomBarOpen, value); }
    }

    /// <summary>
    /// Add render photo item command
    /// </summary>
    public ICommand IncrementalUpdateCommand;

    /// <summary>
    /// Photo items collection 
    /// </summary>
    public IList<PhotoItemViewModel> Photos = new ObservableCollection<PhotoItemViewModel>();

    /// <summary>
    /// Constructor
    /// </summary>
    public MainPageViewModel()
    {
        this.IncrementalUpdateCommand = DelegateCommand.FromAsyncHandler(this.IncrementalUpdate);

        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo01.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo02.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo03.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo04.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo05.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo06.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo07.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo08.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo09.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo10.png" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo11.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo12.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo13.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo14.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo15.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo16.jpg" });
        this.ItemsSource.Add(new PhotoItemViewModel { ImageSource = @"ms-appx:///Assets/Photos/photo17.jpg" });
    }

    /// <summary>
    /// Update image height size
    /// </summary>
    /// <returns>Task</returns>
    public async Task Initilize()
    {
        foreach (var photo in this.ItemsSource)
        {
            var file = await StorageFile.GetFileFromApplicationUriAsync(new System.Uri(photo.ImageSource));
            using (var stream = await file.OpenAsync(FileAccessMode.Read))
            {
                var bitmap = new BitmapImage();
                bitmap.SetSource(stream);
                photo.RowSpan = (int)Math.Round(180d * bitmap.PixelHeight / bitmap.PixelWidth, MidpointRounding.AwayFromZero);
            }
        }
        for (this.index = 0; this.index < 7; this.index++)
        {
            this.Photos.Add(this.ItemsSource[this.index]);
        }
    }

    /// <summary>
    /// Add render photo item 
    /// </summary>
    /// <returns>Task</returns>
    private async Task IncrementalUpdate()
    {
        for (var i = 0; i < 3; i++)
        {
            this.index = (this.index + 1)%this.ItemsSource.Count;
            this.Photos.Insert(0, this.ItemsSource[this.index]);

            await Task.Delay(10);
        }
    }
}

最初は 7 枚の写真のみ Photos に追加するようにして、IncrementalUpdateCommand 経由で IncrementalUpdate を呼び出して 3 枚ずつ追加表示するようにしました

最後に View

イベントとコマンドを連携させるためにライブラリを追加します

f:id:matatabi_ux:20150720171338p:plain

「Universal Windows」-「拡張」にある「Behavior SDK」を追加します

<Page
    x:Class="SplitViewSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SplitViewSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    RequestedTheme="Dark"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">        
        <local:VariableHeightGridView x:Name="GridView"
                                      Background="#222222"
                                      IsHaltedScrolling="{x:Bind ViewModel.IsBottomBarOpen, Mode=TwoWay, FallbackValue=True}"
                                      ScrollViewer.HorizontalScrollMode="Disabled"
                                      ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                                      ScrollViewer.VerticalScrollMode="Auto"
                                      ScrollViewer.VerticalScrollBarVisibility="Auto"
                                      ScrollViewer.ZoomMode="Disabled">
            <i:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="RequestIncrementalUpdate">
                    <core:InvokeCommandAction Command="{x:Bind ViewModel.IncrementalUpdateCommand}"/>
                </core:EventTriggerBehavior>
            </i:Interaction.Behaviors>
            
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VariableSizedWrapGrid Orientation="Horizontal"
                                           ItemWidth="180" 
                                           ItemHeight="1" 
                                           HorizontalAlignment="Center"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Border Padding="2,2,0,0">
                        <Grid>
                            <Image Source="{Binding ImageSource}" Stretch="Uniform"/>
                        </Grid>
                    </Border>
                </DataTemplate>
            </GridView.ItemTemplate>
        </local:VariableHeightGridView>
    </Grid>
    
    ~ 中略 ~
</Page>

GridView の内部に EventTriggerBehavior を追加し、InvokeCommand アクションで RequestIncrementalUpdate イベント発生時にIncrementalUpdateCommand を実行するようバインディングしました

さてお試し

f:id:matatabi_ux:20150720171813g:plain

データの読み込み処理は入っていませんが表示の遅さも気にならなくなりましたね