Android/Kotlin

<정리> 메모 앱 만들기 2일차

re트 2023. 12. 15. 19:41
728x90

2일차가 시작되었다.

이번에는 기능을 추가하거나 그런 것보다는 기존 기능을 보완하고 완성하는 쪽으로 진행했다.

 

1. 내비게이션 드로어 완성

디자인적으로나 기능적으로 완성이 아니라 UI 구성 측면에서 완성이 되었다.

어떻게 보면 이 UI 구성을 하기 위해서 안드로이드 스튜디오에서 주는 NavigationView를 쓰지 않고 따로 만들었다고 볼 수 있다.

저번에 헤더까지는 만들었었고 오늘은 바디와 푸터를 구성해서 넣어줬다.

바디는 ConstraintLayout안에 버튼 역할을 할 ImageView와 버튼 이름의 역할을 할 TextView가 6개 있고 아래에 큰 이미지 버튼을 생각하고 하나 넣어놨다.

ConstraintLayout의 높이는 wrap_content로 지정했다.

<!-- drawer_body_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">

    <!-- 화면 이동1 시작 -->
    <ImageView
        android:id="@+id/img_move_btn1"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginTop="32dp"
        app:layout_constraintEnd_toStartOf="@+id/img_move_btn2"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동1"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn1"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn1"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn1" />
    <!-- 화면 이동1 끝 -->

    <!-- 화면 이동2 시작 -->
    <ImageView
        android:id="@+id/img_move_btn2"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="@+id/img_move_btn1"
        app:layout_constraintEnd_toStartOf="@+id/img_move_btn3"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/img_move_btn1"
        app:layout_constraintTop_toTopOf="@+id/img_move_btn1"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동2"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn2"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn2"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn2" />
    <!-- 화면 이동2 끝 -->

    <!-- 화면 이동3 시작 -->
    <ImageView
        android:id="@+id/img_move_btn3"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="@+id/img_move_btn2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/img_move_btn2"
        app:layout_constraintTop_toTopOf="@+id/img_move_btn2"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동3"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn3"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn3"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn3" />
    <!-- 화면 이동3 끝 -->

    <!-- 화면 이동4 시작 -->
    <ImageView
        android:id="@+id/img_move_btn4"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginTop="32dp"
        app:layout_constraintEnd_toStartOf="@+id/img_move_btn5"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_move1"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동4"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn4"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn4"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn4" />
    <!-- 화면 이동4 끝 -->

    <!-- 화면 이동5 시작 -->
    <ImageView
        android:id="@+id/img_move_btn5"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="@+id/img_move_btn4"
        app:layout_constraintEnd_toStartOf="@+id/img_move_btn6"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/img_move_btn4"
        app:layout_constraintTop_toTopOf="@+id/img_move_btn4"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동5"
        app:layout_constraintBottom_toBottomOf="@+id/tv_move4"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn5"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn5"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn5" />
    <!-- 화면 이동5 끝 -->

    <!-- 화면 이동6 시작 -->
    <ImageView
        android:id="@+id/img_move_btn6"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="@+id/img_move_btn5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/img_move_btn5"
        app:layout_constraintTop_toTopOf="@+id/img_move_btn5"
        app:srcCompat="@drawable/ic_category" />

    <TextView
        android:id="@+id/tv_move6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="화면이동6"
        app:layout_constraintEnd_toEndOf="@+id/img_move_btn6"
        app:layout_constraintStart_toStartOf="@+id/img_move_btn6"
        app:layout_constraintTop_toBottomOf="@+id/img_move_btn6" />
    <!-- 화면 이동6 끝 -->

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="80dp"
        android:text="아주 좋은 기능"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintTop_toBottomOf="@+id/tv_move4"
        tools:layout_editor_absoluteX="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

이거 하나 넣었는데 엄청 길다...

 

다음은 푸터 레이아웃이다.

푸터도 마찬가지로 ConstraintLayout를 베이스로 했고 가장 위에 수평 경계선과 클릭이 가능하도록 설정한 TextView가 들어있다.

ConstraintLayout의 높이는 wrap_content으로 지정했다.

<!-- drawer_footer_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
    
    <!-- 수평 경계선 -->
    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginHorizontal="20dp"
        android:background="#979797"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:gravity="center"
        android:text="휴지통"
        app:layout_constraintStart_toStartOf="@+id/view"
        app:layout_constraintTop_toBottomOf="@+id/view" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

그 다음 메인 액티비티의 레이아웃에 가서 두개의 레이아웃을 include로 합쳤는데 구상했던 형태가 안 되더라

이건 LinearLayout의 한계로 인정하고 RelativLayout으로 변경했다.

변경하고 레이아웃의 관계를 설정하려는데 빨간줄이 계속 떴다.

이게 뭔가 하고 찾아보니까 include한 레이아웃의 너비와 높이를 지정해줘야 관계를 설정할 수 있다는 거였다.

그래서 지정하고 설정했더니 원하는 형태로 구성되었다.

<!-- activity_main.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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- Toolbar 레이아웃 가져오기 -->
        <include layout="@layout/toolbar_layout" />

        <!-- 수평 경계선 -->
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#979797" />
    </LinearLayout>

    <RelativeLayout
        android:id="@+id/navigation_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="@color/white"
        android:orientation="vertical"
        android:fitsSystemWindows="true">

        <include layout="@layout/drawer_header_layout"
            android:id="@+id/drawer_header"
            android:layout_alignParentTop="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <include layout="@layout/drawer_body_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/drawer_header"/>
        <include layout="@layout/drawer_footer_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>

지금까지가 한 개였고 나머지 하나도 만만치 않게 시간을 들여서 구현했다.

 

2. 툴바 검색창 구현

아~~ 정말 여러가지로 찾고 해보고 수정하고를 반복하면서 결과를 냈다.

물론 이게 맞는 방법인지, 최적의 방법인지는 모르겠지만 먼저는 구현한 것에 큰 의의가 있다고 생각한다.

 

먼저는 툴바 레이아웃에서 EditText를 하나 만들고 밑줄을 없애고 힌트를 넣고 visibility를 gone으로 뒀다.

gone으로 한 것은 그래야지 처음 상태에서 액션바에 제목이 뜨기 때문이다.

<!-- toolbar_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/white"
    app:titleTextColor="@color/black">
    <!-- Toolbar 내용은 대부분 코드 상에서 채움 -->

    <EditText
        android:id="@+id/edit_toolbar_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent"
        android:hint="메모 검색"
        android:visibility="gone" />
</androidx.appcompat.widget.Toolbar>

 

이후부터는 모두 메인 액티비티 코드 상에서 진행되었다.

이게 다 기능들이 연결되다보니까 나눠서 설명하기가 참 어려운데...

내가 하고 싶은건 액션바의 검색 버튼을 눌렀을 때 검색창이 뜨는 거다.

물론 그것만은 아니고 메뉴들이 사라지거나 변경되는 것까지도 원했다.

그래서 어쨌든 검색 버튼을 눌렀다는 걸 확인을 해야하니까 onOptionsItemSelected에서 검색 버튼을 추가했다.

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        // 홈 버튼(내비게이션 메뉴 버튼)
        android.R.id.home -> {
            if (isClicked) {
                changeToolbarStatus(false, R.drawable.ic_menu)
            } else {
                drawerLayout.openDrawer(GravityCompat.START)
            }
        }
        // 검색 버튼
        R.id.btn_search -> {
            changeToolbarStatus(true, R.drawable.ic_btn_back)
        }
        // BottomSheet 버튼
        R.id.btn_more -> {
            val bottomSheet = BottomSheet()
            bottomSheet.show(supportFragmentManager, bottomSheet.tag)
        }
    }

    return super.onOptionsItemSelected(item)
}

 

그리고 여기서부터가 오늘의 고비였다.

도대체 어떻게 저 검색 버튼과 옵션 버튼을 사라지게 할 것인가...

진짜 뭐... 찾아도 잘 안 나오고 해보는 것마다 그냥 에뮬레이터는 무시하고 스트레스가 마구마구 올라가는 시점에 onPrepareOptionMenu()와 invalidateOptionsMenu()이라는 걸 알게 된다.

이게 뭐냐하고 이리 저리 만져보는데 invalidateOptionsMenu()를 호출할 때마다 onPrepareOptionMenu()가 호출되는 걸 로그를 통해서 알 수 있었다.

그리고 메뉴에 속하는 검색 버튼과 옵션 버튼을 onPrepareOptionsMenu() 안에서 접근해서 속성을 변경할 수 있다는 것도, 그것이 바로 반영된다는 것도 알았다..!!

이제 거의 다 마무리다.(글로 쓰니까 몇 줄인데 진짜 여기까지 오는데 1~2시간은 걸린 거 같다.)

다시 약간 돌아가서 나는 isClicked라는 변수를 둬서 검색버튼이 눌렸는지 확인하도록 했다.

그걸 이제 변경하면서 툴바의 변화가 실시간으로 보여지게 했다.

onPrepareOptionsMenu()에서는 툴바에서 gone 해놨던 EditText를 띄우고 메뉴들을 지웠다.

override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
    val editToolbarSearch: EditText = findViewById(R.id.edit_toolbar_search)
    
    if (isClicked) {
        menu?.get(0)?.isVisible = false
        menu?.get(1)?.isVisible = false
        editToolbarSearch.visibility = View.VISIBLE
    } else {
        menu?.get(0)?.isVisible = true
        menu?.get(1)?.isVisible = true
        editToolbarSearch.visibility = View.GONE
    }
    
    return super.onPrepareOptionsMenu(menu)
}

 

그리고 네비게이션 드로어를 여는 버튼이었던 좌측 상단 버튼을 함수를 하나 만들어 변경하도록 했다.(여기서 isClicked 변수도 변경된다.)

이 함수가 호출되며 onPrepareOptionsMenu()가 호출된다.

fun changeToolbarStatus(flag: Boolean, btnImg: Int) {
    isClicked = flag
    supportActionBar?.setHomeAsUpIndicator(btnImg)
    invalidateOptionsMenu()
}

 

이렇게 하고 나서 끝인가 했지만 아직 하나가 남아있다. 그건 바로 뒤로가기 버튼 처리다.

지금 여기서 마무리를 짓는다면 검색 버튼을 눌러 검색창을 띄운 상태에서 뒤로가기 버튼을 누르면 앱이 종료된다.

내가 원하는 것은 뒤로가기 버튼을 누르면 초기 툴바 상태로 가는 것이다.

그 처리는 이전에 만들어놨던 콜백함수에서 조건을 추가해 해결했다.

private val callback = object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        // 네비게이션뷰가 열려있다면
        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.closeDrawer(GravityCompat.START)
        } else if (isClicked) { // 검색창이 노출되어 있다면
            changeToolbarStatus(false, R.drawable.ic_menu)
        } else {
            finish()
        }
    }
}

 

3. 네비게이션 드로어 슬라이드 제한

나는 버튼을 눌러서 네비게이션 드로어가 열리고 네비게이션 드로어의 빈 공간을 눌러서 네비게이션 드로어가 닫히길 원했다.

이거는 간단하게 두개의 코드만 추가했더니 됐다.

drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN)
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)

 

 

이렇게 세가지를 하면서 느낀건 1일차에 들인 시간보다 2일차에 시간을 더 쓴 것 같다는 거다.

역시 안 해본 걸 하면 찾아보고 실험해보느라 시간이 더 들어간다.

한 마디로 다 해본 걸로 만들면 걸리는 시간이 줄어든다는 것과 동일하다.

아직 갈길이 멀었다.

이건 한... 몇일차까지 가려나 감이 안 오기는 한다.

그래도 마무리 내는 거까지 달려봐야겠다.

 

<결과화면>

반응형