국가 수도 맞추기 앱 만들기 #03



 지난번에는 prev_button, next_button 시에 updateQuestion()이 실행되도록 하였습니다.

updateQuestion()은 onCreate() 시에도 실행됩니다. updateQuestion() 내부에는 아래와 같이 세 개의 함수가 존재하며 각 함수의 역할은 다음과 같습니다.


showQuestion() : 문제와 국가 정보를 표기

setQuestionNumber() : 보기에 표시될 수도 정보 생성

setAnswerButtonText() : 생성된 수도 정보를 버튼에 표시 


showQuestion()은 제일 처음 시간에 살펴봤던 함수이므로 넘어가겠습니다.


 setQuestionNumber()는 list에 네 개의 수도 정보를 담는 역할을 합니다. 이때에 랜덤으로 네 개의 수도 정보를 담고 그 중 첫번째를 문제의 국가 정보에 해당하는 수도 정보로 교체합니다. 즉, 정답을 하나 심는 과정입니다. 그리고 셔플하여 섞어줍니다. 중복되지 않도록 중복처리도 했습니다.


여기서 list를 복습할 수 있습니다.


Java

 private List<Integer> mAnswer = new ArrayList<Integer>();


코틀린

 private val mAnswer = mutableListOf<Int>() 


 updateQuestion()을 통해서 setQuestionNumber()가 호출될 때마다 add 되므로 mutableListOf를 사용하였습니다.


 mAnswer.clear()

 mAnswer.add(0, -1)

 mAnswer.add(1, -1)

 mAnswer.add(2, -1)

 mAnswer.add(3, -1) 


 이렇게 완성된 보기는 버튼에 다음과 같이 표시해줍니다.


    private fun setAnswerButtonText() {

        answer_one.setText(resources.getString(mCountryList.mQuestions[mAnswer.get(0)].capital))

        answer_two.setText(resources.getString(mCountryList.mQuestions[mAnswer.get(1)].capital))

        answer_three.setText(resources.getString(mCountryList.mQuestions[mAnswer.get(2)].capital))

        answer_four.setText(resources.getString(mCountryList.mQuestions[mAnswer.get(3)].capital))

    } 


 버튼을 눌렀을 때 표시된 정보와 문제의 국가 정보에 해당하는 수도가 일치하면 정답이라는 토스트 메세지를 보여줍니다. 틀리면 오답이라는 토스트 메세지를 보여줍니다. 다음과 같이 각 버튼에서 조건을 체크하여 어떤 토스트를 보여줄지 정합니다.

mCountryList.mQuestions[mCurrentIndex].capital는 정답이 되는 수도 정보이고 mCountryList.mQuestions[mAnswer.get(0)].capital는 첫번째 보기에 해당하는 수도 정보입니다.

mAnswer.get(1), mAnswer.get(2), mAnswer.get(3)은 각각 두번째, 세번째, 네번째 보기에 해당하는 수도 정보가 되겠죠.


        answer_one.setOnClickListener {

            if (resources.getString(mCountryList.mQuestions[mCurrentIndex].capital) == resources.getString(mCountryList.mQuestions[mAnswer.get(0)].capital)) {

                Toast.makeText(applicationContext, R.string.answer_true, Toast.LENGTH_SHORT).show()

            } else {

                Toast.makeText(applicationContext, R.string.answer_false, Toast.LENGTH_SHORT).show()

            }

        }


        answer_two.setOnClickListener {

            if (resources.getString(mCountryList.mQuestions[mCurrentIndex].capital) == resources.getString(mCountryList.mQuestions[mAnswer.get(1)].capital)) {

                Toast.makeText(applicationContext, R.string.answer_true, Toast.LENGTH_SHORT).show()

            } else {

                Toast.makeText(applicationContext, R.string.answer_false, Toast.LENGTH_SHORT).show()

            }

        }


        answer_three.setOnClickListener {

            if (resources.getString(mCountryList.mQuestions[mCurrentIndex].capital) == resources.getString(mCountryList.mQuestions[mAnswer.get(2)].capital)) {

                Toast.makeText(applicationContext, R.string.answer_true, Toast.LENGTH_SHORT).show()

            } else {

                Toast.makeText(applicationContext, R.string.answer_false, Toast.LENGTH_SHORT).show()

            }

        }


        answer_four.setOnClickListener {

            if (resources.getString(mCountryList.mQuestions[mCurrentIndex].capital) == resources.getString(mCountryList.mQuestions[mAnswer.get(3)].capital)) {

                Toast.makeText(applicationContext, R.string.answer_true, Toast.LENGTH_SHORT).show()

            } else {

                Toast.makeText(applicationContext, R.string.answer_false, Toast.LENGTH_SHORT).show()

            }

        } 


 이렇게 보기 정보를 생성하고 보기 버튼을 눌렀을 때 정답인지 오답인지 표기하는 기능까지 추가되어 앱이 완성되었습니다.




국가 수도 맞추기 앱 만들기 #02


 지난번에는 문제번호와 국가 정보만 노출되는 부분까지 구현했습니다. 

 이번에는 이전, 다음 버튼의 Click에 대한 동작을 구현하겠습니다. 이것을 Java로 구현한다면 다음과 같습니다.

        mPrevButton = (Button) findViewById(R.id.prev_button);

        mPrevButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                mCurrentIndex = mCurrentIndex - 1;

                if (mCurrentIndex < 0) {

                    mCurrentIndex = mQuestions.length - 1;

                }

                updateQuestion();

            }

        });


        mNextButton = (Button) findViewById(R.id.next_button);

        mNextButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                mCurrentIndex = (mCurrentIndex + 1) % mQuestions.length;

                updateQuestion();

            }

        });


 버튼에 사용할 변수에 레이아웃 위젯을 할당하고 그 변수에 클릭 리스너를 달고 onClick 함수를 오버라이딩 하고 있습니다. 코클린은 람다식을 지원하여 이와같은 부분이 축약될 수 있습니다.


람다식이 기억나지 않는다면? 람다 표현식 문법


 람다식을 적용하면 onClick 함수에 View 타입의 변수 v를 가지고 있어서 v -> 를 통해서 변수를 받아서 사용할 수 있습니다. 다음과 같이 말이죠.

mPrevButton.setOnClickListener {

v -> body


 지금은 변수 v를 사용하지 않으니 해당 부분도 불필요합니다. 그래서 다음과 같은 뼈대만 남고 여기에 필요한 동작을 넣어주면 됩니다.


mPrevButton.setOnClickListener {

 ...

}


 그러면 다음과 같이 되겠죠? 버튼 변수가 mPrevButton이 아닌 prev_button라고 사용한 이유는 앞서 말씀드렸듯이 코틀린에서는 레이아웃 위젯의 id를 바로 코드에서 사용가능하기 때문이라는 사실, 다시 한 번 상기할 수 있습니다.


        prev_button.setOnClickListener {

            mCurrentIndex = mCurrentIndex - 1

            if (mCurrentIndex < 0) {

                mCurrentIndex = mCountryList.mQuestions.size - 1

            }

            updateQuestion()

        }


        next_button.setOnClickListener {

            mCurrentIndex = (mCurrentIndex + 1) % mCountryList.mQuestions.size

            updateQuestion()

        }


 코드가 한층 간결해졌다는 느낌을 받을 수 있습니다. 정말 별 것 아닌 일이지만 Java에서 코틀린으로 넘어가는 기본 단계라고 생각합니다. 이 예제를 통해 각종 클릭 리스너를 등록할 때 코틀린을 적용할 수 있을 것 같습니다.


 클릭시 동작에 관해 간단히 설명하자면 mCurrentIndex는 현재 문제의 번호입니다. mCountryList는 전체 국가 정보가 들어있는 리스트이므로 해당 국가의 개수가 총 문제의 수와 같습니다. 따라서 1번 문제에서 이전 버튼시 가장 끝 번호로 갈수 있도록 if 문으로 처리되었습니다. 다음 버튼을 눌렀을 때는 mCurrentIndex 1씩 증가하며 끝번호로 가면 다시 처음부터 시작되도록 하였습니다.


 그 후 updateQuestion()가 동작하는데 해당 함수는 다음 시간에 살펴보겠습니다. 함수 내용은 문제의 번호와 국가 정보를 갱신하고 Answer 버튼에 들어갈 정답, 오답 수도 정보를 갱신하는 동작을 합니다.


 다음에는 Answer 4개 버튼에 대한 처리 및 랜덤으로 Answer 버튼을 배치하고 앱을 완성하도록 하겠습니다. 


참고로 모든 기능이 구현된 코드는 이미 아래 GitHub에 업로드 되어 있습니다.

https://github.com/MyStoryG/CapitalQuiz

국가 수도 맞추기 앱 만들기 #01


 첫번째 시간에는 간단하게 레이아웃을 구성해보도록 하겠습니다. 그리고 지금은 당장 쓰이진 않지만 앱의 핵심 데이터인 국가, 수도 정보도 추가하도록 하겠습니다. 이번 시간 최종 목표는 아래와 같은 구성을 가지는 것입니다.



 기본 구성은 다음과 같이 질문, 국가, 4개의 선택지, 이전, 다음 버튼으로 구성됩니다.

2개의 텍스트뷰와 6개의 버튼으로 이루어진 간단한 레이아웃입니다.





activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/activity_quiz_all"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:gravity="center"

    android:orientation="vertical"

    tools:context="kotlinapp.circus.com.kotlinapplication.MainActivity">


    <TextView

        android:id="@+id/question_text"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:text="Question"

        android:textSize="@dimen/qustion_size"/>


    <TextView

        android:id="@+id/question_country_text"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:paddingBottom="20dp"

        android:text="Country"

        android:textSize="@dimen/qustion_size"/>



    <LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:orientation="vertical">


        <LinearLayout

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:orientation="horizontal">


            <Button

                android:id="@+id/answer_one"

                android:layout_width="150dp"

                android:layout_height="wrap_content"

                android:textSize="17dp"/>


            <Button

                android:id="@+id/answer_two"

                android:layout_width="150dp"

                android:layout_height="wrap_content"

                android:textSize="17dp"/>

        </LinearLayout>


        <LinearLayout

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:orientation="horizontal">


            <Button

                android:id="@+id/answer_three"

                android:layout_width="150dp"

                android:layout_height="wrap_content"

                android:textSize="17dp"/>


            <Button

                android:id="@+id/answer_four"

                android:layout_width="150dp"

                android:layout_height="wrap_content"

                android:textSize="17dp"/>

        </LinearLayout>

    </LinearLayout>


    <LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:orientation="horizontal"

        android:paddingTop="20dp">


        <Button

            android:id="@+id/prev_button"

            android:layout_width="wrap_content"

            android:layout_height="50dp"

            android:layout_gravity="left"

            android:layout_marginRight="30dp"

            android:drawableLeft="@drawable/arrow_left"

            android:text="@string/prev_button"

            android:textSize="20dp"/>


        <Button

            android:id="@+id/next_button"

            android:layout_width="wrap_content"

            android:layout_height="50dp"

            android:layout_gravity="right"

            android:drawableRight="@drawable/arrow_right"

            android:text="@string/next_button"

            android:textSize="20dp"/>

    </LinearLayout>

</LinearLayout>



 위와 같이 레이아웃 생성 후 텍스트뷰에 질문과 국가 정보를 출력할 수 있도록 연결해보겠습니다.

먼저 텍스트뷰의 id를 코드에서 바로 사용하기 위해서 activity_main의 모든 위젯들을 import 해줍니다.


MainActivity.kt

 package kotlinapp.circus.com.kotlinapplication


import android.support.v7.app.AppCompatActivity

import android.os.Bundle

import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {


    private var mCurrentIndex: Int = 0


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)


        showQuestion()

    }


    private fun showQuestion() {

        var countryList = CountryList()

        question_text.setText("${mCurrentIndex + 1}" + ". " + resources.getString(R.string.question_title))

        question_country_text.setText("[" + resources.getString(countryList.mQuestions[mCurrentIndex].country) + "]")

    }

}


 그 후 텍스트뷰의 id를 곧바로 사용하여 setText를 통해 표시할 텍스트를 지정해줍니다.

 만약 showQuestion() 메서드를 자바로 구현했다면 텍스트뷰에 대한 변수를 선언해주고 해당 변수를 레이아웃의 위젯과 연결해주는 과정이 필요합니다. 


 위젯의 id를 바로 사용할 수 있는 코틀린에서는 이러한 과정이 없어 매우 편리합니다.


public class MainActivity extends AppCompatActivity {


    private TextView mQuestionTextView;

    private TextView mQuestionCountryTextView;

        ...

        ...


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ...

        ...


        mQuestionTextView = (TextView) findViewById(R.id.question_text);

        mQuestionCountryTextView = (TextView) findViewById(R.id.question_country_text);


        showQuestion();

}

    private void showQuestion() {

        mQuestionTextView.setText(mCurrentIndex + 1 + ". " + getResources().getString(R.string.question_title));

        mQuestionCountryTextView.setText("[" + getResources().getString(mQuestions[mCurrentIndex].getCountry()) + "]");

    }

        ...

        ...

}


 그리고 국가와 수도 정보를 가진 Question 클래스를 살펴보면 코틀린의 간결함과 편리함을 다시 한 번 느낄 수 있습니다.


Question.java

public class Question {

    private int mCountry;

    private int mCapital;


    public Question(int country, int capital) {

        mCountry = country;

        mCapital = capital;

    }


    public int getCountry() {

        return mCountry;

    }


    public int getCapital() {

        return mCapital;

    }

}

 어려운 내용들은 아니지만 국가, 수도 정보를 위한 변수와 생성자, getter가 존재하여 코드가 많아 보입니다.


Question.kt

 class Question(val country: Int, val capital: Int)

 primary constructor(주생성자)에 필요한 변수가 존재하며 getter, setter는 생략가능한 코틀린의 특성으로 인해 아주 간결한 모습입니다.


CountryList 클래스는 국가, 수도 정보를 가지는 Question 객체를 담고 있는 array를 포함하고 있습니다.


여기까지의 코드는 GitHub에 올려두었습니다. 해당 repository에 지속적으로 commit 하여 앱을 완성해 나가도록 해보겠습니다.


https://github.com/MyStoryG/CapitalQuiz


+ Recent posts