

액티비티 위에 화면만한 프래그먼트를 띄우고 버튼을 누르면 띄워진 프래그먼트를 내리고 다른 프래그먼트를 띄우는 방식


FragmentContainerView를 먼저 화면에 드래그하여 배치하려하면 Fragment가 없다고 에러 메시지가 뜸

화면에 배치하는게 아니라 Component Tree에 드래그를 해주면 정상적으로 추가가 됨

그래서 디자인탭에서 추가하기보다는 코드로 작성하는것을 권장함
New > Fragment > Fragment (Blank)

Fragment(Blank) 선택



MainFragment.kt 파일이 생성되고 처음에 생성된 코드는 지운다
다시 디자인탭에서 FragmentContainerView를 화면에 드래그해주면 정상적으로 추가가 가능해짐

생성한 MainFragment를 선택해준다.

name 속성은 지워준다.

// MainActivity.kt
package kr.co.lion.android33_fragment
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kr.co.lion.android33_fragment.databinding.ActivityMainBinding
// Activity의 역할 :
// 1. Fragment 들을 관리
// 2. 각 Fragment 들이 공통적으로 사용하는 데이터들을 관리
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// MainFragment가 보여지도록 한다.
replaceFragment(FragmentName.MAIN_FRAGMENT)
}
// 지정한 Fragment를 보여주는 메서드
fun replaceFragment(name:FragmentName){
// Fragment를 교체할 수 있는 객체를 추출
val fragmentTransaction = supportFragmentManager.beginTransaction()
// 새로운 Fragment를 담을 변수
var newFragment:Fragment? = null
// 이름으로 분기한다.
// Fragment의 객체를 생성하여 변수에 담아준다.
when(name){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
}
}
if(newFragment != null){
// Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
// 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
// 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
fragmentTransaction.replace(R.id.mainContainer, newFragment)
// Fragment 교체를 확정한다.
fragmentTransaction.commit()
}
}
}
// Fragment들의 이름
enum class FragmentName{
MAIN_FRAGMENT,
INPUT_FRAGMENT
}
// 학생 정보를 담을 클래스
data class StudentInfo(var name:String, var age:Int, var kor:Int, var math:Int, var eng:Int){
}
InputFragment 추가

Fragment파일은 onCreateView만 빼고 모두 삭제
Activity와 동일한 방식으로 바인딩을 해준다.
// MainActivity.kt
class MainFragment : Fragment() {
lateinit var fragmentMainBinding: FragmentMainBinding
// Fragment가 눈에 보여질 때 호출되는 메서드
// 반환하는 View를 화면에 보여준다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
fragmentMainBinding = FragmentMainBinding.inflate(inflater)
//fragmentMainBinding = FragmentMainBinding.inflate(layoutInflater)
return inflater.inflate(R.layout.fragment_main, container, false)
}
}
Activity는 layoutInflater를 프로퍼티로 가지고 있으나 Fragment는 가지고 있지 않기 때문에 이를 매개변수로 받아온다. 추후에 Fragment도 프로퍼티로 layoutInflater를 갖고 있도록 수정되었다.
Fragment를 교체하는건 MainActivity에서 하고 있음 따라서 Fragment에서도 MainActivity를 불러와줘야 함
MainActivity에서 Fragment교체하는 코드에 InputActivity를 추가해줌
// MainActivity.kt
// 지정한 Fragment를 보여주는 메서드
fun replaceFragment(name:FragmentName){
// Fragment를 교체할 수 있는 객체를 추출
val fragmentTransaction = supportFragmentManager.beginTransaction()
// 새로운 Fragment를 담을 변수
var newFragment:Fragment? = null
// 이름으로 분기한다.
// Fragment의 객체를 생성하여 변수에 담아준다.
when(name){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
newFragment = InputFragment()
}
}
if(newFragment != null){
// Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
// 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
// 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
fragmentTransaction.replace(R.id.mainContainer, newFragment)
// Fragment 교체를 확정한다.
fragmentTransaction.commit()
}
}
MainFragment에서 viewSetting설정
// MainFragment.kt
class MainFragment : Fragment() {
lateinit var fragmentMainBinding: FragmentMainBinding
lateinit var mainActivity: MainActivity
// Fragment가 눈에 보여질 때 호출되는 메서드
// 반환하는 View를 화면에 보여준다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
fragmentMainBinding = FragmentMainBinding.inflate(inflater)
//fragmentMainBinding = FragmentMainBinding.inflate(layoutInflater)
// Activity의 주소값을 담아준다.
mainActivity = activity as MainActivity
viewSetting()
return fragmentMainBinding.root
}
// View 설정
fun viewSetting(){
fragmentMainBinding.apply {
// 버튼
buttonShowInput.setOnClickListener {
// InputFragment로 교체한다.
mainActivity.replaceFragment(FragmentName.INPUT_FRAGMENT)
}
}
}
}
앱을 실행하고

버튼을 누르면 InputFragment가 보임

MainActivity의 replaceFragment메서드에 addToBackStack 매개변수 추가
// MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// MainFragment가 보여지도록 한다.
replaceFragment(FragmentName.MAIN_FRAGMENT, true)
}
// 지정한 Fragment를 보여주는 메서드
fun replaceFragment(name:FragmentName, addToBackStack:Boolean){
// Fragment를 교체할 수 있는 객체를 추출
val fragmentTransaction = supportFragmentManager.beginTransaction()
// 새로운 Fragment를 담을 변수
var newFragment:Fragment? = null
// 이름으로 분기한다.
// Fragment의 객체를 생성하여 변수에 담아준다.
when(name){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
newFragment = InputFragment()
}
}
if(newFragment != null){
// Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
// 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
// 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
fragmentTransaction.replace(R.id.mainContainer, newFragment)
// addToBackStack 변수의 값이 true면 새롭게 보여질 Fragment를 BackStack에 포함시켜 준다.
if(addToBackStack == true){
fragmentTransaction.addToBackStack(null)
}
// Fragment 교체를 확정한다.
fragmentTransaction.commit()
}
}
}
MainFragment에서 viewSetting에 true값 추가하고
MainActivity에서 replaceFragment호출 부분에는 false값을 넣어준다.
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// MainFragment가 보여지도록 한다.
replaceFragment(FragmentName.MAIN_FRAGMENT, false)
// 가장 처음 보여줄 Fragment는 BackStack에 포함시키지 않는다.
}
// MainFragment.kt
// View 설정
fun viewSetting(){
fragmentMainBinding.apply {
// 버튼
buttonShowInput.setOnClickListener {
// InputFragment로 교체한다.
mainActivity.replaceFragment(FragmentName.INPUT_FRAGMENT, true)
}
}
}
MainFragment는 BackStack에 포함되어있지 않기 때문에 InputFragment에서 Back button을 누르면 앱이 종료된다.

InputFragment에서 뒤로가기 버튼 누를 시 앱이 종료됨

MainActivity에서 replaceFragment를 호출하는 부분에서 true값을 넣었을 경우에는 MainFragment도 BackStack에 포함되기 때문에 InputFragment에서 MainFragment로 뒤로가기가 가능해지나 MainFragment에서 다시 한번 뒤로가기를 누를 경우에는 빈 화면이 나타나게 된다.



회원가입 화면을 구성할때 이름 -> 주소 -> ID,PW -> 메인화면 순서로 Fragment를 구성했다면
메인화면으로 왔을때 다시 입력받는 Fragment로 돌아가면 안된다. 하지만 입력받는 Fragment에서는 주소 입력에서 이름 입력으로 돌아올수 있게 해야 한다.
BackStack에서는 Fragment를 제거할 수 있는 기능이 있어서 메인화면으로 왔을 때 입력 Fragment를 제거해주면 된다. 그렇게 되면 입력할 때에는 뒤로가기가 가능해지나 회원가입을 완료하고 메인화면으로 돌아오면 뒤로가기를 클릭할때 회원가입이 아닌 앱종료가 된다.
// InputFragment.kt
class InputFragment : Fragment() {
lateinit var fragmentInputBinding: FragmentInputBinding
lateinit var mainActivity: MainActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
fragmentInputBinding = FragmentInputBinding.inflate(inflater)
mainActivity = activity as MainActivity
viewSetting()
return fragmentInputBinding.root
}
fun viewSetting(){
fragmentInputBinding.apply {
// 버튼
buttonPrev.setOnClickListener {
// BackStack에서 Fragment를 제거해 이전 Fragment가 보이도록 한다.
mainActivity.removeFragment()
}
}
}
}



// MainActivity.kt
// 지정한 Fragment를 보여주는 메서드
fun replaceFragment(name:FragmentName, addToBackStack:Boolean){
// Fragment를 교체할 수 있는 객체를 추출
val fragmentTransaction = supportFragmentManager.beginTransaction()
// 새로운 Fragment를 담을 변수
var newFragment:Fragment? = null
// 이름으로 분기한다.
// Fragment의 객체를 생성하여 변수에 담아준다.
when(name){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
newFragment = InputFragment()
}
}
if(newFragment != null){
// Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
// 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainer의 ID
// 두 번째 매개 변수 : 보여주고자 하는 Fragment 객체
fragmentTransaction.replace(R.id.mainContainer, 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를 BackStack에서 제거한다.
supportFragmentManager.popBackStack(name.str, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
// Fragment들의 이름
enum class FragmentName(var str:String){
MAIN_FRAGMENT("mainFragment"),
INPUT_FRAGMENT("inputFragment")
}
// InputFragment.kt
fun viewSetting(){
fragmentInputBinding.apply {
// 버튼
buttonPrev.setOnClickListener {
// BackStack에서 Fragment를 제거해 이전 Fragment가 보이도록 한다.
mainActivity.removeFragment(FragmentName.INPUT_FRAGMENT)
}
}
}
popBackStack에 없는 아이디(문자열)를 지정하면 아무일도 일어나지 않는다.
이전 프래그먼트에 적용할 애니메이션이 있고 새로운 프래그먼트에 적용할 애니메이션이 있기 때문에 2개의 프래그먼트 객체가 필요하다.
// MainActivity.kt
class MainActivity : AppCompatActivity() {
// 이전 Fragment
var oldFragment:Fragment? = null
// 새로 나타날 Fragment
var newFragment:Fragment? = null
}
처음 프래그먼트(F1)를 보여줄 때 newFragment에 Fragment객체(F1)를 담는다.
다음 프래그먼트(F2)를 보여줄 때 newFragment에 있던 처음 Fragment 객체(F1)를 oldFragment에 담아주고 newFragment에 다음 프래그먼트 객체(F2)를 담아주는 방식
F1에는 처음 나타날때 애니메이션을 적용하지 않지만 F2에서 F1로 돌아올때 애니메이션을 적용해줘야 한다.
F2는 F1에서 F2로 새롭게 나타날 때 애니메이션을 적용해준다. 추후 F3가 있다면 F3에서 F2로 돌아올때 애니메이션을 적용해줄 수 있다.
newFragment에 있는 객체주소를 oldFragment에 담는 과정이 필요
replaceFragment메서드에 새로운 Fragment를 담을 변수 newFragment는 지워주고
매개변수로 isAnimate:Boolean를 받는다.
첫화면은 애니메이션을 주지 않는다.
// MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
// 이전 Fragment
var oldFragment:Fragment? = null
// 새로 나타날 Fragment
var newFragment:Fragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// MainFragment가 보여지도록 한다.
replaceFragment(FragmentName.MAIN_FRAGMENT, false, false)
}
// 지정한 Fragment를 보여주는 메서드
fun replaceFragment(name:FragmentName, addToBackStack:Boolean, isAnimate:Boolean){
// 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){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
newFragment = InputFragment()
}
}
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.mainContainer, 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()
SystemClock.sleep(200)
// 지정한 이름으로 있는 Fragment를 BackStack에서 제거한다.
supportFragmentManager.popBackStack(name.str, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
// MainFragment.kt
// View 설정
fun viewSetting(){
fragmentMainBinding.apply {
// 버튼
buttonShowInput.setOnClickListener {
// InputFragment로 교체한다.
mainActivity.replaceFragment(FragmentName.INPUT_FRAGMENT, true, true)
}
}
}
Activity간에 데이터를 전달할 때 intent를 사용했는데 Fragment에서는 intent가 없음
A Fragment와 B Fragment에서 사용하는 데이터가 있을 때 Activity에서 프로퍼티를 정의하고 A Fragment에서 Activity의 프로퍼티 값을 저장하면 B Fragment에서도 Activity의 프로퍼티 값을 가져와 사용할 수 있다.
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
// MainFragment가 보여지도록 한다.
replaceFragment(FragmentName.MAIN_FRAGMENT, false, false, null)
}
// 지정한 Fragment를 보여주는 메서드
// name : 프래그먼트 이름
// addToBackStack : BackStack에 포함 시킬 것인지
// isAnimate : 애니메이션을 보여줄 것인지
// data : 새로운 프래그먼트에 전달할 값이 담겨져 있는 Bundle 객체
fun replaceFragment(name:FragmentName, addToBackStack:Boolean, isAnimate:Boolean, data:Bundle?){
// 새로운 Fragment에 전달할 Bundle 객체가 있다면 arguments 프로퍼티에 넣어준다.
if(data != null){
newFragment?.arguments = data
}
}
// MainFragment.kt
// View 설정
fun viewSetting(){
fragmentMainBinding.apply {
// 버튼
buttonShowInput.setOnClickListener {
// 데이터를 담을 Bundle 객체를 생성한다.
val bundle = Bundle()
// 데이터를 담는다.
bundle.putInt("data1",10)
bundle.putDouble("data2",11.11)
bundle.putString("data3","문자열")
// InputFragment로 교체한다.
mainActivity.replaceFragment(FragmentName.INPUT_FRAGMENT, true, true, bundle)
}
}
}
// InputFragment.kt
fun viewSetting(){
fragmentInputBinding.apply {
// 버튼
buttonPrev.setOnClickListener {
// BackStack에서 Fragment를 제거해 이전 Fragment가 보이도록 한다.
mainActivity.removeFragment(FragmentName.INPUT_FRAGMENT)
}
// TextView
textViewInput.apply {
// arguments 프로퍼티를 통해 data 추출한다.
text = "data1 : ${arguments?.getInt("data1")}\n"
append("data2 : ${arguments?.getDouble("data2")}\n")
append("data3 : ${arguments?.getString("data3")}")
}
}
}

※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스