안드로이드와 앱/안드로이드

사전캠프 2주차 강의 [로또번호 생성기]

정혜현 2024. 5. 28. 10:51



 

 

요약

  1. 사전준비 : 공 만들기, 배경 만들기
  2. xml : 코드와 팔레트로 화면 생성
  3. kotlin : 컴포넌트에 따른 코드 입력, 이벤트에 따른 버튼 코드 입력, 로또번호 생성기 완성 후 사용
  4. 문제해결 및 회고

 

 

1. 사전준비

 

1.1 공 만들기

drawable - New File - circle_color.xml 생성

 

xml선언부 로 첫줄을 쓰는 것은 표준버전을 준수하고 있다는 것과 UTF-8유니코드 인코딩되었음을 의미한다. Shape 모양을 만들다. oval Solid 색을 채우다. 원이므로 width, height의 size를 동일하게 설정한다.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">

    <solid
        android:color="#2196F3"/>

    <size
        android:width="44dp"
        android:height="44dp"/>
</shape>

 

 

 

1.2 배경 만들기

drawable - New File - bg.xml 생성. Rectangle 직사각형 stroke 테두리

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid
        android:color="#D6EDEB"/>
    <stroke
        android:width="1dp"
        android:color="#000000"/>
</shape>

 

 

 

 


 

 

 

2. xml

2.1 코드와 팔레트로 화면구성하기

이미지 넣을 경우, android : src="@drawable/이미지이름"
NumberPicker : 소스코드를 넣어야 움직인다.
그라데이션 넣을 경우, android : solidColor 와 android : background 
가로 같은 비율로 차지하게 하려면 app : layout_constraintHorizontal_bias="0.5"

2.2 상자에 공 넣기

LinearLayout : 직접 만든 배경색을 넣을 때는 android : background="@drawable/bg" 
왜 배경색을 android : background="@color"로 넣지 않고 제작해서 넣는건지 궁금해서 컬러로 채워봤는데 이상 없었다. 만들어서 넣는 방법을 알려주려고 그런 것 같다.
보통은 </>self closing이 가능하지만 안에 코드가 들어갈 경우 따로 닫아줘야 한다. 상자 안에 공이 들어가야 하므로 < LinearLayout></LinearLayout>닫는 코드를 따로 입력해줘야한다.
화면에 보이지 않게 하려면 android : visibility="gone" 보이게 하려면 "visiable"

 

 

 


 

 

3. kotlin

 

3.1 코드 작동 이해하기

onCreate가 발생하면 xml화면을 안드로이드 폰에 올린다. 앱이 시작하면 onCreate부터 작동한다고 보면 된다. 그래서 onCreate 위에는 정의하는 선언부, 아래에는 화면에 일어날 일들을 코딩한다.

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


이벤트가 발생하는 컴포넌트의 경우, by lazy로 선언은 하지만 정의는 지연시킬 수 있다.

private val clearButton by lazy { findViewById<Button>(R.id.btn_clear) }


6개의 공을 담을 리스트도 만든다.

private val numTextViewList : List<TextView> by lazy {
    listOf<TextView>(
        findViewById(R.id.tv_num1)
        ,findViewById(R.id.tv_num2)
        ,findViewById(R.id.tv_num3)
        ,findViewById(R.id.tv_num4)
        ,findViewById(R.id.tv_num5)
        ,findViewById(R.id.tv_num6)
    )
}


NumberPicker는 최대값 최소값 설정해야한다.

numPick.minValue = 1
numPick.maxValue = 45

 

 

 

 

 


여기부터 어려웠다...

numTextViewList 을 담은 리스트랑 pickNumSet 숫자를 담은 컬렉션은 다르니 혼동하지 말 것.

 

 

 

 

 

 

 

3.2 addButton : 사용자가 NumberPicker에서 직접 고른 공을 추가해주는 버튼

3.2.1 when 조건문 1 : 선택한 공 추가하기
각각의 상황에 따라 조건문을 쓸 때는 if가 아닌 when을 쓴다. 조건에 따라 메시지를 계속 띄우므로 아예 Toast 함수를 정의하고 showToast()로 호출해 사용한다.  

private fun showToast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

 

예외처리 메시지

      /*상황1*/
didRun은 실행여부를 확인하는 역할로 초기값은 false, 실행된 경우 true가 된다. 공이 꽉찬 경우 추가버튼이 실행되었다고 판단되어 true로 바뀌고 더 이상 추가할 수 없다. 따라서 메시지를 띄운다.
      /*상황2*/
hashSetOf 컬렉션인 pickNumSet은 선택된 숫자를 담아둘 공간이다. 로또에서 내가 직접 선택할 수 있는 숫자는 최대 5개이므로 초과시 메시지를 띄운다.
      /*상황3*/
내가 선택한 숫자가 이미 컬렉션에 포함되어 있으면 중복해서 담을 수 없으므로 메시지를 띄운다. 

 

위 3가지 예외상항에 해당되지 않는다면 직접 고른 숫자이 문제없이 추가되어야 하므로 else로 정상작동 구문을 만든다.

textView 변수로 의 모습을 정의한다.
[pickNumSet.size]선택된 숫자의 개수만큼 numTextViewList에서 공을 꺼내온다. 공은 visibility="gone"으로 설정했기 때문에 .isVisible = true로 보이게 해주고, textView.text = numPick.value.toString() 공에 숫자를 써준다.

 

색깔도 바꿔줘야한다.

setNumBack(numPick.value, textView) 그 숫자에 맞는 색으로 바꿔준다. (이하 2번째 when으로 정의되어있다.)

 

사용자가 NumberPicker에서 직접 고른 숫자를 문제없이 추가한다.

pickNumSet.add(numPick.value) 고른 숫자를 컬렉션에 담는다.

private fun initAddButton() {
    addButton.setOnClickListener {
        when {
            /*상황1*/didRun -> showToast("초기화 후에 시도해주세요.")
            /*상황2*/pickNumSet.size >= 5 -> showToast("숫자는 최대 5개까지 선택할 수 있습니다.")
            /*상황3*/pickNumSet.contains(numPick.value) -> showToast("이미 선택한 숫자입니다.")
            else -> {
                /*공*/val textView = numTextViewList[pickNumSet.size]
                textView.isVisible = true
                textView.text = numPick.value.toString()
                /*색깔*/setNumBack(numPick.value, textView)
                /*숫자*/pickNumSet.add(numPick.value)
                }
            }
        }
    }

 

3.2.2 when 조건문 2 : 숫자에 따른 색 지정하기
textView의 background를 number에 따라 when에서 해당하는 색으로 지정해주는 함수이다. ContextCompat은 리소스 값을 가져올 때 쓰는 클래스이다. ContextCompat.getDrawable은 drawable에 있는 파일을 불러오기 위해 쓰고 in .. 으로 범위를 지정할 수 있다. 

private fun setNumBack(number: Int, textView: TextView) {
	val background = when (number) {
	    in 1..10 -> R.drawable.circle_yellow
	    in 11..20 -> R.drawable.circle_blue
	    in 21..30 -> R.drawable.circle_red
	    in 31..40 -> R.drawable.circle_gray
	    else -> R.drawable.circle_green
	    }
    textView.background = ContextCompat.getDrawable(this, background)
    }

 

 

 

3.3 clearButton : 선택된 공을 모두 없애고 초기화시켜주는 버튼

컬렉션에 담긴 숫자를 clear()로 비워 깨끗하게 하고, 공은 forEach{}로 반복문을 돌려서 isVisiable = true를 false로 바꿔준다. didRun과 numPick도 false와 1로 바꿔 초기값으로 돌려준다.

private fun initClearButton() {
    clearButton.setOnClickListener {
        pickNumSet.clear()
        numTextViewList.forEach { it.isVisible = false }
        didRun = false
        numPick.value = 1
    }
}

  

 

 

3.4 runButton : 공 6개가 채워지도록 자동생성해주는 버튼

랜덤값을 가져오기 위해 getrandom() 함수를 사용한다. filter{}와 !in 부정연산자로 컬렉션에 담긴 숫자가 아니어야 된다는 필터를 걸어 numbers에 담는다.
(pickNumSet 사용자 지정 숫자 + numbers 제외한 나머지 숫자.shuffled()를 섞은 것.take에서 꺼내준다(6 -pickNumSet.size)6에서 사용자 지정 숫자의 개수를 뺀 나머지 개수만큼).sorted()앞에서 합쳐진 6개의 숫자를 정렬   

private fun getRandom(): List<Int> {
	val numbers = (1..45).filter { it !in pickNumSet }
    return (pickNumSet + numbers.shuffled().take(6 - pickNumSet.size)).sorted()
}

 

6개의 숫자도 공으로 만들어줘야 한다. add버튼의 공을 만들어줄 때와 비교해보면 같은 부분도 있고 다른 부분도 있다.
지역변수 val textView로 선언해주고 공을 담아주는 것은 동일하나, forEachIndexed{}로 반복문을 돌린다. 값도 NumPick value가 아니라 number인 점이 다르다. 

forEach, forEachIndexed 반복문 알아보기!
컬렉션타입의 데이터 개수만큼 특정 구문을 반복 실행할 때 유용하다. 

forEach
각 원소들에 대해서 특정한 작업을 수행할 수 있도록 해준다.

forEachIndexed
forEach 기능에 index도 사용하여 몇 번째 원소를 수행하는지 알 수 있다는 차이가 있다.

 

private fun initRunButton() {
	runButton.setOnClickListener {
	val list = getRandom()
    didRun = true

    list.forEachIndexed { index, number ->
        val textView = numTextViewList[index]
        textView.isVisible = true
        textView.text = number.toString()

        setNumBack(number, textView)
        }
    }
}

 

 

 

 

 


 

 

4. 문제해결

발생하지 않았다.

 

 

 

 

 


회고

이번 프로젝트에서 특히 어려웠던 것은 리스트들의 개념이 헷갈려서 여러번 해석해보고 숫자와 공으로 키워드를 잡아 혼동하지 않으려 주의했다. 화면 하나로 만드는 거라 간단할 줄 알았는데 레이아웃이 많은 것보다 발생하는 이벤트가 많은 게 더 어렵다.

 

조건문과 반복문은 그대로 실행했을 때 문제되는 부분은 없는지 살펴봐야하고 예외사항은 꼼꼼히 처리해야된다는 걸 배웠고, 사용자에게 자유도와 편의성을 쥐어주려는 만큼 값을 넣고 빼고 섞고 옮기는 등 코드를 세심히 짜야된다는 걸 배웠다.

(사용자와 개발자의 편의는 반비례한다...)