Android 앱개발 공부/TIL(Today I Learned)

[Android] TIL 35일차

bunny code 2024. 7. 12. 22:55

프래그먼트(Fragment)


* 프래그먼트란?

  • 프래그먼트는 한 개의 액티비티 화면 안에서 특정 영역만 교체하는 것(액티비티와 분리되어 독립적으로 동작 불가능)
  • 여러 개의 프래그먼트를 하나의 액티비티에 조합할 있고, 하나의 프래그먼트를 여러 액티비에서 재사용할 수도 있음

* 액티비티와 프래그먼트 비교

Activity : 시스템의 액티비티 매니저에서 인텐트(intent)를 해석해 액티비티 간 데이터 전달

Fragement : 액티비티의 프래그먼트 매니저에서 메소드(method)로 프래그먼트간 데이터 전달

 

* 프래그먼트 사용 이유

  1. Activity 화면을 계속 넘기는 것보다 Fragement 일부만 바꾸는 자원 이용량이 적어 속도가 빠르기 때문
  2. 복잡도 감소
  3. 재사용할 있는 레이아웃을 분리해서 관리 가능

* 프래그먼트 정의 : Activity 동일하게 하나의 코틀린 소스와 하나의 xml파일로 정의

 

 

gradle -> dependencies에 fragment 설정 추가

val fragment_version = "1.8.1"

implementation("androidx.fragment:fragment-ktx:$fragment_version")

 

Kotlin 소스 파일 생성

class FirstFragment : Fragment() {  
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

 

 

* 프래그먼트를 액티비티 레이아웃 파일에 추가하는 방법은 총 두 가지

 

1. 프래그먼트 레이아웃 안에 선언하여 정적으로 추가

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
    <fragment
        android:name="com.skmns.fragmentbasic.FirstFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fragment" />
</LinearLayout>

 

2. Kotlin 코드에서 동적으로 프래그먼트 추가하기

supportFragmentManager.commit {
            replace(R.id.frameLayout, frag)
            setReorderingAllowed(true)
            addToBackStack("")
        }
  • supportFragmentManager : Fragment를 추가, 삭제 같은 작업을 할 수 있게 해주는 매니저
  • replace : 어느 프레임 레이아웃에 띄울것이냐, 어떤 프래그먼트냐
  • setReorderingAllowed : 애니메이션과 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화
  • addToBackStack : 뒤로가기 버튼 클릭시 다음 액션 (이전 fragment로 가거나 앱이 종료되거나)

(보통은 정적보단 동적을 많이 사용)

 

 

 

프래그먼트 데이터 전달


* 프래그먼트 데이터 전달 방식은 총 3가지

  1. Activity에서 Fragment 데이터 전달
  2. Fragment에서 Fragment 데이터 전달
  3. Fragment에서 Activity 데이터 전달

 

1. Activity에서 Fragement로 데이터 전달

* 데이터 전달하는 Activity(MainActivity)

class MainActivity : AppCompatActivity() {
    private val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.run {
            fragment1Btn.setOnClickListener {
                val dataToSend = "Hello First Fragment! \n From Activity"
                val fragment = FirstFragment.newInstance(dataToSend)
                setFragement(fragment)
            }
            fragment2Btn.setOnClickListener {
                val dataToSend = "Hello Second Fragment! \n From Activity"
                val fragment = SecondFragment.newInstance(dataToSend)
                setFragement(fragment)
            }
        }

        setFragement(FirstFragment())
    }

    private fun setFragement(frag : Fragment){
        supportFragmentManager.commit {
            replace(R.id.frameLayout, frag)
            setReorderingAllowed(true)
            addToBackStack("")
        }
    }
}

 

1 변수 dataTosend에는 출력하고자 하는 문구 작성
2 변수 fragment에는 dataTosend를 프래그먼트(FirstFragment.newInstance)로 넘겨주는 내용 작성
3 setFragment를 통해 변수 fragment가 프래그먼트에 넘겨짐
4 밑에 fragment2Btn도 동일하게 진행

 

 

* 데이터 전달받는 Fragment(FirstFragment)

private const val ARG_PARAM1 = "param1"

class FirstFragment : Fragment() {
    private val binding by lazy {FragmentFirstBinding.inflate(layoutInflater)}
    private var param1: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //1. Activity -> Fragment
        binding.tvFrag1Text.text = param1
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            FirstFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }
}

 

1 액티비티(MainActivity)에서 변수 fragment가 newInstance로 들어옴
2 newInstance의 파라미터인 param1에 변수 fragment가 들어옴
3 param1에 변수 fragment가 담겨진 채로 밑에 onViewCreated에 들어감
4 tvFrag1Text의 text에 param1(=fragment) 내용이 들어감
5 SecondFragment도 FirstFragment와 동일하게 작성

 

* Fragment을 생성할 때 binding과 onViewCreated를 제외한 나머지는 자동 생성되니 그 점 참고

 

 

2.  Fragment에서 Fragement로 데이터 전달

* 데이터를 전달하는 Fragment(FirstFragment ->)

private const val ARG_PARAM1 = "param1"

class FirstFragment : Fragment() {
    private val binding by lazy {FragmentFirstBinding.inflate(layoutInflater)}
    private var param1: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        //2. FirstFragment -> SecondFragment
        binding.btnGofrag2.setOnClickListener {
            val dataTosend = "Hello Fragment2! \n from Fragment1"
            val fragment2 = SecondFragment.newInstance(dataTosend)
            requireActivity().supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, fragment2)
                .addToBackStack(null)
                .commit()
        }
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            FirstFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }
}

 

1 변수 dataTosend에는 출력하고자 하는 문구 작성
2 변수 fragment2에는 dataTosend를 프래그먼트(SecondFragment.newInstance)로 넘겨주는 내용 작성
3 서포트프래그먼트매니저를 이용하여 변수 fragment2를 이동시킴

 

* 데이터를 전달받는 Fragment(-> SecondFregment)

private const val ARG_PARAM1 = "param1"

class SecondFragment : Fragment() {
    private var param1: String? = null

    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.tvFrag2Text.text = param1
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            SecondFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}
1 코드는 전반적으로 1번 유형과 동일
2 binding에 관한 변수 선언 추가
3 맨 아래에 onDestroyView() 내용 추가

 

 

3.  Fragment에서 Activity로 데이터 전달

interface를 하나 생성해야 함

 

* 인터페이스(FragmentDataListener)

interface FragmentDataListener {
    fun onDataReceived(data : String)
}

 

* 데이터를 전달하는 Fragment(SecondFragment)

private const val ARG_PARAM1 = "param1"

class SecondFragment : Fragment() {

    private var param1: String? = null
    private var listener: FragmentDataListener? = null
    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!

    override fun onAttach(context: Context) {
        super.onAttach(context)

        if (context is FragmentDataListener){
            listener = context
        } else {
            throw RuntimeException("$context must implement FragmentDataListener")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //2. FirstFragment -> SecondFragment로 데이터 전달
        binding.tvFrag2Text.text = param1

        binding.btnSendActivity.setOnClickListener {
            val dataTosend = "Hello from SecondFragment!"
            listener?.onDataReceived(dataTosend)
        }
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            SecondFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        listener = null
    }
}

 

 

* 데이터를 전달받는 Activity(MainActivity)

class MainActivity : AppCompatActivity(), FragmentDataListener {
    private val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.run {
            fragment1Btn.setOnClickListener {
                val dataToSend = "Hello First Fragment! \n From Activity"
                val fragment = FirstFragment.newInstance(dataToSend)
                setFragement(fragment)
            }
            fragment2Btn.setOnClickListener {
                val dataToSend = "Hello Second Fragment! \n From Activity"
                val fragment = SecondFragment.newInstance(dataToSend)
                setFragement(fragment)
            }
        }

        setFragement(FirstFragment())
    }

    private fun setFragement(frag : Fragment){
        supportFragmentManager.commit {
            replace(R.id.frameLayout, frag)
            setReorderingAllowed(true)
            addToBackStack("")
        }
    }

    override fun onDataReceived(data: String) {
        Toast.makeText(this,data,Toast.LENGTH_SHORT).show()
    }
}​

'Android 앱개발 공부 > TIL(Today I Learned)' 카테고리의 다른 글

[Android] TIL 37일차  (0) 2024.07.16
[Android] TIL 36일차  (0) 2024.07.15
[Android] TIL 34일차  (0) 2024.07.11
[Android] TIL 33일차  (0) 2024.07.10
[Android] TIL 32일차 - 2  (0) 2024.07.09