Activity 간에 이동하는 법이 조금 헷갈려서 어떻게 해야될지 개념을 정리해 보았다.
contract (계약 객체)를 생성해야 한다.
contract 에 있는 StratActivityForResult() 메서드를 사용하기 위해서다.
StratActivityForResult()는 말 그대로 어떤 결과를 위해 다른 Activity를 실행하는 메서드다. 예를 들어 사진을 찍어 올리는 버튼이라면? 우선 사진을 찍어야 된다. 그럼 사진 찍는 과정을 쪼개서 생각해 보자.
'카메라 Activity 실행 → 사진 찍고 → 원래 앱으로 다시 돌아옴'
사진을 찍기 위해 아이콘을 누르면 카메라 앱이 실행된다. 그리고 단말기 화면이 카메라 앱으로 전환되면서 사진을 찍을 수 있게 된다. 이렇게 앱에서 앱으로 이동하는 것도 결국 Activity의 이동이다. 결과를 가져오기 위해 다른 Activity를 실행한다는 게 이런 의미였다.
// ShowActivity의 런처
val contract1 = ActivityResultContracts.StartActivityForResult()
val showActivityLauncher = registerForActivityResult(contract1){
}
// InputActivity의 런처
val contract2 = ActivityResultContracts.StartActivityForResult()
val inputActivityLauncher = registerForActivityResult(contract2){
}
intent 를 사용해주면 된다.
intent를 사용해서 B로 넘어갔다면, 작업이 끝난 뒤 finish()를 통해 다시 A 액티비티로 넘어올 수 있다.
작업이 끝난 뒤 다시 원래 액티비티로 돌아오면
registerForActivityResult(contract1) { }의 코드가 동작 한다. 그러므로 돌아와서 어떤 행동을 추가적으로 하고 싶다면 여기다 작성해주면 된다.
하지만 보통 Activity 는 페이지 전환이 상대적으로 무겁기 때문에.. 앱 내에서 화면 전환할 땐 Fragment 를 많이 사용한다. 따라서 Fragment 간 이동에 대해서도 알고 있어야 한다.
- Fragment 생성
- xml에 Fragment 배치
- Fragment들의 이름을 담을 enum Class 생성
- old Fragment, newFragment 프로퍼티 정의
- Fragment Basic Code 복붙
→ replaceFragment 메서드, removeFragment 메서드 코드- MainFragment 작성
→ recyclerView 등- MainActivity에서 replaceFragment() 메서드를 호출하면,
매개변수로 전달된 Fragment가 보여진다.
= Activity의 단점을 보완하기 위해.
Activity의 단점 ?
1) 각 뷰에 대한 코드를 나눠서 작업할 수 없다.
Activity는 한 화면을 관리하는 요소다. 그런데 그 안에 여러개의 뷰가 들어가 있다면(textView, button … ) 무조건 같은 액티비티 파일 내에서 작업해야 된다. 이렇게 되면 하나의 파일이 너무 커진다.
2) Activity끼리 코드를 넘겨주기가 불편하다.
만약 Activity A에서 구현한 코드를 Activity B에서 재사용하기 위해선 Laucher를 설정하고 Intent를 통해 넘겨주는 복잡한 작업이 필요하다. (Intent는 Pacelable 인터페이스 구현한 것만 넘겨줄 수 있음)
3) Activity는 실행에 오래 걸린다.
Fragment에 비해 무겁기 때문에… Activity를 많이 만드는 것은 권장되지 않는 추세라고 함.
1) 자기를 소유하고 있는 Activity에 접근 가능하다.
예를들어 MainActivity에 MainFragment를 붙이면, MainFragment는 MainActivity의 프로퍼티나 메서드에 접근이 가능하다.
따라서 Fragment들에게 공통적으로 사용하는 프로퍼티나 메서드를 Activity에 정의해주고, 필요 시에 사용하면 코드 재사용성이 향상된다.
2) Activity의 위에 쌓인다.
3) BackStack에 포함시킬지 말지 개발자가 설정할 수 있다.
주의할 점은
첫 프레그먼트를 백스택에 포함시키면?
=> 메인 액티비티에서 백스택 true로 설정하면 메인 프레그먼트를 백스텍에 포함시킴
=> 인풋 프레그먼트를 백스텍에 포함시킨다
=> 첫화면임에도 백버튼을 누르면 하얀 화면이 보이고
=> 여기서 또 백버튼을 누르면 메인 액티비티가 종료된다.
=> 하얀 화면을 사용자에게 보여준 뒤 앱을 종료하는 건 너무 어색함..
첫 프레그먼트를 백스택에 포함시키지 않으면?
=> 메인 액티비티에서 백버튼 눌렀을때 앱 자체가 종료된다.
=> 하얀화면을 보이지 않고 바로 종료시킬 수 있는 것임.
우선 Fragment는 눈에 보이는 실행 단위가 아니라는 점을 기억해야한다. (왜냐면 Activity가 아니기 때문에)
- 이름을 지정해 줄 수 있다.
- 만약 우리가 어플을 실행시켰을 때, 처음 보이는 화면이 A라는 Activity라면, A는 무조건 BackStack에 자동으로 들어간다.
- 하지만 Fragment는 BackStack에 포함시킬지 말지 설정해줘야 한다.
- 왜냐하면
- 그 Activity 위에다
- 안드로이드 레이아웃 배경은 기본적으로 투명색이다.
- 따라서 Activity 웨이
1) 화면 전환?
ex) MainFragment → 툴바버튼을 눌렀을 때 → InputFragment 로 이동하고 싶다면 ?
= 툴바 설정 메서드에서 메뉴아이템 리스너를 사용해준다.
= 그리고 mainActivity에서 사용했던 replaceFragment 메서드를 사용해준다.
// 툴바 설정
fun setToolbar(){
fragmentMainBinding.apply {
toolbarMain.apply {
// 타이틀
title = "할일목록"
// menu 생성
inflateMenu(R.menu.menu_main)
// 메뉴아이템 리스너
setOnMenuItemClickListener {
when(it.itemId) {
// 추가
R.id.menu_item_main_input ->
mainActivity.replaceFragment(FragmentName.INPUT_FRAGMENT, true, true, null)
}
true
}
}
}
}
(5)
// Fragment 기반의 어플리케이션 작업시 초기 작성 코드 순서
// 1. activity_main.xml 에 FragmentContainerView를 배치해 준다.
// 2. MainActivity.kt 파일에 Fragment 들의 이름을 표현하기 위한 enum class를 작성해준다.
// 3. MainActivity에 oldFragment와 newFragment 프로퍼티를 정의해준다.
// 4. fragment basic code.txt 에 있는 메서드 코드를 붙혀넣는다.
// 5. 빨간색으로 뜨는 부분들 import 설정을 해준다.
// 새로운 Fragment 추가
// 1. Fragment를 생성해준다.
// 2. FragmentName enum 클래스에 Fragment 이름을 추가해준다.
// 3. replaceFragment 메서드 내의 when에 Fragment 관련 코드를 작성해준다.
: Activity와 동일
: app - 마우스우클릭 - new - Fragment - Fragment (Blank) - name 작성 - ok
1) View 바인딩
2) 화면 만들기
MainFragment 의 상단 + 메뉴를 누르면 입력 화면이 나타난다
입력은 이름, 나이, 국어점수, 영어점수, 수학점수를 입력받는다.
입력 후 MainFragment로 돌아오면 RecyclerView의 항목으로 보여진다.
MainFragment의 RecyclerView의 항목을 누르면 해당 학생의 정보를 보여주는 화면이 나타난다.
✅ 1. MainActivity에 강사님이 적어주신 Fragment Basic Code 를 복붙해준다// 지정한 Fragment를 보여주는 메서드
(1) fun replaceFragment 생성
name = 새로 보여줄 Fragment의 이름
addToBackStack = BackStack에 포함시킬건지 여부 (Boolean)
isAnimate = 애니메이션을 보여줄것인지
data = 새로운 Fragment에
// BackStack에서 Fragment를 제거한다.
(2) fun removeFragment
// 지정한 Fragment를 보여주는 메서드
// name : 프래그먼트 이름
// addToBackStack : BackStack에 포함 시킬 것인지
// isAnimate : 애니메이션을 보여줄 것인지
// data : 새로운 프래그먼트에 전달할 값이 담겨져 있는 Bundle 객체
fun replaceFragment(name:FragmentName, addToBackStack:Boolean, isAnimate:Boolean, data:Bundle?){
SystemClock.sleep(200)
// Fragment를 교체할 수 있는 객체를 추출한다.
val fragmentTransaction = supportFragmentManager.beginTransaction()
// oldFragment에 newFragment가 가지고 있는 Fragment 객체를 담아준다.
if(newFragment != null){
oldFragment = newFragment
}
// 이름으로 분기한다.
// Fragment의 객체를 생성하여 변수에 담아준다.
when(name){
FragmentName.MAIN_FRAGMENT -> {
newFragment = MainFragment()
}
FragmentName.INPUT_FRAGMENT -> {
newFragment = InputFragment()
}
}
// 새로운 Fragment에 전달할 객체가 있다면 arguments 프로퍼티에 넣어준다.
if(data != null){
newFragment?.arguments = data
}
if(newFragment != null){
// 애니메이션 설정
if(isAnimate == true){
// oldFragment -> newFragment
// oldFragment : exitTransition
// newFragment : enterTransition
// newFragment -> oldFragment
// oldFragment : reenterTransition
// newFragment : returnTransition
// MaterialSharedAxis : 좌우, 위아래, 공중 바닥 사이로 이동하는 애니메이션 효과
// X - 좌우
// Y - 위아래
// Z - 공중 바닥
// 두 번째 매개변수 : 새로운 화면이 나타나는 것인지 여부를 설정해준다.
// true : 새로운 화면이 나타나는 애니메이션이 동작한다.
// false : 이전으로 되돌아가는 애니메이션이 동작한다.
if(oldFragment != null){
// old에서 new가 새롭게 보여질 때 old의 애니메이션
oldFragment?.exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
// new에서 old로 되돌아갈때 old의 애니메이션
oldFragment?.reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
oldFragment?.enterTransition = null
oldFragment?.returnTransition = null
}
// old에서 new가 새롭게 보여질 때 new의 애니메이션
newFragment?.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
// new에서 old로 되돌아갈때 new의 애니메이션
newFragment?.returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
newFragment?.exitTransition = null
newFragment?.reenterTransition = null
}
// Fragment를 교체한다.(이전 Fragment가 없으면 새롭게 추가하는 역할을 수행한다)
// 첫 번째 매개 변수 : Fragment를 배치할 FragmentContainerView의 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){
SystemClock.sleep(200)
// 지정한 이름으로 있는 Fragment를 BackStack에서 제거한다.
supportFragmentManager.popBackStack(name.str, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}