유행은 지났지만
https://simritest.com/reaction/start
다음과 같은 반응속도 테스트 게임을 스마트폰 앱 버전으로 만들어보기로 했다.
위의 이미지와 같은 게임이다.
게임의 과정은 다음과 같다.
1. 시작하려면 화면을 클릭한다.
2. 화면이 초록색으로 바뀌면 빠르게 누른다.
위의 두개의 과정으로 매우 간단한 게임이다.
그런데 고려해야할 점이 있다. 사용자가 만약에 초록색으로 바뀌기 전에 광클을 한다면? 계속 누르고 있다면, 매우 빠른 반응 속도가 나와 측정에 의미가 없는 것이다.
해결하기 위해 만약 게임을 시작하고, 초록화면이 나오기 전에 화면을 클릭하게 되면 실격 처리되도록 구현을 해야한다.
구현
1. Status.kt (enum class)
게임의 상태는 ENUM Class로 구현을 하였다.
총 5가지로 하여,
1. MAIN : 게임 준비 단계
2. START : 게임이 시작되었음 (초록색 화면 등장)
3. PAUSE : 게임이 시작되었음 (아직은 초록색이 등장하지 않음)
4. FAST : 빠르게 눌렀을 경우 -> 실격 처리
5. END : 게임 끝 (결과창)
enum class Status(val status : Int) {
MAIN(0),
START(1),
FAST(2),
PAUSE(3),
END(4)
}
다음과 같은 Enum class를 만들어 주었다.
2. MainViewModel
ViewModel에 상태를 저장하여, 각 버튼의 입력마다 Status값을 저장하도록 하였다.
import android.os.Looper
import androidx.databinding.ObservableField
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import java.util.logging.Handler
class MainViewModel : ViewModel() {
private val _status = MutableLiveData<Status>()
val status : LiveData<Status> get() = _status
var msg = ObservableField<String>()
private var startTime = 0L
private var endTime = 0L
private val handler = android.os.Handler(Looper.getMainLooper())
init {
_status.value = Status.MAIN
msg.set("화면을 클릭하면 게임이 시작합니다.\n 녹색이 보이면 빠르게 누르세요.")
}
fun gameBtnClick() {
when(status.value)
{
Status.MAIN -> {
gameStart()
}
Status.START -> {
endTime = System.nanoTime()
msg.set("당신의 반응속도는 ${(((endTime - startTime)/1_000_000_000.0).toDouble())}초 입니다.")
_status.value = Status.END
}
Status.PAUSE -> {
_status.value = Status.FAST
_status.value = Status.END
msg.set("너무 빨리 눌렀어요")
handler.removeCallbacksAndMessages(null)
}
Status.END -> {
_status.value = Status.MAIN
msg.set("화면을 클릭하면 게임이 시작합니다.\n 녹색이 보이면 빠르게 누르세요.")
}
else -> {
}
}
}
private fun gameStart()
{
_status.value = Status.PAUSE
val range = (10 until 30)
val randN = range.random() * 100
handler.postDelayed(Runnable {
_status.postValue(Status.START)
startTime = System.nanoTime()
msg.set("클릭!")
}, randN.toLong())
}
}
위의 코드에서 각 Status에 따라 클릭에 대한 이벤트가 다르게 발생하도록 구현하였다.
1에서 3초 사이의 랜덤으로 초록색 화면을 출력하기 위해 random함수를 사용하였다.
al range = (10 until 30)
val randN = range.random() * 100
함수 딜레이를 위해 Handler를 사용하였다.
handler.postDelayed(Runnable {
_status.postValue(Status.START)
startTime = System.nanoTime()
msg.set("클릭!")
}, randN.toLong())
또한 초록색이 나오기 전에 클릭하게 되면, 기존에 등록된 Handler를 삭제해야 하므로, 아래 코드를 통해 취소 기능을 추가하였다.
handler.removeCallbacksAndMessages(null)
3. 레이아웃
레이아웃은 간단하다. 다음과 같이 AppCompatButton하나가 레이아웃 전체를 차지하며, 클릭시 ViewModel에 정의된 gameBtnClick이 실행되도록 하였고, msg의 값과 버튼의 텍스트와 동기화 되도록 하였다.
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<data>
<variable
name="mainViewModel"
type="com.shino72.physical.ui.main.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/gameBtn"
android:onClick="@{()->mainViewModel.gameBtnClick()}"
android:textStyle="bold"
android:textSize="20sp"
android:textAlignment="center"
android:textColor="@color/white"
android:text="@={mainViewModel.msg}"
android:background="@color/mainColor"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
4. MainActivity
MainActivity는 다음과 같다. 각 상태를 Observing하여, 상태를 감지하여, 버튼의 색상을 바뀌도록 구현하였다.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import com.shino72.physical.R
import com.shino72.physical.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private val vm : MainViewModel by viewModels()
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.mainViewModel = vm
setContentView(binding.root)
vm.status.observe(this, Observer {
when(it)
{
Status.FAST -> {
binding.gameBtn.setBackgroundColor(resources.getColor(R.color.pauseColor))
}
Status.START -> {
binding.gameBtn.setBackgroundColor(resources.getColor(R.color.startColor))
}
Status.MAIN -> {
binding.gameBtn.setBackgroundColor(resources.getColor(R.color.mainColor))
}
else -> {
}
}
})
}
}
[전체 코드]
https://github.com/Myeongcheol-shin/physical_test_application/tree/main
5. 시연 영상
6. 마무리
간단하게 구현을 하였는데, 추가적인 기능으로 다른 유저들과 점수 배틀을 할 수 있도록 Google play 기능을 다음에 넣어보도록 하겠다.
'안드로이드 > 반응속도 테스트 앱 만들기' 카테고리의 다른 글
[Android - 반응속도 앱] 2. play console 리더보드 추가하기 (2) | 2023.05.31 |
---|