Lesson 5: Layouts

Hanbinยท2021๋…„ 8์›” 12์ผ
0

Teach Android Development

๋ชฉ๋ก ๋ณด๊ธฐ
5/13
post-thumbnail

๐Ÿ’ก Teach Android Development

๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ต์œก์ž๋ฃŒ๋ฅผ ์ •๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

Android Development Resources for Educators

Layouts in Android

Android devices

  • Android ๊ธฐ๊ธฐ๋Š” ๋‹ค์–‘ํ•œ ํผ ํŒฉํ„ฐ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  • ์ธ์น˜ ๋‹น ์ ์  ๋” ๋งŽ์€ ํ”ฝ์…€์ด device ํ™”๋ฉด์— ์ฑ„์›Œ์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž๋Š” device ๊ฐ„์— ์ผ๊ด€๋œ layout ์น˜์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Density-independent pixels (dp)

View์˜ ๋„ˆ๋น„, ๋†’์ด์™€ ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์˜ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•  ๋•Œ dp๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • Density-independent pixels(dp)๋Š” ํ™”๋ฉด ๋ฐ€๋„๋ฅผ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • Android view๋Š” density-independent pixels๋กœ ์ธก์ •๋ฉ๋‹ˆ๋‹ค.
  • dp = (ํ”ฝ์…€์˜ ๋„ˆ๋น„ * 160) / ํ™”๋ฉด ๋ฐ€๋„

Screen-density buckets

Android View rendering cycle

Drawing region

View margins and padding

ConstraintLayout

Deeply nested layouts are costly

  • ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ ViewGroup์—๋Š” ๋” ๋งŽ์€ ๊ณ„์‚ฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ทฐ๋Š” ์—ฌ๋Ÿฌ๋ฒˆ ์ธก์ •๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • UI ์†๋„ ์ €ํ•˜ ๋ฐ ์‘๋‹ต์„ฑ ๋ถ€์กฑ์„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ConstraintLayout์„ ์ด์šฉํ•ด ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

What is ConstraintLayout?

  • Android ๊ถŒ์ „ ๊ธฐ๋ณธ ๋ ˆ์ด์•„์›ƒ
  • ๋„ˆ๋ฌด ๋งŽ์ด ์ค‘์ฒฉ๋œ ๋ ˆ์ด์•„์›ƒ์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋™์‹œ์— ๋ณต์žกํ•œ ํ–‰๋™ ํ—ˆ์šฉ
  • ์ผ๋ จ์˜ ์ œ์•ฝ ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ ๋‚ด์—์„œ ์œ„์น˜ ์ง€์ • ๋ฐ ํฌ๊ธฐ ์กฐ์ •

What is a constraint?

๋ ˆ์ด์•„์›ƒ์ด ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๋ทฐ์˜ ์†์„ฑ์— ๋Œ€ํ•œ ๊ทœ์ œ ๋˜๋Š” ์ œํ•œ

Relative positioning constraints

์ƒ์œ„ ์ปจํ„ฐ์ด๋„ˆ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ œ์•ฝ ์กฐ๊ฑด์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
layout_constraint<SourceConstraint>_to<TargetConstraint>Of

TextView ์†์„ฑ์˜ ์˜ˆ์‹œ

app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"

์œ„์น˜ ๊ธฐ์ค€์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

Simple ConstraintLayout example

๋ถ€๋ชจ์˜ ์ •์ค‘์— TextView๋ฅผ ๋ฐฐ์น˜ํ•˜๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        ...
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Chains

  • ์„œ๋กœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ view๋ฅผ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋กœ ๋˜๋Š” ์„ธ๋กœ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค,.
  • ๋งŽ์€ LinearLayout๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Chain styles

๋‹ค์–‘ํ•œ ์ฒด์ธ ์Šคํƒ€์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ view ์‚ฌ์ด์˜ ๊ณต๊ฐ„์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

Additional topics for ConstraintLayout

Guidelines

  • ํ•˜๋‚˜์˜ ๊ฐ€์ด๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ view๋ฅผ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ˆ˜์ง๊ณผ ์ˆ˜ํ‰์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋””์ž์ธ/UX ํŒ€๊ณผ์˜ ํ˜‘์—… ๊ฐ•ํ™”.
  • ๊ธฐ๊ธฐ์— ๊ทธ๋ ค์ง€์ง€ ์•Š์Œ

Guidelines in Android Studio

์ˆ˜์ง, ์ˆ˜ํ‰ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์‚ฌ์šฉํ•˜์—ฌ view๋ฅผ ๋ฐฐ์น˜ํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

Example Guideline

์ˆ˜์ง ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์‚ฌ์šฉํ–ˆ์œผ๋ฉฐ ์‹œ์ž‘ ์œ„์น˜์—์„œ 16dp ๋–จ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.

<ConstraintLayout>
   <androidx.constraintlayout.widget.Guideline
       android:id="@+id/start_guideline"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="vertical"
       app:layout_constraintGuide_begin="16dp" />
   <TextView ...
       app:layout_constraintStart_toEndOf="@id/start_guideline" />
</ConstraintLayout>

Creating Guidelines

  • layout_constraintGuide_begin : ์‹œ์ž‘ ์œ„์น˜์— ์ง€์ •
  • layout_constraintGuide_end : ๋ ์œ„์น˜์— ์ง€์ •
  • layout_constraintGuide_percent : ๋น„์œจ๋กœ ์œ„์น˜ ์ง€์ •

Groups

  • widget ์„ธํŠธ์˜ visibility๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค
  • Group visibility๋ฅผ ์ฝ”๋“œ์—์„œ toggle ํ•  ์ˆ˜ ์žˆ๋‹ค.

Example group

constraint_referenced_ids๋ฅผ ์‚ฌ์šฉํ•ด์„œ Group ๋Œ€์ƒ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

<androidx.constraintlayout.widget.Group
    android:id="@+id/group"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="locationLabel,locationDetails"/>

Groups app code

override fun onClick(v: View?) {
    if (group.visibility == View.GONE) {
        group.visibility = View.VISIBLE
        button.setText(R.string.hide_details)
    } else {
        group.visibility = View.GONE
        button.setText(R.string.show_details)
    }
}

Data binding

Current approach: findViewById()

๋งค๋ฒˆ view ๊ณ„์ธต์„ ์ˆœํšŒํ•ฉ๋‹ˆ๋‹ค.

Use data binding instead

๋ ˆ์ด์•„์›ƒ์˜ UI ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์•ฑ์˜ data source์— ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.

Modify build.gradle file

์•ฑ ์ˆ˜์ค€์˜ build.gradle ํŒŒ์ผ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

android {
   ...
   buildFeatures {
       dataBinding true
   }
}

Add layout tag

์ ์šฉํ•  XML์„ layout tag๋กœ ๊ฐ์‹ธ์ค๋‹ˆ๋‹ค.

<layout>
    <androidx.constraintlayout.widget.ConstraintLayout>
        <TextView ... android:id="@+id/username" />
        <EditText ... android:id="@+id/password" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Layout inflation with data binding

// ์ œ๊ฑฐ
setContentView(R.layout.activity_main)

// ๋Œ€์ฒด
val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main)

// XML view ์ ‘๊ทผ ๋ฐฉ์‹
binding.username = "Melissa

Data binding layout variables

<layout> 
   <data>
       <variable name="name" type="String"/>
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout>
       <TextView
           android:id="@+id/textView"
           android:text="@{name}" />
   </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MainActivity.kt ์—์„œ binding.name = "John" ์„ค์ • ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Data binding layout expressions

XML์—์„œ ๋‹ค์–‘ํ•œ ํฌํ˜„์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ 

<layout>
   <data>
       <variable name="name" type="String"/>
   </data>

   <androidx.constraintlayout.widget.ConstraintLayout>
       <TextView
           android:id="@+id/textView"
           android:text="@{name.toUpperCase()}" />
   </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Displaying lists with RecyclerView

RecyclerView

  • ๋ฐ์ดํ„ฐ ๋ชฉ๋ก์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•œ widget
  • ์Šคํฌ๋กค ์„ฑ๋Šฅ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด item view๋ฅผ ์žฌ์‚ฌ์šฉ
  • dataset์˜ ๊ฐ item์— ๋Œ€ํ•œ list item layout์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐ ์ „ํ™˜ ์ง€์›

RecyclerView.Adapter

  • RecyclerView๊ฐ€ ํ‘œ์‹œํ•˜๋Š” data ์™€ layout์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • custom Adpater๋Š” RecyclerView.Adpater์—์„œ ํ™•์žฅ๋˜๊ณ  override๋ฉ๋‹ˆ๋‹ค.
    • override functions
      • getItenCount
      • onCreateViewHolder
      • onBindViewHolder

View recycling in RecyclerView

  • item์ด ํ™”๋ฉด ๋ฐ–์œผ๋กœ ์Šคํฌ๋กค๋˜๋ฉด ํŒŒ๊ดด๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. item์€ ์žฌ์‚ฌ์šฉ์„ ์œ„ํ•ด pool์— ๋„ฃ์Šต๋‹ˆ๋‹ค.
  • onBindViewHolder๋Š” view์— ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋ฐ”์ธ๋”ฉํ•œ ๋‹ค์Œ view๋ฅผ list์— ๋‹ค์‹œ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

Add RecyclerView to your layout

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Create a list item layout

res/layout/item_view.xml

<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content">
   <TextView
       android:id="@+id/number"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
</FrameLayout>

Create a list adapter

class MyAdapter(val data: List<Int>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

   class MyViewHolder(val row: View) : RecyclerView.ViewHolder(row) {
       val textView = row.findViewById<TextView>(R.id.number)
   }
   
   // item layout ์„ค์ • ๋ฐ ViewHolder ์ƒ์„ฑ
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
       val layout = LayoutInflater.from(parent.context).inflate(R.layout.item_view,
                    parent, false)
       return MyViewHolder(layout)
   }
   
   // holder์— ํ•„์š”ํ•œ ๋ฐ”์ธ๋”ฉ ์ž‘์—… ์ง„ํ–‰
   override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
       holder.textView.text = data.get(position).toString()
   }
   
   // list item count ์„ค์ •
   override fun getItemCount(): Int = data.size

Set the adapter on the RecyclerView

MainActivity.kt :

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

    val rv: RecyclerView = findViewById(R.id.rv)
    
    // RecyclerView layoutManager ์„ธํŒ…
    rv.layoutManager = LinearLayoutManager(this)
    
    // RecyclerView.Adapter ์„ธํŒ…
    rv.adapter = MyAdapter(IntRange(0, 100).toList())
}

0๊ฐœ์˜ ๋Œ“๊ธ€