본문 바로가기
GDSC : KOTLIN STUDY

[7팀] 퀴즈 앱 안드로이드 12 part 3

by 나영수 2022. 11. 6.

1. activity_quiz_questions.xml

 

TextView 작성에 있어서 background 속성의 중요성을 알게되었다.

background로 작성되어있는 내용을 가져와서 사용하게 되면 다시 사용하게 될때 코드를 한 번 더 생성할 필요 없이 불러서 사용하면 되기 때문에 유지보수면에서 좋다고 생각한다.

<!-- 옵션 선택을 위한 textview -->
        <!-- background의 drawable을 따로 만들어 준 이유는 코드의 재활용성을 높이기 위함이라고 생각함.
        drawable/default_option_border_bg을 구현해 놓으면 똑같은 shape을 사용할 다른 text에게도 적용할때
        한 줄만 작성하면 되기 때문에 유지보수나 활용성에 있어서 효율적이라고 생각함.
        만약 background로 불러오는 것을 몰랐다면 그냥 textview 내부에 전부 구현했을것 이라고 생각함.-->
        
        <TextView
            android:id="@+id/tv_option_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="@drawable/default_option_border_bg"
            android:gravity="center"
            android:padding="15dp"
            android:textColor="#7A8089"
            android:textSize="18sp"
            tools:text="Apple" />

2. default_option_border_bg.xml

 

위에서 언급한 background에서 불러오는 xml 파일 내부를 자세하게 살펴보았다.

TextView에 적용시키기 위한 전체적인 구조를 정의하는 tag가 shape태그이다.

그 안에 부가적으로 테두리 및 모서리의 radius를 표현한 여러 속성들을 선언해주는 형식으로 만들어지게 된다.

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

<!--어떤 모양으로 만들지를 정의하기 위해서 사용  직사각형 모양의 shape 사용-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!--테두리를 만들기 위해 사용 -->
    <stroke
        android:width="1dp"
        android:color="#E8E8E8" />
    <!--배경을 만들기 위해 사용 -->
    <solid android:color="@android:color/white" />
<!-- 둥근 모서리를 구현하기 위해 corner의 radius 조정 radius가 커질수록 더욱 둥글어짐 -->
    <corners android:radius="5dp" />
</shape>

3. constant.kt

 

강의에서 보여주는 것과 같이 여러 나라의 국기와 옵션들이 나오기 위해서는 일일이 정의해놔야 한다.

constant.kt 파일이 옵션에 대한 정의를 모아놓은 곳이라고 생각하면 된다. 

constant object에서 선언된 아래와 같은 getQuestions함수를 통해서 정의해놓은 옵션들을 불러올 수 있게 되는 것이다.

<<<<1>>>>에 선언되어 있는 여러 변수들의 값들을 <<<2>>>에서 모두 직접선언해 놓은 것이다.

즉 ArrayList형식으로 Question class type의 여러 속성들을 저장해 놓는 것이다.  ArrayList형식으로 저장하게 되면 크기가 가변적이기 때문에 계속해서 추가해주는 것이 용이하다.

<<<<1>>>>

package eu.tutorials.quizapp
//단순히 question class가 사용할 변수들을 선언
data class Question(
    val id:Int,
    val question:String,
    val image:Int,
    val optionOne:String,
    val optionTwo:String,
    val optionThree:String,
    val optionFour:String,
    val correctAnswer:Int
)
<<<2>>>

fun getQuestions(): ArrayList<Question> {
            val questionsList = ArrayList<Question>()

            // 1
            val que1 = Question(
                1, "What country does this flag belong to?",
                R.drawable.ic_flag_of_argentina,
                "Argentina", "Australia",
                "Armenia", "Austria", 1
            )

4. QuizQuestionsActivity.kt

 

구현에 있어서 중요한 부분이 들어있는 곳이라고 할 수 있다. onclick event에 대한 정의나 default구조에 대한 정의를 모두 생성해 놓은 부분이기 때문이다. 코드 각각의 자세한 내용은 아래의 주석 처리 내용과 같다.

class QuizQuestionsActivity : AppCompatActivity(), View.OnClickListener {
    //create variables for each view in the layout
    private var progressBar:ProgressBar?=null
//    진행바 옆에 있는 텍스트 뷰가 필요하기 떄문에 선언(진행이 될때마다 숫자가 바뀌어야 하기 때문에 값을 받아와야 한다.)
    private var tvProgress: TextView? = null
    private var tvQuestion:TextView? = null
//    image도 화면이 전환됨에 따라 변화되어야 한다.
    private var ivImage: ImageView? = null
//    아래의 4개 또한 화면이 전환됨에 따라 값이 받아와저야 하기 때문에(동적) 값을 받아야 한다.
    private var tvOptionOne:TextView? = null
    private var tvOptionTwo:TextView? = null
    private var tvOptionThree:TextView? = null
    private var tvOptionFour:TextView? = null
   private var buttonSubmit:Button? = null


    private var mCurrentPosition: Int = 1 // Default and the first question position
    //   Question class type의 ArrayList 선언
    private var mQuestionsList: ArrayList<Question>? = null
    private var mCorrectAnswers: Int = 0
    private var mUserName: String? = null
    private var mSelectedOptionPosition: Int = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_quiz_questions)

        mUserName = intent.getStringExtra(Constants.USER_NAME)
       
        //r.id에 해당하는 id를 찾아서 변수에 저장(해당 textview를 받아온다.)
        //textview에 해당하는 기능이나 구조를 변경하기 위해 변수들을 선언해 놓았음.
        progressBar=findViewById(R.id.progressBar)
        tvProgress = findViewById(R.id.tv_progress)
        tvQuestion = findViewById(R.id.tv_question)
        ivImage = findViewById(R.id.iv_image)
        tvOptionOne = findViewById(R.id.tv_option_one)
        tvOptionTwo = findViewById(R.id.tv_option_two)
        tvOptionThree = findViewById(R.id.tv_option_three)
        tvOptionFour = findViewById(R.id.tv_option_four)
        buttonSubmit = findViewById(R.id.btn_submit)

		//constant.kt에서 구현한 getQuestions를 mQuestionList에 가져온다.
        mQuestionsList = Constants.getQuestions()

        setQuestion()

        //this를 통해 onclicklistener을 사용하면 코드가 준다.
        //this는 사실 class 이름인 QuizQuestionsActivity를 나타내는데 이것은 View.OnClickListener로 설정되어 있기 때문에
        //tvoptionone과 같은 옵션들의 onclick 이벤트가 발생하면 밑에서 구현한 onclick fun이 실행되게 된다.

        tvOptionOne?.setOnClickListener(this)
        tvOptionTwo?.setOnClickListener(this)
        tvOptionThree?.setOnClickListener(this)
        tvOptionFour?.setOnClickListener(this)
        buttonSubmit?.setOnClickListener (this)

    }

    private fun setQuestion() {

        //58번 줄에서 가져온 mQuestionList를 통해서 1에서부터 10전까지 받아온다.(배열형식)
        //!! 연산자의 역할은 어떠한 값이든 Non-null 타입으로 변경하며, Null이 값으로 들어오면 exception을 발생시킴(아무거나 선택을 할 것이기 때문에)
        val question: Question =
            mQuestionsList!![mCurrentPosition - 1]
           defaultOptionsView()
//        마지막에 도달했을때 submit의 이름을 종료로 바꾸어 준다.
//        mQuestionList의 size만큼 다 도달했다면 끝까지 돈 것으로 판정
        if (mCurrentPosition == mQuestionsList!!.size) {
            buttonSubmit?.text = "FINISH"
        } else {
            buttonSubmit?.text = "SUBMIT"
        }
//        진행바에 대한 변수(진행이 됨에 따라 숫자가 증가하게 되어야 한다.)
        progressBar?.progress =
            mCurrentPosition // Setting the current progress in the progressbar using the position of question
        tvProgress?.text =
            "$mCurrentPosition" + "/" + progressBar?.max // Setting up the progress text


//      질문 객체에 접근해서 text로 뽑아낸다.(화면이 전환됨에 따라)
        tvQuestion?.text = question.question
        ivImage?.setImageResource(question.image)
        tvOptionOne?.text = question.optionOne
        tvOptionTwo?.text = question.optionTwo
        tvOptionThree?.text = question.optionThree
        tvOptionFour?.text = question.optionFour
    }



    private fun defaultOptionsView() {
        //필요한 정보들을 TextView의 options에 저장한다. options 통해 text들의 초기 상태를 지정할 수 있다.
        val options = ArrayList<TextView>()
        tvOptionOne?.let {
            options.add(0, it)
        }
        tvOptionTwo?.let {
            options.add(1, it)
        }
        tvOptionThree?.let {
            options.add(2, it)
        }
       tvOptionFour?.let {
           options.add(3,it)
       }

        for (option in options) {
            // 위의 options들에 더해진 textview에 index들 즉 나라 이름들의 문자색깔을 지정
            option.setTextColor(Color.parseColor("#7A8089"))
            //옵션을 선택했을때 바뀌게 하기위해
            option.typeface = Typeface.DEFAULT
            //전에 재사용성이 좋다고 한 이유를 여기에서 적용할 수 있다. 같은 shape을 사용하기 위해 호출하기만 하면 된다.
            option.background = ContextCompat.getDrawable(
                this@QuizQuestionsActivity,
                R.drawable.default_option_border_bg
            )
        }
    }
//override를 통해 AppCompatActivity()의 함수를 상속받아 사용한다.
//click event가 발생했을때의 상태변환을 위해 만든 함수
    override fun onClick(view: View?) {
        when (view?.id) {

            R.id.tv_option_one -> {
            tvOptionOne?.let {
                selectedOptionView(it, 1)
            }

            }

            R.id.tv_option_two -> {
                tvOptionTwo?.let {
                    selectedOptionView(it, 2)
                }

            }

            R.id.tv_option_three -> {
                tvOptionThree?.let {
                    selectedOptionView(it, 3)
                }

            }

            R.id.tv_option_four -> {
                tvOptionFour?.let {
                    selectedOptionView(it, 4)
                }

            }
        }
    }
    private fun selectedOptionView(tv: TextView, selectedOptionNum: Int) {
        //defaultoptionView의 함수를 호출해서 눌림 이벤트를 당할
		//텍스트를 가져온다.
        
        defaultOptionsView()
		
        //선택한 내용이 정답인지 int로 확인하기 때문에 선택한 곳의 int가 꼭 저장되어야 한다. 
        mSelectedOptionPosition = selectedOptionNum

        tv.setTextColor(
            Color.parseColor("#363A43")
        )
        tv.setTypeface(tv.typeface, Typeface.BOLD)
        tv.background = ContextCompat.getDrawable(
            this@QuizQuestionsActivity,
            R.drawable.selected_option_border_bg
        )
    }