웹에서 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>
버튼을 앱 초기화시 생성해주기 위해 필요합니다.
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)
}
}
이 함수는 MainActivity
의 onCreate
와 유사합니다.
activity_fragment_menu.xml
에서 만든 버튼 세개를 뷰로 생성해주는 것입니다.
각각의 파라미터는 다음과 같습니다.
파라미터 | 정의 |
---|---|
inflater | 프레그먼트의 모든 뷰를 확장시킬 수 있는 LayoutInflater 객체 |
container | 프레그먼트의 UI를 연결할 상위 view |
savedInstanceState | null 이 아닌 경우 프레그먼트는 이전에 저장된 상태에서 재생성됨. |
각각은 별도의 파일이며 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
는 앱 프래그먼트에서 작업을 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스입니다.
여기에서 supportFragmentManager
는 FragmentManager
에 접근하기 위한 도구입니다. 프래그먼트의 모든 요소를 제어할 수 있습니다.
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
로 Fragment
사용하기이제부터 사용할 것은 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
구성 만들어주기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 Type
을 Clip Art
로 잡아준 다음 Clip Art
에서 사용하자 하는 아이콘을 선택합니다.
이 때 상단의 Name
을 알맞게 바꿔줍니다.
그 다음 창에서 완료를 누르면 drawable
디렉터리에 새로운 xml
파일들이 생겨납니다.
우리는 foreground
로 되어있는 것을 사용할 것이므로 navBar
아이템을 담당하는 xml 파일의 icon
은 foreground
로 지정해줍니다.
activity_main.xml
에 FrameLayout
과 navBar
만들어주기네비게이션 메뉴 조작에 따라 다른 화면을 보여줄 것이기 때문에 상단메뉴 예시와 마찬가지로 뷰와 네비게이션바를 만들어줍니다. 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>
BottomNavigationView
의 app:menu
속성을 앞에서 우리가 만들어준 bottom_navi_menu
로 지정해줍니다.
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
은 가장 처음 보여줄 화면을 설정합니다. 아래에 별도로 작성되어 있습니다.
bottomNaviView
는 activity_main.xml
에서 만들어준 네비게이션바가 들어가는 공간입니다. 이 자리에 ItemSelectedListener
로 뷰에 대해 장면전환을 할 수 있도록 해줍니다. 이 때, menuItem
으로 it
가 들어갑니다.
true
는 setOnNavigationItemSelectedListener
가 동작하게 해줍니다.
fun setCurrentFragment(fragment:Fragment) = supportFragmentManager.beginTransaction().apply {
replace(R.id.flFragment, fragment)
commit()
}
}
setCurrentFragment
함수는 장면전환을 도와주는 함수입니다. fragment
를 매개변수로 받아서 flFragment
즉 프레임 레이아웃을 매개변수로 받은 fragment
로 변환해주고 커밋해주는 함수입니다.