다음을 통해 공유


당겨서 새로 고침

당겨서 새로 고침을 사용하면 터치로 데이터 목록을 아래로 당겨서 더 많은 데이터를 검색할 수 있습니다. 당겨서 새로 고침은 터치 스크린이 장착된 디바이스에서 널리 사용되고 있습니다. 여기 나온 API를 사용하여 앱에서 당겨서 새로 고침을 구현할 수 있습니다.

당겨서 새로 고침 GIF

올바른 컨트롤인가요?

정기적으로 새로 고침을 수행하고 싶은 데이터 목록이나 그리드가 있을 때는 당겨서 새로 고침을 사용합니다. 그러면 앱이 터치 우선 디바이스에서 실행될 것입니다.

RefreshVisualizer를 사용하여 새로 고침 단추와 같은 다른 방법으로 호출되는 일관된 새로 고침 환경을 만들 수도 있습니다.

새로 고침 컨트롤

당겨서 새로 고침은 2개의 컨트롤로 활성화됩니다.

  • RefreshContainer - 풀 투 새로 고침 환경을 위한 래퍼를 제공하는 ContentControl입니다. 터치 조작을 처리하고 내부 새로 고침 시각화 도우미의 상태를 관리합니다.
  • RefreshVisualizer - 다음 섹션에서 설명하는 새로 고침 시각화를 캡슐화합니다.

주 컨트롤은 RefreshContainer이며, 사용자가 새로 고침을 트리거하기 위해 끌어오는 콘텐츠 주위에 래퍼로 배치합니다. RefreshContainer는 터치 방식으로만 작동하므로 터치 인터페이스가 없는 사용자를 위한 새로 고침 단추도 준비하는 것이 좋습니다. 새로 고침 단추를 앱의 적정 위치(명령 모음이나 새로 고침 중인 표면과 가까운 위치)에 배치할 수 있습니다.

시각화 새로 고침

기본 새로 고침 시각화는 새로 고침이 발생하는 때를 알리는 데 사용되는 순환 진행률 스피너로써, 개시 후 새로 고침의 진행 상태를 나타냅니다. 새로 고침 시각화 도우미의 상태는 5가지입니다.

사용자가 새로 고침을 시작하기 위해 목록에서 끌어와야 하는 거리를 임계값이라고 합니다. 시각화 도우미 상태는 이 임계값과 관련된 끌어오기 상태에 의해 결정됩니다. 가능한 값은 RefreshVisualizerState 열거형에 포함됩니다.

유휴 상태

시각화 도우미의 기본 상태는 유휴 상태입니다. 사용자가 터치를 통해 RefreshContainer와 상호 작용을 하고 있지 않으며, 새로 고침이 진행되고 있지 않습니다.

새로 고침 시각화 도우미는 눈에 보이는 증거가 없습니다.

상호 작용 중

사용자가 PullDirection 속성에 지정된 방향으로 목록을 끌어오면 임계값에 도달하기 전에 시각화 도우미가 상호 작용 상태에 있습니다.

  • 사용자가 이 상태에 있는 동안 컨트롤을 해제하면 컨트롤이 유휴 상태로 돌아갑니다.

    당겨서 새로고침 시작 전 임계값

    시각적으로 아이콘은 비활성 상태로 표시됩니다(불투명도 60%). 또한 이 아이콘은 스크롤 조작을 통해 한 바퀴를 완전히 회전합니다.

  • 사용자가 임계값을 초과하여 목록을 끌어오면 시각화 도우미가 상호 작용 에서 보류 중으로 전환됩니다.

    임계값에서 당겨서 새로 고침

    시각적으로, 이 아이콘은 불투명도가 100%로 바뀌고 크기가 150%까지 커졌다가 다시 100% 크기로 되돌아갑니다.

보류 중

사용자가 임계값을 초과하여 목록을 끌어오면 시각화 도우미가 보류 중 상태입니다.

  • 사용자가 목록을 해제하지 않고 임계값 이상으로 다시 이동하면 상호 작용 상태로 돌아갑니다.
  • 사용자가 목록을 해제하면 새로 고침 요청이 시작되고 새로 고침 상태로 전환됩니다.

임계값 후 끌어오기-새로 고침

시각적으로 아이콘의 크기와 불투명도가 모두 100%입니다. 이 상태에서 아이콘은 스크롤 조작 시 계속해서 아래로 이동하지만, 더 이상 회전하지 않습니다.

새로 고침

사용자가 임계값을 초과하여 시각화 도우미를 해제하면 새로 고침 상태가 됩니다.

이 상태가 입력되면 RefreshRequested 이벤트가 발생합니다. 이는 앱의 콘텐츠 새로 고침이 시작된다는 것을 알리는 신호입니다 이벤트 인수(RefreshRequestedEventArgs)에는 이벤트 처리기에서 핸들을 가져와야 하는 Deferral 개체가 포함됩니다. 그런 다음, 새로 고침을 수행하기 위한 코드가 완료되면 지연을 완료로 표시해야 합니다.

새로 고침이 완료되면 시각화 도우미가 유휴 상태로 돌아갑니다.

시각적으로 아이콘은 임계값 위치로 다시 돌아오고 새로 고침 시간 동안 회전합니다. 이러한 회전은 새로 고침의 진행율을 표시하는 데 사용되고, 수신 콘텐츠에 대한 애니메이션으로 대체됩니다.

미리 보는 중

사용자가 새로 고침이 허용되지 않는 시작 위치에서 새로 고침 방향을 가져오면 시각화 도우미가 피킹 상태로 들어갑니다. 일반적으로 사용자가 당기기를 시작할 때 ScrollViewer가 위치 0에 있지 않은 경우에 이런 상황이 발생합니다.

  • 사용자가 이 상태에 있는 동안 컨트롤을 해제하면 컨트롤이 유휴 상태로 돌아갑니다.

끌어오기 방향

기본적으로 사용자는 새로 고침을 시작하기 위해 위에서 아래로 목록을 당깁니다. 목록이나 그리드가 다른 방향을 향하고 있으면 이에 맞춰 새로 고침 컨테이너의 당기기 방향을 변경해야 합니다.

PullDirection 속성은 BottomToTop, TopToBottom, RightToLeft 또는 LeftToRight와 같은 RefreshPullDirection 값 중 하나를 사용합니다.

당기기 방향이 변경되면 시각화 도우미의 진행율 스피너의 시작 부분이 자동으로 회전되어 당기기 방향에 적합한 위치에서 화살표가 시작됩니다. 필요한 경우 RefreshVisualizer.Orientation 속성을 변경하여 자동 동작을 재정의할 수 있습니다. 대부분의 경우 자동의 기본값을 그대로 두는 것이 좋습니다.

당겨서 새로 고침 구현

WinUI 3 갤러리 앱에는 대부분의 WinUI 3 컨트롤, 기능 및 기능의 대화형 예제가 포함되어 있습니다. Microsoft Store에서 앱을 가져오거나 GitHub에서 소스 코드를 가져옵니다.

당겨서 새로 고침 기능을 목록에 추가하려면 몇 가지 단계만 거치면 됩니다.

  1. RefreshContainer 컨트롤에 목록을 래핑합니다.
  2. RefreshRequested 이벤트를 처리하여 콘텐츠를 새로 고칩니다.
  3. 필요에 따라 RequestRefresh 를 호출하여 새로 고침을 시작합니다(예: 단추 클릭에서).

참고

RefreshVisualizer를 자체적으로 시작할 수 있습니다. 하지만 터치를 지원하지 않는 시나리오라 하더라도 RefreshContainer에 콘텐츠를 래핑하고 RefreshContainer.Visualizer 속성이 제공하는 RefreshVisualizer를 사용하는 것이 좋습니다. 이 문서에서는 항상 새로 고침 컨테이너에서 시각화 도우미를 가져온다고 가정합니다.

또한 편의상 새로 고침 컨테이너의 RequestRefresh 및 RefreshRequested 멤버를 사용합니다. refreshContainer.RequestRefresh()refreshContainer.Visualizer.RequestRefresh()와 동일하며, 둘 다 RefreshContainer.RefreshRequested 이벤트와 RefreshVisualizer.RefreshRequested 이벤트가 발생합니다.

새로 고침 요청

새로 고침 컨테이너는 사용자가 터치를 통해 콘텐츠를 새로 고침할 수 있도록 터치 조작을 처리합니다. 새로 고침 단추나 음성 컨트롤과 같이 터치가 지원되지 않는 인터페이스에는 다른 어포던스를 제공하는 것이 좋습니다.

새로 고침을 시작하려면 RequestRefresh 메서드를 호출합니다.

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

RequestRefresh를 호출하면 시각화 도우미 상태가 유휴 상태에서 새로 고침으로 직접 이동합니다.

새로 고침 요청 처리

필요에 따라 새 콘텐츠를 가져오려면 RefreshRequested 이벤트를 처리합니다. 이벤트 처리기에서 새로 고침 콘텐츠를 다운로드하려면 사용자 앱과 관련된 코드가 필요합니다.

이벤트 인수(RefreshRequestedEventArgs)에는 Deferral 개체가 포함됩니다. 이벤트 처리기에서 이러한 지연을 관리합니다. 그런 다음, 새로 고침을 수행하기 위한 코드가 완료되면 지연을 완료로 표시합니다.

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

상태 변경에 응답

필요한 경우 시각화 도우미의 상태 변화에 응답할 수 있습니다. 예를 들어 새로 고침 요청이 여러 개 발생하는 것을 막기 위해 시각화 도우미가 새로 고침 중인 상태에서 새로 고침 단추를 비활성화할 수 있습니다.

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

RefreshContainer에서 ScrollViewer 사용

참고

RefreshContainer의 Content는 ScrollViewer, GridView, ListView 등과 같은 스크롤이 가능한 컨트롤이어야 합니다. Content를 Grid 같은 컨트롤로 설정하면 정의되지 않은 동작이 발생합니다.

이 예제는 스크롤 뷰어에서 당겨서 새로 고침을 사용하는 방법을 보여줍니다.

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

    </ScrollViewer>
</RefreshContainer>

ListView에 '끌어 내려 새로 고침' 기능 추가

이 예제는 목록 보기에서 당겨서 새로 고침을 사용하는 방법을 보여줍니다.

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

UWP 및 WinUI 2

중요

이 문서의 정보 및 예제는 Windows 앱 SDKWinUI 3을 사용하는 앱에 최적화되어 있지만 일반적으로 WinUI 2를 사용하는 UWP 앱에 적용됩니다. 플랫폼별 정보 및 예제는 UWP API 참조를 확인하세요.

이 섹션에는 UWP 또는 WinUI 2 앱에서 컨트롤을 사용하는 데 필요한 정보를 다룹니다.

UWP 앱을 위한 새로 고침 컨트롤은 WinUI 2의 일부로 포함됩니다. 설치 지침을 비롯한 자세한 내용은 WinUI 2를 참조하세요. 이 컨트롤에 대한 API는 Windows.UI.Xaml.Controls (UWP) 및 Microsoft.UI.Xaml.Controls (WinUI) 네임스페이스에 모두 있습니다.

최신 WinUI 2 를 사용하여 모든 컨트롤에 대한 최신 스타일, 템플릿 및 기능을 가져오는 것이 좋습니다.

이 문서의 코드를 WinUI 2와 함께 사용하려면 XAML의 별칭(여기서는 muxc 사용)을 사용하여 프로젝트에 포함된 Windows UI 라이브러리 API를 표현합니다. 자세한 내용은 WinUI 2 시작 을 참조하세요.

xmlns:muxc="using:Microsoft.UI.Xaml.Controls"

<muxc:RefreshContainer />