하나의 Activity를 화면을 구성하기 위한 최소 단위로 볼 수 있는데, 이 Activity를 여러 화면으로 나눠서 처리하고자 하는 것이 Flagment입니다. 예컨대 여러 화면을 Swipe를 통해 전환하는 경우 전환이 이루어지는 화면을 Flagment로 미리 만들어 화면을 구성해 하나의 Activity안에서 전 한하는 경우나 태블릿과 같은 큰 화면에서 목록과 상세화면을 하나의 Activity안에서 나누어 표시하기 위한 용도입니다.
1. Activity에 Flagment 추가하기
Android Studio에서 java 폴더에 마우스 오른쪽 버튼을 눌러 New -> Fragement -> Framment (Blank)를 선택합니다.
Flagement Name을 임의로 지정한뒤 Finish를 눌러줍니다. 현재 예제에서는 이름을 myFlagment로 하였습니다.
그러면 다음과 같이 새로운 Flagment가 생성됩니다.
다시 MainActivity로 돌아와 Layout 카테고리에 있는 FrameLayout을 선택하고 MainActiviy의 화면에 배치합니다. 이때 기본적으로 ID가 설정되어 있지 않은데 frameLayout처럼 임의의 ID를 지정해 줍니다.
그리고 MainActivity에 Fragment가 추가될 수 있도록 하는 일련의 코드를 onCrate안에 만들어 둡니다. 예제에서는 onCrate안에 모든 코드를 넣는 것보다 따로 메서드를 만들어 onCreate 안에서 호출하도록 하였습니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment()
}
fun addFragment() {
var myFraement: MyFragment = MyFragment()
var transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, myFraement)
transaction.commit()
}
}
Fragment를 추가하기 위한 addFragment를 보면 하나의 Fragment를 추가하기 위한 방법이 상당히 독특함을 알 수 있는데, Fragment추가 과정이 하나의 트랜잭션으로 묶여서 처리되고 있습니다.
이제 App을 실행하면 MainActivity의 FrameLayout안에서 새롭게 생성한 Fragment화면을 볼 수 있습니다.
상기 예제는 코드를 통해서 생성한 Fragment를 Activity에 추가했는데 이 방법이외에도 Fragment를 하나의 위젯처럼 디자인 모드에서 곧바로 추가하는 방법도 있습니다.
이를 위해 우선 위에서 추가한 addFragment와 관련한 코드를 모드 삭제하고 MainActivity에서 추가한 FrameLayout 위젯도 제거합니다. 그리고 Container영역에 있는 Fragment를 선택해 다시 MainActivity 화면에 배치합니다.
이때 어떤 Fragment를 추가할지에 대한 팝업이 나타나는데
여기서 이전에 생성한 MyFragment를 선택하고 OK버튼을 눌러줍니다.
그러면 해당 Fragment가 위젯으로 추가됩니다. 그런데 상기 예제처럼 화면에 아무것도 나오지 않고 회색으로만 표시되는데 디자인 모드에서 Fragment의 화면을 보고자 한다면 다음과 같이 Layout속성을 선택하여 새롭게 추가한 Fragment를 선택해주면 됩니다.
2. Fragment간 화면 전환
Flagment를 하나 더 추가해 Flagment에서 다른 Flagment로 화면을 전환하는 경우를 살펴보도록 하겠습니다. 이를 위해 상기 방법과 동일하게 Flagment를 하나 더 추가합니다. 단 이름은 MyFragment2라고 하겠습니다.
MainActivity에서 MyFragment와 동일하게 Activity에 Fragment를 추가하는 메서드를 작성합니다. 단지 차이점이라면 추가되는 Fragment가 MyFragment2로 바뀌고 Commit() 위에 addToBackStack메서드가 추가된다는 것뿐입니다.
fun callFragment() {
var myFraement2: MyFragment2 = MyFragment2()
var transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, myFraement2)
transaction.addToBackStack("MyFragment2")
transaction.commit()
}
addToBackStack() 메서드를 추가하는 이유는 MyFragment2의 트랜잭션 자체를 BackStack에 담아두기 위해서입니다. 이렇게 하면 MyFragment2가 화면에 표시되었을 때 사용자가 '뒤로'버튼을 누르게 되면 BackStack에 담아두었던 트랜잭션 자체를 제거하면서 이전 화면으로 돌아갈 수 있게 됩니다. 이러한 조치 없이 '뒤로'버튼이 사용된다면 Activity자체가 종료되어 버리는 상황이 발생합니다.
MyFragment2에서는 실제 '뒤로'버튼을 눌렀을 때 동작할 Back() 메서드를 작성합니다. 그리고 메서드 안에서 onBackPressed() 메서드를 호출해 '뒤로 가기'를 수행하도록 합니다.
fun goBack() {
onBackPressed()
}
위에까지 모든 메서드는 MainActivity에 작성합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment()
}
fun addFragment() {
var myFraement: MyFragment = MyFragment()
var transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, myFraement)
transaction.commit()
}
fun callFragment() {
var myFraement2: MyFragment2 = MyFragment2()
var transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, myFraement2)
transaction.addToBackStack("MyFragment2")
transaction.commit()
}
fun goBack() {
onBackPressed()
}
}
이제 MyFragment로 돌아와 버튼 하나를 생성하고 해당 버튼을 누르면 MyFragment2로 넘어가도록 처리를 추가하겠습니다.
이를 위해 MyFragment에서 기존에 TextView를 제거하고 버튼 하나를 다음과 같이 배치합니다. 버튼은 'MyFragment2 호출'로 표시하고 ID를 btn_call_fragment로 지정했습니다.
이제 버튼의 리스너를 설정해서 버튼 클릭 시 동작하는 코드를 넣어야 하는데 그전에 해야 할 것이 있습니다. 실제 MyFragment2를 Activity에 추가하는 코드가 MainActivity에 있으므로 MainActivity에 있는 메서드를 호출할 수 있도록 MainActivity를 다음과 같이 가져오도록 합니다.
var mainActivity : MainActivity? = null
그리고 onAttach 메서드 안에서 context를 위에서 선언한 mainActivity에 담을 수 있도록 합니다.
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity = context as MainActivity
}
이제까지 추가한 MyFragment의 전체 코드는 다음과 같습니다.
class MyFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
var mainActivity : MainActivity? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity = context as MainActivity
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MyFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
MyFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
그리고 onCreate메서드 안에 return inflater.inflate(R.layout.fragment_my, container, false) 부분을 아래와 같이 수정합니다.
var view = inflater.inflate(R.layout.fragment_my, container, false)
view.btn_call_fragment.setOnClickListener {
mainActivity?.callFragment()
}
return view
MyFragment는 어쨌건 MainActivity를 통해 View로 노출되므로 결국 위에서 배치한 버튼은 View에 있게 됩니다. 따라서 View에 있는 버튼의 리스너를 생성해 다른 Fragment를 추가하는 메서드를 호출하도록 하는 것입니다.
이와 같은 작업이 동일하게 MyFragment2에도 적용되어야 합니다. 따라서 다음과 같이 버튼을 생성하고
코드도 MyFragment와 동일하게 작성하도록 합니다.
class MyFragment2 : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
var mainActivity : MainActivity? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity = context as MainActivity
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_my2, container, false)
view.btn_call_myfragment.setOnClickListener {
mainActivity?.Back()
}
return view
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MyFragment2.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
MyFragment2().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
마지막으로 MyFragment2에서 배경색을 설정하고 clickable 속성을 true로 설정합니다. 배경색을 지정하지 않으면 기본적으로 Fragment가 쌓이는 형태로 표시되기 때문에 이전에 표시된 Fragment화면과 이후에 표시되는 Fragment가 겹쳐 보일 수 있습니다. 또한 이후에 표시된 Fragment에의 빈 공간을 클릭하면 공교롭게도 이전 Fragment에서 같은 위치에 버튼이 있을 경우 버튼의 기능이 동작할 수 있기 때문에 clickable 속성도 true로 맞춰주는 것입니다.
이제 앱을 실행시켜 Fragment 간 화면 전환을 확인해 보도록 하겠습니다.
3. Fragment에 데이터 전달하기
Flagment로 필요한 값을 전달하기 위해서는 arguments를 사용할 수 있는데 실제 활용방법을 알아보기 위해 예제에서 MyFlagment2를 불러오기 위해 사용한 callFragment() 메서드를 다음과 같이 수정합니다.
fun callFragment() {
var myFraement2: MyFragment2 = MyFragment2()
var bundle = Bundle()
bundle.putString("key1", "abcdefg")
bundle.putInt("key2", 12345)
myFraement2.arguments = bundle
var transaction = supportFragmentManager.beginTransaction()
transaction.add(R.id.frameLayout, myFraement2)
transaction.addToBackStack("MyFragment2")
transaction.commit()
}
arguments를 통해 값을 전달하기 위해서는 Bundle을 사용해 필요한 값을 저장하고 다시 값을 저장한 Bundle을 arguments로 전달하는 순서로 진행됩니다.
이제 값을 전달받은 Flagment에서는 전달받은 값을 가져오기만 하면 되는데 이전에 Flagment를 생성할 때 이미 arguments에서 값을 가져오는 기반 코드가 자동으로 생성되었기 때문에 필요한 일부분만 수정해 주면 곧바로 값을 가져올 수 있습니다.
우선 arguments로 값을 받은 MyFragment2의 코드를 보면 2개의 param이 생성된 것을 볼 수 있는데 이 중에서 두 번째 param2를 Int형식으로 변경합니다.
private var param1: String? = null
private var param2: Int? = null
arguments를 위한 Bundle에 값을 지정할 때 두 번째 값이 Int형식이기 때문에 그에 맞게 param을 수정한 것입니다. 이어서 코드의 제일 상단에 보면 역시 2개의 const변수를 볼 수 있는데 이 const변수의 값을 아래와 같이 수정합니다. 이 const변수는 Bundle에 지정한 key값을 담는 변수입니다.
private const val ARG_PARAM1 = "key1"
private const val ARG_PARAM2 = "key2"
마지막으로 onCreate를 아래와 같이 수정해 arguments에서 값을 가져올 수 있도록 합니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getInt(ARG_PARAM2)
}
Log.d("test", param1)
Log.d("test", param2.toString())
}
위에서 이미 ARG_PARAM과 param을 예제에 맞게 변경했으므로 정상적으로 앱이 실행된다면 로그에 값이 출력될 것입니다.
이미 생성된 Fragment사이에서 값을 주고받기 위해서는 우선 값을 받아야 하는 Flagment에서 다음과 같이 값을 받기 위한 용도의 메서드를 작성합니다.
fun receiveValue(param: String) {
Log.d("test", param)
}
그리고 Activity에서는 값을 받고 그 받은 값을 다시 다른 Flagment에 전달하는 메서드를 작성합니다.
fun passValue(param: String) {
var myFraement2: MyFragment2 = MyFragment2()
myFraement2.receiveValue(param)
}
보시는 바와 같이 메서드의 매개변수를 통해 값을 받은 뒤 값을 전달해야 할 Flagment의 메서드(예제에서는 MyFragment2의 receiveValue메서드)를 호출해 받은 값을 전달하고 있습니다.
마지막으로 값을 넘겨야 하는 Flagment에서는 Activity에 만들어둔 passValue() 메서드를 호출하여 값을 전달할 수 있도록 하면 됩니다.
var mainActivity : MainActivity? = null
fun throwValue() {
mainActivity?.passValue("안뇽!")
}
4. 생명주기 메서드
Flagment도 화면에 보이는 상황을 기점으로 생성과 소멸 단계를 거치게 되는데 이때 상황에 따라 생성과 소멸 시 다양한 메서드를 호출하게 됩니다. 일부 메서드는 Flagment생성시 기본으로 생성되는 메서드도 존재합니다.
생성과 소멸시 상황에 따라 호출되는 메서드가 다르므로 필요한 처리가 있으면 시점에 맞는 적절한 메서드를 골라 해당 메서드가 호출되는 경우를 활용해 원하는 처리를 메서드에 필요한 코드를 추가하여 처리하면 됩니다.
onAttach | Flagment가 Transaction에 의해 Commit 되는 순간 호출됩니다. |
onCreate | Flagment가 생성되는 시점에 호출됩니다. |
onCreateView | onCreate이 후 곧바로 호출됩니다. 주로 뷰를 초기화 하는 용도로 사용됩니다. |
onStart | Flagment가 화면에서 사라졌다가 다시 보이는 순간 호출됩니다. |
onResume | Flagment의 전체 화면이 완전히 가려져 있지 않은 상태에서 다시 표시되는 순간 onStart를 거치지 않고 호출됩니다. |
<생성메서드>
onPause | Flagment가 화면에서 사라지면 호출됩니다. (화면중 일부가 보이는 경우만 호출) |
onStop | Flagment가 화면에서 완전히 사라지는 경우 호출됩니다. |
onDestroyView | 이 메서드가 호출되면 Flagment에서 inflater로 생성한 View가 소멸됩니다. |
onDestroy | 이 메서드가 호출되면 Flagment자체가 소멸됩니다. 이 시점에 필요한 경우 Flagment에서 사용중인 모든 자원을 해제하면 됩니다. |
onDetach | Activity와 Flagment와의 연결이 해제됩니다. |
<소멸메서드>
'Mobile > Kotlin' 카테고리의 다른 글
[Kotlin] Widget 만들기 (0) | 2020.12.22 |
---|---|
[Kotlin] View (0) | 2020.12.22 |
[Kotlin] RecyclerView (0) | 2020.12.21 |
[Kotlin] Spinner (0) | 2020.12.18 |
[Kotlin] Activity (0) | 2020.12.18 |