FLO 어플 클론코딩을 진행하면서 위와 같은 화면을 구성해야 하는 상황에 닥쳤다.
화면 구성을 위와같은 식으로 잡았다.
우선 AlbumFragment.kt 의 아래쪽에 삽입되는 3개의 Fragment를 제쳐두고, 위쪽을 보니 [수록곡 , 상세정보 , 영상] 부분은 Scroll이 되면서 화면 상단에 도착했을 때 고정이 되고 아래쪽 Fragment의 ScrollView가 작동하는 식이다. 'ScrollView 상단고정' 뭐 이런 느낌으로 구글링을 해 본 결과 StickyScrolView 라는 키워드를 알게 되었다.
그래서
https://github.com/amarjain07/StickyScrollView
위의 GitHub 를 참고해서 코드를 작성 해 보았는데, 어렵쇼....? AlbumFragment.kt 의 StickyScroll 은 원하는 대로 상단에 고정 되면서 잘 작동하는데, AlbumIncludedFragment.kt 내부의 scroll 이 작동이 안된다...
여기서 많은 삽질을 하고 동아리 질문방에 질문을 해 본 결과, Scroll을 여러개 중첩 하려면 ScrollView가 아닌 NestedScrollView 를 사용해야 한다는 점과, 중복 Scoll과 함께 일부 View의 상단 고정까지 구현하고 싶다면 CoordinatorLayout 을 공부해 보라는 답변을 받았다.
그렇다면 CoordinatorLayout 에 대해서 알아보자.
https://developer.android.com/reference/androidx/coordinatorlayout/widget/CoordinatorLayout
CoordinatorLayout 이란??
CoordinatorLayout은 FrameLayout에 기반을 둔 Layout 으로 2개의 주요한 기능이 있다.
- 최상위 Decor 뷰로서의 사용
- 자식 뷰들간의 인터렉션을 위한 컨테이너로서의 사용
대부분 CoordinatorLayout은 스크롤의 움직임에 따라 상단 Appbar에 변화를 주고자 할 때 사용한다. 아래 예시처럼 Appbar의 크기가 변화하는 화면을 만들고 싶을 대 CoordinatorLayout을 사용한다.
CoordinatorLayout 은 구조를 잘 파악해야 한다. 기본 구조는 아래와 같다.
<CoordinatorLayout>
<AppBarLayout>
<CoollapsingToolbarLayout>
<Toolbar
==> 상단 AppBar 에 고정 할 내용들
/>
==> Scroll을 할 때 위쪽으로 올라가면서 Collapsing 되는 부분
</CollapsingToolbarLayout>
</AppBarLayout>
<NestedScrollView OR RecyclerView>
<Layout~~~~>
</NestedScrollView OR RecyclerView>
</CoordinatorLayout>
우선 Toolbar를 사용 할 것이므로 기본으로 설정되어 있는 Actionbar 는 없애주도록 하자.
FLO 클론 어플에 CoordinatorLayout을 적용했을 때 레이아웃과 코드는 아래와 같다.
<Toolbar>
<CollapsingToolbarLayout , AppbarLayout>
<TabLayout>
<ViewPager2>
레이아웃의 색상별로 나눠 보았다. 하단의 Fragment 전환은 TabLayout과 Viewpager2 를 활용하였다. 이에 대해서는 다음에 다른 글에서 설명 하도록 하겠다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/album_top_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="@android:color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="pin"
app:contentInsetStart="0dp"
app:contentInsetEnd="0dp">
<!--Android Studio Toolbar 내부에 원하는 레이아웃을
할때 치우침(특히 오른쪽으로) 현상이 생긴다
이는 Toolbar 레이아웃이 기본적으로 16dp의 여백을 가지고 있기 때문이다.
이 여백은 툴바에서 사용되는 홈 버튼, 앱 아이콘 등을 위해 설정되어
있는 것으로 앱의 메인 AppBar로 사용되는 경우 건들지 않는 것이 좋다.
위 기능 사용 목적이 아닌 내부 레이아웃을 배치하기 위함이라면
아래 코드를 추가하여 치우침 현상을 해결할 수 있다.-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/album_btn_arrow_back_iv"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginStart="10dp"
android:src="@drawable/btn_arrow_black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_btn_like_off_iv"
android:layout_width="30dp"
android:layout_height="36dp"
android:src="@drawable/ic_my_like_off"
android:layout_marginEnd="10dp"
app:layout_constraintBottom_toBottomOf="@+id/album_btn_arrow_back_iv"
app:layout_constraintEnd_toStartOf="@id/album_btn_more_iv"
app:layout_constraintTop_toTopOf="@+id/album_btn_arrow_back_iv" />
<ImageView
android:id="@+id/album_btn_more_iv"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="10dp"
android:src="@drawable/btn_player_more"
app:layout_constraintBottom_toBottomOf="@+id/album_btn_like_off_iv"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/album_btn_like_off_iv" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="38dp"
android:paddingBottom="35dp">
<TextView
android:id="@+id/album_title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IU 5th Album 'LILAC'"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="#000000"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/album_singer_tv"/>
<TextView
android:id="@+id/album_singer_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="아이유 (IU)"
android:textSize="14sp"
android:layout_marginTop="3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/album_title_tv"/>
<TextView
android:id="@+id/album_date_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2021.03.25 | 정규 | 댄스 팝"
android:textSize="11sp"
android:layout_marginTop="3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/album_singer_tv"/>
<androidx.cardview.widget.CardView
android:id="@+id/album_photo_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/album_date_tv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:cardCornerRadius="10dp"
android:layout_marginTop="10dp">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/img_album_exp2"
android:scaleType="centerCrop"
android:adjustViewBounds="true"/>
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:src="@drawable/widget_black_play"
android:layout_gravity="right|bottom"/>
</FrameLayout>
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/album_lp_iv"
android:layout_width="0dp"
android:layout_height="120dp"
android:adjustViewBounds="true"
android:src="@drawable/img_album_lp"
app:layout_constraintTop_toTopOf="@id/album_photo_iv"
app:layout_constraintBottom_toBottomOf="@id/album_photo_iv"
app:layout_constraintStart_toEndOf="@id/album_photo_iv"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchor="@id/album_top_appbar"
app:layout_anchorGravity="bottom"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.tabs.TabLayout
android:id="@+id/album_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorFullWidth="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/album_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/album_tab_layout"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
코드를 그냥 복붙 해 온거여서 말도안되게 길다. 하지만 기본 구조는 위에서 설명한 구조와 동일하다. 살만 붙인거다!!
위에서 CoordinatorLayout의 구조를 설명할 때 상단 AppbarLayout 부분 말고 아래쪽에는 NestedScrollView 또는 RecyclerView 가 온다고 했었는데 나의 경우에는 ConstraintLayout으로 감싸진 TabLayout과 ViewPager2 가 있다. 어렵쇼....뭐지??? 가 아니다. ViewPager와 연결 된 Fragment에 NestedScrollView가 있다 !!!
++ 알아두면 좋은 속성들
1. android:fitsSystemWindows="true"
위 속성을 상단에 고정이 되는 Toolbar를 제외 한 CoordinatorLayout, AppbarLayout, CollapsingToolbarLayout 에 추가 해 줘야 한다.
사실 이 속성이 의미하는 바를 잘 모르겠다. 더 많은 공부와 구글링이 필요할 거 같다. 아무튼 이 속성을 CoordinatorLayout 랑 CollapsingToolbarLayout 에 추가했다. 이 속성을 추가하면 화면이 스크롤 됨과 동시에 올라가면서 Status bar 영역까지 채워지면서 스크롤 된다. 우리는 Status bar를 투명하게 했으므로 투명한 Status bar 뒤로 View가 채워진다.
2. app:layout_scrollFlags
위 속성을 CollapsintToolbarLayout 에 사용해서 스크롤 시에 다양한 효과를 줄 수 있다. 위 속성을 이용하면 스크롤 시 Toolbar를 보여지게 하거나 감추는 등의 flag를 적용할 수 있다.
scroll | enterAlways
enterAlways는 스크롤이 아래로 이동했을 때 Appbar가 완전히 사라졌다가, 위쪽으로 이동했을 때 Appbar 전체가 나타난다. 웹 브라우저에서 스크롤을 아래로 내릴 땐 주소창이 감춰지다가, 위쪽을 스크롤을 하게 되면 주소창이 보여지는 경우에 적용된다.
scroll | enterAlways | enterAlwaysCollapsed
enterAlwaysCollapsed는 스크롤이 아래로 이동했을 때 Appbar가 완전히 사라졌다가, 위쪽으로 이동했을 때 Toolbar가 minheight 만큼만 내려오고 스크롤이 최 상단에 도착했을 때 나머지 Appbar 전체가 나타난다.
이 경우에는 Toolbar에 android:minHeight 속성을 적용 시키거나 Toolbar의 height를 직접 설정해 주면 된다. 또한 enterAlwaysCollapsed는 반드시 enterAlways와 조합해서 사용해야 한다.
scroll | exitUntilCollased
exitUntilCollapsed는 스크롤을 아래, 위로 이동할 때 Toolbar 의 minHeight 만큼만 보여지고 스크롤이 최 상단에 도착 시 나머지 Appbar의 전체가 내려오게 된다. 위에서 소개한 enterAlways | enterAlwaysCollapsed 와 다른 점은 아래쪽으로 스크롤링 해도 AppbarLayout 이 완전히 사라지지 않는 다는 것이다.
scroll | snap
snap 은 마치 자석에 달라붙는 것 같은 느낌으로 AppbarLayout size 의 절반 크기를 기준으로 아래 위로 달라 붙는 flag 이다.
3. app:layout_collapseMode
위 속성을 Toolbar 안에 설정하면 스크롤이 발생했을 때에 Toolbar의 최종 형태가 어떤 형태인지를 결정할 수 있다.
pin
CollapsingToolbarLayout 이 완전히 축소되었을 때에 Toolbar는 화면 상단에 고정(pin) 된다.
parallx
툴바가 축소되는 동안Parallax모드로 동작하도록 한다. ImageView안에 layout_collapseMode=”parallax”를 이용하면 스크롤을 끝까지 올리면 이미지의 중간부분이 보여지고, 해당 옵션을 없애면 위부터 아래로 사라진다.
4. app:layout_behavior="@string/appbar_scrolling_view_behavior"
위의 전체코드를 보면 TabLayout과 ViewPager2 를 감싸고 있는 ConstraingLayout에 app:layout_behavior="@string/appbar_scrolling_view_behavior" 라는 속성이 있다. CoordinatorLayout은 FrameLayout을 상속 한 레이아웃이므로 위의 속성을 추가하지 않으면 TabLayout과 ViewPager는 Appbar와 중첩되어 그려진다. 그러므로 매우 필수적인 속성이라고 볼 수 있다.
추가적으로 Android Studio 의 기본 템플릿 중 ScrollingActivity의 코드를 살펴보면 anchor 라는 속성이 있다. 아래 코드의 의미를 파악 해 보면 FloatingActionButton을 appbar 를 기준으로 ( app:layout_anchor="@id/app_bar" ) 오른쪽 아래에 배치 하겠다는 뜻이다. ( app:layout_anchorGravity="bottom|end" )
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@android:drawable/ic_dialog_email" />
그리고 AppBarLayout 의 속성 중에서 app:elevation="0dp" 속성이 있다. 스크롤이 최하단으로 내려갔을때 toolbar의 주변으로 그림자가 생기는데 이를 없애주는 속성이다. android:elevation="0dp" 가 아니라 app:elevation="0dp" 인것에 유의하자.
참고 :
https://kangmin1012.tistory.com/33
https://one-delay.tistory.com/68
https://freehoon.tistory.com/38
layout_scrollFlags : http://areemak.blogspot.com/2018/04/blog-post.html
'안드로이드 > [Kotlin]' 카테고리의 다른 글
[Kotlin] registerForActivityResult() 사용법 (Feat. startActivityForResult 의 deprecated) (0) | 2021.10.25 |
---|---|
[Kotlin] ViewPager2 & TabLayout (Feat. FragmentStateAdatper, TabLayoutMediator) (0) | 2021.10.24 |
[Kotlin] ImageView 모서리 라운딩 (feat. CardView & Glide 라이브러리) (0) | 2021.10.05 |
[Kotlin] 상단의 Status Bar를 투명하게 만들어 보자. (0) | 2021.10.04 |
[Kotlin] ListView 생성. Custom Adapter 사용하기 (0) | 2021.08.20 |