저번시간에 우리가 원하는 UI를 만들어 봤다. 하지만!!! 나는 '코더'보단 '엔지니어'이기 때문에 궁금증을 몇개 짚고 넘어가겠다.

 

2024.01.19 - [Android] - [Kotlin]Android Studio를 이용한 간단한 퀴즈 앱 만들기 pt.1

 

저번 포스팅에서 언급됐던 res 라는 상위 폴더는 resource의 줄임말이다. 그렇다면 resource는 무엇일까? resource의 정의는 "A piece of your application that is not code – things like image files, audio files, and XML files." 이다. 즉, 코드가 아닌 다른 형태의 자원들을 일컫는 말이다. 

 

그렇다면, 코드가 아닌 리소스들을 어떻게 참조할까? 

정답은 resource ID를 이용해 참조하는것이다.(여기서 또 해시테이블 생각이 났다). 레이아웃을 위한 리소스 아이디는

R.layout.activity_main. 이다. 여기서 R은 자동으로 컴파일때 생성되는 클래스이고, 이 클래스에는 모든 리소스 아이디가 정수 형태로 나열되어 있다. 이 아이디는 우리가 직접 지정해주지 않아도 컴파일을 하면 build process가 자동으로 우리에게 리소스 아이디를 부여해준다. [사실, build process는 모든 리소스에 리소스 아이디를 자동으로 부여해준다]

 

R.layout.activity_main  을 참조할때, activity_main이라고 이름 지어진 정수형 아이디를 R 클래스 하위의 layout에서 참조하는것이다.

 

 

자바의 R(resource) 클래스

    package com.bignerdranch.android.geoquiz;

    public final class R {
        public static final class anim {
            ...
        }
        ...
        public static final class id {
            ...
        }
        public static final class layout {
            ...
            public static final Int activity_main=0x7f030017;
        }
        public static final class mipmap {
            public static final Int ic_launcher=0x7f030000;
        }
        public static final class string {
            ...
            public static final Int app_name=0x7f0a0010;
            public static final Int false_button=0x7f0a0012;
            public static final Int question_text=0x7f0a0014;
            public static final Int true_button=0x7f0a0015;
        }
    }

 

 

위의 string의 정적 클래스를 살펴보자. 우리는 아무런 아이디를 부여하지 않았지만, build process가 이렇게 자동으로 아이디를 부여해줬다.

 

Android은 전체 레이아웃 및 각 문자열에 대한 리소스 ID를 생성했지만, activity_main.xml의 개별 "뷰"에 대한 리소스 ID는 생성하지 않았다. 모든 뷰가 리소스 ID가 필요한 것은 아니다! 이 장에서는 코드에서 두 버튼만 상호 작용하므로 그 두 버튼만 리소스 ID가 필요하다.

뷰에 대한 리소스 ID를 생성하려면 뷰의 정의에 android:id 속성을 포함해야 한다. activity_main.xml에서 각 버튼에 android:id 속성을 추가해보자.

 

<LinearLayout ... >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="24dp"
        android:text="@string/question_text" />

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

        <Button
            android:id="@+id/true_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/true_button" />

        <Button
            android:id="@+id/false_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/false_button" />

    </LinearLayout>

</LinearLayout>

 

 

id에 대한 값에는 + 기호가 있지만 android:text에 대한 값에는 + 기호가 없다. 이것은 리소스 ID를 생성하고 문자열을 참조하기만 하기 때문이다.

 

이제 버튼에도 아이디가 주어졌으니(참조 가능), 버튼들과 상호작용 해보도록 해보자!

 

이제 MainActivity에서 참조 할 수 있다.

 

MainActivity.kt 파일에 아래와 같이 코드를 작성한다. 위에서 설명했듯 R.id.true_button은 "Resource클래스의 id라는 정적 클래스의 true버튼의 아이디를 참조할것이고, findViewById라는 메서드를 통해 id를 리턴받고 trueButton변수에 아이디를 부여한다.

class MainActivity : AppCompatActivity() {

    private lateinit var trueButton: Button
    private lateinit var falseButton: Button

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

        trueButton = findViewById(R.id.true_button)
        falseButton = findViewById(R.id.false_button)
    }
}
findViewById 메서드는 View타입을 리턴받지만, 더 자세하게 말하면 우리가 원하는 View의 하위 타입으로 캐스팅 되어 리턴된다. 위의 경우에는 View의 Button을 리턴한다.

 

위의 코드에서는 버튼의 리소스 ID를 사용하여 팽창된 객체를 검색하고 이를 뷰 속성에 할당한다. 뷰 객체는 onCreate(…)에서 setContentView(…)이 호출된 후에 메모리에 팽창되어 사용 가능하기 때문에 속성 선언에서 lateinit을 사용하여 해당 속성의 내용을 사용하기 전에 비어 있지 않은 뷰 값을 제공할 것이라고 컴파일러에 알린다.그런 다음 onCreate(…)에서 뷰 객체를 찾아 적절한 속성에 할당한다. 

 

 

※ 이제, 버튼이 "상호작용" 할 수 있도록 Event Listener를 추가해줘야 한다.

 

안드로이드 애플리케이션은 일반적으로 이벤트 기반이다. 명령 줄 프로그램이나 스크립트와 달리, 이벤트 기반 애플리케이션은 시작하고 나서 사용자가 버튼을 누르는 등의 이벤트를 기다린다.

애플리케이션이 특정 이벤트를 기다리고 있을 때 해당 이벤트를 "듣고 있는" 것이라고 한다. 이벤트에 응답하기 위해 생성하는 객체를 리스너(listener)라고하며, 리스너는 해당 이벤트를 위한 리스너 인터페이스를 구현한다.

안드로이드 SDK에는 다양한 이벤트에 대한 리스너 인터페이스가 제공되므로 직접 작성할 필요가 없다! 이 경우, 듣고 싶은 이벤트는 버튼이 눌렸을 때 또는 "클릭"되었을 때이므로 리스너는 View.OnClickListener 이다.

 

자 그러면, trueButton의 이벤트 리스너를 추가해주자.

 

MainActivity.kt 파일로 가서

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

    trueButton = findViewById(R.id.true_button)
    falseButton = findViewById(R.id.false_button)

    trueButton.setOnClickListener { view: View ->
        // Do something in response to the click here
    }
}

 

혹시 view-> 같은 식을 처음 본다면 람다식을 검색해보자

 

이런식으로 이벤트 리스너를 추가해주자. {} 괄호 사이에는 버튼이 클릭되었을때 어떤 일을 해야할지 알려주면 된다. 

 

같은 방법으로 false button에도 이벤트 리스너를 추가하자!

 

※Toast 사용하기

우리가 만들 앱은 퀴즈 앱이고, 문제가 나왔을때 true, false를 눌러 정답인지 아닌지 '팝업'을 띄우는 것이다. 이 팝업을 띄울때 사용하는 속성이 toast이다.(왜 토스트지? 토스트기에서 빵이 시간되면 팍 하고 튀어올라오는 모습을 생각한건가?)

토스트는 사용자에게 어떤 사실을 알리지만 입력이나 조치를 필요로하지 않는 간단한 팝업이다(즉, 메세지를 보여주는 팝업만 띄우는거지 거기에 사용자한테 입력을 받거나 하지는 않는다). 

 

1. 다시 strings.xml 파일로 들어가 우리가 팝업창에 보여줄 문자열을 생성한다.

 

<resources>
    <string name="app_name">GeoQuiz</string>
    <string name="question_text">Canberra is the capital of Australia.</string>
    <string name="true_button">True</string>
    <string name="false_button">False</string>
    <string name="correct_toast">Correct!</string>
    <string name="incorrect_toast">Incorrect!</string>
</resources>

 

2. 이번엔 다시 MainActivity.kt로 들어가 아래 코드를 써준다

 

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    trueButton.setOnClickListener { view: View ->
        // Do something in response to the click here
        Toast.makeText(
            this,
            R.string.correct_toast,
            Toast.LENGTH_SHORT
        ).show()
    }

    falseButton.setOnClickListener { view: View ->
        // Do something in response to the click here
        Toast.makeText(
            this,
            R.string.incorrect_toast,
            Toast.LENGTH_SHORT
        ).show()
    }
}

 

 

토스트를 생성하기 위해서는 정적함수 Toast.makeText(Context, Int, Int)를 호출한다.

자바에서의 정적 함수는 선언 당시에 메모리에 할당되어 객체를 만들지 않고도 함수명만을 호출하여 함수의 기능을 한다!

두번째 인자로 받은 Int는 토스트가 팝업에 보여줄 문자열의 resource ID 이다. Context 파라미터는 토스트 클래스가 문자열의 resource ID를 찾고 사용하기 위해 필요하다. 마지막 int파라미터는 팝업이 얼마나 오랫동안 보여지는지를 결정하는 파라미터이다. 토스트를 생성한 후, Toast.show()메서드를 이용하여 화면에 보여줄것이다.

 

 

자, 그럼 이제 우리가 만든 앱이 화면에 어떻게 보이는지 Emulator를 실행해서 보자!

우리는 안드로이드 가상 기기를 통해 확인을 할건데, 가상 기기를 구축하는법은 Tools - AVD manager - create virtual device - emulate a Pixel 4 를 선택해준다.

 

그 다음 창에서는 API 32 버전을 다운로드 해준다!

 

 

그러면 이제 편집기 오른쪽에 안드로이드 폰 모양이 보일것인데, 우리가 만든 GeoQuiz 앱이 보일것이다

짜잔!! 저기서 TRUE 버튼과 False 버튼을 누르면 이제 우리가 위에서 Toast로 만들었던 팝업이 보일것이다.

 

짝짝짝!!

 

정말 별건 아니지만, 그래도 내 손으로 만든 첫 앱이 되었으니 뭔가 뿌듯하다. 이 앱을 만들면서 새로운 지식들도 습득하였고(리소스, 아이디, 코틀린, 스트링, 리소스 아이디, 이벤트리스너 등등), 재밌었다. 다음 포스팅에는 이것보다 더 많은 기능을 구현하는 다른 앱을 한번 만들어 보겠다!!

+ Recent posts