목적
1. 복잡한 UI의 모듈화를 통한 유연한 관리
2. Activity를 기능별 프레그먼트로 분할하여 유연하게 관리
효과
1. 모듈화 : 작은 단위로 효율적으로 관리, 복잡한 UI를 가진 앱 개발에 유용
2. 재사용성 : 여러 액티비티에서 재사용 가능, 코드 중복을 줄이고 개발 시간을 단축시키는데 도움이 된다.
3. 유연함 : 액티비티의 내에서 여러 프레그먼트를 유연하게 사용하여, 화면의 크기와 비율에 맞는 UI를 제공
아래는 안드로이드 스튜디오에서 기본 제공하는 프래그먼트 구조들이다.
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textviewFirst.text = "First Fragment"
binding.buttonFirst.setOnClickListener {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class FirstFragment : Fragment() { : Fragment() 라는 클래스를 상속한다.
_binding = FragmentFirstBinding.inflate(inflater, container, false) : FragmentFirstBinding.xml 이라는 파일을 참조한다.
override fun onViewCreated : 아래에 바인딩 된 동작들이 있다.
<!-- fragment_first.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".FirstFragment">
<Button
android:id="@+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next"
app:layout_constraintBottom_toTopOf="@id/textview_first"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textview_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/lorem_ipsum"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_first" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout : 가장 큰 레이아웃
<Button, <TextView : 그 아래에 버튼과 텍스트가 하나씩 있다.
FragmentContainnerView를 추가한다.android:name 속성에 Fragment의 전체 클래스 이름을 지정한다.<!-- activity_main.xml -->
<androidx.fragment.app.FragmentContainerView
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:name="com.yourpackage.FirstFragment" />
또는 동적으로 추가한다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Fragment를 동적으로 추가하는 코드
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FirstFragment())
.commit()
}
}
}
ViewPager2와 FragmentStateAdapter를 사용해서 프레그먼트 페이징을 구현해보자
액티비티 XML파일에 ViewPager2 레이아웃 추가
<!-- activity_main.xml -->
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FragmentStateAdapter 만들기
class NameOfFragmentStateAdapter(activity: AppCompatActivity) : FragmentStateAdapter(activity) {
override fun getItemCount() = 2 //추가 하고 싶은 Fragment 갯수
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> FirstFragment() // 각 Position 별 Fragment 연결
else -> SecondFragment()
}
}
}
class NameOfFragmentStateAdapter(activity: AppCompatActivity) : FragmentStateAdapter(activity) { : FragmentStateAdapter를 상속받는 어댑터(클래스) 생성
override fun getItemCount() : 프래그먼트 갯수
verride fun createFragment(position: Int): Fragment { : 인덱스마다 보여줄 프래그먼트 설정
ViewPager2에 adapter 연결
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
with(binding) {
setContentView(root)
// viewPager에 FragmentStateAdapter를 상속해서 만든 Adapter 연결
viewPager.adapter = NameOfFragmentStateAdapter(this@MainActivity)
}
}
}
페이징 시 인디케이터 닫기
// build.gradle
dependencies {
implementation("me.relex:circleindicator:2.1.6")
<!-- activity_main.xml -->
<me.relex.circleindicator.CircleIndicator3
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginBottom="23dp"
app:ci_drawable="@drawable/indicator_black_circle"
app:ci_drawable_unselected="@drawable/indicator_gray_circle"
app:ci_height="8dp"
app:ci_margin="10dp"
app:ci_width="8dp"
app:layout_constraintBottom_toBottomOf="@+id/view_pager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
with(binding) {
setContentView(root)
val adapter = NameOfFragmentStateAdapter(this@MainActivity)
viewPager.adapter = adapter
// 위의 코드 아래에 추가
indicator.setViewPager(viewPager)
adapter.registerAdapterDataObserver(indicator.adapterDataObserver)
}
프래그먼트라는건 결국 객체지향과 방향성이 비슷한것같다. 효율을 따지다보니 객체지향처럼 된건지 아니면 객체지향이라는게 이미 나온 이후에 코틀린이 나왔기 때문에 안드로이드 개발에도 이러한 방식이 추가된 건지는 모르겠지만, "각자의 역할을 수행하는 것들"로 조각 내서 관리하는 방법이 매우 효율적인 건 맞는 것 같다.
사용자가 볼때는 차이가 있다. 객체지향은 개발자의 유지보수를 좋게 하기 위한 것이며 사용자는 객체지향적으로 만들어진 프로그램과 아닌 것의 차이점을 알기 힘들다. 프래그먼트는 프로그래머의 유지보수 능력도 좋아지지만, 사용자가 보기에 편해진다는 특징이 있다,