안드로이드는 여러 종류의 모바일폰과 태블릿 등 다양한 구동환경이 있다. 그런데 Activity는 하나의 화면 단위이기 때문에 구성요소가 많아지면 디스플레이 해상도에 따른 화면 구성 관리가 더욱 어려워진다.
그래서 사용되기 시작한 것이 Fragment이다. Fragment를 사용해 화면 구성을 나누고 화면의 각 부분별로 관리함으로써 다양한 디바이스(특히 디스플레이)에도 유연하게 대응할 수 있다.
일종의 파편화된 activity와 같다.
특징
Activity처럼 라이프 사이클이 존재하며 더욱 다양하다.
Activity에 종속적이다.
//fragment.ko 파일과 라이프 사이클
package com.example.myapplication
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class Fragment1:Fragment() {
override fun onAttach(context: Context) {
Log.d("life_cycle","F onAttach aaa")
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("life_cycle","F onCreateView aaa")
//fragment가 interface를 처음으로 그릴 때 호출
//inflator 뷰를 그리는 역할
// container fragment를 붙일 부모 뷰
return inflater.inflate(R.layout.fragment1, container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d("life_cycle","F onViewCreated aaa")
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.d("life_cycle","F onActivityCreated aaa")
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
Log.d("life_cycle","F onStart aaa")
super.onStart()
}
override fun onResume() {
Log.d("life_cycle","F onResume aaa")
super.onResume()
}
override fun onStop() {
Log.d("life_cycle","F onStop aaa")
super.onStop()
}
override fun onDestroyView() {
Log.d("life_cycle","F onDestroyView aaa")
super.onDestroyView()
}
override fun onDetach() {
Log.d("life_cycle","F onDetach")
super.onDetach()
}
}
사용방법
방법1. Layout.xml에 <fragment> 컴포넌트를 명시적으로 작성해 놓는다. 정적.
방법2. 소스코드에서 fragment객체를 생성해서 xml에 동적으로 추가한다.
방법1. Layout.xml 파일에 명시적으로 <fragment> 작성
- 원하는 Fragment class 파일과 xml 파일을 생성한다.
- Activity 안에 fragment를 포함시킬 자리에 <fragment> 컴포넌트를 넣는다.
- 화면 구현을 위한 동작을 Activity파일에 작성한다.
- Activity.xml 파일에 있는 <fragment>컴포넌트에 속성 name 값을 fragment kotlin(or java)파일의 경로로 지정한다.
//activity_layout.xml에 선언되어 있는 <fragment> 컴포넌트
<fragment
android:id="@+id/frag1"
android:layout_width="match_parent"
android:layout_height="300dp"
android:name="com.example.myapplication.Fragment1"
>
</fragment>
//fargment 파일 (fragment1.kt)
package com.example.myapplication
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class Fragment1:Fragment() {
override fun onAttach(context: Context) {
Log.d("life_cycle","F onAttach aaa")
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("life_cycle","F onCreateView aaa")
//fragment가 interface를 처음으로 그릴 때 호출
//inflator 뷰를 그리는 역할
// container fragment를 붙일 부모 뷰
return inflater.inflate(R.layout.fragment1, container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d("life_cycle","F onViewCreated aaa")
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.d("life_cycle","F onActivityCreated aaa")
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
Log.d("life_cycle","F onStart aaa")
super.onStart()
}
override fun onResume() {
Log.d("life_cycle","F onResume aaa")
super.onResume()
}
override fun onStop() {
Log.d("life_cycle","F onStop aaa")
super.onStop()
}
override fun onDestroyView() {
Log.d("life_cycle","F onDestroyView aaa")
super.onDestroyView()
}
override fun onDetach() {
Log.d("life_cycle","F onDetach")
super.onDetach()
}
}
//fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#654321">
</LinearLayout>
방법2. 소스코드에서 fragment인스턴스를 생성해 xml에 동적으로 추가.
- 원하는 fragment class와 xml을 작성한다.
- Activity class에서 fragmentManager를 통해 fragment를 제어하도록 정의한다.
//Activity에서 fragment를 동적으로 동작
findViewById<Button>(R.id.fragBtn).setOnClickListener {
val frag1 : Fragment1 = Fragment1()
val fragmentManager:FragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragContainer, frag1)
fragmentTransaction.commit()
//commit() 시스템이 알아서 실행
//commitnow() 바로 실행
}
//Activity에서 fragment를 동적으로 삭제
findViewById<Button>(R.id.fragCABtn).setOnClickListener {
val fragmentManager:FragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
//detach(frag1) 으로 제거시 다시 붙이기가 안된다.
//remove는 제거 후 다시 붙이기도 가능하다.
fragmentTransaction.remove(frag1)
fragmentTransaction.commit()
}
Activity에서 Fragment로 data 전송
Android에서 제공하는 bundle 객체를 이용해서 key-value형식으로 데이터를 전송할 수 있다.
- bundle 인스턴스 생성
- bundle 인스턴스 에 key-value 형식으로 값을 바인딩.
- 전달하려는 fragment 인스턴스 arguments 필드에 bundle 객체를 바인딩 한다.
// activity.ko 파일에서
// bundle 타입 객체를 생성한다.
val bundle:Bundle = Bundle()
// bundle 타입 객체에 키-값을 묶는다.
bundle.putString("hello","hello")
// fragment 객체에 arguments 필드에 번들을 대입하는 것으로 해당 fragment에서 사용할 수 있다.
frag1.arguments = bundle
fragment 객체에서는 argument필드에서 해당 값을 꺼내 쓸 수 있다.
단 null을 주의해야 한다.
// fragment1.ko 파일
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.d("life_cycle","F onActivityCreated aaa")
val data = arguments?.getString("hello")
super.onActivityCreated(savedInstanceState)
}
동적으로 fragment를 추가할 경우 주의해야 하는 점
fragment class 인스턴스 생성시 파라미터를 전달하거나, 입력값이 필요할 경우가 있다.
이런 경우 동적으로 fragment를 생성하면서 값을 전달하면 되는데, 만약 xml에 명시적으로 해당 fragment를 미리 선언해 사용하고 있다면 전달값이 없이 activity가 생성될때 fragment를 생성하려 하기
// 만약 fragment1이 activity.xml에 포함이 되어 있다면, activity가 생성되면서
// fragment1 객체를 생성하려 하지만, onActivityCreated() 메서드가 실행될 때 전달 받은
// arguments를 찾을 수가 없어서 에러가 발생한다.
// 왜냐면 해당 arguments는 런타임시 전달되는 값이기 때문이다.
// 따라서 activity.xml 상에는 fragment1을 명시적으로 선언해 놓지 않아야 한다.
// activity.xml 상에서 fragment를 명시해놓은 경우.
// activity가 생성되면서 바로 fragment 객체도 생성하려 한다.
<fragment
android:id="@+id/frag1"
android:layout_width="match_parent"
android:layout_height="300dp"
android:name="com.example.myapplication.Fragment1"
>
</fragment>
Fragment에서 Activity로 데이터 전송
Activity에서 fragment로 데이터를 전송할 땐 bundle을 지원하는것과 달리 반대로는 직접 작성해야한다.
- fragment에서 Listener 인터페이스를 선언한다.
- lateinit 변수로 해당 리스너 변수를 선언한다. (생성, 초기화 x)
- onAttach() 메서드 내에서 2에서 선언한 변수에 context를 캐스팅해 넣는다.
- onViewCreated() 메서드 안에서 리스너 객체를 사용해 선언해 놓은 메서드 인자값으로 데이터를 넣어준다.
- activity 파일에서 fragment에 선언되어 있는 Listener 인터페이스를 구현하고 해당 메서드에서 인자값을 받아 데이터를 사용한다.
// Fragment.ko 에서 인터페이스 선언
interface OnDataPassListener{
fun onDataPass(data: String?)
}
// 해당 인터페이스 변수 선언
lateinit var dataPassListener: OnDataPassListener
// as(casting) 으로 위에 선언한 변수에 context를 대입해준다.
override fun onAttach(context: Context) {
super.onAttach(context)
dataPassListener = context as OnDataPassListener
}
// Listener 객체로 메서드를 호출하면서 전달할 데이터를 인자로 넣어준다.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.fragSubBtn).setOnClickListener {
dataPassListener.onDataPass("Good bye")
}
}
// Activity.ko 파일에서 Fragment에 선언된 Interface인 OnDataPassListener를 구현한다.
// 해당 인터페이스에 있는 onDataPass() 메서드를 구현하면서 인자로 data를 받는다.
class FragmentActivity : AppCompatActivity() , Fragment1.OnDataPassListener{
override fun onDataPass(data: String?) {
Log.d("pass",data.toString())
}
}
'Android > Android기본' 카테고리의 다른 글
Resource (0) | 2021.11.24 |
---|---|
Binder, Binding (0) | 2021.11.24 |
Context (0) | 2021.11.24 |
Thread (0) | 2021.11.24 |
Permission (0) | 2021.11.24 |
Uploaded by Notion2Tistory v1.1.0