[Android/Flutter 교육] 47일차

MSU·2024년 3월 6일

Android-Flutter

목록 보기
49/85
post-thumbnail

게시판 프로젝트

ContentActivity

ContentActivity에서 보여줄 프래그먼트들의 이름 enum class 작성

// Tools.kt


// ContentActivity에서 보여줄 프래그먼트들의 이름
enum class ContentFragmentName(var str:String){
    A_FRAGMENT("a"),
    B_FRAGMENT("b"),
}

ContentActivity에서 프래그먼트 교체 코드 작성

replaceFragment메서드와 removeFragment메서드의 매개변수 name의 타입을 ContentFragmentName로 바꿔주기

fragmentTransaction.replace 메서드에 인자값을 받을 프래그먼트컨테이너뷰 id를 containerContent로 꼭 바꿔주기

// ContentActivity.kt

    // 프래그먼트 객체를 담을 변수
    var oldFragment:Fragment? = null
    var newFragment:Fragment? = null


    // 지정한 Fragment를 보여주는 메서드
    // name : 프래그먼트 이름
    // addToBackStack : BackStack에 포함 시킬 것인지
    // isAnimate : 애니메이션을 보여줄 것인지
    // data : 새로운 프래그먼트에 전달할 값이 담겨져 있는 Bundle 객체
    fun replaceFragment(name:ContentFragmentName, 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.fragmentContainerView, newFragment!!)

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

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

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

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

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

MainFragment

MainFragment 생성

ContentFragmentName 수정

// Tools.kt


// ContentActivity에서 보여줄 프래그먼트들의 이름
enum class ContentFragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    B_FRAGMENT("b"),
}

프래그먼트 교체 코드의 when절 수정

// ContentActivity.kt

        when(name){
            // 게시글 목록 화면
            ContentFragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            ContentFragmentName.B_FRAGMENT -> {
                
            }
        }

ContentActivity에서 MainFragment를 띄워준다.

// ContentActivity.kt


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityContentBinding = ActivityContentBinding.inflate(layoutInflater)
        setContentView(activityContentBinding.root)

        settingNavigationView()

        // MainFragment가 나타나도록 한다.
        replaceFragment(ContentFragmentName.MAIN_FRAGMENT, false, false, null)
    }

fragment_main.xml 작성

CoordinatorLayout이 같이 배치된다.

SearchBar와 SearchView를 같이 사용한다.

SearchBar를 클릭하면 SearchView가 나오게 한다.

SearchView의 layout_anchor 속성에 SerchBar의 아이디를 넣어 연결되게 해야 한다.

SearchBar 다음에 RecyclerView를 배치하면 CoordinatorLayout상에서 위치가 겹치기 때문에 margint_top 값을 넣어서 띄어준다.

<?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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:transitionGroup="true"
    tools:context=".fragment.MainFragment">

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

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.search.SearchBar
            android:id="@+id/searchBarMain"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

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

        <com.google.android.material.search.SearchView
            android:id="@+id/searchViewMain"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_anchor="@id/searchBarMain">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerViewMainSearch"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </com.google.android.material.search.SearchView>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</LinearLayout>

처음에는 SearchBar가 보이고

SearchBar를 클릭하면 SearchView가 나타난다.

뷰바인딩 및 툴바 셋팅

// MainFragment.kt


class MainFragment : Fragment() {

    lateinit var fragmentMainBinding: FragmentMainBinding
    lateinit var contentActivity: ContentActivity

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

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        settingToolbar()

        return fragmentMainBinding.root
    }

        // 툴바 설정
    fun settingToolbar(){
        fragmentMainBinding.apply {
            toolbarMain.apply {
                // 타이틀
                title = "전체 게시판"
                // 네비게이션
                setNavigationIcon(R.drawable.menu_24px)
                setNavigationOnClickListener {
                    // Drawer 메뉴가 나타나게 한다.
                    contentActivity.activityContentBinding.drawerLayoutContent.open()
                }
            }
        }
    }

}

RecyclerView 구성

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

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

    <TextView
        android:id="@+id/textViewRowMainNickName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="TextView" />
</LinearLayout>

두개의 리사이클러뷰 모두 동일한 row_main.xml 사용함

리사이클러뷰 어댑터 작성

  • 메인화면의 리사이클러뷰 어댑터
// MainFragment.kt


    // 메인 화면의 RecyclerView의 어댑터
    inner class MainRecyclerViewAdapter : RecyclerView.Adapter<MainRecyclerViewAdapter.MainViewHolder>(){
        inner class MainViewHolder(rowMainBinding: RowMainBinding) : RecyclerView.ViewHolder(rowMainBinding.root){
            val rowMainBinding:RowMainBinding

            init {
                this.rowMainBinding = rowMainBinding

                this.rowMainBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
            }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
            val rowMainBinding = RowMainBinding.inflate(layoutInflater)
            val mainViewHolder = MainViewHolder(rowMainBinding)
            return mainViewHolder
        }

        override fun getItemCount(): Int {
            return 100
        }

        override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
            holder.rowMainBinding.textViewRowMainSubject.text = "제목 $position"
            holder.rowMainBinding.textViewRowMainNickName.text = "닉네임 $position"
        }
    }
  • 검색화면의 리사이클러뷰 어댑터
// MainFragment.kt


    // 검색 화면의 RecyclerView의 어댑터
    inner class SearchRecyclerViewAdapter : RecyclerView.Adapter<SearchRecyclerViewAdapter.SearchViewHolder>(){
        inner class SearchViewHolder(rowMainBinding: RowMainBinding) : RecyclerView.ViewHolder(rowMainBinding.root){
            val rowMainBinding:RowMainBinding

            init {
                this.rowMainBinding = rowMainBinding

                this.rowMainBinding.root.layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
            }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
            val rowMainBinding = RowMainBinding.inflate(layoutInflater)
            val mainViewHolder = SearchViewHolder(rowMainBinding)
            return mainViewHolder
        }

        override fun getItemCount(): Int {
            return 100
        }

        override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
            holder.rowMainBinding.textViewRowMainSubject.text = "제목 $position"
            holder.rowMainBinding.textViewRowMainNickName.text = "닉네임 $position"
        }
    }

작성한 리사이클러뷰 어댑터 적용

// MainFragment.kt


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

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        settingToolbar()
        settingRecyclerViewMain()
        settingRecyclerViewMainSearch()

        return fragmentMainBinding.root
    }
    

    // 메인 화면의 RecyclerView 설정
    fun settingRecyclerViewMain(){
        fragmentMainBinding.apply {
            recyclerViewMain.apply {
                // 어댑터
                adapter = MainRecyclerViewAdapter()
                // 레이아웃 매니저
                layoutManager = LinearLayoutManager(contentActivity)
                // 데코레이션
                val deco = MaterialDividerItemDecoration(contentActivity, MaterialDividerItemDecoration.VERTICAL)
                addItemDecoration(deco)
            }
        }
    }

    // 검색 화면의 RecyclerView 설정
    fun settingRecyclerViewMainSearch(){
        fragmentMainBinding.apply {
            recyclerViewMainSearch.apply {
                // 어댑터
                adapter = SearchRecyclerViewAdapter()
                // 레이아웃 매니저
                layoutManager = LinearLayoutManager(contentActivity)
                // 데코레이션
                val deco = MaterialDividerItemDecoration(contentActivity, MaterialDividerItemDecoration.VERTICAL)
                addItemDecoration(deco)
            }
        }
    }

SearchBar, SearchView 설정

메뉴 xml 생성

<?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/menuItemMainSearchAdd"
        android:icon="@drawable/add_24px"
        android:title="글작성"
        app:showAsAction="always" />
</menu>

SearchBar, SearchView 설정할 메서드

// MainFragment.kt


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

        fragmentMainBinding = FragmentMainBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        settingToolbar()
        settingRecyclerViewMain()
        settingRecyclerViewMainSearch()
        settingSearchBar()

        return fragmentMainBinding.root
    }

    // SearchView 설정
    fun settingSearchBar(){
        fragmentMainBinding.apply {
            searchBarMain.apply {
                // SearchBar에 보여줄 메시지
                hint = "여기를 눌러 검색해주세요"
                // 메뉴
                inflateMenu(R.menu.menu_main_search_menu)
            }
            
            searchViewMain.apply { 
                // SearchView에 보여줄 메시지
                hint = "검색어를 입력해주세요"
            }
        }
    }

AddContentFragment

AddContentFragment 생성

ContentFragmentName 수정

// Tools.kt


// ContentActivity에서 보여줄 프래그먼트들의 이름
enum class ContentFragmentName(var str:String){
    MAIN_FRAGMENT("MainFragment"),
    ADD_CONTENT_FRAGMENT("AddContentFragment"),
}

프래그먼트 교체 코드의 when절 수정

// ContentActivity.kt

        when(name){
            // 게시글 목록 화면
            ContentFragmentName.MAIN_FRAGMENT -> {
                newFragment = MainFragment()
            }
            // 게시글 작성 화면
            ContentFragmentName.B_FRAGMENT -> {
                newFragment = AddContentFragment()
            }
        }

SearchBar에서 AddContentFragment 이동

// MainFragment.kt


    // SearchView 설정
    fun settingSearchBar(){
        fragmentMainBinding.apply {
            searchBarMain.apply {
                // SearchBar에 보여줄 메시지
                hint = "여기를 눌러 검색해주세요"
                // 메뉴
                inflateMenu(R.menu.menu_main_search_menu)
                setOnMenuItemClickListener {
                    // 글 작성 화면이 나타나게 한다.
                    contentActivity.replaceFragment(ContentFragmentName.ADD_CONTENT_FRAGMENT, true, true, null)
                    true
                }
            }
            
            searchViewMain.apply { 
                // SearchView에 보여줄 메시지
                hint = "검색어를 입력해주세요"
            }
        }
    }

fragment_add_content.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=".fragment.AddContentFragment">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbarAddContent"
        style="@style/Theme.AndroidProject4BoardApp.Toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            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"
                app:startIconDrawable="@drawable/subject_24px">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/textFieldAddContentSubject"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="text"
                    android:textAppearance="@style/TextAppearance.AppCompat.Large" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.button.MaterialButtonToggleGroup
                android:id="@+id/toggleAddContentType"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                app:selectionRequired="true"
                app:singleSelection="true">

                <Button
                    android:id="@+id/buttonAddContentType1"
                    style="@style/Widget.Material3.Button.OutlinedButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="자유"
                    android:textAppearance="@style/TextAppearance.AppCompat.Medium" />

                <Button
                    android:id="@+id/buttonAddContentType2"
                    style="@style/Widget.Material3.Button.OutlinedButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="유머"
                    android:textAppearance="@style/TextAppearance.AppCompat.Medium" />

                <Button
                    android:id="@+id/buttonAddContentType3"
                    style="@style/Widget.Material3.Button.OutlinedButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="시사"
                    android:textAppearance="@style/TextAppearance.AppCompat.Medium" />

                <Button
                    android:id="@+id/buttonAddContentType4"
                    style="@style/Widget.Material3.Button.OutlinedButton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="스포츠"
                    android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
            </com.google.android.material.button.MaterialButtonToggleGroup>

            <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"
                app:startIconDrawable="@drawable/description_24px">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/textFieldAddContentText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="text|textMultiLine"
                    android:textAppearance="@style/TextAppearance.AppCompat.Large" />
            </com.google.android.material.textfield.TextInputLayout>

            <ImageView
                android:id="@+id/imageViewAddContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:adjustViewBounds="true"
                app:srcCompat="@drawable/panorama_24px" />

        </LinearLayout>
    </ScrollView>

</LinearLayout>

이미지뷰의 adjustViewBounds 속성은 true로 할 시 이미지가 꽉차게 보이게 해준다.

<?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/menuItemAddContentCamera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:icon="@drawable/photo_camera_24px"
        android:title="카메라"
        app:showAsAction="always" />
    <item
        android:id="@+id/menuItemAddContentAlbum"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:icon="@drawable/photo_album_24px"
        android:title="앨범"
        app:showAsAction="always" />
    <item
        android:id="@+id/menuItemAddContentReset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:icon="@drawable/restart_alt_24px"
        android:title="초기화"
        app:showAsAction="always" />
    <item
        android:id="@+id/menuItemAddContentDone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:icon="@drawable/done_24px"
        android:title="완료"
        app:showAsAction="always" />
</menu>

뷰바인딩 및 툴바 셋팅

// AddContentFragment.kt



class AddContentFragment : Fragment() {

    lateinit var fragmentAddContentBinding: FragmentAddContentBinding
    lateinit var contentActivity: ContentActivity

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

        fragmentAddContentBinding = FragmentAddContentBinding.inflate(inflater)
        contentActivity = activity as ContentActivity

        settingToolbarAddContent()

        return fragmentAddContentBinding.root
    }
    
    // 툴바 셋팅
    fun settingToolbarAddContent(){
        fragmentAddContentBinding.apply {
            toolbarAddContent.apply {
                // 타이틀
                title = "글 작성"
                // Back
                setNavigationIcon(R.drawable.arrow_back_24px)
                setNavigationOnClickListener {
                    contentActivity.removeFragment(ContentFragmentName.ADD_CONTENT_FRAGMENT)
                }
                // 메뉴
                inflateMenu(R.menu.menu_add_content)
            }
        }
    }

}

profile
안드로이드공부

0개의 댓글