코틀린 안드로이드 - 로또 번호 추첨기1

Jamwon·2021년 6월 2일
0

Kotlin_Android

목록 보기
8/30
post-thumbnail

이번 챕터는 로또 번호 추첨기 어플!
뭔가 어플을 만든다고 하면 제일 많이나오는 예시 어플인거 같다 ㅋㅋㅋ

기능

  1. 사용자가 원하는 번호 추가
  2. 나머지 번호들 or 전체 로또 번호 자동으로 생성

드로어블 리소스

shape 드로어블 사용

xml code로 되어있어서 사이즈도 작고 수정도 용이하다.

비트맵 이미지를 사용하면 앱자체의 용량도 커지고 수정할때 일일이 다른 이미지로 바꿔줘야되기 때문에 효율적이지 않다고 한다.

collections - set,list,map

공식문서

코틀린에서는 기본적으로 3개의 collection type을 사용한다.
set, list , map

mutableListOf - 원소가 들어가있는 list (mutable은 변할수 있다는 뜻이다 data 추가나 삭제 가능)


화살표는 상속이다.
Iterable - 최상의 멤버를 차례로반환이 가능한 객체
Set - 집합 동일한 data가 존재할수 없는 집단 data 순서보장
Map - mapping하다의 map key-value 상태로 저장되어있다. key는 중복이 될수 없다.

constructing collections

컬랙션을 만드는 법
listOf setOf mutableListOf 이런식으로 간단하게 만들수있다.

ex) val numberSet = setOf("one","two","three","four") 추가삭제 불가능
val mutableSetOf() - data가 아직들어있지 않은 Set 추가삭제 가능

collection opertaions

isEmpty() - collection이 비어있으면 true 반환 아니면 false반환
list에서 get() 등등 여러가지 operator가 있다.

자세한것들은 공식문서에서 찾아보자

random

random 생성자를 해서 random 객체를 얻을수있다. random객체에서는 seed라는 인자를 받는다. seed를 기준으로 특정한 알고리즘을 돌리기때문에 seed값이 같으면 다음값을 예측 할 수 있다. -> 따라서 seed값도 계속 바꾸어 주어야 된다.

그래서 시간을 주로 이용한다. 코틀린에서는 nano second를 사용한다.(default)

import java.util.*

랜덤 객체를 사용하려면 java.util.* 을 import 해주자.

import java.util.*

fun main() {
  val  random = Random()
	print("${random.nextInt(45)+1}")
}

이런식으로 랜덤 숫자를 생성할수 있는데 nextInt() 안에 인자는 bound로 인자의 전까지의 숫자(1~45)까지 랜덤으로 나오게 된다.
45를 인자로하면 0~44 여기다가 +1을해서 1~45다!

import java.util.*

fun main() {
    val  random = Random()
    for (i in 1..6){
    println("${random.nextInt(45)}")
    }
}
 

위처럼 for문을 6번 반복시키면 1~45까지의 숫자가 6번 랜덤하게 생성된다. 하지만 6번 따로 새로운 랜덤 숫자를 생성하는거기 때문에 중복되는 숫자가 나올수 있다. 로또 번호는 중복되면 안된다!

이때 콜렉션을 사용!

list를 사용한 방법

fun main() {
    val  random = Random()
    val list = mutableListOf<Int>()
    
    while (list.size<6){
        val randomNumber = random.nextInt(45) +1
        if (list.contains(randomNumber)){
            continue
        }
        list.add(randomNumber)
        
    }
    print(list)
}

수정가능한 Int형 mutableList를 생성한뒤에 list의 사이즈가 6이될때 까지 randomNumber 가 list에 있는걸 확인하여 (contains() 활용) 없으면 추가해주고 있으면 추가해주지 않는다.

set을 사용한 방법

set은 중복된 원소를 넣을수 없기때문에 contains()를 활용하지 않아도 된다.

fun main() {
    val  random = Random()
    val numberSet = mutableSetOf<Int>()
    
    while (numberSet.size<6){
        val randomNumber = random.nextInt(45) +1
        numberSet.add(randomNumber)
	}
    print(numberSet)
}

위와 같이 짤수있다. 결과적으로 중복을 허용하지 않기때문에 set을 사용하는게 더 유용하다! (둘다써도된다)

또다른 방법

강의에서 마지막으로 사용한다는 방법 list에 45개의 숫자를 미리 넣어놓고 list를 shuffle()한 다음에 앞의 6개의 원소를 잘라내는 식으로 구현하였다.

fun main() {
    val  random = Random()
    val list = mutableListOf<Int>().apply{
        for (i in 1..45){
            this.add(i)
        }
    }
    list.shuffle()
    print(list.subList(0,6))
}

apply로 객체에 접근하여 1~45의 숫자를 넣어주고 list.shuffle()함수로 list를 무작위로 섞어준다. 그리고 list.subList(0,6) 라고하면 0부터 6번째(즉 5번index) 원소를 뽑아낸다고 생각하면된다.

이러한 방법으로도 random하고 중복되지 않은 로또번호 6개를 출력할 수 있다.

constraintlayout

이번에는 constraintlayout을 이용한다(현업에서 많이쓴다고 한다.)

공식문서 링크
constraintlayout은 여러가지 제약을 이용해서 layout을 만든다.


위 사진처럼 5가지 축들이있는데 일반적으로 left가 start다. 이 축들 끼리 제약을 두어서 상대적인 위치를 잡아 layout을 구성하는 것이다.

여러가지 내용들이 있는데 공식문서에서 확인해보자! 일일이 사용해보면서 배우는게 빠르다.

constraintlayout은 matchparent를 권장하지 않는다 !!!
0dp 를 사용하면 MATCH_CONSTRAINT가 된다.

layout 만들기

NumberPicker 사용

숫자 하나를 고를수있는 NUmberPicker를 사용한다.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <NumberPicker
        android:id="@+id/numPicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="10dp"
        android:text="번호 추가하기"
        app:layout_constraintEnd_toStartOf="@id/btn_clear"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/numPicker"

        />

    <Button
        android:id="@+id/btn_clear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="초기화"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btn_add"
        app:layout_constraintTop_toBottomOf="@+id/numPicker"
        app:layout_constraintTop_toTopOf="@id/btn_add" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_add">

        <TextView
            android:id="@+id/txt_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold" />
    </LinearLayout>

    <Button
        android:id="@+id/btn_run"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:text="자동생성 시작"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

constraintlayout으로 만들어주고 안에 linearlayout안에 숫자 5개를 넣은 형태이다.

chainStyle - 두개의 요소들의 chain을 정의한다. packed가 되어있으면 붙어있다. 여기서 두개의 버튼의 위치를 바꾸고 싶으면 constraintHorizontal_bais를 설정해준다.
0 - 맨왼쪽
0.5 - 가운대 정렬(default)
1 - 오른쪽

visibility

xml에서 android:visibility를 사용해서 app에서 보여지는 것을 조절
tools: visibility를 사용해서 xml tools(안드로이드스튜디오)에서 보여지는 것을 조절

         <TextView
            android:id="@+id/txt_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="1"
            android:textSize="18sp"
            android:textStyle="bold"
            android:visibility="gone"
            tools:visibility="visible" />   

위와 같이 textView를 적용하면

안드로이드스튜디오에서는 보이지만 앱을 실행하면 textView들이 보이지 않는다.

버튼 기능 연결하기 + 랜덤넘버 생성

class MainActivity : AppCompatActivity() {

    private val clearButton: Button by lazy {
        findViewById<Button>(R.id.btn_clear)
    }
    private val addButton: Button by lazy {
        findViewById<Button>(R.id.btn_add)
    }
    private val runButton: Button by lazy {
        findViewById<Button>(R.id.btn_run)
    }
    private val numberPicker: NumberPicker by lazy {
        findViewById<NumberPicker>(R.id.numPicker)
    }

    private val numberTextViewList: List<TextView> by lazy {
        listOf<TextView>(
            findViewById<TextView>(R.id.txt_1),
            findViewById<TextView>(R.id.txt_2),
            findViewById<TextView>(R.id.txt_3),
            findViewById<TextView>(R.id.txt_4),
            findViewById<TextView>(R.id.txt_5),
            findViewById<TextView>(R.id.txt_6)
        )
    }
    private var didRun = false

    private val pickNumberSet = hashSetOf<Int>()


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

        numberPicker.minValue = 1
        numberPicker.maxValue = 45

        initRunButton()
        initAddButton()
        initClearButton()
    }

    private fun initRunButton() {
        runButton.setOnClickListener {
            val list = getRandomNumber()

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

            }
            Log.d("MainActivity", list.toString())
        }
    }

    private fun initClearButton() {
        clearButton.setOnClickListener{
            pickNumberSet.clear()
            numberTextViewList.forEach{
                it.isVisible = false
            }
            didRun = false
        }
    }

    private fun getRandomNumber(): List<Int> {
        val numberList = mutableListOf<Int>().apply {
            for (i in 1..45) {
                if (pickNumberSet.contains(i)){
                    continue
                }
                this.add(i)
            }
        }
        numberList.shuffle()
        val newList = pickNumberSet.toList()+ numberList.subList(0, 6-pickNumberSet.size) //from to 0,1,2,3,4,5
        return newList.sorted()
    }

    private fun initAddButton() {
        addButton.setOnClickListener {
            if (didRun) {
                Toast.makeText(this, "초기화 후에 시도해주세요.", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            if (pickNumberSet.size >= 5) {
                Toast.makeText(this, "번호는 5개까지만 선택할수 있습니다.", Toast.LENGTH_SHORT).show()
                return@setOnClickListener

            }
            if (pickNumberSet.contains(numberPicker.value)) {
                Toast.makeText(this, "이미 선택한 번호입니다", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            } //여기까지 예외처리
            val textView = numberTextViewList[pickNumberSet.size]
            textView.isVisible =true
            textView.text = numberPicker.value.toString()
            pickNumberSet.add(numberPicker.value)

        }
    }
}

버튼들을 불러와서 지정해주고 TextView들은 6개가 있기때문에 TextView를 원소로 가지는 List를 만들어 거기에 넣어준다.

initRunButton() - 자동생성

로또 번호를 자동생성해주는 함수다. list.forEachIndexed로 index와 로또 넘버값들을 받아와서 6개의 TextView에 로또번호를 띄워준다.

initClearButton() - 초기화

생성된 로또 번수들 TextView를 clear해준다. RandomNumber있는 Set를 clear() 함수로 비워주고 numberTexTViewList.forEach{} ->List안에 있는 각각의 원소를 처리할때 쓴다.
다시 isVisible = false로 해서 사라지게 한다.
TextView는 Clear 한다고보는게 아니라 사용자에게 안보이게 하는 식이다.
초기화 했기때문에 didRun를 false로 해줘야된다.

getRandomNumber()

위에 썻던것 처럼 1~45까지의 숫자가 들어있는 리스트를 만든 후 shuffle하고 앞에 6개원소의 리스트를 subList를 사용해서 만들어준다.
pickerNumberset에 사용자가 미리 지정해놓은 로또 번호가 있으면 list에 추가하지 않는다.

initAddButton()

추가하기 버튼에 대한 함수로 numberPicker에서 숫자를 고른다음에 추가할때 사용된다.

예외처리로 위에 서술해둔 자동생성이 true면 초기화해달라고 말한다.
랜덤생성된 set이 5보다 크거나 같으면 번호는 5개 까지만 선택할수 있다는 예외처리

사용자가 선택한 numberPicker의 값이 이미 도출된 로또 번호에 존재하면 이미 선택한 번호라고 알려준다.

예외가 없으면 gone이였던 List에 들어가있던 textView를 하나씩 선언한뒤에 그 textView를 isVisble=true로 만들어주고 사용자가 입력한 nubmerPicker의 값을 넣어준다.

TextView리스트를 만들어놔서 넣어놓은다음에 버튼이 눌릴때마다 다음 TextView를 선언하게 하는 방법이 굉장히 좋은거같다. 예외처리 하기도 편해보이고 이 방법을 꼭 기억하자.

새로 배운것

android:visibility="gone" ->앱에서 요소는 보이지 않음
다시 보여줄려면 isVisible = true를 해줘야된다.
tools:visibility = "visible" -> xml상에서는 보임

forEach - 객체의 모든 원소에 접근

forEachIndexed - forEach는 index값이 넘어오지 않기때문에 index값이 필요할때 사용한다.

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

            }

이런식으로 사용된다.

완성 화면


잘 돌아간다

여러개의 요소를 계속해서 반복적으로 사용하면 List를 만들어서 그 index로 요소를 접근하면 효율적이다.

로또앱에선 6개의 숫자 즉 6개의 TextView를 사용하기때문에
6개 원소의 TextViewList
x개 원소의 사용자입력 로또번호 List
6-x개 원소의 Set
을 이용해서 TextView을 초기화 해주었다.

numberPicker

숫자를 고를때 사용되는 요소

subList()

list에서 부분 List를 만들때 쓴다.

다음 챕터는 어플을 좀더 이쁘게 만드는 파트

profile
한걸음씩 위로 자유롭게

0개의 댓글