📱 Android

[Android] 하나의 화면에 다양한 UI 블록을 구성하는 3가지 방식

콩드로이드 2025. 7. 28. 21:55

앱 만들다 보면 한 화면에 여러 종류의 UI를 스크롤 되게 구성해야 할 일이 자주 생기는데,
이런 경우에 사용하는 3가지 방식에 대해 알아보겠습니다
 


 

 FragmentContainerView 방식 (Fragment 삽입)

 

 특징

  • 각 UI 블록을 Fragment로 나눠서 FragmentContainerView에 삽입
  • 고정된 레이아웃이나, 특정 위치에 동적으로 fragment를 붙이는 방식

 

 장점

  • 각 블록마다 생명주기, ViewModel 분리 가능
  • 로직이 복잡하거나 독립된 기능일 경우 구조화에 유리

 

 단점

  • fragment가 많아지면 성능 저하 발생
  • ScrollView 안에 fragment 여러 개 → 비추천 구조 (nested fragment 문제 등)

 
[layout]

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/bannerFragmentContainer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/listFragmentContainer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</ScrollView>

 
[activity]

  supportFragmentManager.beginTransaction()
            .replace(R.id.bannerFragmentContainer, BannerFragment())
            .commit()

  supportFragmentManager.beginTransaction()
            .replace(R.id.listFragmentContainer, ItemListFragment())
            .commit()

 

addLayout()방식 

 
 

 특징

  • API 응답을 순회하며 뷰를 직접 생성해서 addView()로 붙이는 방식
  • LinearLayout이나 ConstraintLayout 안에서 뷰 조합

 

 장점

  • 구조가 단순하고 유연함 (서대로 뷰만 붙이면 됨)
  • 빠르게 화면 만들기 좋고, 레이아웃 순서를 동적으로 바꾸기도 쉬움

 

 단점

  • 뷰 재사용이 안 되기 때문에 수가 많아지면 성능 저하
  • 코드가 복잡해지기 쉬움 (뷰, 데이터, 로직이 뒤엉김)

 
[layout]

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/container"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</ScrollView>

 
 
[activity]

class MainActivity : AppCompatActivity() {

    private lateinit var container: LinearLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        container = findViewById(R.id.container)

        val titles = listOf("Title 1", "Title 2", "Title 3")

        titles.forEach { title ->
            val textView = TextView(this).apply {
                text = title
                textSize = 20f
                setPadding(16, 16, 16, 16)
            }
            container.addView(textView)
        }
    }
}

 

 RecyclerView + ViewType 방식 

 
 

 특징

  • 다양한 UI 블록을 ViewType으로 분기해서 하나의 RecyclerView로 구성
  • ViewHolder 기반으로 뷰 재사용 가능

 
 

 장점

  • 성능 최고 (뷰 재사용, ViewHolder 풀링)
  • 각 타입별로 클래스 분리 가능 → 유지보수, 테스트 용이
  • 구조화된 패턴으로 확장성 높음

 
 

 단점

  • 초기 설계가 조금 번거롭다 (ViewType 정의, sealed class 설계..)

 
[layout]

<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

 
[adapter]

sealed class UiModel {
    data class Title(val text: String) : UiModel()
    data class Image(val url: String) : UiModel()
}

class MultiViewAdapter(private val items: List<UiModel>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun getItemViewType(position: Int): Int {
        return when (items[position]) {
            is UiModel.Title -> 0
            is UiModel.Image -> 1
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            0 -> TitleViewHolder(LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false))
            else -> ImageViewHolder(ImageView(parent.context))
        }
    }

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[position]) {
            is UiModel.Title -> (holder as TitleViewHolder).bind(item.text)
            is UiModel.Image -> (holder as ImageViewHolder).bind(item.url)
        }
    }

    class TitleViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        fun bind(text: String) {
            (itemView as TextView).text = text
        }
    }

    class ImageViewHolder(private val imageView: ImageView) : RecyclerView.ViewHolder(imageView) {
        fun bind(url: String) {
            imageView.setImageResource(android.R.drawable.ic_menu_gallery)
        }
    }
}

 
[activity]

  val recyclerView: RecyclerView = findViewById(R.id.recyclerView)

        val items = listOf(
            UiModel.Title("Hello"),
            UiModel.Image("https://example.com/image1.jpg"),
            UiModel.Title("World"),
            UiModel.Image("https://example.com/image2.jpg")
        )

        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = MultiViewAdapter(items)




 
각각 3가지 타입이 있는데, 
 

  • 기능 단위로 분리하고 싶고, 각각 UI가 독립적이면 → FragmentContainerView
  • 뷰 몇 개 순서대로 붙이기만 하면 되면 → addLayout()
  • 여러 UI 타입이 섞여 있고 리스트가 길어질 수 있으면 → RecyclerView + ViewType

이 와중에 FragmentContainerView랑 recyclerview가 각각 언제 사용되면 좋은지 좀 헷갈렸는데, 
 

  • 이 블록이 미니 화면처럼 동작하고 독립적으로 동작해야 한다? :  FragmentContainerView
    (ex. 영상 플레이어, 지도 등)
  • 그냥 여러 UI를 스크롤되게 보여주고 성능도 중요하다? : RecyclerView + ViewType
    (ex. 홈피드, 뉴스 피드, 상품 리스트 등)

이렇게 생각하면 이해가 편하더라구요!