기획 및 디자인을 보면 화면마다 중복되는 부분이 있다.
바로, 툴바(Toolbar)다.
위 이미지의 빨간색 선 구역을 우린 보통 Toolbar, Appbar로 지칭하는데 이 부분은 대부분의 화면에 들어가기 때문에 이 부분을 기본적으로 구현하고 있는 Activity를 만들것이다. 그리고 이 툴바가 필요한 화면을 만들때는 이 툴바가 구현된 Activity를 상속받을 것이다.
여러 종류의 툴바 제공
사이드 네비게이션 제공
화면 전환 시 애니메이션 제공
위 3가지 기능을 제공하는 Activity를 상속받아 중복되는 코드 줄이는 것이 최종적인 이번 구현의 목적이다.
기본이 되는 화면의 UI 구조는 아래와 같다.
메뉴 버튼을 누르면 왼쪽에서 메뉴 화면이 슬라이드되면서 나온다. 이 화면이 필요없으면 DrawerLayout이랑 NavigationView는 제외하고 CoordinatorLayout부터 만들면 된다.
activity_base.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".component.BaseActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background"
app:elevation="0dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:collapsedTitleTextColor="@color/black"
app:contentScrim="@color/background"
app:expandedTitleTextColor="@color/white"
app:expandedTitleTextAppearance="@style/TextAppearance.Design.CollapsingToolbar.Expanded.Shadow"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/backgroundImageView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/background"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:visibility="gone" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:elevation="0dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetStart="0dp"
app:contentInsetEnd="0dp"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/activity_container"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/background"
android:fitsSystemWindows="true"
app:headerLayout="@layout/drawer_hearder"
app:menu="@menu/drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
각 코드에 주석 참고
BaseActivity.kt
// 툴바 타입
enum class ToolbarType {
MENU,
BACK,
HOME,
}
// 애니메이션 타입
enum class TransitionMode {
NONE,
HORIZON,
VERTICAL
}
open class BaseActivity(
private val toolbarType: ToolbarType,
private val transitionMode: TransitionMode = TransitionMode.NONE,
) : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
// activity_base의 dataBinding
protected lateinit var baseBinding: ActivityBaseBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
baseBinding = ActivityBaseBinding.inflate(layoutInflater)
setContentView(baseBinding.root)
setSupportActionBar(baseBinding.toolbar)
setToolbar()
// navigationView에 리스너 추가
baseBinding.navigationView.setNavigationItemSelectedListener(this)
// 애니메이션 모드에 따라 애니메이션 설정 (화면 나타날때)
when (transitionMode) {
TransitionMode.HORIZON -> overridePendingTransition(R.anim.horizon_enter, R.anim.none)
TransitionMode.VERTICAL -> overridePendingTransition(R.anim.vertical_enter, R.anim.none)
else -> {}
}
}
override fun finish() {
super.finish()
// 애니메이션 모드에 따라 애니메이션 설정 (화면 사라질 때)
when (transitionMode) {
TransitionMode.HORIZON -> overridePendingTransition(R.anim.none, R.anim.horizon_exit)
TransitionMode.VERTICAL -> overridePendingTransition(R.anim.none, R.anim.vertical_exit)
else -> {}
}
}
override fun onBackPressed() {
// navigationView가 나와있으면 onBackPressed()가 아닌 navigationView 닫기
if (baseBinding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
baseBinding.drawerLayout.closeDrawers()
} else {
super.onBackPressed()
if (isFinishing) {
when (transitionMode) {
TransitionMode.HORIZON -> overridePendingTransition(
R.anim.none,
R.anim.horizon_exit
)
TransitionMode.VERTICAL -> overridePendingTransition(
R.anim.none,
R.anim.vertical_exit
)
else -> Unit
}
}
}
}
override fun onDestroy() {
// navigationView 리스너 제거
baseBinding.navigationView.setNavigationItemSelectedListener(null)
super.onDestroy()
}
// Toolbar의 아이템 클릭 별 수행 코드
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_search -> {
actionMenuSearch()
}
R.id.action_favorite -> {
actionMenuFavorite()
}
R.id.action_home -> {
actionMenuHome()
}
android.R.id.home -> {
actionHome()
}
}
return super.onOptionsItemSelected(item)
}
// 툴바 타입에 따라 메뉴 아이템 변경
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
when (toolbarType) {
ToolbarType.MENU -> menuInflater.inflate(R.menu.menu_with_search, menu)
ToolbarType.BACK -> menuInflater.inflate(R.menu.back_with_search, menu)
ToolbarType.HOME -> menuInflater.inflate(R.menu.back_with_home, menu)
}
return true
}
private fun setToolbar() {
// 툴바 타입에 따라 왼쪽 상단 이미지 변경
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setBackgroundDrawable(
ColorDrawable(
ContextCompat.getColor(
applicationContext,
R.color.background
)
)
)
when (toolbarType) {
ToolbarType.MENU -> supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu)
ToolbarType.BACK -> supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_back)
ToolbarType.HOME -> supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_back)
}
}
// 상단에 이미지 띄우는게 필요할 때 사용 (상속받는 Activity에서 호출)
fun setImageView(url: String) {
if (url.isNotEmpty()) {
Glide.with(baseBinding.root)
.load(url)
.centerCrop()
.error(R.drawable.no_picture)
.into(baseBinding.backgroundImageView)
} else {
baseBinding.backgroundImageView.setImageResource(R.drawable.no_picture)
}
}
// 상단에 타이틀이 필요한 경우 사용 (상속받는 Activity에서 호출)
fun setTitle(title: String) {
baseBinding.toolbarLayout.title = title
baseBinding.backgroundImageView.isVisible = true
supportActionBar?.setBackgroundDrawable(
ColorDrawable(
ContextCompat.getColor(
applicationContext,
android.R.color.transparent
)
)
)
}
private fun actionMenuSearch() {
Toast.makeText(this, "검색하기", Toast.LENGTH_SHORT).show()
}
private fun actionMenuFavorite() {
Toast.makeText(this, "즐겨찾기", Toast.LENGTH_SHORT).show()
}
private fun actionMenuHome() {
Toast.makeText(this, "호오옴", Toast.LENGTH_SHORT).show()
}
private fun actionHome() {
Toast.makeText(this, "왼쪽 상단 버튼", Toast.LENGTH_SHORT).show()
}
// navigationView의 item 클릭 시 수행 코드
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menuItem1 -> Toast.makeText(this, "item1 is clicked", Toast.LENGTH_SHORT).show()
R.id.menuItem2 -> Toast.makeText(this, "item2 is clicked", Toast.LENGTH_SHORT).show()
R.id.menuItem3 -> Toast.makeText(this, "item3 is clicked", Toast.LENGTH_SHORT).show()
}
return false
}
}
이제 기본 Activity를 상속받는 Activity를 만들어보자.
activity_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/itemImageView"
android:layout_width="150dp"
android:layout_height="150dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"
android:background="@drawable/background_image" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ItemActivity.kt
// BaseActivity를 상속하고 인자로 툴바 타입과 애니메이션 방향 타입 전달
class ItemActivity: BaseActivity(ToolbarType.HOME, TransitionMode.HORIZON) {
private lateinit var binding: ActivityItemBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityItemBinding.inflate(layoutInflater)
// BaseActivity의 contentLayout (ScrollView)에 추가
baseBinding.contentLayout.addView(binding.root)
}
}
이후로 좀 더 개발이 진행된 상태다.