WinRT C#/XAML お手軽に実装できる引っ張って更新コントロールの紹介
これは XAML Advent Calendar 2014 16日目の記事です。
@ideaki19です。WindowsストアをメインにC#/XAMLでアプリ開発を2年続けています。
2ちゃんねる専用ブラウザ sanka をWindowsストアアプリで公開しています。
この記事ではsankaの一部機能を紹介します。
タイトルにがっつりネタバレしてありますが、引っ張って更新です!
いつかの記事で引っ張って更新を紹介しましたが...
WinRT C#/XAML Pull To Refresh Sample - ideaki's blog
実装がかなりめんどうです。引っ張りたいオブジェクトごとに長ったらしいXAMLとコードビハインドを書くのはいただけません。
記事内のXAMLが下
<ScrollViewer x:Name="scrollViewer" HorizontalScrollMode="Disabled" Loaded="scrollViewer_Loaded" SizeChanged="scrollViewer_SizeChanged" VerticalScrollBarVisibility="Hidden" ViewChanged="scrollViewer_ViewChanged" ZoomMode="Disabled"> <StackPanel Orientation="Vertical"> <Grid Height="60"> <TextBlock x:Name="textBlock1" HorizontalAlignment="Center" VerticalAlignment="Top" Opacity="0" Text="refresh..." /> <TextBlock x:Name="textBlock2" HorizontalAlignment="Center" VerticalAlignment="Bottom" Opacity="0" Text="pull to..." /> </Grid> <ListView x:Name="listView" Width="{Binding ActualWidth, ElementName=scrollViewer, Mode=OneWay}" Height="{Binding ActualHeight, ElementName=scrollViewer, Mode=OneWay}" ScrollViewer.VerticalScrollBarVisibility="Auto" /> </StackPanel> </ScrollViewer>
今回紹介するのが下
<ika:PullToRefreshPanel> <ListViewx:Name="listView"/> </ika:PullToRefreshPanel>
すげー短い!
こんなに簡単なら実装しない手はないですね!
ContentControlを継承しているのでなんでも引っ張れます
デモ
引っ張って更新 https://t.co/2Gs4b2xnrD
— ika (@ideaki19) 2014, 12月 15
ソースコードはこちら
ideaki/Ika.Controls · GitHub
以下作成手順です。
※コピペ注意報
1. テンプレートコントロールの追加
プロジェクトをを右クリックし、新しい項目を選択します。
クラス名をPullToRefreshPanel.csにして追加ボタンを押します。
プロジェクトにはPullToRefreshPanel.csファイルのほかに、ThemesフォルダとGeneric.xamlファイルが追加されています。
Generic.xamlにはPullToRefreshPanel.csのXAMLを記述します。
2. XAML(Style)の記述
PullToRefreshPanel.csはいったん置いといて、一緒に追加されたGeneric.xamlを開きましょう。
中身はResourceDictionaryですね、PullToRefreshPanel.csのStyleが記述されています。
不思議なことにこのResourceDictionaryをApp.xamlに追加しなくても、中のStyleを使用することができます(なんで?!)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App11"> <Style TargetType="local:PullToRefreshPanel"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:PullToRefreshPanel"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Styleをざっくり書き換えてやりましょう。
中身はあの長ったらしいXAMLをTemplateにして記述しただけですが、ちょっとしたアニメーションを追加してます。
C#でアニメーションを記述するのはめんどうですがXAMLからだとらくちんです。
ちなみに下に書き換えてビルドすると失敗はしませんがエラーを吐きます、PullToRefreshPanel.csに存在しないプロパティを見に行ってるからです。
なのでちゃっちゃとC#を書きましょう!
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App11"> <Style TargetType="local:PullToRefreshPanel"> <Setter Property="FontSize" Value="24"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:PullToRefreshPanel"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="VisualStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="Pull"> <Storyboard> <FadeInThemeAnimation TargetName="PullContent" /> <FadeOutThemeAnimation TargetName="RefreshContent" /> </Storyboard> </VisualState> <VisualState x:Name="Refresh"> <Storyboard> <FadeInThemeAnimation TargetName="RefreshContent" /> <FadeOutThemeAnimation TargetName="PullContent" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ScrollViewer x:Name="ScrollViewer" HorizontalScrollMode="Disabled" VerticalScrollBarVisibility="Hidden" ZoomMode="Disabled"> <StackPanel x:Name="StackPanel"> <Grid Name="PullGrid" Width="{Binding Width, ElementName=ScrollViewer}" Height="{TemplateBinding PullRange}"> <ContentControl x:Name="RefreshContent" HorizontalAlignment="Center" VerticalAlignment="Bottom" Content="{TemplateBinding RefreshContent}" /> <ContentControl x:Name="PullContent" HorizontalAlignment="Center" VerticalAlignment="Bottom" Content="{TemplateBinding PullContent}" /> </Grid> <Grid x:Name="ContentGrid"> <ContentPresenter /> </Grid> </StackPanel> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
3. C#の記述
PullToRefreshPanel.csを開きましょう
すでにコンストラクタに1行のおまじないが追加されています。
namespace App11 { public sealed class PullToRefreshPanel : Control { public PullToRefreshPanel() { this.DefaultStyleKey = typeof(PullToRefreshPanel); } } }
下に書き換えます。
Styleやその中のTemplate、そして下のコードについて解説したいんですがいろいろと勉強不足なので...
namespace App11 { public class PullToRefreshPanel : ContentControl { bool isPullRefresh = false; public event EventHandler PullToRefresh; public PullToRefreshPanel() { DefaultStyleKey = typeof(PullToRefreshPanel); } public object RefreshContent { get { return (object)GetValue(RefreshContentProperty); } set { SetValue(RefreshContentProperty, value); } } public static readonly DependencyProperty RefreshContentProperty = DependencyProperty.Register("RefreshContent", typeof(object), typeof(PullToRefreshPanel), new PropertyMetadata("離して更新")); public object PullContent { get { return (object)GetValue(PullContentProperty); } set { SetValue(PullContentProperty, value); } } public static readonly DependencyProperty PullContentProperty = DependencyProperty.Register("PullContent", typeof(object), typeof(PullToRefreshPanel), new PropertyMetadata("引っ張って")); public double PullRange { get { return (double)GetValue(PullRangeProperty); } set { SetValue(PullRangeProperty, value); } } public static readonly DependencyProperty PullRangeProperty = DependencyProperty.Register("PullRange", typeof(double), typeof(PullToRefreshPanel), new PropertyMetadata(200.0)); void UpdateView() { var grid = GetTemplateChild("PullGrid") as Grid; ScrollViewer.ChangeView(null, grid.ActualHeight, null); var contentgrid = GetTemplateChild("ContentGrid") as Grid; contentgrid.SetValue(HeightProperty, ScrollViewer.ActualHeight); contentgrid.SetValue(WidthProperty, ScrollViewer.ActualWidth); UpdateTransform(); } void UpdateStates(bool useTransitions) { if (ScrollViewer.VerticalOffset == 0.0) VisualStateManager.GoToState(this, "Refresh", useTransitions); else VisualStateManager.GoToState(this, "Pull", useTransitions); } void UpdateTransform() { var element = GetTemplateChild("StackPanel") as UIElement; var transform = element.RenderTransform as CompositeTransform ?? new CompositeTransform(); transform.TranslateY = (ScrollViewer.VerticalOffset - PullRange) * 0.8; element.RenderTransform = transform; } protected virtual void OnPullToRefresh(EventArgs e) { if (PullToRefresh != null) PullToRefresh(this, e); } protected override void OnApplyTemplate() { ScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; ScrollViewer.SizeChanged += (s, e) => UpdateView(); ScrollViewer.ViewChanged += ScrollViewer_ViewChanged; this.Loaded += (s, e) => UpdateView(); base.OnApplyTemplate(); } void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) { UpdateStates(true); UpdateTransform(); if (ScrollViewer.VerticalOffset != 0.0) isPullRefresh = true; if (!e.IsIntermediate) { if (ScrollViewer.VerticalOffset == 0.0 && isPullRefresh) { OnPullToRefresh(new EventArgs()); //await Task.Delay(50); } isPullRefresh = false; var grid = GetTemplateChild("PullGrid") as Grid; ScrollViewer.ChangeView(null, grid.ActualHeight, null); } } ScrollViewer scrollviewer; ScrollViewer ScrollViewer { get { return scrollviewer; } set { scrollviewer = value; } } } }
出来上がりです。
試してみましょう。
MainPage.xamlを開いて下のXAMLを貼り付けます。
<Page x:Class="App11.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:App11" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <local:PullToRefreshPanel> <ListView> <ListViewItem Content="0" /> <ListViewItem Content="1" /> <ListViewItem Content="2" /> <ListViewItem Content="3" /> <ListViewItem Content="4" /> <ListViewItem Content="5" /> <ListViewItem Content="6" /> <ListViewItem Content="7" /> <ListViewItem Content="8" /> <ListViewItem Content="9" /> </ListView> </local:PullToRefreshPanel> </Grid> </Page>
以上です。
明日は koty さんです。よろしくお願いします!