[안드로이드] 키오스크 만들기 (1) - 메인 화면

YJ·2023년 7월 3일
0
post-thumbnail

전체 APP에 대한 기본 설명

이 앱은 카페 키오스크 앱으로 주문부터 결제까지 사용자에게 편리한 기능을 서비스하는 애플리케이션이다.

  1. 앱을 시작하면 맨 처음에 로고 화면이 포함된 로딩 화면이 2초간 나온 후 간단한 문구가 있는 홈 화면으로 이동

  2. 해당 화면 어디든지 터치하면 카페 메뉴가 있는 화면으로 이동

  3. 메뉴는 여러 카테고리별로 구성되어 있으며 정해진 시간 안에 원하는 메뉴와 해당 옵션을 선택하여 리스트에 담을 수 있다.

  4. 또한 메뉴 삭제는 물론 수량까지 변경이 가능하며 원하는 메뉴를 담았다면 결제하기 버튼을 통해 사용자가 선택한 전체 리스트들을 총 수량, 총금액과 함께 한눈에 볼 수 있는 화면이 보이며 사용자는 선택한 목록들을 확인한 후에 다음 화면으로 장소를 선택할 수 있는 화면이 나타난다.

  5. 원하는 장소 선택 후 카드 결제 창이 나타나는데 총 결제 금액을 확인한 후 승인 요청을 하면 된다.

  6. 결제가 승인되면 결제가 완료되었다는 화면이 나타나면서 5초 후에 자동으로 다시 메인 화면으로 되돌아가는 간단한 키오스크 애플리케이션이다.

1. 메인 화면

화면 설명

홈 화면에서 화면을 터치하면 나오는 메인 화면으로 사용자가 원하는 카테고리에서 메뉴를 선택할 수 있는 화면
메뉴를 옆으로 슬라이드 하거나, 원하는 카테고리 탭 클릭으로 메뉴 선택 가능
총 300초가 주어지며 새로운 메뉴가 추가될 때마다 300초로 초기화
시간이 초과된다면 모든 리스트를 비우고 홈 화면으로 돌아가는 구조


✅ 1) 메뉴 카테고리를 <TabLayout> 사용하여 표시

  • xml 코드
<com.google.android.material.tabs.TabLayout
    android:id="@+id/lyj_tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabMode="scrollable">
</com.google.android.material.tabs.TabLayout>

app:tabMode="scrollable" 를 통해 탭이 많은 경우 스크롤 가능하도록 설정


✅ 2) 여러 메뉴 페이지를 <FrameLayout>, <ViewPager2> 사용하여 표시

  • xml 코드
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="7">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/lyj_viewPager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

<FrameLayout> 안에 <ViewPager2>를 포함하여 여러 페이지를 스와이프 하면서 해당 위치에 각 페이지의 콘텐츠를 표시

  • 메뉴 표시 하는 코틀린 코드
tabLayout = findViewById(R.id.lyj_tabLayout)
viewPager2 = findViewById(R.id.lyj_viewPager2)
val viewPager2Adapter = ViewPager2Adapter(this)
viewPager2.adapter = viewPager2Adapter
// 탭 메뉴 추가
val tabs = listOf(
    "커피(HOT)",
    "커피(ICE)",
    "에이드&티",
    "음료",
    "디저트"
)

TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
    // 리스트 목록을 가져와 탭에 보여주기
    tab.text = tabs[position]
}.attach()
  • ViewPager2Adapter 를 생성하여 어댑터 설정
  • tabs 변수에 표시할 탭 목록 작성
  • TabLayoutMediator 를 사용하여 tabLayout, viewPager2 연결시키기
  • 탭과 페이지를 연결하여 화면을 스와이프하거나 탭 선택하여 해당 페이지의 콘텐츠를 표시
  • tab.text 로 해당 position에 있는 텍스트 전달받아 해당 탭에 표시

✅ 3) 각 카테고리에 해당하는 메뉴 화면을 Fragment 사용하여 표시

  • fragment_hotcof.xml 코드 (일부 생략)
<FrameLayout 
    tools:context=".hotCof_Fragment">
    <ScrollView>
        <LinearLayout>
            <LinearLayout>
                <LinearLayout
                    android:id="@+id/lyj_hot_ameri">
                    <ImageView
                        android:id="@+id/lyj_hot_ameri_img"
                        android:background="@drawable/hot_cof"/>
                    <TextView
                        android:id="@+id/lyj_hot_ameri_text"
                        android:text="(HOT) 아메리카노"/>
                    <LinearLayout
                        android:gravity="center">
                        <TextView
                            android:id="@+id/lyj_hot_ameri_price"
                            android:text="1500"/>
                        <TextView
                            android:text=""/>
                    </LinearLayout>
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </ScrollView>
</FrameLayout>
  • <FrameLayout> 안에 <ScrollView> 를 작성하여 메뉴 스크롤 가능하도록 구현
  • <LinearLayout> 전체 구조 안에 한 줄 씩 <LinearLayout> 형태로 구성
  • 각각의 메뉴는 다음과 같은 형태로 존재
  • <LinearLayout> 안에 <ImageView>, <TextView> 로 존재

  • 코틀린 코드
class ViewPager2Adapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = 5

    // Fragment 연결
    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> hotCof_Fragment()
            1 -> iceCof_Fragment()
            2 -> adeTea_Fragment()
            3 -> drink_Fragment()
            4 -> dessert_Fragment()
            else -> iceCof_Fragment()
        }
    }
}
  • createFragment 로 현재 position에 따라 해당하는 각각의 프래그먼트 화면을 반환

✅ 4) 각 메뉴 클릭 이벤트

  • hotCof_Fragment.kt 외 나머지 프래그먼트 코드
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
    val view = inflater.inflate(R.layout.fragment_hotcof, container, false)
    val linearLayouts = listOf<LinearLayout>(
        view.findViewById(R.id.lyj_hot_ameri),
        view.findViewById(R.id.lyj_hot_latte),
        // 생략
        
    )
    for (linearLayout in linearLayouts) {
        linearLayout.setOnClickListener {
            handleLinearClick(linearLayout)
        }
    }
    return view
}
  • 각 메뉴의 <LinearLayout> 에 for 루프를 통해 클릭이벤트 부여

  • 해당 리니어에 존재하는 id 값들 가져오기 (handleLinearClick 함수)
val linearLayoutId = linearLayout.id
// 해당 리니어의 id_~ 값들 가져오기
val textId = linearLayout.resources.getIdentifier(
    "${resources.getResourceEntryName(linearLayoutId)}_text",
    "id",
    requireActivity().packageName
)
// "_price", "_img" 코드 생략
  • linearLayout.id 를 통해 클릭한 해당 LinearLayout의 id를 가져옴
  • resources.getIdentifier() 를 사용하여 리소스 id를 동적으로 가져옴
  • 해당 리소스 이름은 클릭된 리니어 id와 일치하며 각각 "_img", "_text", "_price" 가 붙는다

  • 클릭된 <LinearLayout> 에서 메뉴 이름, 가격, 이미지를 가져오기
var text: String? = null
var price: Int? = null
var imageDrawable: Drawable? = null

// 해당 리니어의 정보 가져오기
linearLayout.findViewById<TextView>(textId)?.let {
    text = it.text.toString()
}
linearLayout.findViewById<TextView>(priceId)?.let {
    price = it.text.toString().toInt()
}
linearLayout.findViewById<ImageView>(imageId)?.let { imageView ->
    imageDrawable = imageView.background
}
  • 위의 코드에서 가져온 id 값(textId, priceId, imageId) 를 이용하여 해당하는 각각의 TextView, ImageView를 찾아 변수에 할당

✅ 5) 클릭한 메뉴 정보 옵션 다이얼로그에 전달

if (text != null && price != null && imageDrawable != null) {
    // 다이얼로그에 정보 전달
    val dialog = optionDialog(
        resources.getResourceEntryName(linearLayoutId), text!!, price!!, imageDrawable!!
    )
    dialog.show(activity?.supportFragmentManager!!, "CustomDialog")
}

✅ 6) 시간 이벤트

  • 총 시간을 300초부터 카운트 시작
  • 메뉴가 추가 될 때마다 300초로 초기화
  • 300초가 지나면 자동으로 리스트 비우고 홈 화면으로 이동

  • 코틀린 코드 (MainActivity.kt)
private lateinit var countDownTimer: CountDownTimer
private var isCountDownRunning = false
private val COUNTDOWN_TIME = 300000 // 300초를 밀리초로 표현 300000
private val COUNTDOWN_INTERVAL = 1000 // 카운트다운 간격을 1초로 설정
private var initialCountDownTime: Long = COUNTDOWN_TIME.toLong()
  • resetCountDown()
private fun resetCountDown() {
    stopCountDown()
    startCountDown()
}
- 현재 카운트다운을 중지시키고 다시 초기화된 카운트다운 시작

  • onResume()
override fun onResume() {
    super.onResume()
    if (!isCountDownRunning) {
        startCountDown()
    }
}
- 액티비티가 다시 활성화될 때 카운트다운 시작
- isCountDownRunning 값이 false인 경우에 카운트다운 시작
- 카운트다운 중복이 발생하는 경우 방지

  • onPause()
override fun onPause() {
    super.onPause()
    isCountDownRunning = true
    stopCountDown()
}
- 액티비티가 일시 중지되었을 때 호출
isCountDownRunning 를 true로 설정하여 일시적으로 중지 상태임을 표시하여 
onResume()가 내부적으로 호출되어 카운트다운이 중복 시작되는 것을 방지
- stopCountDown()를 호출하여 현재 진행 중인 카운트다운 중지

🛎️ 시간 초과되어 홈 화면 이동하였는데 계속 일정 주기로 "시간 초과"의 토스트 메시지가 나타남
-> onPause()로 해결

- onPause()는 활동이 더 이상 전면에 표시되지 않고, 백그라운드로 이동할 때 호출
- MainActivity가 백그라운드에 있을 때 카운트다운을 정지
- onResume()로 액티비티가 다시 활성화될 때 카운트다운 시작

  • onDestroy()
override fun onDestroy() {
    super.onDestroy()
    stopCountDown()
}
- 액티비티가 소멸되지 전에 호출하여 카운트다운 중지

  • stopCountDown()
private fun stopCountDown() {
    if (isCountDownRunning) {
        countDownTimer.cancel()
    }
    // 카운트다운 중지
    isCountDownRunning = false
}
- 현재 카운트다운 실행 중일 경우 카운트다운 중지
- countDownTimer를 호출하여 중지하고 false로 설정하여 중지 되었음을 표시

  • startCountDown()
private fun startCountDown() {
    // 카운트다운 시간 초기화
    initialCountDownTime = COUNTDOWN_TIME.toLong()
    countDownTimer =
        object : CountDownTimer(COUNTDOWN_TIME.toLong(), COUNTDOWN_INTERVAL.toLong()) {
            override fun onTick(millisUntilFinished: Long) {
                val second = millisUntilFinished / 1000
                lyj_countDownTime.text = second.toString()
                initialCountDownTime = millisUntilFinished // 카운트 시간 업데이트
            }
            // 카운트다운 종료
            override fun onFinish() {
                clearItemListAll()
                val intent = Intent(this@MainActivity, HomeActivity::class.java)
                startActivity(intent)
                Toast.makeText(applicationContext, "주문 시간이 만료되었습니다.", Toast.LENGTH_SHORT).show()
            }
        }
    countDownTimer.start()
    isCountDownRunning = true
}
- 카운트다운 시작
- countDownTimer 객체 생성하여 카운트다운 시간과 간격 설정
- onTick() 함수는 정의한 COUNTDOWN_INTERVAL(1초) 마다 호출되는 로직 정의
- millisUntilFinished / 1000 로 나누어 "초" 단위 변환
- onFinish() 함수에 카운트다운이 종료될 경우 clearItemListAll() 함수 호출하여 리스트 모두 비운 후
토스트 메시지와 함께 홈 화면으로 이동
profile
함께 배워나가고 싶습니다!

0개의 댓글

관련 채용 정보