Android : Fragment

Beautify.log·2022년 2월 7일
0

Android with Kotlin

목록 보기
10/17
post-thumbnail

Fragment란?


웹에서 SPA라는 개념을 들어보셨을겁니다. Single Page Application의 약자인데, 페이지가 이동할 때마다 새로운 페이지를 불러오지 않고 현재의 페이지를 동적으로 다시 작성하는 기술을 말합니다.

Fragment도 이와 유사합니다. 단어의 의미는 조각이라는 뜻이지요.

예를 들어 상단에 버튼 세개가 그 아래에 뷰가 있다고 가정할 때, 누르는 버튼에 따라 각각 다른 페이지를 보여주는 것입니다.

우리가 자주 사용하는 카카오톡, 인스타그램 등 다양한 애플리케이션이 프레그먼트를 지원합니다.

그럼 우선 상단에 버튼을 주고 버튼의 조작에 따라 다른 뷰를 보여주는 앱을 구성해보겠습니다.


상단에 버튼을 주고 조작에 따라 다른 뷰 표시하기


Button 메뉴 만들어주기

우선 layout 디렉터리에 activity_fragment_menu.xml을 만들어주고 아래와 같이 버튼 세개를 작성해줍니다. 이 때 레이아웃은 LinearLayout임에 주의합시다.

<!-- activity_fragment_menu.xml -->

<?xml version="1.0" encoding="utf-8"?>
<!-- LinearLayout의 orientation은 default가 horizontal임 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent">


    <Button
            android:id="@+id/button1"
            android:text="First"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
    />
    <Button
            android:id="@+id/button2"
            android:text="Second"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
    />
    <Button
            android:id="@+id/button3"
            android:text="Third"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
    />
</LinearLayout>

각 뷰에 보여 줄 페이지 작성하기

버튼을 조작할 때마다 뷰에 보여줄 컴포넌트를 작성합니다.
버튼이 세개 있으므로 세개의 컴포넌트를 만들어주어야 합니다.

각각의 파일명은 activity_fragment_one처럼 뒤에 숫자가 영어로 붙는 방식으로 해주겠습니다.

activity_fragment_one.xml

<!-- activity_fragment_one.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ee9900"
>

    <TextView
            android:text="First Fragment"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_fragment_two.xml

<!-- activity_fragment_two.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#0099ff"
>

    <TextView
            android:text="Second Fragment"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_fragment_three.xml

<!-- activity_fragment_three.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#9900ff"
>

    <TextView
            android:text="Third Fragment"
            android:textColor="#ffffff"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml에서 FrameLayout 지정하기

버튼의 조작에 따라 다른 페이지를 보여줄 뷰를 설정해야 합니다. 이를 FrameLayout으로 지정할 것입니다.

그리고 상단에 위에서 만든 버튼 세개를 넣어주기 위해 <fragment>를 추가해주었습니다.

이 때 <fragment>의 속성에서 android:name에는 클래스를 기반으로 각 메뉴가 앱 초기화 시점에서 생성될 수 있도록 해줄 코틀린 패키지가 들어갑니다.

<!-- activity_main.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <fragment
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/fragmentMenu"
            android:name="com.example.sample23.FragmentMenu"
            tools:layout="@layout/activity_fragment_menu"
            tools:ignore="MissingConstraints"
    />

    <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="805dp"
            tools:ignore="MissingConstraints"
            android:layout_marginTop="20dp" app:layout_constraintTop_toBottomOf="@+id/fragmentMenu"
            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Kotlin 패키지 파일 작성

버튼을 앱 초기화시 생성해주기 위해 필요합니다.

FragmentMenu

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class FragmentMenu : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        
        return inflater.inflate(R.layout.activity_fragment_menu, container, false)
    }
}

이 함수는 MainActivityonCreate와 유사합니다.
activity_fragment_menu.xml에서 만든 버튼 세개를 뷰로 생성해주는 것입니다.

각각의 파라미터는 다음과 같습니다.

파라미터정의
inflater프레그먼트의 모든 뷰를 확장시킬 수 있는 LayoutInflater 객체
container프레그먼트의 UI를 연결할 상위 view
savedInstanceStatenull이 아닌 경우 프레그먼트는 이전에 저장된 상태에서 재생성됨.

FragmentOne ~ FragmentThree

각각은 별도의 파일이며 Fragment를 상속받아 만들어줍니다.

class FragmentOne : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.activity_fragment_fragment1, container, false)
    }
}

class FragmentTwo : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.activity_fragment_fragment2, container, false)
    }
}

class FragmentThree : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.activity_fragment_fragment3, container, false)
    }
}

MainActivity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment

class MainActivity : AppCompatActivity(), View.OnClickListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val fm = supportFragmentManager
        val fragmentTransaction = fm.beginTransaction()
        fragmentTransaction.add(R.id.content, FragmentOne())
        fragmentTransaction.commit()
    }

여기에서 supportFragmentManager를 사용합니다.

FragmentManager는 앱 프래그먼트에서 작업을 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스입니다.

여기에서 supportFragmentManagerFragmentManager에 접근하기 위한 도구입니다. 프래그먼트의 모든 요소를 제어할 수 있습니다.

beginTransaction()을 통해 트랜젝션하여 프래그먼트를 편집할 것임을 알려줍니다.

여기에 add를 사용하여 텍스트뷰로 작성한 내용을 FragmentOne이라는 객체에 넣어 앱을 처음 실행했을 때 보여줄 것입니다.

    // fragment_menu에서 사용할 onClick 멤버구현
    override fun onClick(v: View?) {
        Log.d("button", "clicked")

        var fr:Fragment? = null

        if(v?.id == R.id.button1) {
            fr = FragmentOne()
        } else if (v?.id == R.id.button2) {
            fr = FragmentTwo()
        } else if (v?.id == R.id.button3) {
            fr = FragmentThree()
        }

        val fm = supportFragmentManager
        val fragmentTransaction = fm.beginTransaction()

        // fragment 교체
        fragmentTransaction.replace(R.id.content, fr!!)
        fragmentTransaction.commit()
    }
}

각 뷰에는 onClick이 적용되어 있습니다. 이 함수는 View.OnClickListener를 클래스에 상속시켜주어야 멤버함수로 오버라이드하여 구현할 수 있습니다.

프래그먼트를 변수 fr로 잡아주고 버튼의 아이디에 따라 보여줄 뷰(fr)를 지정해줍니다.

replace 메서드로 fr 값에 따라 어떤 컨텐츠를 보여줄 지 선언해주고 커밋합니다.



이제부터 사용할 것은 navBar라는 개념입니다. 많은 애플리케이션에서 하단에 navBar를 위치시켜 사용자의 편의성을 도모하고 있습니다.

하단 navBar를 만들어서 Fragment를 적용해보겠습니다.

의존성 추가

우선 navBar를 사용하기 위해 의존성을 추가해줘야 합니다.

app 디렉터리에 있는 gradle.build를 열고 다음과 같이 추가해줍니다.

plugins {
    // navBar 사용하기 위해 플러그인 추가
    id 'kotlin-android-extensions'
}

...

dependencies {
    // navBar 사용하기 위해 의존성 추가
    implementation 'com.google.android.material:material:1.4.0'
}

navBar에는 이전 예시와 마찬가지로 세개의 메뉴를 넣어줄 것입니다.

<!-- bottom_navi_menu.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/person"
        android:title="Person"
        android:icon="@drawable/ic_person_foreground"
    />
    <item
        android:id="@+id/home"
        android:title="Home"
        android:icon="@drawable/ic_home_foreground"
    />
    <item
        android:id="@+id/setting"
        android:title="Setting"
        android:icon="@drawable/ic_setting_foreground"
    />
</menu>

여기에서 icon은 다음과 같이 만들어줍니다.

res 디렉터리의 drawable 디렉터리에서 새로 만들기 -> Image Asset을 클릭해줍니다.

아이콘 타입을 Launcher Icons(Adaptive and Legacy)로 하고 아래 Asset TypeClip Art로 잡아준 다음 Clip Art에서 사용하자 하는 아이콘을 선택합니다.

이 때 상단의 Name을 알맞게 바꿔줍니다.

그 다음 창에서 완료를 누르면 drawable 디렉터리에 새로운 xml 파일들이 생겨납니다.

우리는 foreground로 되어있는 것을 사용할 것이므로 navBar 아이템을 담당하는 xml 파일의 iconforeground로 지정해줍니다.

activity_main.xmlFrameLayoutnavBar 만들어주기

네비게이션 메뉴 조작에 따라 다른 화면을 보여줄 것이기 때문에 상단메뉴 예시와 마찬가지로 뷰와 네비게이션바를 만들어줍니다. layout 디렉터리에 들어갑니다.

<!-- activity_main.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <FrameLayout
            android:id="@+id/flFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@+id/bottomNaviView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
    />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNaviView"
        android:layout_width="match_parent"
        android:layout_height="75dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_navi_menu"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

BottomNavigationViewapp:menu 속성을 앞에서 우리가 만들어준 bottom_navi_menu로 지정해줍니다.

메뉴 선택 시 보여줄 View 만들기

Layout 디렉터리에 메뉴 선택시 보여 줄 View를 만들어주겠습니다.

<!-- activity_fragment_one.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ee9900"
>

    <TextView
            android:text="First Fragment"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
<!-- activity_fragment_two.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#0099ff"
>

    <TextView
            android:text="Second Fragment"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
<!-- activity_fragment_three.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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#9900ff"
>

    <TextView
            android:text="Third Fragment"
            android:textColor="#ffffff"
            android:textSize="30sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

각 메뉴를 클래스로 생성하기

이전 예시에서는 onCreateView 메서드를 오버라이드하여 사용했지만 여기서는 조금 다른 방법으로 시도합니다.

바로 뷰에 보여줄 각 xml 파일을 Fragment 클래스의 생성자로 사용하는 방법입니다.

마찬가지로 각각의 파일입니다.

import androidx.fragment.app.Fragment
class FragmentOne : Fragment(R.layout.activity_fragment_one) {}

import androidx.fragment.app.Fragment
class FragmentTwo : Fragment(R.layout.activity_fragment_two) {}

import androidx.fragment.app.Fragment
class FragmentThree : Fragment(R.layout.activity_fragment_three) {}

MainActivity에서 연결하기

이제 완성된 것들을 연결시켜주겠습니다.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val firstFragment = FragmentOne()
        val secondFragment = FragmentTwo()
        val thirdFragment = FragmentThree()

메뉴를 누를 때마다 보여줄 바뀔 페이지 객체를 생성합니다.

        // 가장 처음 보여주는 화면
        setCurrentFragment(firstFragment)

        bottomNaviView.setOnNavigationItemSelectedListener {
            when (it.itemId) {
                R.id.person -> setCurrentFragment(firstFragment)
                R.id.home -> setCurrentFragment(secondFragment)
                R.id.setting -> setCurrentFragment(thirdFragment)
            }
            true
        }

    }
 

setCurrentFragment은 가장 처음 보여줄 화면을 설정합니다. 아래에 별도로 작성되어 있습니다.

bottomNaviViewactivity_main.xml에서 만들어준 네비게이션바가 들어가는 공간입니다. 이 자리에 ItemSelectedListener로 뷰에 대해 장면전환을 할 수 있도록 해줍니다. 이 때, menuItem으로 it가 들어갑니다.

truesetOnNavigationItemSelectedListener가 동작하게 해줍니다.

    fun setCurrentFragment(fragment:Fragment) = supportFragmentManager.beginTransaction().apply {
        replace(R.id.flFragment, fragment)
        commit()
    }
}

setCurrentFragment 함수는 장면전환을 도와주는 함수입니다. fragment를 매개변수로 받아서 flFragment 즉 프레임 레이아웃을 매개변수로 받은 fragment로 변환해주고 커밋해주는 함수입니다.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글