Logo
Published on
·9 min read

상하 대괄호([ ]) 모양(로또 용지 번호 모양) 만들기(android)

서비스 바로가기

로또 번호 생성기


로또 용지와 유사한 UI의 로또 번호 생성기 서비스는 웹 서비스와, 안드로이드 서비스로 각각 만들었습니다. 웹 서비스에서의 로또 용지 번호 모양 css는 아래 포스팅을 참고해 주세요.

상하 대괄호([ ]) 모양(로또 용지 번호 모양) 만들기(css)

설명할 내용

이번 포스팅에서는 안드로이드에서 로또 용지 번호 모양을 구현한 내용을 설명합니다.

아래 이미지의 1 ~ 45번까지 상하 대괄호([ ])로 표시된 부분이 어떻게 구현되었는지 알 수 있습니다.

안드로이드에서 구현된 UI
캡쳐

구현

1. drawable 생성

  • 선택되지 않은 번호 drawable: app/src/main/res/drawable/border_lotto_number.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- @color/lotto 색상의 2dp 사이즈의 테두리가 있는 사각형 -->
    <item>
        <shape android:shape="rectangle">
            <stroke android:width="2dp" android:color="@color/lotto"/>
            <solid android:color="@color/lotto_background"/>
        </shape>
    </item>
    <!-- 위/아래 10dp 떨어진 위치에 배경색 @color/lotto_background 사각형을 덮어줍니다 -->
    <item android:top="10dp" android:bottom="10dp">
        <shape android:shape="rectangle">
            <!-- 포스팅 하면서 보니 아래 stroke 부분은 제거해도 동일하게 동작합니다. -->
            <stroke android:width="2dp" android:color="@color/lotto_background"/>
            <solid android:color="@color/lotto_background"/>
        </shape>
    </item>
</layer-list>
  • 선택된 번호 drawable: app/src/main/res/drawable/border_lotto_number_selected.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
	<!-- @color/lotto 색상의 2dp 사이즈의 테두리가 있는 사각형 -->
    <item>
        <shape android:shape="rectangle">
            <stroke android:width="2dp" android:color="@color/lotto"/>
            <solid android:color="@color/lotto_background"/>
        </shape>
    </item>
    <!-- 위/아래 10dp 떨어진 위치에 배경색 @color/lotto_background 사각형을 덮어줍니다 -->
    <item android:top="10dp" android:bottom="10dp">
        <shape android:shape="rectangle">
            <stroke android:width="2dp" android:color="@color/lotto_background"/>
            <solid android:color="@color/lotto_background"/>
        </shape>
    </item>
    <!-- 상/하/좌/우 2dp씩 들어간 위치에 @color/black 사각형을 덮어줍니다 -->
    <item android:top="2dp" android:bottom="2dp" android:left="2dp" android:right="2dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/black"/>
        </shape>
    </item>
</layer-list>

위 Code를 안드로이드 스튜디오 > Design 또는 Split으로 미리 보기 하면 아래와 같이 보입니다.

좌) 선택되지 않은 번호 drawable, 우) 선택된 번호 drawable

2. drawable 리소스를 TextView의 background로 사용

위에서 생성된 drawable 리소스를 TextView의 background로 사용합니다.

app/src/main/res/layout/number_item.xml > android:background="@drawable/border_lotto_number"

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/txt_lotto_number"
    android:layout_width="29dp"
    android:layout_height="45dp"
    android:gravity="center"
    tools:text="1"
    android:background="@drawable/border_lotto_number"
    android:textColor="@color/lotto"
    android:textStyle="bold"
    android:layout_marginBottom="10dp"
    />

위 Code를 안드로이드 스튜디오 > Design 또는 Split으로 미리보기하면 아래와 같이 보입니다.

TextView 미리보기

3. RecyclerView에서 number_item 사용

RecyclerView 에서 GridLayoutManager로 위에서 만든 number_item.xml 사용합니다.

아래 layout 코드에서는 number_item과의 연결을 찾을 수 없지만, 소스 코드에서 Adapter로 연결됩니다.

app/src/main/res/layout/game_item.xml > <androidx.recyclerview.widget.RecyclerView .../>

<?xml version="1.0" encoding="utf-8"?>
<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="457dp"
    android:background="@drawable/border_lotto"
    android:layout_marginLeft="16dp"
    android:layout_marginTop="16dp"
    android:layout_marginRight="16dp"
    android:layout_marginBottom="16dp"
    >

    <!-- 게임 회차 및 선택된 번호 표시 부분 축약-->
    <LinearLayout
        android:id="@+id/game_summary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <!-- 게임 회차 표시 부분 축약 -->
        <TextView
            android:id="@+id/txt_game_number"
            tools:text="1"/>
    </LinearLayout>

    <!-- 여기가 로또 번호 표시 부분. LottoNumberAdapter 코드 참고 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/view_number_recycler"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/game_summary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:spanCount="7"
        android:padding="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

위 Code를 안드로이드 스튜디오 > Design 또는 Split으로 미리 보기 하면 아래와 같이 보입니다. (item0 imtem1 item2...로 보이는 부분에 위에서 만든 number_item이 표시됩니다.)

RecyclerView 미리보기

소스 코드에서 위 RecyclerViewnumber_item을 연결하는 부분입니다.

app/src/main/java/kr/sosone/lotto/adapter/LottoNumberAdapter.kt

package kr.sosone.lotto.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import kr.sosone.lotto.R
import kr.sosone.lotto.model.LottoNumber

class LottoNumberAdapter (private val context: Context, private val dataset: List<LottoNumber>)
    : RecyclerView.Adapter<LottoNumberAdapter.ItemViewHolder>()
{
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view){
        // number_item.xml에 정의된 TextView
        val txtLottoNumber: TextView = view.findViewById(R.id.txt_lotto_number)
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.number_item, parent, false)
        return ItemViewHolder(adapterLayout);
    }

    override fun onBindViewHolder(holder: LottoNumberAdapter.ItemViewHolder, position: Int) {
        val item = dataset[position]
        if(item.isPick){
            // 선택된 아이템일 경우 border_lotto_number_selected 배경 및 텍스트 색상 변경
            holder.txtLottoNumber.setBackgroundResource(R.drawable.border_lotto_number_selected)
            holder.txtLottoNumber.setTextColor(ContextCompat.getColor(context, R.color.white))
        }

        holder.txtLottoNumber.text = item.number.toString()
    }

    override fun getItemCount() = dataset.size
}

app/src/main/java/kr/sosone/lotto/model/LottoNumber.kt

package kr.sosone.lotto.model

data class LottoNumber(val number: Int, val isPick: Boolean)

LottoNumberAdapter는 상위 RecyclerView Adapter에서 할당됩니다.

(해당 코드는 여기서 설명하려는 부분에서 벗어나는 부분이므로 축약합니다. )

app/src/main/java/kr/sosone/lotto/model/LottoGame.kt

class LottoGameAdapter(private val context: Context, private val dataset: List<LottoGame>)
    : RecyclerView.Adapter<LottoGameAdapter.ItemViewHolder>()
{
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view){
        // 중첩 RecyclerView 처리
        val viewNumberRecycler: RecyclerView = view.findViewById(R.id.view_number_recycler)
    }

    override fun onBindViewHolder(holder: LottoGameAdapter.ItemViewHolder, position: Int) {
        // 중첩 RecyclerView 처리
        holder.viewNumberRecycler.adapter = LottoNumberAdapter(context, item.fullNumbers)
        holder.viewNumberRecycler.setHasFixedSize(true)
    }
}