코딩 (독학)/★ Kotlin

[CH.★] Android Studio Kotlin을 이용한 화면 Fragment에 대하여

짱득이 2022. 1. 24. 16:12
300x250
반응형

* 시작에 앞서

개인 프로젝트, 코딩 과정을 상세하게 기록하고 리뷰하기 위해 작성된 블로그입니다.
공부와 함께 진행되는 스파르타식 프로젝트이므로, 부족한 부분이 많을 수 있습니다.
Feedback은 언제나 환영입니다.

본 포스트는 Android Studio 환경에서 Kotlin 으로 작성된 Fragment 구성에 대한 포스트입니다.

Android Developer Guide의 권장사항은 다수의 화면일 때, New Activity가 아닌 Fragment 사용을 권장한다.


* Fragment ?

Fragment는 Activity 내에서 UI의 일부분을 나타내는 요소로, 한 화면에 여러개의 화면을 보여주기 위해 많이 사용된다.

 

물론 여러개의 Activity를 생성, 사용해서 화면을 보여 줄 수 있지만, Android Guide에서는 일반적인 카K오톡, F이스북, 인★그램 등과 같은 하단 탐색 바가 있는 전역적인 요소를 사용하면서 네비게이션 선택에 따라 컨텐츠가 보이는 부분은 프래그먼트로 사용하는 것을 권장 하고 있다.

출처 : https://developer.android.com/guide/fragments?hl=ko

※ 화면이 하나만 필요한 경우는 프래그먼트를 사용하지 않고, 다수의 화면이 필요할 때 사용하길 권장

* Fragment 요소

Fragment

  • Activity 내에서 UI의 일부분을 나타내는 요소, Activity의 부분 집합이 될 수 있음.

Fragment Manager

  • Activity의 FarmeLayout에 Fragment를 등록, 교체, 삭제 등의 기능을 포함한 클래스
  • Fragment 추가, 교체, 삭제 등의 변경 사항은 FragmentTransaction이라는 객체에서 등록 후 Commit()으로 실행
  • FragmentTransaction 객체는 supportFragmentManager.beginTransaction() 함수의 반환 값.

FragmentLayout

  • Activity 내에서 화면 전환이 필요한 구역의 컨테이너로 사용

supportFragmentManager.beginTransaction()

  • Fragment를 Manage 할 수 있는(Fragment를 교체할 수 있고, 관리할 수 있는)
  • beginTransaction()은 트랜잭션을 begin (시작) 하는
  • => Fragment Transaction을 begin 하되, Manage 할 수 있는

* Fragment 구현하기

  • FrameLayout으로 구현하되, SWITCH와 REMOVE 버튼 구현, FragmentA와 B 구현
  • SWITCH 버튼을 누르면 FrameLayout에 FragmentA와 FragmentB가 교차되는 기능 구현
  • REMOVE 버튼을 누르면 FrameLayout의 Fragment 연결이 제거되는 기능 구현

* 프로젝트 생성

  • 프로젝트 명 : Fragment
  • 언어 및 환경: Kotlin (Android Studio)

* Fragment 구현하기 (ViewBinding 환경 설정)

모듈 단의 build.gradle에 아래 코드를 추가한다.

android {
    ...
    viewBinding {
        enabled=true
    }
    ...
    ...
}

 

코드를 추가하고, 아래와 같이 Sync Now를 클릭하여 Gradle 변경 사항을 적용한다. (필수)

ViewBinding은 Layout에 있는 View의 ID를 코틀린 코드에서 직접 사용할 수 있도록 해 주는 도구로, 기존의 Kotlin-android-extensions로 지원되었던 것을 대체한 기능이다.

※ kotlin-android-extensions와 ViewBinding의 차이
  * kotlin-android-extensions : View의 ID로 코드에서 직접적인 접근 (기존의 방법)
 - 코드에 사용 될 모든 View의 ID가 고유한 값을 가져야 함.
 - 따라서 코드가 지저분해 질 수 있음.
 - 중복된 ID를 사용하는 View 참조시 에러가 발생할 수 있음.

   * ViewBinding : XML 레이아웃 파일의 결합 클래스를 사용 (현재의 방법)
 - 유효하지 않은 View ID로 인한 Null 포인터 예외가 발생할 위험이 없음.
 - XML 레이아웃 전체를 클래스화 하기 때문에, 레이아웃과 코드의 비호환성 문제가 없음.

*Fragment 추가하기

 

 

 

위와 같은 과정으로 Fragment 2개 (Fragment_A / Fragment_B) 를 생성한다.

 

 

 

그러면 이와 같이 2개의 .kt 파일과 .xml 레이아웃 파일이 생성된다.

 

 

 

 

 

UI 만들기

각 레이아웃.xml에 아래와 같이 작성한다.

 

* activity_main.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="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="12dp"
        app:layout_constraintBottom_toTopOf="@+id/btnSwitch"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
         />

    <Button
        android:id="@+id/btnSwitch"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="Switch"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btnRemove"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        />

    <Button
        android:id="@+id/btnRemove"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="Remove"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btnSwitch"
        />


</androidx.constraintlayout.widget.ConstraintLayout>

 

* fragment_a.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    tools:context=".Fragment_A">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/teal_200"
        android:text="This is Fragment_A"
        android:textColor="#000000"
        android:textSize="36sp"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

* fragment_b.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".Fragment_A">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/purple_200"
        android:text="This is Fragment_B"
        android:textColor="#000000"
        android:textSize="36sp"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

여기까지 진행 된 UI 화면은 아래와 같다. (왼쪽부터 activity_main.xml / fragment_a.xml / fragment_b.xml 순)

생성된 UI 화면

💠 코틀린 코드 작성1 : Fragment 사용

MainActivity.kt의 FrameLayout에 FragmentA를 띄우기 위한 코드는 아래처럼 작성한다.

package com.jjangdeuk.fragment

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.jjangdeuk.fragment.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    lateinit var binding : ActivityMainBinding
		// 전역변수로 바인딩 객체 선언
        
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


		// 자동 생성된 View Binding Class에서의 inflate라는 Method를 활용해서
        // Activity에서 사용할 Binding Class의 인스턴스 생성
        binding = ActivityMainBinding.inflate(layoutInflater)

		// getRoot method로 레이아웃 내부의 최상위 위치 뷰의
        // 인스턴스를 활용하여 생성된 View를 액티비티에 표시함.
        val view = binding.root
        setContentView(view)
        //  setContentView(binding.root) 로 사용 가능

        binding.btnSwitch.setOnClickListener {
            setFragment()
        }
    }

				//supportFragmentManager 는 Fragment를 관리하는 녀석을 불러온 것
                // .beginTransaction()은 Transaction (작업)을 begin (시작) 하는 것.
    private fun setFragment() {
        val transaction = supportFragmentManager.beginTransaction()
            .add(R.id.frameLayout, Fragment_A())
            // frameLayout 부분에 Fragment_A 이름을 가진 view를 add하여 띄워주라는 액션
        transaction.commit()
        // 지금 실행한 transaction을 commit (저장) 하는 액션
    }

}

 

* lateint

 - 멤버변수는 ?를 사용해 선언하고 Null Check를 해 줘야 하는데, 해당 예약어를 사용하면 해당 변수는 ?로 선언되어 nullable 하다는 표시를 하지 않아도 된다.

 - 전역변수로 선언 후 null 값을 지정하지 않고 초기화 하는 방법으로, var 키워드를 사용해 선언해야 한다.

 - 접근/사용 전 변수가 초기화된다는 확신을 가지고 초기 선언/생성 시 초기화하지 않을 경우 사용한다.

 - 말 그대로 늦은(late) 선언(initialize)으로 해석할 수 있다.

* 참고한 글
https://blog.mindorks.com/learn-kotlin-lateinit-vs-lazy

 

 

* ActivityMainBinding 및 viewBinding, binding 등

https://timradder.tistory.com/20 를 참고하자. 도움이 많이 될 것이다.

 

<  현재 구현된 화면  >

 

💠 코틀린 코드 작성2 : 최종 코드

Fragment 처리를 위해 생성된 FragmentTransaction  객체에는 위 코드에서 사용한 add()  method 이외에 replace(), remove() 등의 method를 사용할 수 있다. 이 method들을 사용하여 MainActivity.kt의 기능을 덧 붙여보자.

 

package com.jjangdeuk.fragment

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.jjangdeuk.fragment.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    lateinit var binding : ActivityMainBinding
    // 전역변수로 바인딩 객체 선언

    var flag = 0
    /*
    - when flag is 0 : 초기 화면
    - when flag is 1 : Fragment_A 화면으로 replace
    - when flag is 2 : Fragment_B 화면으로 replace
     */

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

        // 자동 생성된 View Binding Class에서의 inflate라는 Method를 활용해서
        // Activity에서 사용할 Binding Class의 인스턴스 생성
        binding = ActivityMainBinding.inflate(layoutInflater)

        // getRoot method로 레이아웃 내부의 최상위 위치 뷰의
        // 인스턴스를 활용하여 생성된 View를 액티비티에 표시함.
        val view = binding.root
        setContentView(view)
    //  setContentView(binding.root) 로 사용 가능

        binding.btnSwitch.setOnClickListener {
            switchFragment()
            // binding 변수를 활용하여 xml 파일 내의 View id에 접근하였음.
            // btnSwitch = SWITCH 버튼.
            // View id도 pascalCase + camelCase의 Naming 규칙 적용으로 인해
            // btn_swtich 이거나 하면 btnSwtich 로 자동 변환되게 됨.
        }

        binding.btnRemove.setOnClickListener {
            removeFragment()
        }
    }

    // SWITCH 버튼에 대한 Fragment
    // 버튼을 누르면 FrameLayout 부분에 표시되는 Fragment가 A <--> B 간에 서로 전환됨
    private fun switchFragment() {
        val transaction = supportFragmentManager.beginTransaction()
        when(flag) {            //supportFragmentManager 는 Fragment를 관리하는 녀석을 불러온 것
            0 -> {              // .beginTransaction()은 Transaction (작업)을 begin (시작) 하는 것.
                transaction.add(R.id.frameLayout, Fragment_A())
                flag=1
            }
            1 -> {
                transaction.replace(R.id.frameLayout, Fragment_B())
                flag=2
            }
            2 -> {
                transaction.replace(R.id.frameLayout, Fragment_A())
                flag=1
            }
        }
        transaction.addToBackStack(null)
        transaction.commit()
    }

    // REMOVE 버튼에 대한 Fragment
    // 버튼을 누르면 FrameLayout 부분에 연결된 Fragment가 삭제됨.
    private fun removeFragment() {
        val transaction = supportFragmentManager.beginTransaction()
        val frameLayout=supportFragmentManager.findFragmentById(R.id.frameLayout)
        transaction.remove(frameLayout!!)
        transaction.commit()
    }
}

백스택 설정

transaction.addToBackStack(null)

 

Fragment는 기본적으로 backStack에 저장되지 않는다. backStack에 저장하고 싶은 경우 FragmentTransaction에 위와 같은 코드를 추가하여 뒤로가기 버튼 클릭 시 이전 화면을 볼 수 있도록 설정이 가능하다.

 

❓ backStack이 뭐야..?

backStack에 대한것이 궁금하다면...

 

여차저차 하여 위 코드대로 작성하면 결과는 아래와 같다.

  • Switch 버튼 : FrameLayout 단에 표시되는 Fragment A와 B가 서로 전환됨
  • Remove 버튼 : FrameLayout 단에 표시되는 Fragment가 삭제됨.

 

<  최종 구현된 화면  >

 

728x90
반응형