[Android/Flutter 교육] 43일차

MSU·2024년 2월 28일

Android-Flutter

목록 보기
45/85
post-thumbnail

화면 기획에서 입력받는 부분은 제일 마지막에 진행한다.
만약 입력받는 부분을 먼저 기획하는 경우 나중에 다시 수정할 수 있으므로 번거로워짐
화면 UI 요소 배치를 가장 먼저 구현
화면 UI 요소에 대한 기능을 그다음에 구현

기획이 완료된 후

  • 안드로이드 프로젝트 생성
  • ViewBinding 셋팅
  • 화면부터 만들어준다.(UI 요소 배치, 네비게이션)
  • 각 화면내에서 화면 요소들과 관련된 기능 구현
  • 내부 기능 구현(데이터는 임의로 적용)

메모 프로젝트 1일차

초기 셋팅

  • ViewBinding 셋팅
  • activity_main.xml 작업
  • Tools.kt에서 프래그먼트 이름 enum class 작성
class Tools {
}

// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    A_FRAGMENT("A"),
    B_FRAGMENT("B"),
}

MainFragment

  • MainActivity에서 프래그먼트 교체 코드 작성
    // 지정한 Fragment를 보여주는 메서드
    // name : 프래그먼트 이름
    // addToBackStack : BackStack에 포함 시킬 것인지
    // isAnimate : 애니메이션을 보여줄 것인지
    // data : 새로운 프래그먼트에 전달할 값이 담겨져 있는 Bundle 객체
    fun replaceFragment(name:FragmentName, addToBackStack:Boolean, isAnimate:Boolean, data:Bundle?){

        // Fragment 전환에 딜레이를 조금 준다.
        SystemClock.sleep(200)

        // Fragment를 교체할 수 있는 객체를 추출
        val fragmentTransaction = supportFragmentManager.beginTransaction()
        // 새로운 Fragment를 담을 변수
        // var newFragment:Fragment? = null 이 코드는 지워주기

        // oldFragment에 newFragment가 가지고 있는 Fragment 객체를 담아준다.
        if(newFragment != null){
            oldFragment = newFragment
        }

        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){

        }

        // 새로운 Fragment에 전달할 Bundle 객체가 있다면 arguments 프로퍼티에 넣어준다.
        if(data != null){
            newFragment?.arguments = data
        }

        if(newFragment != null){
            // 애니메이션 설정
            if(isAnimate){
                // oldFragment -> newFragment
                // oldFragment : exit
                // newFragment : enter

                // newFragment -> oldFragment
                // oldFragment : reenter
                // newFragment : return

                // MaterialSharedAxis : 좌우, 위아래, 공중 바닥 사이로 이동하는 애니메이션 효과
                // X - 좌우
                // Y - 위아래
                // Z - 공중 바닥
                // 두 번째 매개변수 : 새로운 화면이 나타나는 것인지 여부를 설정
                // true : 새로운 화면이 나타나는 애니메이션이 동작한다.
                // false : 이전으로 되돌아가는 애니메이션이 동작한다.
                if(oldFragment != null){
                    // old에서 new가 새롭게 보여질 때 old의 애니메이션
                    oldFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
                    // new에서 old로 되돌아갈 때 old의 애니메이션
                    oldFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)

                    oldFragment?.enterTransition = null
                    oldFragment?.returnTransition = null
                }

                // old에서 new가 새롭게 보여질 때 new의 애니메이션
                newFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
                // new에서 old로 되돌아갈 때의 애니메이션
                newFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)

                newFragment?.exitTransition = null
                newFragment?.reenterTransition = null
            }

            // Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
            // 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
            // 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
            fragmentTransaction.replace(R.id.containerMain, newFragment!!)

            // addToBackStack 변수의 값이 true면 새롭게 보여질 Fragment를 BackStack에 포함시켜 준다.
            if(addToBackStack == true){
                // BackStack에 포함시킬 때 이름을 지정해주면 원하는 Fragment를 BackStack에서 제거할 수 있다.
                fragmentTransaction.addToBackStack(name.str)
            }

            // Fragment 교체를 확정한다.
            fragmentTransaction.commit()
        }
    }

    // BackStack에서 Fragment를 제거한다.
    fun removeFragment(name:FragmentName){
        // BackStack에 가장 위에 있는 Fragment를 BackStack에서 제거한다
        // supportFragmentManager.popBackStack()

        // Fragment 전환에 딜레이를 조금 준다.
        SystemClock.sleep(200)

        // 지정한 이름으로 있는 Fragment를 BackStack에서 제거한다.
        supportFragmentManager.popBackStack(name.str, FragmentManager.POP_BACK_STACK_INCLUSIVE)
    }
  • MainFragment 생성
class MainFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

}
  • FragmentName에 A_FRAGMENT를 MAIN_FRAGMENT로 수정
// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    B_FRAGMENT("B"),
}
  • MainActivity의 프래그먼트 교체 코드에서 프래그먼트 이름으로 분기하는 when절 수정
    fun replaceFragment(name:FragmentName, addToBackStack:Boolean, isAnimate:Boolean, data:Bundle?){

	......
    
        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 첫 화면 프래그먼트
            FragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            FragmentName.B_FRAGMENT -> {

            }
        }
  • MainFragment가 나타나도록 설정
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        // MainFragment가 나타나도록 한다.
        replaceFragment(FragmentName.MAIN_FRAGMENT, false, false, null)
    }
  • 실행해서 프래그먼트가 잘 나오는지 확인

  • fragment_main.xml 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:transitionGroup="true"
    tools:context=".MainFragment" >

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarMain"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/containerMain2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
  • MainFragment 뷰바인딩

class MainFragment : Fragment() {

    lateinit var fragmentMainBinding: FragmentMainBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        return fragmentMainBinding.root
    }

}

MainFragment의 툴바 셋팅

  • 구글폰트 사이트에서 툴바에 셋팅할 아이콘 구하기

https://fonts.google.com/icons

  • 메뉴 파일 추가

  • 툴바의 메뉴 아이템 셋팅

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/menuItemMainCalendar"
        android:icon="@drawable/calendar_month_24px"
        android:title="일별보기"
        app:showAsAction="always" />
    <item
        android:id="@+id/menuItemMainShowAll"
        android:icon="@drawable/clear_all_24px"
        android:title="전체보기"
        app:showAsAction="always" />
    <item
        android:id="@+id/menuItemMainAdd"
        android:icon="@drawable/add_24px"
        android:title="추가"
        app:showAsAction="always" />
</menu>
  • MainFragment에 툴바 셋팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()

        return fragmentMainBinding.root
    }

    // 툴바 구성
    fun settingToolbar(){
        fragmentMainBinding.apply {
            toolbarMain.apply {
                // 타이틀
                title = "메모"
                // 메뉴
                inflateMenu(R.menu.main_menu)
            }
        }
    }

MainFragment의 하위 Fragment 셋팅

  • MainFragment의 하위 Fragment이름 정의
// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    B_FRAGMENT("B"),
}

// MainFragment의 하위 Fragment의 이름
enum class MainSubFragmentName(var str:String){
    CALENDAR_FRAGMENT("CalendarFragment"),
    SHOW_ALL_FRAGMENT("ShowAllFragment")
}
  • 하위 프래그먼트 교체 코드 작성
    MainActivity.kt에서 작성한 교체 코드를 그대로 가져다 쓰고 FragmentName만 MainSubFragmentName으로 교체해주면 된다.
    supportFragmentManager는 mainActivity.supportFragmentManager로 교체한다.
    하위 프래그먼트를 담을 곳인 R.id.containerMain는 R.id.containerMain2로 교체한다.
class MainFragment : Fragment() {
......
    // 프래그먼트를 담을 프로퍼티
    var oldFragment:Fragment? = null
    var newFragment:Fragment? = null
......
    // 지정한 Fragment를 보여주는 메서드
    // name : 프래그먼트 이름
    // addToBackStack : BackStack에 포함 시킬 것인지
    // isAnimate : 애니메이션을 보여줄 것인지
    // data : 새로운 프래그먼트에 전달할 값이 담겨져 있는 Bundle 객체
    fun replaceFragment(name:MainSubFragmentName, addToBackStack:Boolean, isAnimate:Boolean, data:Bundle?){

        // Fragment 전환에 딜레이를 조금 준다.
        SystemClock.sleep(200)

        // Fragment를 교체할 수 있는 객체를 추출
        val fragmentTransaction = mainActivity.supportFragmentManager.beginTransaction()
        // 새로운 Fragment를 담을 변수
        // var newFragment:Fragment? = null 이 코드는 지워주기

        // oldFragment에 newFragment가 가지고 있는 Fragment 객체를 담아준다.
        if(newFragment != null){
            oldFragment = newFragment
        }

        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 일자별 메모 보기 화면
             MainSubFragmentName.CALENDAR_FRAGMENT -> {
                
            }
            // 전체 메모 보기 화면
            MainSubFragmentName.SHOW_ALL_FRAGMENT -> {

            }
        }

        // 새로운 Fragment에 전달할 Bundle 객체가 있다면 arguments 프로퍼티에 넣어준다.
        if(data != null){
            newFragment?.arguments = data
        }

        if(newFragment != null){
            // 애니메이션 설정
            if(isAnimate){
                // oldFragment -> newFragment
                // oldFragment : exit
                // newFragment : enter

                // newFragment -> oldFragment
                // oldFragment : reenter
                // newFragment : return

                // MaterialSharedAxis : 좌우, 위아래, 공중 바닥 사이로 이동하는 애니메이션 효과
                // X - 좌우
                // Y - 위아래
                // Z - 공중 바닥
                // 두 번째 매개변수 : 새로운 화면이 나타나는 것인지 여부를 설정
                // true : 새로운 화면이 나타나는 애니메이션이 동작한다.
                // false : 이전으로 되돌아가는 애니메이션이 동작한다.
                if(oldFragment != null){
                    // old에서 new가 새롭게 보여질 때 old의 애니메이션
                    oldFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
                    // new에서 old로 되돌아갈 때 old의 애니메이션
                    oldFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)

                    oldFragment?.enterTransition = null
                    oldFragment?.returnTransition = null
                }

                // old에서 new가 새롭게 보여질 때 new의 애니메이션
                newFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
                // new에서 old로 되돌아갈 때의 애니메이션
                newFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)

                newFragment?.exitTransition = null
                newFragment?.reenterTransition = null
            }

            // Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
            // 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
            // 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
            fragmentTransaction.replace(R.id.containerMain2, newFragment!!)

            // addToBackStack 변수의 값이 true면 새롭게 보여질 Fragment를 BackStack에 포함시켜 준다.
            if(addToBackStack == true){
                // BackStack에 포함시킬 때 이름을 지정해주면 원하는 Fragment를 BackStack에서 제거할 수 있다.
                fragmentTransaction.addToBackStack(name.str)
            }

            // Fragment 교체를 확정한다.
            fragmentTransaction.commit()
        }
    }

    // BackStack에서 Fragment를 제거한다.
    fun removeFragment(name:MainSubFragmentName){
        // BackStack에 가장 위에 있는 Fragment를 BackStack에서 제거한다
        // supportFragmentManager.popBackStack()

        // Fragment 전환에 딜레이를 조금 준다.
        SystemClock.sleep(200)

        // 지정한 이름으로 있는 Fragment를 BackStack에서 제거한다.
        mainActivity.supportFragmentManager.popBackStack(name.str, FragmentManager.POP_BACK_STACK_INCLUSIVE)
    }

CalendarFragment

표시할 메모는 제목만 표시한다.

  • CalendarFragment 생성

  • MainFragment의 프래그먼트 교체 when절에 CalendarFragment 객체를 담는 코드 작성

        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 일자별 메모 보기 화면
             MainSubFragmentName.CALENDAR_FRAGMENT -> {
                newFragment = CalendarFragment()
            }
            // 전체 메모 보기 화면
            MainSubFragmentName.SHOW_ALL_FRAGMENT -> {

            }
        }
  • MainFragment에서 먼저 일자별 화면이 나오도록 셋팅한다.
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()
        
        // 일자별 화면이 나오도록 한다.
        replaceFragment(MainSubFragmentName.CALENDAR_FRAGMENT, false, false, null)

        return fragmentMainBinding.root
    }

  • fragment_calendar.xml에서 뷰 작성
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    tools:context=".CalendarFragment" >

    <CalendarView
        android:id="@+id/calendarMain"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/buttonMainToday"
        style="@style/Widget.Material3.Button.IconButton.Outlined"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="오늘" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerMain"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp" />
</LinearLayout>

  • 리사이클러뷰 항목 레이아웃 파일 추가

<?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="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/textCalendarSubject"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout>
  • CalendarFragment 뷰바인딩 셋팅
class CalendarFragment : Fragment() {

    lateinit var fragmentCalendarBinding: FragmentCalendarBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentCalendarBinding = FragmentCalendarBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        return fragmentCalendarBinding.root
    }

}
  • CalendarFragment의 리사이클러뷰 어댑터 클래스 작성
    // RecyclerView의 어댑터
    inner class RecyclerMainAdapter : RecyclerView.Adapter<RecyclerMainAdapter.RecyclerMainViewHolder>(){

        inner class RecyclerMainViewHolder(rowCalendarBinding: RowCalendarBinding):RecyclerView.ViewHolder(rowCalendarBinding.root){
            val rowCalendarBinding:RowCalendarBinding

            init {
                this.rowCalendarBinding = rowCalendarBinding
                this.rowCalendarBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
            }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerMainViewHolder {
            val rowCalendarBinding = RowCalendarBinding.inflate(layoutInflater)
            val recyclerMainViewHolder = RecyclerMainViewHolder(rowCalendarBinding)
            return recyclerMainViewHolder
        }

        override fun getItemCount(): Int {
            return 10
        }

        override fun onBindViewHolder(holder: RecyclerMainViewHolder, position: Int) {
            holder.rowCalendarBinding.textCalendarSubject.text = "메모 : $position"
        }
    }
  • 작성한 리사이클러뷰 어댑터 셋팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentCalendarBinding = FragmentCalendarBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingRecyclerMain()

        return fragmentCalendarBinding.root
    }

    // RecyclerView 설정
    fun settingRecyclerMain(){
        fragmentCalendarBinding.apply {
            recyclerMain.apply {
                // 어댑터 설정
                adapter = RecyclerMainAdapter()
                // 레이아웃 매니저
                layoutManager = LinearLayoutManager(mainActivity)
                // 데코
                val deco = MaterialDividerItemDecoration(mainActivity,MaterialDividerItemDecoration.VERTICAL)
                addItemDecoration(deco)
            }
        }
    }

ShowAllFragment

  • ShowAllFragment 생성

  • MainFragment의 프래그먼트 교체 when절에 ShowAllFragment 객체를 담는 코드 작성

        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 일자별 메모 보기 화면
             MainSubFragmentName.CALENDAR_FRAGMENT -> {
                newFragment = CalendarFragment()
            }
            // 전체 메모 보기 화면
            MainSubFragmentName.SHOW_ALL_FRAGMENT -> {
                newFragment = ShowAllFragment()
            }
        }
  • MainFragment의 툴바셋팅 메서드에 메뉴 항목을 클릭하면 id로 분기하는 코드 작성
    // 툴바 구성
    fun settingToolbar(){
        fragmentMainBinding.apply {
            toolbarMain.apply {
                // 타이틀
                title = "메모"
                // 메뉴
                inflateMenu(R.menu.main_menu)
                // 메뉴의 항목을 눌렀을 때.
                setOnMenuItemClickListener {
                    // 메뉴의 항목 id로 분기한다.
                    when(it.itemId){
                        // 일자별 항목 보기 메뉴
                        R.id.menuItemMainCalendar -> {
                            replaceFragment(MainSubFragmentName.CALENDAR_FRAGMENT, false, false, null)
                        }
                        // 전체 메모 보기 메뉴
                        R.id.menuItemMainShowAll -> {
                            replaceFragment(MainSubFragmentName.SHOW_ALL_FRAGMENT, false, false, null)
                        }
                        // 추가
                        R.id.menuItemMainAdd -> {

                        }
                    }
                    true
                }
            }
        }
    }
  • 앱을 실행해서 메뉴 항목이 잘 눌러지는 지 확인

  • fragment_show_all.xml 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    tools:context=".ShowAllFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerShowAll"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

  • 리사이클러뷰의 항목으로 사용할 레이아웃 추가

<?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="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/textShowAllSubject"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />

    <TextView
        android:id="@+id/textShowAllWriteDate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />
</LinearLayout>
  • ShowAllFragment 뷰바인딩 셋팅
class ShowAllFragment : Fragment() {

    lateinit var fragmentShowAllBinding: FragmentShowAllBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentShowAllBinding = FragmentShowAllBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        return fragmentShowAllBinding.root
    }

}
  • ShowAllFragment 리사이클러뷰 어댑터 클래스 작성
    // RecyclerView의 어댑터
    inner class RecyclerShowAllAdapter : RecyclerView.Adapter<RecyclerShowAllAdapter.RecyclerShowAllViewHolder>(){

        inner class RecyclerShowAllViewHolder(rowShowAllBinding: RowShowAllBinding) : RecyclerView.ViewHolder(rowShowAllBinding.root){
            val rowShowAllBinding:RowShowAllBinding

            init {
                this.rowShowAllBinding = rowShowAllBinding
                this.rowShowAllBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
            }

        }

        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): RecyclerShowAllViewHolder {
            val rowShowAllBinding = RowShowAllBinding.inflate(layoutInflater)
            val recyclerShowAllViewHolder = RecyclerShowAllViewHolder(rowShowAllBinding)
            return recyclerShowAllViewHolder
        }

        override fun getItemCount(): Int {
            return 10
        }

        override fun onBindViewHolder(holder: RecyclerShowAllViewHolder, position: Int) {
            holder.rowShowAllBinding.textShowAllSubject.text = "메모 제목 : $position"
            holder.rowShowAllBinding.textShowAllWriteDate.text = "2024-02-28"
        }
    }
  • 작성한 리사이클러뷰 어댑터 셋팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentShowAllBinding = FragmentShowAllBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingRecyclerShowAll()

        return fragmentShowAllBinding.root
    }

	// RecyclerView 구성 메서드
    fun settingRecyclerShowAll(){
        fragmentShowAllBinding.apply {
            recyclerShowAll.apply {
                // 어댑터 설정
                adapter = RecyclerShowAllAdapter()
                // 레이아웃 매니저
                layoutManager = LinearLayoutManager(mainActivity)
                // 데코
                val deco = MaterialDividerItemDecoration(mainActivity,
                    MaterialDividerItemDecoration.VERTICAL)
                addItemDecoration(deco)
            }
        }
    }

MemoAddFragment

  • MemoAddFragment 생성

  • FragmentName에 B_FRAGMENT를 MEMO_ADD_FRAGMENT로 변경

// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    MEMO_ADD_FRAGMENT("MemoAddFragment"),
}
  • MainActivity의 프래그먼트 교체 when절에 MemoAddFragment 객체를 담는 코드 작성
        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 첫 화면 프래그먼트
            FragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            FragmentName.MEMO_ADD_FRAGMENT -> {
                newFragment = MemoAddFragment()
            }
        }
  • MainFragment의 툴바셋팅 메서드에 메뉴 항목을 클릭하면 id로 분기하는 코드 작성
                    // 메뉴의 항목 id로 분기한다.
                    when(it.itemId){
                        // 일자별 항목 보기 메뉴
                        R.id.menuItemMainCalendar -> {
                            replaceFragment(MainSubFragmentName.CALENDAR_FRAGMENT, false, false, null)
                        }
                        // 전체 메모 보기 메뉴
                        R.id.menuItemMainShowAll -> {
                            replaceFragment(MainSubFragmentName.SHOW_ALL_FRAGMENT, false, false, null)
                        }
                        // 추가
                        R.id.menuItemMainAdd -> {
                            // 메모를 추가하는 화면이 보이게 한다.
                            mainActivity.replaceFragment(FragmentName.MEMO_ADD_FRAGMENT, true, true, null)
                        }
                    }
  • 앱을 실행해서 메뉴 항목이 잘 눌러지는 지 확인

  • fragment_add_memo.xml 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:transitionGroup="true"
    tools:context=".MemoAddFragment" >

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarMemoAdd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

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

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="메모 제목"
            app:endIconMode="clear_text">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoAddSubject"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:hint="메모 내용"
            app:endIconMode="clear_text">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoAddText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text|textMultiLine" />
        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>
</LinearLayout>

  • MemoAddFragment 뷰바인딩 셋팅
class MemoAddFragment : Fragment() {

    lateinit var fragmentMemoAddBinding: FragmentMemoAddBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoAddBinding = FragmentMemoAddBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        return fragmentMemoAddBinding.root
    }

}

MemoAddFragment 툴바 셋팅

  • done, back 아이콘 추가

  • MemoAddFragment 툴바 메뉴 셋팅

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/menuItemMemoAddDone"
        android:icon="@drawable/done_24px"
        android:title="완료"
        app:showAsAction="always" />
</menu>
  • MemoAddFragment에 툴바 세팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoAddBinding = FragmentMemoAddBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()

        return fragmentMemoAddBinding.root
    }

    // 툴바 설정 메서드
    fun settingToolbar(){
        fragmentMemoAddBinding.apply {
            toolbarMemoAdd.apply {
                // 타이틀
                title = "메모 추가"
                // Back
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    // 현재 Fragment를 BackStack에서 제거하여 이전 화면이 보이게 한다.
                    mainActivity.removeFragment(FragmentName.MEMO_ADD_FRAGMENT)
                }
                // 메뉴
                inflateMenu(R.menu.memo_add_menu)
            }
        }
    }

MemoReadFragment

  • MemoReadFragment 생성

  • FragmentName에 MEMO_READ_FRAGMENT 추가

// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    MEMO_ADD_FRAGMENT("MemoAddFragment"),
    MEMO_READ_FRAGMENT("MemoReadFragment"),
}
  • MainActivity의 프래그먼트 교체 when절에 MemoReadFragment 객체를 담는 코드 작성
        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 첫 화면 프래그먼트
            FragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            FragmentName.MEMO_ADD_FRAGMENT -> {
                newFragment = MemoAddFragment()
            }
            FragmentName.MEMO_READ_FRAGMENT -> {
                newFragment = MemoReadFragment()
            }
        }
  • CalendarFragment에서 리사이클러뷰 항목을 누르면 동작하는 리스너 작성
        override fun onBindViewHolder(holder: RecyclerMainViewHolder, position: Int) {
            holder.rowCalendarBinding.textCalendarSubject.text = "메모 : $position"

            // 항목을 누르면 동작하는 리스너
            holder.rowCalendarBinding.root.setOnClickListener {
                // 메모를 보는 화면이 나타나게 한다.
                mainActivity.replaceFragment(FragmentName.MEMO_READ_FRAGMENT, true, true, null)
            }
        }
  • ShowAllFragment에서 리사이클러뷰 항목을 누르면 동작하는 리스너 작성
        override fun onBindViewHolder(holder: RecyclerShowAllViewHolder, position: Int) {
            holder.rowShowAllBinding.textShowAllSubject.text = "메모 제목 : $position"
            holder.rowShowAllBinding.textShowAllWriteDate.text = "2024-02-28"

            // 항목을 누르면 동작하는 리스너
            holder.rowShowAllBinding.root.setOnClickListener {
                // 메모를 보는 화면이 나타나게 한다.
                mainActivity.replaceFragment(FragmentName.MEMO_READ_FRAGMENT, true, true, null)
            }
        }
  • 앱을 실행하여 리사이클러뷰 항목을 눌러서 MemoReadFragment로 이동하는지 확인하기

  • fragment_memo_read.xml 작성
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:transitionGroup="true"
    tools:context=".MemoReadFragment" >

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarMemoRead"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

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

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="제목">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoReadSubject"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:editable="false"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:hint="작성날짜">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoReadDate"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:editable="false"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:hint="내용">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoReadText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:editable="false"
                android:inputType="text|textMultiLine" />
        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>

</LinearLayout>

TextInput 속성으로 enabled를 false로 해주면 비활성화처리가 되지만 내용물이 회색으로 연하게 출력된다.

따라서 enabled 속성 대신 editable 속성을 false로 처리해주면 비활성화와 함께 내용물이 제대로 출력된다.

  • 뷰바인딩 셋팅
class MemoReadFragment : Fragment() {

    lateinit var fragmentMemoReadBinding: FragmentMemoReadBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoReadBinding = FragmentMemoReadBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        return fragmentMemoReadBinding.root
    }

}

MemoReadFragment 툴바 셋팅

  • delete, edit 아이콘 추가

  • MemoReadFragment 툴바 메뉴 셋팅

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/memuItemMemoReadModify"
        android:icon="@drawable/edit_24px"
        android:title="수정"
        app:showAsAction="always" />
    <item
        android:id="@+id/memuItemMemoReadDelete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:icon="@drawable/delete_24px"
        android:title="삭제"
        app:showAsAction="always" />
</menu>
  • MemoReadFragment에 툴바 세팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoReadBinding = FragmentMemoReadBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()

        return fragmentMemoReadBinding.root
    }

    // 툴바 설정
    fun settingToolbar(){
        fragmentMemoReadBinding.apply {
            toolbarMemoRead.apply {
                // 타이틀
                title = "메모 보기"
                // Back
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    mainActivity.removeFragment(FragmentName.MEMO_READ_FRAGMENT)
                }
                // 메뉴
                inflateMenu(R.menu.memo_read_menu)
            }
        }
    }

MemoReadFragment 내용 셋팅

  • 임의로 각각의 텍스트필드에 출력할 내용을 미리 셋팅해준다.
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoReadBinding = FragmentMemoReadBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()
        settingTextField()

        return fragmentMemoReadBinding.root
    }

    // textField의 내용을 설정해준다.
    fun settingTextField(){
        fragmentMemoReadBinding.textFieldMemoReadSubject.setText("제목입니다")
        fragmentMemoReadBinding.textFieldMemoReadText.setText("내용입니다")
        fragmentMemoReadBinding.textFieldMemoReadDate.setText("2024-10-10")
    }

MemoModifyFragment

  • MemoModifyFragment 생성

  • FragmentName에 MEMO_MODIFY_FRAGMENT 추가

// 프래그먼트들의 이름
enum class FragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    MEMO_ADD_FRAGMENT("MemoAddFragment"),
    MEMO_READ_FRAGMENT("MemoReadFragment"),
    MEMO_MODIFY_FRAGMENT("MemoModifyFragment"),
}
  • MainActivity의 프래그먼트 교체 when절에 MemoModifyFragment 객체를 담는 코드 작성
        // 이름으로 분기한다.
        // Fragment의 객체를 생성하여 변수에 담아준다.
        when(name){
            // 첫 화면 프래그먼트
            FragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            // 메모 추가 화면
            FragmentName.MEMO_ADD_FRAGMENT -> {
                newFragment = MemoAddFragment()
            }
            // 메모 읽기 화면
            FragmentName.MEMO_READ_FRAGMENT -> {
                newFragment = MemoReadFragment()
            }
            // 메모 수정 화면
            FragmentName.MEMO_MODIFY_FRAGMENT -> {
                newFragment = MemoModifyFragment()
            }
        }
  • MemoReadFragment에서 툴바 메뉴에 수정 선택 추가
    // 툴바 설정
    fun settingToolbar(){
        fragmentMemoReadBinding.apply {
            toolbarMemoRead.apply {
                // 타이틀
                title = "메모 보기"
                // Back
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    mainActivity.removeFragment(FragmentName.MEMO_READ_FRAGMENT)
                }
                // 메뉴
                inflateMenu(R.menu.memo_read_menu)
                setOnMenuItemClickListener {
                    when(it.itemId){
                        // 수정
                        R.id.memuItemMemoReadModify -> {
                            mainActivity.replaceFragment(FragmentName.MEMO_MODIFY_FRAGMENT, true, true, null)
                        }
                    }
                    true
                }
            }
        }
    }
  • 앱을 실행하여 MemoModifyFragment가 잘 실행되는지 확인

  • fragment_memo_modify.xml 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:transitionGroup="true"
    tools:context=".MemoModifyFragment" >

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarMemoModify"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

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

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="제목">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoModifySubject"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:hint="내용">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/textFieldMemoModifyText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="text|textMultiLine" />
        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>
</LinearLayout>

  • 뷰바인딩 셋팅
class MemoModifyFragment : Fragment() {

    lateinit var fragmentMemoModifyBinding: FragmentMemoModifyBinding
    lateinit var mainActivity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoModifyBinding = FragmentMemoModifyBinding.inflate(inflater)
        mainActivity = activity as MainActivity
        return fragmentMemoModifyBinding.root
    }

}

MemoModifyFragment 툴바 셋팅

  • MemoModifyFragment 툴바 메뉴 생성
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/menuItemMemoModifyDone"
        android:icon="@drawable/done_24px"
        android:title="완료"
        app:showAsAction="always" />
</menu>

  • MemoModifyFragment에 툴바 세팅
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoModifyBinding = FragmentMemoModifyBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()
        
        return fragmentMemoModifyBinding.root
    }

    // 툴바를 구성한다.
    fun settingToolbar(){
        fragmentMemoModifyBinding.apply {
            toolbarMemoModify.apply {
                // 타이틀
                title = "메모 수정"
                // Back
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    mainActivity.removeFragment(FragmentName.MEMO_MODIFY_FRAGMENT)
                }
                // 메뉴
                inflateMenu(R.menu.memo_modify_menu)
            }
        }
    }

MemoModifyFragment 내용 셋팅

  • 임의로 각각의 텍스트필드에 출력할 내용을 미리 셋팅해준다.
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        fragmentMemoModifyBinding = FragmentMemoModifyBinding.inflate(inflater)
        mainActivity = activity as MainActivity

        settingToolbar()
        settingTextField()

        return fragmentMemoModifyBinding.root
    }
    
    // TextField에 문자열을 설정한다.
    fun settingTextField(){
        fragmentMemoModifyBinding.apply {
            textFieldMemoModifySubject.setText("메모 제목")
            textFieldMemoModifyText.setText("메모 내용")
        }
    }

남은 작업 내용

  1. MainFragment의 Toolbar 에서 첫 번째 메뉴와 두 번째 메뉴가 서로 번갈아 가면서 노출되게 한다.
  2. CalendarFragment 에서 "오늘" 버튼을 누르면 오늘 날짜가 선택되게 한다.
        fragmentCalendarBinding.apply {
            buttonMainToday.setOnClickListener {
                calendarMain.setDate(Date().time,true,true)
            }
        }
  1. RecyclerView 항목을 누르면 배경으로 나타나는 효과를 넣어준다.
    항목xml 최상위 레이아웃에 background속성 설정
    android:background="?android:attr/selectableItemBackground"
  2. ShowAllFragment 에서 항목을 누르면 배경으로 나타나는 효과를 넣어준다.
  3. 다른 화면 갔다가 MainFragment로 돌아올 때 화면 상태가 유지되도록 한다.
  4. MemoAddFragment 가 보일 때 메모 제목에 포커스를 준다.
  5. MemoAddFragment 에서 완료 메뉴를 눌렀을 때 입력에 대한 검사 처리를 해줘야 한다.
  6. MemoAddFragment 에서 완료 메뉴를 누르면 MemoReadFragment로 이동되게 한다.
    8-2. MemoAddFragment의 툴바에 초기화 메뉴를 추가하고 이를 누르면 입력 요소들을 비운다.
  7. MemoReadFragment 에서 삭제 메뉴를 누르면 사용자에게 진짜 삭제할 것인지 물어보는 다이얼로그를 띄운다.
  8. 삭제 여부를 묻는 다이얼로그에서 삭제 버튼을 누르면 MainFragment로 이동되게 한다.
  9. MemoModifyFragment 에서 비어있는 입력 요소가 있을 경우를 대비한 검사 처리를 해줘야 한다.
  10. MemoModifyFragment 에서 완료 메뉴를 누르면 수정 확인을 묻는 다이얼로그를 띄워준다.
  11. 다이얼로그에서 완료 버튼을 누르면 수정이 되고 MemoReadFragment로 돌아간다.
  12. MemoModifyFragment 에 초기화 메뉴를 추가하고 메뉴를 누를 경우 원래의 메모 글로 초기화 시켜준다.



※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스 
profile
안드로이드공부

0개의 댓글