유튜브 같은 어플을 보시면 스크롤을 아래로 내릴 때 영상이 작아지고, 다시 위로 올리면 커지는 것들을 보신 적 있으실 겁니다. 그와 관련된 동작이 바로 CoordinatorLayout을 통해 제공되고 있는데요.
이번 포스팅에서는 앱 상단에 고정되어 있는 Toolbar를 만들고, 아래 NestedScrollView 또는 RecyclerView를 두어 스크롤 시 툴바가 반응하는 것을 살펴보고 예제 코드도 작성해보겠습니다.
먼저 핵심 역할을 해줄 CoordinatorLayout을 알아볼 텐데, 이번 포스팅에서는 간단하게만 살펴보고 추후에 이녀석을 주인공으로 제대로 다뤄보겠습니다.
CoordinatorLayout
CoordinatorLayout은 간단하게 말해서 강력한 FrameLayout 입니다.
Behavior를 통해서 자식 뷰들 간에 다양한 상호작용(interaction)을 제공합니다.
요즘 핫한 Brain Out 같은 게임이 바로 이 CoordinatorLayout을 잘 이용한 앱이라고 볼 수 있습니다.
CoordinatorLayout의 Behaviors를 이용하면 자식 뷰들을 드래그해서 움직이는 것도 가능하고 패널들을 스와이프 해서 지우기, 요소들에 애니메이션 적용 등 다양한 기능을 활용할 수 있습니다.
AppBarLayout
안드로이드에는 원래 ActionBar가 기본적으로 제공되고 있었지만, 액션바는 안드로이드 API 버전마다 다른 기능과 다른 동작을 제공해 개발자가 사용자에게 일관된 UX를 제공하는 데 불편함이 있었습니다.
이에 대한 해결책으로 google은 material design 패키지에 있는 AppBarLayout과 ToolBar를 제시하고 있습니다.
AppbarLayout은 vertical LinearLayout을 상속한 레이아웃으로, 특히나 스크롤 제스처에 대해 몇 가지 동작을 적용할 수 있습니다.
AppbarLayout 아래 자식들은 setScrollFlags(int) 메소드나 xml 속성으로 app:layout_scrollFlags을 적용하면 scroll에 대한 행동(behavior)을 설정할 수 있습니다.
단, 주의할 점은 AppBarLayout는 CoordinatorLayout의 바로 아래 자식이어야 합니다.
만약 다른 ViewGroup 안에서 AppBarLayout을 사용한다면 해당 기능은 동작하지 않을 것입니다.
AppBarLayout이 스크롤을 인식하기 위해서는 CoordinatorLayout 안에 같은 수준으로 선언된 형제 뷰(sibling)가 RecyclerView나 NestedScrollView처럼 스크롤 가능한 뷰여야 합니다.
스크롤 가능한 sibling view는 AppBarLayout.ScrollingViewBehavior를 통해 AppBarLayout과 바인딩을 해야 합니다.
정확하게는 scrolling view의 behavior를 AppBarLayout.ScrollingViewBehavior 인스턴스로 설정해줘야 합니다.
여기서 독특한 점은 String 값으로 패키지를 포함한 Full class name을 통해서 설정해준다는 것입니다.
이에 대해서는 예제 코드에서 다시 언급하겠습니다.
Sample Code
참고로 예제는 androidx 패키지와 코틀린(kotlin)을 이용해 작성하였습니다.
먼저 module 수준의 build.gradle 파일에서 머티리얼 디자인 패키지를 implementation 해줍니다.
build.gradle
// ...
dependencies {
// ...
implementation 'com.google.android.material:material:1.2.0-alpha01'
// ...
}
// ...
다음으로 AppBarLayout을 적용하기에 앞서 우리는 ActionBar를 사용하지 않을 것이기 때문에 Theme를 NoActionBar로 변경해주어야 합니다. 따라서 values 패키지 안의 styles.xml에서 다음과 같이 변경해주거나 androidmanifest.xml 파일에서 theme를 변경해주시면 되겠습니다.
참고로 저는 MaterialCompnonts 패키지의 NoActionBar로 설정했지만 이외에도 다양한 NoActionBar가 있으니 취향에 맞게 선택하시면 되겠습니다.
values/styles.xml
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
설정해주셨다면, 다음으로 액티비티의 레이아웃을 정의해줍니다.
layout/activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/app_toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:title="@string/app_name"
app:titleTextColor="@android:color/white"
app:layout_scrollFlags="scroll|enterAlways"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bible_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
그리고 values 패키지에 strings.xml 파일을 통해 app_name과 appbar_scrolling_view_behavior 값을 정의해줍니다.
values/strings.xml
<resources>
<string name="app_name">Ready Story</string>
<string name="appbar_scrolling_view_behavior" translatable="false">
com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior
</string>
</resources>
activity_main.xml 코드를 먼저 살펴보자면 가장 상위 레이아웃을 CoordinatorLayout으로 선언하고, 그 아래 AppBarLayout과 RecyclerView를 같은 수준의 형제 뷰(sibling)로 선언합니다.
그리고 AppBarLayout 안에는 ToolBar를 선언해줍니다.
이제 눈여겨보실 것은 ToolBar의 app:layout_scrollFlags 입니다.
scrollFlags는 아래와 같은 동작을 설정할 수 있습니다.
scroll | 스크롤 이벤트와 직접 연관되어 스크롤 됩니다. 스크롤 이벤트에 반응할 모든 뷰에 설정해줘야 합니다. |
snap | 스크롤이 종료될 때 View가 부분적으로만 표시되면, View가 스냅되어 가장 가까운 가장자리로 스크롤됩니다. |
enterAlways | 아래로 스크롤 할 때마다 이 View가 아래로 보여지게 됩니다. |
enterAlwaysCollapsed | enterAlways 속성과 비슷하지만 스크롤 뷰가 맨 위에 도달했을 때 전체뷰가 보여지게 됩니다. |
exitUntilCollapsed | 스크롤을 아래, 위로 이동 시 View의 minHeight 만큼만 보여지고 스크롤이 최상단에 도착 시 나머지 View의 전체가 보여지게 됩니다. |
리사이클러뷰는 간단하게 TextView에 1~50까지 숫자 리스트를 통해 스크롤을 동작하는 용도로만 작성해보겠습니다.
layout/item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
이제 MainActivity 코드를 작성하겠습니다.
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ToolBar를 ActionBar로 설정해줘야 합니다.
setSupportActionBar(app_toolbar)
// 리사이클러뷰의 데이터는 1~50 숫자로 설정합니다.
val dataSet = (1..50).toList()
bible_recycler_view.adapter = SampleAdapter(dataSet)
}
}
SampleRecyclerAdapter.kt
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
class SampleAdapter(
private val dataSet: List<Int>
) : RecyclerView.Adapter<SampleAdapter.NumberViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NumberViewHolder {
val layoutView: LinearLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item, parent, false) as LinearLayout
return NumberViewHolder(layoutView)
}
override fun getItemCount(): Int = dataSet.size
override fun onBindViewHolder(holder: NumberViewHolder, position: Int) {
if(position < dataSet.size) {
holder.number.text = dataSet[position].toString()
}
}
inner class NumberViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val number = itemView.findViewById(R.id.number) as TextView
}
}
결과
enterAlways 속성의 경우 보시다시피 스크롤을 위로 올림과 동시에 축소되었던 ToolBar가 함께 확대됩니다.
enterAlwaysCollapsed 속성은 스크롤을 내릴 때 축소된 ToolBar가 스크롤이 가장 최상단에 도달했을 때에서야 다시 확대됩니다.
이 포스트에서 다루어진 코드는 Github 저장소 에서 확인할 수 있습니다.
'Android > Basic' 카테고리의 다른 글
[Android] WebView Scroll 다루기 - page Up/Down, OverScrollMode (0) | 2020.04.26 |
---|---|
[Android] 액티비티 옆으로 전환하기(feat. Transition) (2) | 2019.11.29 |
[Android] 디바이스 화면 가로, 세로 길이 구하기 (0) | 2019.10.19 |
[Android] 화면 회전(Screen Rotation) 방지하기 (0) | 2019.10.17 |
[Android] Activity, Service 여러 번 호출시 인스턴스는? (0) | 2019.08.19 |