뷰를 이용한 화면 구성 - 뷰 클래스

이윤설·2024년 8월 24일
0

6-2 뷰 클래스

뷰 클래스의 기본 구조

안드로이드에서 화면을 만들어 표시하는 컴포넌트는 액티비티이다.
액티비티가 실행되면서 뷰 클래스를 이용해 화면을 구성한다.

뷰 객체의 계층 구조


액티비티 화면을 구성할 때 사용하는 클래스는 모두 View의 하위 클래스다.
따라서 화면 구성과 관련한 클래스를 통칭하여 View Class라고 한다.

각 클래스의 역할

  • View: 모든 뷰 클래스의 최상위 클래스
  • ViewGroup: 자체 UI는 없어서 화면에 아무것도 출력을 하지 않는다. 다른 뷰 여러개를 묶어서 제어할 목적으로 사용한다.
  • TextView: 특정 UI를 출력할 목적으로 사용하는 클래스, 문자열을 출력하는 뷰

레이아웃 클래스

<?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:orientation="vertical">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BUTTON1" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BUTTON2" />

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

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BUTTON3" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BUTTON4" />
    </LinearLayout>
</LinearLayout>
  • 레이아웃 클래스만 작성하면 아무것도 화면에 출력하지 않는다.
<?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:orientation="vertical">

여기까지는 레이아웃을 정의한 것이다. 이는 마치 그릇만 있고 아무 음식도 담기지 않은 상태와 같다. 밥을 먹으려면 음식을 담아야 한다.
뷰가 곧 음식이 되며, 레이아웃 안에 뷰를 담아야 한다.

  • 레이아웃 중첩

위 코드를 적용하면 위와 같이 화면에 표시된다.
이는 레이아웃 중첩을 활용한 것이다.
뷰의 계층 구조는 레이아웃 객체는 중첩해서 복잡하게 구성할 수도 있다.
버튼 4개를 하나의 레이아웃에 추가할 수도 있지만, 이처럼 중첩해서 구성할 수도 있다.
빨간색, 노란색 모두 레이아웃이다.

화면을 구현하는 프론트엔드 애플리케이션을 개발할 때는 여러 객체를 계층으로 묶거나
레이아웃처럼 컨테이너 역할을 하는 클래스를 이용해 계층을 중첩하는 구조를 자주 사용한다. 따라서 이를 잘 기억하도록 하자.

레이아웃 XML의 뷰를 코드에서 사용하기

화면 구성을 레이아웃 XML 파일에 작성하고, 액티비티에서 setContentView() 함수로 XML 파일을 지정하면 화면을 출력한다.

그런데 때로는 이렇게 XML에 선언한 뷰 객체를 코드에서 사용해야 할 때가 있다.
이때 사용하는 속성이 id다.
id는 꼭 지정해야 하는 필수속성은 아니며, 레이아웃 XML에 선언한 뷰를 구별할 필요가 없을 때는 생략해도 좋다. 단, id는 유일해야 한다.

// id 부여
<TextView
  android:id="@+id/text1"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="hello" />

이처럼 XML에 id 속성을 추가하면 자동으로 R.java 파일에 상수 변수로 추가된다.
id 속성값은 "@+id/text1" 형태로 추가하는데 XML 속성값이 @로 시작하면 R.java를 의미한다. 따라서 이 표현식은 R.java에 text1이라는 상수 변수를 추가하라는 의미다.

// XML 화면 출력
setContentView(R.layout.activity_main)
// id 값으로 뷰 객체 획득
val textView1: TextView = findViewById(R.id.text1)

val textView1: TextView = findViewById(R.id.text1)를 작성하지 않아도 "hello"는 출력되나요?

"hello"는 출력된다.

setContentView(R.layout.activity_main)를 호출하면 activity_main.xml에 정의된 모든 뷰가 화면에 표시된다.
여기에는 "hello"라는 텍스트를 가진 TextView도 포함되므로 이 TextView는 화면에 "hello"를 출력한다.

"hello"가 출력되는데 굳이 코드로 작성하는 이유가 뭡니까?

  • 뷰 참조와 조작
val textView1: TextView = findViewById(R.id.text1)
textView1.text = "New Text"

findViewById(R.id.text1)는 XML 레이아웃 파일에서 정의된 TextView 객체를 코드에서 참조할 수 있게 해준다. 이 참조를 통해 코드에서 해당 TextView의 속성이나 동작을 조작할 수 있다.

예를 들어, 텍스트를 동적으로 변경하거나, 클릭 이벤트를 처리하거나, 뷰의 가시성을 조절하는 등의 작업을 할 수 있다.

  • 이벤트 핸들링, UI 조작:
    버튼 클릭, 텍스트 입력 등 사용자 상호작용 또는 뷰의 속성(예: 색상, 크기, 텍스트 등)을 처리하기 위해 뷰의 참조가 필요하다.

뷰의 크기를 지정하는 방법

<?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="#ffff00"
    android:orientation="vertical">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:backgroundTint="#0000ff"
        android:text="BUTTON1" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:backgroundTint="#ff0000"
        android:text="BUTTON2" />
</LinearLayout>
  • LinearLayout의 가로는 match_parent, BUTTON1의 가로는 wrap_content, BUTTON2의 가로는 match_parent다.
  • Button 객체의 상위 계층은 LinearLayout이고, LinearLayout이 확보하는 크기는 기기의 전체 화면 크기가 된다.
  • 따라서 첫번째 버튼의 가로 크기는 자신의 콘텐츠를 감쌀 정도의 크기이며 두번째 버튼의 가로 크기는 부모, 즉 LinearLayout의 가로 크기이다.

수치 대신 wrap_content, match_parent를 사용하자!

뷰의 크기를 수치로 지정하는 것보다 wrap_content, match_parent과 같은 설정값을 사용하는 것이 좋다. 왜냐하면 안드로이드 기기의 크기가 다양해서 호환성을 생각해야 하기 때문이다.

뷰의 간격 설정

1. Padding

  • 정의: padding은 뷰의 내부와 경계(border) 사이의 간격을 설정한다. 즉, 뷰의 내용(content)과 뷰의 경계 사이에 여유 공간을 추가한다.

  • 속성:

    • android:padding : 모든 방향에 동일한 패딩을 설정합니다.
    • android:paddingLeft : 왼쪽 패딩을 설정합니다.
    • android:paddingTop : 위쪽 패딩을 설정합니다.
    • android:paddingRight : 오른쪽 패딩을 설정합니다.
    • android:paddingBottom : 아래쪽 패딩을 설정합니다.
  • 용도: 뷰의 콘텐츠와 경계 간에 여유 공간을 추가하여 시각적으로 콘텐츠가 경계에 너무 가까워 보이지 않도록 한다.

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="Hello, World!" />

2. Margin

  • 정의: margin은 뷰의 외부와 다른 뷰나 부모 레이아웃 사이의 간격을 설정한다. 즉, 뷰와 주변 뷰 또는 레이아웃 경계 사이의 여유 공간을 추가한다.

  • 속성:

    • android:layout_margin : 모든 방향에 동일한 마진을 설정합니다.
    • android:layout_marginLeft : 왼쪽 마진을 설정합니다.
    • android:layout_marginTop : 위쪽 마진을 설정합니다.
    • android:layout_marginRight : 오른쪽 마진을 설정합니다.
    • android:layout_marginBottom : 아래쪽 마진을 설정합니다.
  • 용도: 뷰와 주변 요소들 사이에 여유 공간을 추가하여 뷰가 너무 가까이 붙어 있지 않도록 한다.

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="Hello, World!" />

    위 예제는 TextView의 모든 방향에 16dp의 마진을 추가하여 TextView와 주변 요소들 사이에 여유 공간을 만든다.

예제

<?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:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:backgroundTint="#0000ff"
        android:text="BUTTON1"
        android:padding="30dp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:backgroundTint="#ff0000"
        android:text="BUTTON2"
        android:paddingBottom="50dp"
        android:layout_marginLeft="50dp"/>
</LinearLayout>

padding과 margin이 적용된 것을 그림을 통해 알 수 있다.

그나저나 이전 예제와는 다르게 Button2가 Button1와 동일한 열에 위치한다.
Button2가 Button1과 같은 줄에 위치하는 이유는 LinearLayout의 orientation 속성 때문이다.

android:orientation="horizontal": 이 속성은 LinearLayout 내부의 자식 뷰들을 수평으로 배치하도록 지정한다. 즉, 자식 뷰들이 가로 방향으로 나란히 배치된다.

dp (Density-independent Pixels)

정의:

dp는 Density-independent Pixels의 약자로, 화면 밀도와 관계없이 일관된 크기를 보장하기 위해 사용하는 단위다. 안드로이드에서 UI 요소를 정의할 때 가장 많이 사용되는 단위이다.

특징:

밀도 독립적: dp는 화면의 밀도에 따라 자동으로 조정된다. 즉, 화면의 픽셀 밀도(dpi)가 높든 낮든 상관없이, dp로 지정된 크기는 동일하게 표시된다.

일관성 유지: 다양한 화면 밀도와 해상도에서 UI 요소가 예상한 크기로 표시되도록 도와준다.

cm와 비교

1dp는 160dpi 기준으로 약 1픽셀
1cm는 160dpi 기준으로 약 63픽셀

-> 1dp는 눈에 안보일 정도로 매우 작음

뷰의 표시 여부 설정

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

        // 버튼을 참조
        val button1 = findViewById<Button>(R.id.button1)
        val button2 = findViewById<Button>(R.id.button2)
        val button3 = findViewById<Button>(R.id.button3)

        // 버튼 1 클릭 시 메시지 출력
        button1.setOnClickListener {
            // 예: 버튼 1 클릭 시 버튼 2를 보이게 하기
            button2.visibility = View.VISIBLE
        }

        // 버튼 2 클릭 시 버튼 3을 보이게 하기
        button2.setOnClickListener {
            button3.visibility = View.VISIBLE
        }

        // 버튼 3 클릭 시 버튼 1을 숨기기
        button3.setOnClickListener {
            button1.visibility = View.GONE
        }
    }
}

  • visible: 보이게 한다.
  • invisible: 버튼의 공간을 유지하면서 보이지 않게 한다.
  • gone: 버튼의 공간을 아예 차지하지 않게 한다.

위 화면은 버튼 3을 눌러서 버튼 1이 사라진 모습이다.
gone 속성이여서 기존 버튼 1의 자리를 유지하지 않는다.

cf. 뷰의 속성을 설정하려면 세터 함수를 호출해야 하지 않나요? 즉, 위의 예에서 visibility 속성을 조정하려면 setVisibility() 함수를 호출해야 할 것 같은데요?

뷰의 속성을 변경할 때 setter를 사용해도 무방하다. 즉 아래처럼 작성해도 된다.
targetView.setVisibility(View.VISIBLE)

그런데 코틀린의 변수는 자바와 다르게 field가 아니라 property다.
즉, 변수에 setter, getter가 내장돼 있다.
그러므로 코틀린에서 자바의 세터/게터 함수를 이용할 때 변수처럼 이용해도 내부적으로 세터/게터가 호출된다.

예를 들어 다음처럼 작성된 자바 클래스가 있다고 가정하자.

public class User {
  private String name;
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

User의 name 변수값을 코틀린에서 사용할 때는 다음처럼 사용해도 된다.

user.setName("ryan")
println(user.getName())

하지만 자바에서 선언한 세터/게터 함수도 코틀린에서는 property로 사용할 수 있다.

user.name = "ryan"
println(user.name)

변수에 직접 접근한 것처럼 보이지만 실제론 세터/게터가 호출된다.


정리

1. 뷰 클래스

  • View: 모든 뷰 클래스의 최상위 클래스. 화면에 실제로 보여지는 요소를 정의합니다.
  • ViewGroup: 다른 뷰를 포함할 수 있는 컨테이너 역할을 하는 클래스. 예: LinearLayout, RelativeLayout.
  • TextView: 문자열을 화면에 출력하는 뷰 클래스.

2. 레이아웃 클래스

  • LinearLayout: 자식 뷰를 수직 또는 수평으로 배치합니다. android:orientation 속성으로 방향을 설정합니다.

3. 뷰의 속성 설정

  • visibility: 뷰의 표시 여부를 설정합니다.
    • View.VISIBLE: 뷰를 보이게 합니다.
    • View.INVISIBLE: 뷰를 숨기지만 레이아웃 공간은 유지합니다.
    • View.GONE: 뷰를 숨기고 레이아웃 공간도 제거합니다.

4. 뷰 참조와 조작

  • 참조: findViewById(R.id.view_id)를 사용하여 XML에서 정의된 뷰를 코드에서 참조합니다.
  • 조작: 참조된 뷰의 속성을 코드에서 변경할 수 있습니다. 예를 들어, textView.text = "New Text"TextView의 텍스트를 변경합니다.

5. 뷰의 크기 지정

  • wrap_content: 뷰가 자신의 콘텐츠에 맞춰 크기를 조정합니다.
  • match_parent: 부모 뷰의 크기에 맞춰 뷰 크기를 조정합니다.

6. 뷰의 간격 설정

  • Padding: 뷰의 내부 여백을 설정합니다. android:padding, android:paddingLeft, android:paddingTop, android:paddingRight, android:paddingBottom 속성을 사용합니다.
  • Margin: 뷰의 외부 여백을 설정합니다. android:layout_margin, android:layout_marginLeft, android:layout_marginTop, android:layout_marginRight, android:layout_marginBottom 속성을 사용합니다.

7. dp (Density-independent Pixels)

  • dp: 화면 밀도에 관계없이 일관된 크기를 보장하는 단위입니다. 1dp는 화면 밀도에 따라 픽셀 수가 다르며, 일반적으로 160dpi 기준으로 약 1픽셀입니다.

8. 코드에서 뷰 속성 변경

  • Kotlin Property 접근: 코틀린에서는 변수처럼 프로퍼티를 사용하여 속성을 조작할 수 있습니다. 예: textView.text = "New Text"는 내부적으로 setText() 메서드를 호출합니다.

이 개념들을 이해하면 안드로이드 UI를 보다 효과적으로 설계하고 조작할 수 있습니다.

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글