引っ張って更新Pull to refresh

引っ張って更新を使うと、タッチ操作でデータの一覧を引き下げることで、より多くのデータを取得できます。Pull-to-refresh lets a user pull down on a list of data using touch in order to retrieve more data. 引っ張って更新は、タッチ スクリーンを備えたデバイスで広く使用されます。Pull-to-refresh is widely used on devices with a touch screen. ここに表示されている API を使用して、アプリに引っ張って更新を実装できます。You can use the APIs shown here to implement pull-to-refresh in your app.

重要な API:RefreshContainerRefreshVisualizerImportant APIs: RefreshContainer, RefreshVisualizer

引っ張って更新 gif

適切なコントロールの選択Is this the right control?

ユーザーが定期的に更新するデータのリストやグリッドがあり、アプリがタッチ操作主体のデバイスで実行されることが多いときは、引っ張って更新を使います。Use pull-to-refresh when you have a list or grid of data that the user might want to refresh regularly, and your app is likely to be running on touch-first devices.

RefreshVisualizer を使用して、更新ボタンなど他の方法で呼び出される一貫した更新エクスペリエンスを作成することもできます。You can also use the RefreshVisualizer to create a consistent refresh experience that is invoked in other ways, such as by a refresh button.

更新コントロールRefresh controls

引っ張って更新は、2 つのコントロールで有効になっています。Pull-to-refresh is enabled by 2 controls.

  • RefreshContainer - 引っ張って更新エクスペリエンスのラッパーを提供する ContentControl。RefreshContainer - a ContentControl that provides a wrapper for the pull-to-refresh experience. これは、タッチ操作を処理し、その内部更新ビジュアライザーの状態を管理します。It handles the touch interactions and manages the state of its internal refresh visualizer.
  • RefreshVisualizer - 次のセクションで説明されている更新の視覚エフェクトをカプセル化します。RefreshVisualizer - encapsulates the refresh visualization explained in the next section.

メインのコントロールは、ユーザーが更新をトリガするためにプルするコンテンツ全体のラッパーとして配置する RefreshContainer です。The main control is the RefreshContainer, which you place as a wrapper around the content that the user pulls to trigger a refresh. RefreshContainer はタッチでのみ機能するため、タッチ インターフェイスを持たないユーザーが使用できる更新ボタンを設定することをお勧めします。RefreshContainer works only with touch, so we recommend that you also have a refresh button available for users who don't have a touch interface. 更新ボタンは、コマンド バーまたは更新されるサーフェスに近い場所など、アプリの適切な場所に配置できます。You can position the refresh button at a suitable location in the app, either on a command bar or at a location close to the surface being refreshed.

更新の視覚エフェクトRefresh visualization

既定の更新の視覚エフェクトは、更新が発生したときに通信するために使用される循環進行スピンと、更新が開始された後の更新の進行状況です。The default refresh visualization is a circular progress spinner that is used to communicate when a refresh will happen and the progress of the refresh after it is initiated. 更新ビジュアライザーには、5 つの状態があります。The refresh visualizer has 5 states.

ユーザーが更新を開始するために一覧でプル ダウンする必要がある距離を_しきい値_といいます。The distance the user needs to pull down on a list to initiate a refresh is called the threshold. ビジュアライザー状態 は、このしきい値に関連するプル状態によって決まります。The visualizer State is determined by the pull state as it relates to this threshold. 使用可能な値は、RefreshVisualizerState 列挙に含まれています。The possible values are contained in the RefreshVisualizerState enumeration.

アイドルIdle

ビジュアライザーの既定の状態はアイドルです。The visualizer's default state is Idle. ユーザーはタッチを介して RefreshContainer と対話しておらず、実行中の更新はありません。The user is not interacting with the RefreshContainer via touch, and there is not a refresh in progress.

視覚的に、更新ビジュアライザーの証拠はありません。Visually, there is no evidence of the refresh visualizer.

操作中Interacting

ユーザーが PullDirection プロパティで指定された方向にリストをプルすると、しきい値に達する前にビジュアライザーは対話状態になります。When the user pulls the list in the direction specified by the PullDirection property, and before the threshold is reached, the visualizer is in the Interacting state.

  • この状態でユーザーがコントロールを離すと、コントロールはアイドルに戻ります。If the user releases the control while in this state, the control returns to Idle.

    引っ張って更新の事前しきい値

    視覚的に、アイコンは無効 (60% の不透明度) として表示されます。Visually, the icon is displayed as disabled (60% opacity). さらに、アイコンはスクロールの操作で 1 回転します。In addition, the icon spins one full rotation with the scroll action.

  • ユーザーがしきい値を超えてリストをプルすると、ビジュアライザーは操作中から保留中に切り替わります。If the user pulls the list past the threshold, the visualizer transitions from Interacting to Pending.

    しきい値にある引っ張って更新

    視覚的に、アイコンが 100% の不透明度に切り替わり、切り替えの過程でサイズは最大 150% になり、その後 100% に戻ります。Visually, the icon switches to 100% opacity and pulses in size up to 150% and then back to 100% size during the transition.

保留中Pending

ユーザーがしきい値を超えてリストをプルした場合、ビジュアライザーは保留中状態になります。When the user has pulled the list past the threshold, the visualizer is in the Pending state.

  • ユーザーがリストを解放せずにしきい値を超えて戻すと、操作中状態に戻ります。If the user moves the list back above the threshold without releasing it, it returns to the Interacting state.
  • ユーザーがリストを解放すると、更新要求が開始され、更新中状態に切り替わります。If the user releases the list, a refresh request is initiated and it transitions to the Refreshing state.

引っ張って更新の事後しきい値

図で表すと、アイコンはサイズと不透明度のどちらも 100% になります。Visually, the icon is 100% in both size and opacity. この状態では、アイコンはスクロール操作で下に移動し続けますが、回転することはありません。In this state, the icon continues to move down with the scroll action but no longer spins.

更新していますRefreshing

ユーザーがしきい値を超えてビジュアライザーを解放すると、更新中状態になります。When the user releases the visualiser past the threshold, it's in the Refreshing state.

この状態に入ると、RefreshRequested イベントが発生します。When this state is entered, the RefreshRequested event is raised. これは、アプリのコンテンツの更新を開始する信号です。This is the signal to start the app's content refresh. イベントの引数 (RefreshRequestedEventArgs) には、イベント ハンドラーでハンドルを取得する必要がある Deferral オブジェクトが含まれています。The event args (RefreshRequestedEventArgs) contain a Deferral object, which you should take a handle to in the event handler. その後、更新を実行するコードが完了した時点で、延期を完了とマークする必要があります。Then, you should mark the deferral as completed when your code to perform the refresh has completed.

更新が完了すると、ビジュアライザーはアイドル状態に戻ります。When the refresh is complete, the visualizer returns to the Idle state.

図で表すと、アイコンはしきい値の位置に戻り、更新の期間中回転します。Visually, the icon settles back to the threshold location and spins for the duration of the refresh. この回転は、更新の進行状況を表示するために使用され、受信したコンテンツのアニメーションによって置き換えられます。This spinning is used to show progress of the refresh and is replaced by the animation of the incoming content.

ピークPeeking

ユーザーが、更新が許可されていない開始位置から更新方向でプルすると、ビジュアライザーはピーク状態に入ります。When the user pulls in the refresh direction from a start position where a refresh is not allowed, the visualizer enters the Peeking state. これは通常、ユーザーがプルを開始したときに ScrollViewer が 0 の位置にない場合に発生します。This typically happens when the ScrollViewer is not at position 0 when the user starts to pull.

  • この状態でユーザーがコントロールを離すと、コントロールはアイドルに戻ります。If the user releases the control while in this state, the control returns to Idle.

プルの方向Pull direction

既定では、ユーザーはリストを上から下へプルして更新を開始します。By default, the user pulls a list from top to bottom to initiate a refresh. リストまたはグリッドの方向が異なる場合は、更新コンテナーのプル方向を変更して一致させる必要があります。If you have a list or grid with a different orientation, you should change the pull direction of the refresh container to match.

PullDirection プロパティでは、これらの RefreshPullDirection 値のいずれかを取得します: BottomToTopTopToBottomRightToLeft、または LeftToRightThe PullDirection property takes one of these RefreshPullDirection values: BottomToTop, TopToBottom, RightToLeft, or LeftToRight.

プルの方向を変更すると、ビジュアライザーの進行スピンの開始位置は、プルの方向に適した位置で矢印が起動するように自動的に回転します。When you change the pull dircetion, the starting position of the visualizer's progress spinner automatically rotates so the arrow starts in the appropriate position for the pull direction. 必要に応じて、RefreshVisualizer.Orientation プロパティを変更して自動の動作をオーバーライドできます。If needed, you can change the RefreshVisualizer.Orientation property to override the automatic behavior. ほとんどの場合、既定値の自動のままにしておくことをお勧めします。In most cases, we recommend leaving the default value of Auto.

引っ張って更新を実装するImplement pull-to-refresh

引っ張って更新機能をリストに追加するにはいくつかの手順が必要です。To add pull-to-refresh functionality to a list requires just a few steps.

  1. RefreshContainer コントロールでリストを折り返します。Wrap your list in a RefreshContainer control.
  2. RefreshRequested イベントを処理してコンテンツを更新します。Handle the RefreshRequested event to refresh your content.
  3. 必要に応じて、RequestRefresh (たとえば、ボタンのクリックで) を呼び出して更新を開始します。Optionally, initiate a refresh by calling RequestRefresh (for example, from a button click).

注意

単体で RefreshVisualizer をインスタンス化することができます。You can instantiate a RefreshVisualizer on its own. ただし、タッチ非対応シナリオに対しても、コンテンツを RefreshContainer で折り返し、RefreshContainer.Visualizer プロパティによって提供される RefreshVisualizer を使用することをお勧めします。However, we recommend that you wrap your content in a RefreshContainer and use the RefreshVisualizer provided by the RefreshContainer.Visualizer property, even for non-touch scenarios. この記事では、ビジュアライザーが常に更新コンテナーから取得されることを前提としています。In this article, we assume that the visualizer is always obtained from the refresh container.

さらに便宜上、更新コンテナーの RequestRefresh と RefreshRequested メンバーを使用します。In addition, use the refresh container's RequestRefresh and RefreshRequested members for convenience. refreshContainer.RequestRefresh()refreshContainer.Visualizer.RequestRefresh() に相当し、いずれかで RefreshContainer.RefreshRequested イベントと RefreshVisualizer.RefreshRequested イベントの両方が発生します。refreshContainer.RequestRefresh() is equivalent to refreshContainer.Visualizer.RequestRefresh(), and either will raise both the RefreshContainer.RefreshRequested event and the RefreshVisualizer.RefreshRequested events.

更新の要求Request a refresh

更新コンテナーは、ユーザーがタッチ経由でコンテンツを更新するためのタッチ操作を処理します。The refresh container handles touch interactions to let a user refresh content via touch. 更新ボタンまたは音声コントロールなど、タッチ非対応インターフェイス用の他のアフォーダンスを提供することをお勧めします。We recommend that you provide other affordances for non-touch interfaces, like a refresh button or voice control.

更新を開始するには、RequestRefresh メソッドを呼び出します。To initiate a refresh, call the RequestRefresh method.

// See the Examples section for the full code.
private void RefreshButtonClick(object sender, RoutedEventArgs e)
{
    RefreshContainer.RequestRefresh();
}

RequestRefresh を呼び出すと、ビジュアライザーの状態は直接アイドル状態から更新中に切り替わります。When you call RequestRefresh, the visualizer state goes directly from Idle to Refreshing.

更新要求の処理Handle a refresh request

必要に応じて新しいコンテンツを取得するには、RefreshRequested イベントを処理します。To get fresh content when needed, handle the RefreshRequested event. イベント ハンドラーで、新しいコンテンツを取得するアプリに固有のコードが必要です。In the event handler, you'll need code specific to your app to get the fresh content.

イベントの引数 (RefreshRequestedEventArgs) には、Deferral オブジェクトが含まれています。The event args (RefreshRequestedEventArgs) contain a Deferral object. イベント ハンドラーで、延期へのハンドルを取得します。Get a handle to the deferral in the event handler. その後、更新を実行するコードが完了した時点で、延期を完了とマークします。Then, mark the deferral as completed when your code to perform the refresh has completed.

// See the Examples section for the full code.
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
{
    // Respond to a request by performing a refresh and using the deferral object.
    using (var RefreshCompletionDeferral = args.GetDeferral())
    {
        // Do some async operation to refresh the content

         await FetchAndInsertItemsAsync(3);

        // The 'using' statement ensures the deferral is marked as complete.
        // Otherwise, you'd call
        // RefreshCompletionDeferral.Complete();
        // RefreshCompletionDeferral.Dispose();
    }
}

状態の変更への対応Respond to state changes

必要に応じて、ビジュアライザーの状態の変更に対応できます。You can respond to changes in the visualizer's state, if needed. たとえば、複数の更新要求を防ぐために、ビジュアライザーが更新中は更新ボタンを無効にできます。For example, to prevent multiple refresh requests, you can disable a refresh button while the visualizer is refreshing.

// See the Examples section for the full code.
private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
{
    // Respond to visualizer state changes.
    // Disable the refresh button if the visualizer is refreshing.
    if (args.NewState == RefreshVisualizerState.Refreshing)
    {
        RefreshButton.IsEnabled = false;
    }
    else
    {
        RefreshButton.IsEnabled = true;
    }
}

Examples

RefreshContainer での ScrollViewer の使用Using a ScrollViewer in a RefreshContainer

この例では、スクロール ビューアーで引っ張って更新を使用する方法を示します。This example shows how to use pull-to-refresh with a scroll viewer.

<RefreshContainer>
    <ScrollViewer VerticalScrollMode="Enabled"
                  VerticalScrollBarVisibility="Auto"
                  HorizontalScrollBarVisibility="Auto">
 
        <!-- Scrollviewer content -->

    </ScrollViewer>
</RefreshContainer>

ListView に引っ張って更新を追加するAdding pull-to-refresh to a ListView

この例では、リスト ビューで引っ張って更新を使用する方法を示します。This example shows how to use pull-to-refresh with a list view.

<StackPanel Margin="0,40" Width="280">
    <CommandBar OverflowButtonVisibility="Collapsed">
        <AppBarButton x:Name="RefreshButton" Click="RefreshButtonClick"
                      Icon="Refresh" Label="Refresh"/>
        <CommandBar.Content>
            <TextBlock Text="List of items" 
                       Style="{StaticResource TitleTextBlockStyle}"
                       Margin="12,8"/>
        </CommandBar.Content>
    </CommandBar>

    <RefreshContainer x:Name="RefreshContainer">
        <ListView x:Name="ListView1" Height="400">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:ListItemData">
                    <Grid Height="80">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <TextBlock Text="{x:Bind Path=Header}"
                                   Style="{StaticResource SubtitleTextBlockStyle}"
                                   Grid.Row="0"/>
                        <TextBlock Text="{x:Bind Path=Date}"
                                   Style="{StaticResource CaptionTextBlockStyle}"
                                   Grid.Row="1"/>
                        <TextBlock Text="{x:Bind Path=Body}"
                                   Style="{StaticResource BodyTextBlockStyle}"
                                   Grid.Row="2"
                                   Margin="0,4,0,0" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </RefreshContainer>
</StackPanel>
public sealed partial class MainPage : Page
{
    public ObservableCollection<ListItemData> Items { get; set; } 
        = new ObservableCollection<ListItemData>();

    public MainPage()
    {
        this.InitializeComponent();

        Loaded += MainPage_Loaded;
        ListView1.ItemsSource = Items;
    }

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= MainPage_Loaded;
        RefreshContainer.RefreshRequested += RefreshContainer_RefreshRequested;
        RefreshContainer.Visualizer.RefreshStateChanged += Visualizer_RefreshStateChanged;

        // Add some initial content to the list.
        await FetchAndInsertItemsAsync(2);
    }

    private void RefreshButtonClick(object sender, RoutedEventArgs e)
    {
        RefreshContainer.RequestRefresh();
    }

    private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
    {
        // Respond to a request by performing a refresh and using the deferral object.
        using (var RefreshCompletionDeferral = args.GetDeferral())
        {
            // Do some async operation to refresh the content

            await FetchAndInsertItemsAsync(3);

            // The 'using' statement ensures the deferral is marked as complete.
            // Otherwise, you'd call
            // RefreshCompletionDeferral.Complete();
            // RefreshCompletionDeferral.Dispose();
        }
    }

    private void Visualizer_RefreshStateChanged(RefreshVisualizer sender, RefreshStateChangedEventArgs args)
    {
        // Respond to visualizer state changes.
        // Disable the refresh button if the visualizer is refreshing.
        if (args.NewState == RefreshVisualizerState.Refreshing)
        {
            RefreshButton.IsEnabled = false;
        }
        else
        {
            RefreshButton.IsEnabled = true;
        }
    }

    // App specific code to get fresh data.
    private async Task FetchAndInsertItemsAsync(int updateCount)
    {
        for (int i = 0; i < updateCount; ++i)
        {
            // Simulate delay while we go fetch new items.
            await Task.Delay(1000);
            Items.Insert(0, GetNextItem());
        }
    }

    private ListItemData GetNextItem()
    {
        return new ListItemData()
        {
            Header = "Header " + DateTime.Now.Second.ToString(),
            Date = DateTime.Now.ToLongDateString(),
            Body = DateTime.Now.ToLongTimeString()
        };
    }
}

public class ListItemData
{
    public string Header { get; set; }
    public string Date { get; set; }
    public string Body { get; set; }
}