본문 바로가기
프로그래밍/App 개발

[Xamarin] Custom sliver top area (gradually disappearing view while scrolling)

by 엽기토기 2021. 9. 13.
반응형

https://api.flutter.dev/flutter/material/SliverAppBar-class.html

(flutter의 sliver app bar와 유사, airbnb 뷰와 유사)

 

스크롤 하면서 상단 영역(터치 가능)이 점점 사라지는 커스텀 뷰를 만들어보자.

Let's create a view where the top area (touchable) gradually disappears while scrolling.


xaml 코드를 살펴보자. (전체 코드를 삽입한 것이 아님.)

<pages:DFContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:cards="clr-namespace:PanCardView;assembly=PanCardView"
    xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
    xmlns:proc="clr-namespace:PanCardView.Processors;assembly=PanCardView"
    xmlns:strings="clr-namespace:DreamforaV2.Resources.StringResxFiles"
    xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
    xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
    ios:Page.UseSafeArea="False"
    BackgroundColor="Black">

    <!--#region Resources-->
    <ContentPage.Resources>
    ...
    </ContentPage.Resources>
    <!--#endregion-->

    <controls:ScrollViewDisabledBounce
        x:Name="OuterScrollView"
        Scrolled="OnScrollViewScrolled"
        VerticalScrollBarVisibility="Never">

        <Grid>

            <!--#region Popular Wizard Dreams-->
            <Grid x:Name="PopularDreamsView">
                <StackLayout x:Name="PopularDreamsViewContents">
                    <Label
                        x:Name="discoverLabel"
                        Margin="20,9,0,0"
                        Style="{x:StaticResource HeaderLabel}"
                        Text="{x:Static strings:DreamforaStringResources.discover_title}"
                        TextColor="White" />

                    <Label
                        x:Name="popularLabel"
                        Margin="32,32,0,0"
                        CharacterSpacing="-0.49"
                        FontFamily="{x:Static constants:Fonts.DMSansMedium}"
                        FontSize="22"
                        Text="{x:Static strings:DreamforaStringResources.discover_popular_title}"
                        TextColor="White" />

                    <cards:CoverFlowView
                        x:Name="PopularDreamsScrollView"
                        Margin="32,24,0,0"
                        HeightRequest="383"
                        IsCyclical="True"
                        ItemsSource="{Binding PopularWizardDreamToDisplay}"
                        PositionShiftValue="60">
                        <x:Arguments>
                            <proc:CoverFlowProcessor OpacityFactor="0.25" ScaleFactor="0.75" />
                        </x:Arguments>
                        <cards:CoverFlowView.ItemTemplate>
                            <DataTemplate>
                                <Frame
                                    BackgroundColor="Transparent"
                                    CornerRadius="14"
                                    HeightRequest="383"
                                    HorizontalOptions="Start"
                                    VerticalOptions="Start"
                                    WidthRequest="310">
                                    <Frame.GestureRecognizers>
                                        <TapGestureRecognizer CommandParameter="{Binding .}" Tapped="OnWizardDreamsTapped" />
                                    </Frame.GestureRecognizers>
								...
                                </Frame>
                            </DataTemplate>
                        </cards:CoverFlowView.ItemTemplate>
                    </cards:CoverFlowView>
                </StackLayout>
            </Grid>
            <!--#endregion Popular Wizard Dreams-->

            <BoxView
                x:Name="TopWhiteBox"
                InputTransparent="True"
                Opacity="0" />

           
            <Grid x:Name="BottomDreamsLayout">
               <!--#region Bottom Wizard Dreams-->
                <StackLayout
                    x:Name="allWizardDreamList"
                    Margin="32,104,32,132"
                    BindableLayout.ItemsSource="{Binding AllWizardDreamToDisplay}">
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Frame
                                Margin="0,0,0,28"
                                xct:ShadowEffect.Color="Black"
                                xct:ShadowEffect.OffsetY="10"
                                xct:ShadowEffect.Opacity="0.1"
                                xct:ShadowEffect.Radius="13"
                                CornerRadius="18"
                                HorizontalOptions="Fill"
                                VerticalOptions="Start">
                                <Frame.GestureRecognizers>
                                    <TapGestureRecognizer CommandParameter="{Binding .}" Tapped="OnWizardDreamsTapped" />
                                </Frame.GestureRecognizers>
								...
                            </Frame>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>
                </StackLayout>
                <!--#endregion Bottom Wizard Dreams-->

                <StackLayout
                    x:Name="categoryStackLayout"
                    BackgroundColor="{AppThemeBinding Dark={x:Static constants:Colors.LightDiscoverPageBackgroundColor},
                                                      Light={x:Static constants:Colors.DarkDiscoverPageBackgroundColor}}"
                    HeightRequest="85"
                    VerticalOptions="Start">
                    <Grid>
                        <CollectionView>
                            ...
                        </CollectionView>
                    </Grid>
                </StackLayout>
            </Grid>
            
        </Grid>
    </controls:ScrollViewDisabledBounce>
</pages:DFContentPage>

 

바깥쪽 최 상위 영역에 스크롤뷰로 감싸준다. 이후에 상단 박스의 opacity를 변경해주기 위해, 이 스크롤뷰의 스크롤 위치를 가져올 예정이다.

 

그리고 안쪽에 Grid를 배치한다. 안쪽영역(덮이는 쪽)과 바깥쪽영역(덮는 쪽)을 겹치게 배치하여 자연스럽게 바깥쪽영역이 스크롤되면서 안쪽영역을 덮는 것처럼 만들기 위해서 grid를 사용한다.

grid안에는 세 가지 뷰를 배치할 예정이다.

1. 상단영역 (덮이는 쪽)

2. 스크롤되면서 점점 투명도가 변하는 효과를 주기 위한 boxview

3. 하단영역 (덮는 쪽)

 

상단영역 (덮이는 쪽) 과 하단영역 (덮는 쪽) 모두 전체 화면으로 배치한다.

그리고, 상단역역의 높이를 계산하여 하단영역의 탑 마진에 넣어준다.(중요)

이를 코드로 구현하면,

public partial class Page : DFContentPage
{
       private double _statusBarHeight = 0;
       private double popularDreamsOverViewHeight = 0;
       private double _categoryListScrollViewY = 0;

       public Page()
        {
            InitializeComponent();
            
            ...
            
            discoverLabel.SizeChanged += OnDiscoverLabelSizeChanged;
            popularLabel.SizeChanged += OnPopularLabelSizeChanged;
            
            PopularDreamsScrollView.SizeChanged += OnPopularDreamsScrollViewSizeChanged;
            BottomDreamsLayout.SizeChanged += OnBottomDreamsLayoutSizeChanged;
        }

// (앱 딱 켜지고 호출 순서) 1 -> DiscoverLabel_SizeChanged
        private void OnDiscoverLabelSizeChanged(object sender, EventArgs e)
        {
            discoverLabel.SizeChanged -= OnDiscoverLabelSizeChanged;
            popularDreamsOverViewHeight += discoverLabel.Height;
        }

// 2 -> PopularDreamsScrollView_SizeChanged
        private void OnPopularLabelSizeChanged(object sender, EventArgs e)
        {
            popularLabel.SizeChanged -= OnPopularLabelSizeChanged;
            popularDreamsOverViewHeight += popularLabel.Height + popularLabel.Margin.Top;
        }

// 3
        private void OnPopularDreamsScrollViewSizeChanged(object sender, EventArgs e)
        {
            PopularDreamsScrollView.SizeChanged -= OnPopularDreamsScrollViewSizeChanged;
            popularDreamsOverViewHeight += PopularDreamsScrollView.Margin.Top;
            popularDreamsOverViewHeight += PopularDreamsScrollView.Height;
            BottomDreamsLayout.Margin = new Thickness(0, popularDreamsOverViewHeight + 44, 0, 0);
        }
        
        private void OnBottomDreamsLayoutSizeChanged(object sender, EventArgs e)
        {
            BottomDreamsLayout.SizeChanged -= OnBottomDreamsLayoutSizeChanged;
            _categoryListScrollViewY = BottomDreamsLayout.Y;
        }
}

이렇게 하면 두 영역이 겹치는 형태가 된다. 안쪽영역 터치도 잘 될 것이다.


자, 그리고 상단영역 박스뷰의 투명도 변화 효과도 만들어보자.

스크롤뷰에 'Scrolled" 이벤트 처리기를 등록하고, 이벤트 처리기 안에서 opcitiry 를 변경하는 로직을 추가하면 된다.

 

이번에 만든 앱은 반쯤 왔을 때부터 투명도가 변화하길 원했어서, 전체 높이의 반만큼을 스크롤된 위치값 Y에서 나눠서 Opacity에 넣어줬다.

이를 코드로 구현하면,

        private void OnScrollViewScrolled(object sender, ScrolledEventArgs e)
        {
            ...
            
            if (Device.RuntimePlatform == Device.iOS)
            {
                TopWhiteBox.Opacity = e.ScrollY / (Values.height * 0.5);
            }
        }

이렇게 하면, 최 상위에 있는 스크롤뷰에서 스크롤 이벤트가 발생할 때마다 Opacity 값을 계산해서 변경시키게된다.

 

 

끝!

반응형