안드로이드공부하는게 생각보다 재밌다. 코틀린이라는 언어자체도 생각보다 매력적이다. 그래서 하루하루 공부해나가는게 아직은 재밌다. 물론 쉬운 부분만 하고 있어서 그런거 일수도 있다. 제일 좋은점은 정형화된 방식에 대해서 체계적으로 배우고 있다는 생각이 들어서 좋다. 예컨대 코드를 작성하는 스타일이라던가, 함수를 선언하는 방식이라던가 말이다. 이번에는 로또번호 추첨기를 만들었다. 사실 거의 모든 예제에 등장하는 것 같다. 내가 python을 1학년때 처음 배울때와 JS를 공부할때도 물론이다. 내가 교육봉사하면서 멘티들한테 JS알려줄때도 로또번호 만드는거 알려준거 같기는 하다. 자료형 + 알고리즘이 적절히 조화를 이루고 있어서 그런가 괜찮은 예제인거 같았다.
# 결과물 미리보기
## 구현 기능
- TimePicker에서 번호를 추가시 6개 이하일 경우 번호가 등록 됨. 중복된 번호는 등록되지 않음
- 번호 자동생성을 누를 경우 6가지의 번호가 자동으로 생성되어 시간차로 하나씩 등록됨
- 자동생성시 시간차로 등장하게 하기 위해서 강의와 다르게 코드를 수정했음
- 번호가 생성되고 있는 중에 초기화를 누를경우 초기화 된 상태에서 나머지 번호들이 추첨되는 버그가 있어 수정했음
- 번호가 n개 등록되어 있는 상태에서 번호 자동 생성을 누르면, 기존에 있던 번호를 기억하고 나머지만큼만 번호를 뽑아 정렬시켜서 추첨
# 알게 된 것
- XML에서 가져온 View를 lazy하게 등록시켜 사용하는 방법 🔥
- Button을 등록시킨후 onCreate()에서 init***Button()으로 사용 🔥
- 코틀린 콜렌션(collection) -> 따로 정리
- Handler().postDelayed를 이용하여 시간차를 주는 법 🔥
- kotlin(액티비티)에서 view의 background를 바꿔주는 법 🔥
- constarintLayout 사용법 => 나중에 시간되면 따로 정리
- visibility 에서 gone, visible, invisible 🔥
- numberPicker 🔥
- drawable -> 따로정리
## View를 lazy하게 등록시키는 방법
private val clearButton : Button by lazy {
findViewById(R.id.clearButton)
}
private val addButton : Button by lazy {
findViewById(R.id.addButton)
}
private val sendButton : Button by lazy {
findViewById(R.id.sendButton)
}
private val numberPicker : NumberPicker by lazy {
findViewById(R.id.numberPicker)
}
private val ballList : List<TextView> by lazy {
/.. 생략 ..
}
private var didRun = false
private val pickNumberSet = hashSetOf<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
/ .. 생략 ..
/ .. 생략 ..
}
위와 같이 XML에서 필요한 View들에 대해서 by lazy { ... } 로 선언해 줬다. 이럴 경우 사용되지 않은경우에는 생성되지 않아 메모리측면에서도 이득을 볼 수 있다. onCreate나 여타 다른곳에서 사용되면 그즉시 lazy 안에 내용으로 초기화되어 사용 할 수 있다.
val addButton : Button? = null
addButton?.setOnClickLinster { //... }
위에 경우도 똑같지만 null로 선언해준 덕에 addButton을 사용할때마다 ?또는 !!로 null체크를 해줘야 하는 불편함이 있다. 그래서 사용한다고 한다.
## Button을 등록시킨후 onCreate()에서 init***Button()으로 사용
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRunButton()
initAddButton()
initClearButton()
}
private fun initClearButton() {
clearButton.setOnClickListener {
// ...
}
}
private fun initAddButton() {
addButton.setOnClickListener {
// ...
}
}
private fun initRunButton() {
sendButton.setOnClickListener {
// ...
}
}
private fun getRandomList() : List<Int> {
// ...
}
private fun setNumberBackground(number:Int, textView: TextView) {
// ...
}
다음과 같이 함수의 내부기능은 함수로 따로 구현해놓은 뒤 onCreate()함수내에서 그 함수를 init느낌으로 부착해주는 식의 코딩스타일을 배울 수 있었다. 그전까지는 onClick안에 덕지덕지 붙였는데 가독성과 유지보수 측면에서 상당히 깔끔한 코딩스타일인거 같았다. 함수를 왜 private로 선언해야하는지는 아직 잘 모르다.
## kotlin(액티비티)에서 view의 background를 바꿔주는 법
when(number) {
in 1..9 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_yellow)
in 10..19 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_green)
in 20..29 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_red)
in 30..39 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_blue)
else -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_grey)
}
로또의 번호(자리수)마다 배경색을 달리해줘야 했는데, 그러기 위해서는 내가 정의했던 Drawable에 접근해야 했다. 일반적인 방법으로는 접근이 안됐다. 아래와 같이 접근해서 가져와야 했다.
ContextCompat.getDrawable(this, R.drawable."XML파일")
ContextCompat은 Resoure에서 값을 가져올때 SDK에 버전같은 걸 고려하지 않고 가져오게 해주는 클래스라고 한다. 가져올게 있으면 이렇게 가져오면 되는거 같다. 꼭 기억해두자
## layout에서 visibility 속성 gone, visible, invisible
tools:visibility="visible" //tools는 미리보기에서만 반영되게 해줌
android:visibility="gone"
android:visibility="invisible"
- visible = 보임
- gone = 자리 차지하지않고 없어짐, 안보임
- invisible = 자리는 차지하지만 안보임
## numberPicker
위 형태를 가지며 아래와 같이 사용한다.
numberPicker.minValue = 1 //최솟값
numberPicker.maxValue = 45 //최댓값
numberPicker.value // 값 접근
## Handler().postDelayed를 이용하여 시간차를 주는 법
// Handler이용
Handler().postDelayed({
},1000L)
//
randomNumberList.forEachIndexed{ index, item ->
Handler().postDelayed({
when(index) {
in 0..4 -> voting = true
else -> voting = false
}
pickNumberSet.add(item)
ballList[index].apply {
text = item.toString()
isVisible = true
setNumberBackground(item,this)
}
},(index*500).toLong())
}
JS에서 setTimeout이랑 똑같이 사용하면 된다. 다만 두번째 인자로 시간이 들어오는데, 그시간에는 Long타입을 넣어줘야한다.
난 공마다 시간차를 주고 싶어서 맨처음에는 인자에 500L만 넣어줬는데 6개의 공에 모두 같은 시간이 적용됐다.( 6개의 공이 한번에 0.5초 후 한번에 등장) 공마다 다른 시간을 줘야했기 때문에 index를 곱해주는 방식을 이용했다.
# 앱을 만들며 느낀 것
## .apply{..}
apply를 안지 얼마 안됐는데 꽤나 유용하게 활용 중에 있다. apply의 람다함수의 인자로 들어오는 것은 호출한 놈이 된다. 아래와 같은 경우에서는 ballList[index]에 담겨있는 TextView가 됐고, 그 textView의 접근할 수 있었다. this.**** 으로 속성을 정의해도 되지만 생략가능하다. 코드의 가독성이 상당히 좋아지고 반복문과 활용하게 되니까 정말 유용했다.
ballList[index].apply {
text = item.toString()
isVisible = true
setNumberBackground(item,this)
}
## .when(){...}
if-else문을 조금 더 간결하고 가독성 좋게 만들어주는 when절도 얼마전 공부하면서 처음 알게 됐는데 상당히 잘 활용중에 있다.
when(number) {
in 1..9 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_yellow)
in 10..19 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_green)
in 20..29 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_red)
in 30..39 -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_blue)
else -> textView.background = ContextCompat.getDrawable(this,R.drawable.circle_grey)
}
when(index) {
in 0..4 -> voting = true
else -> voting = false
}
## forEach{...}
얼마전 람다함수를 공부하면서 forEach와 map에 대해서 확실한 차이점을 알게 됐는데 그 이후 어떤 곳에서 활용하면 좋은지에 대해서 명확해졌고 이번에도 잘 사용 할 수 있었다. 아래와 같이 ballList ( mutableList<int> )인 리스트에 요소를 하나씩 끄집어 내, 내가 정의하게 될 람다 함수의 매개변수로 던져 진다. 그러면 그 it을 활용하여 작성하면 된다.
ballList.forEach {
it.text = "0"
it.isGone = true
}
## 동일 속성의 반복되는 뷰를 가져올 경우 리스트에 담는다.
이 부분은 아직 초반부분이여서 이렇게 하드코딩한 것 일 수도 있다. 뒤에서 리사이클러뷰를 만들거나 뷰를 새로 만들 수 도 있을거 같긴 하다. 그러나 일단 이런식으로도 View들을 묶어서 관리할 수 있구나 알 수 있었다. 이럴경우 index와 해당 View가 일치해 다루기도 편리했다.
private val ballList : List<TextView> by lazy {
mutableListOf(
findViewById(R.id.fisrtBallTextView),
findViewById(R.id.secondBallTextView),
findViewById(R.id.thirdBallTextView),
findViewById(R.id.fouthBallTextView),
findViewById(R.id.fivthBallTextView),
findViewById(R.id.sixthBallTextView),
)
}
# 👩💻 궁금한 점
- 왜 private로 선언하는것 일까?
- onCreate()안에 함수를 정의하는 것과 밖에 정의하는 것의 차이
따로 정리하기로 한 것은, 꽤 길어질 거 같아서 별도의 포스팅으로 정리하도록 하겠다. 매우 쉬운 프로젝트였지만 그속에서 나름 배운게 많고 얻게 된 것도 많은거 같다.
++추가