안드로이드 프로젝트를 진행하는 과정에서, 버스의 좌석을 예매하는 UI가 필요했다. 이 때 버스의 종류에 따라(27인승, 45인승 등) 좌석 배치가 다르게 표시되어야 하는데 구글링 했을 때는 xml을 이용해 고정된 좌석 레이아웃만을 나타내는 방법만 찾을 수 있었다.
따라서 이를 커스텀 뷰를 이용해 직접 만들어보기로 결정했고, 과정에서 있었던 에러사항들을 기록으로 남기려 한다.
아래는 실제 실행 화면!
좌석은 하나만 선택이 가능하고, 선택 여부에 따라 잔여 좌석 수, 결제 금액, 선택완료 버튼 활성화 여부가 변경된다.

1. 좌석 배치를 그리는 방법
대부분의 전세버스의 좌석 배치는 비슷하다고 생각했다. 따라서 행 당 좌석 수, 총 행 수, 제일 뒷 자리 좌석 수를 받아와서 이를 토대로
통로(복도) 위치, 통로 사이즈 등을 계산해서 좌석 배치도를 그리도록 했다.

다음으로 들었던 고민이 각각의 좌석을 어떻게 그릴 것인가에 대한 문제였다. 처음엔 GPT에게 커스텀 뷰를 통해 좌석 배치도를 그리는 방법을 물어봤는데, View를 상속받은 커스텀 뷰 안에 Rect를 추가해서 좌석을 그리는 코드를 알려주었다. 다만 Rect로 좌석을 만들 경우 커스텀하게 레이아웃을 변경하기 어렵다는 문제가 있었다. 그래서 ViewGroup을 상속받고 각 좌석을 textView로 그리는 방법을 생각했는데 이럴 경우 레이아웃이 중첩된다는 문제가 있었다. 더 나은 방법을 채택하기 위해 다음과 같이 각 방법의 장단점을 정리해보았다.

1. View를 상속 & 좌석을 Rect로 구성
- 계층 구조가 플랫하게 유지 됨
- text 삽입, border나 radius 등 커스텀하게 레이아웃 변경이 어려움
- onClick 이벤트 할당이 안 되어 선택된 좌석의 레이아웃을 다르게 변경하기 어려움 (touch 이벤트를 통해 선택된 위치에 Rect 존재 여부 확인 후, 해당 Rect의 레이아웃을 다르게 해서 전체 Rect를 다시 로드하는 방법으로 해야함..)
2. ViewGroup을 상속 & 좌석을 TextView로 구성
- 커스텀하게 레이아웃 변경 가능
- onClick 이벤트 할당 가능
- 계층 구조가 깊어짐
나는 좌석의 속성을 더 쉽게 변경하고 관리할 수 있다는 이점이 크게 느껴져서 ViewGroup을 상속받고 각 좌석을 TextView로 생성하는 방법을 선택했다!
다음으로는 어떤 ViewGroup을 선택할 것인가에 대한 문제가 있었는데, RelativeLayout 혹은 ConstraintLayout을 사용하는 방법중에 고민을 했다. 공식문서에는 레이아웃 측정 비용을 줄이기 위해서 RelativeLayout보다 ConstraintLayout을 권장한다고 나와있었다.. 다만 ConstraintLayout은 제약 조건을 설정해줘야 하는데 동적으로 TextView를 추가하는 과정에서 제약 조건 설정이 조금 까다로울 수 있겠다는 생각이 들었다.
생각하기로는 좌석 배치를 그릴 때 행 당 좌석 수, 총 행 수 등의 값을 바탕으로 그리고 있으니 각 좌석의 행/열 값을 알 수 있고, 이를 바탕으로 leftMargin, topMargin값을 구하면 RelativeLayout 내에서 좌석의 위치를 잡기가 쉬울 것 같다고 생각했다. 그래서 빠른 구현을 위해 RelativeLayout을 상속받아서 만들기로 했다!
결과적으로 행 당 좌석 수, 총 행 수, 마지막 줄 좌석 수 이렇게 세 가지 값을 통해 다음과 같이 대부분의 버스 좌석 형태를 그릴 수 있게 되었다.
여기서 [ 행 당 좌석 수 == 마지막 줄 좌석 수 ] 이면 제일 왼쪽 그림처럼 맨 뒷줄에도 통로가 생기고, [ 행 당 좌석 수 < 마지막 줄 좌석 수 ] 이면 제일 마지막 줄의 너비가 기준이 되어 좌석 수 차이 만큼 통로 사이즈를 계산해서 2,3,4번째 그림처럼 출력되어 나온다.
버스 좌석을 보면 행 당 좌석수가 2, 3, 4 이외의 케이스는 없다고 생각해서, 통로의 위치는 하드코딩을 통해 설정해주었다. 행 당 좌석 수가 2,4 이면 통로가 가운데에 오도록, 3 이면 첫 번째 그림처럼 좌석이 2 1로 나뉘도록 설정했다.

2. 좌석 선택 뷰, 좌석 배치도 뷰 2가지로 활용
예매를 위해 1.좌석을 선택하는 뷰와 선택한 좌석의 위치를 확인할 수 있는 2.내 좌석 배치도를 확인하는 뷰 이렇게 UI는 비슷하고 다른 기능이 필요한 두 가지 뷰가 필요했다. 좌석 배치도 확인에서는 매진된 좌석을 확인할 필요도, 좌석을 선택하는 이벤트도 필요없고 위치만 확인하면 된다.

나는 좌석 위치 확인 뷰를 위해 새로운 커스텀 뷰를 만들기 보다 기존의 좌석 선택 뷰를 활용하기로 했다.

xml에서 커스텀 뷰 생성 시 'mode' 커스텀 속성값을 만들어서 enum 값 selectable, read_only 중에 하나를 받아오도록 했다.
이에 따라 좌석 선택 뷰(selectable)이면 매진된 좌석들은 따로 표시 & 좌석들에 클릭 이벤트를 할당시켰고, 좌석 위치 확인 뷰(read_only) 이면 내가 예매한 좌석의 위치만 확인할 수 있도록 했다.
3. 커스텀 뷰에서 데이터 바인딩으로 속성 값 받아오기
xml에서 좌석 배치 커스텀 뷰를 사용할 때 mode값, 행 당 좌석 수, 총 행 수, 마지막 줄 좌석 수, 그리고 좌석 선택 뷰 / 좌석 위치 확인 뷰에 따라 매진된 좌석 리스트 / 예매한 좌석 넘버 값을 받아와야 한다. 커스텀 속성을 통해 값을 받아오도록 했는데 하드 코딩했을 때는 잘 받아오던 값이 데이터바인딩을 통해 가져오니 값을 받아오질 못했다.

우선 데이터 바인딩에 대해 알아볼 필요가 있다. 데이터 바인딩을 적용한 xml을 빌드하면 **Binding.java 라는 클래스 파일이 생긴다.
아래와 같은 xml 파일이 있다고 생각해보자. 그러면 **Binding.java 는 멤버 변수로 myName을 가지고 있게 된다.
<data>
<variable name="my" type="Person"/>
</data>
<TextView
android:id="@+id/myName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{my.name}"
/>
여기서 android:text="@{my.name}" 이 부분에서 데이터 처리를 위해 바인딩 클래스 내부에서 setter를 찾는다.
조금 더 자세히 알아보자면, 바인딩 클래스 내부에서 1. myName(id값)이라는 TextView를 가지고 있는지 확인하고 2. 이 TextView에 setText(속성명이 text) 메서드가 있는지 확인하고 3. setText 메서드의 파라미터 타입이 String 인지 확인 후 적절한 setter임을 확인하면 값을 할당한다. 즉! 해당 바인딩 클래스 내에 TextView에 String 타입의 파라미터를 받는 setter가 있어야 한다는 것이다.
따라서 커스텀 뷰에서 데이터 바인딩을 사용하고 싶다면 적절한 setter를 만들어줘야 한다.
아래와 같이 setter를 정의해서 해결했다.

다음으로는 데이터 바인딩으로 값을 받아올 때, 속성 값을 선언한 순서대로 할당 받지 않는다는 문제가 있었다.
이는 setter가 호출 될 때마다 필요한 값이 모두 할당 되었는지 체크하고 뒤에 로직으로 넘어가도록 해서 해결했다.


4. 재활용 위해 모듈로 분리
이렇게 좌석 배치 커스텀 뷰를 만들었는데, 이러한 뷰는 여러 곳에서 이용될 수 있다고 생각했다. 따라서 라이브러리처럼 활용하면 이러한 기능이 필요한 다른 개발자의 편의를 향상시킬 수 있지 않을까 생각하여 모듈로 분리하였다.
필요한 프로젝트에서 해당 파일을 삽입해 아래처럼 좌석 배치 뷰를 활용할 수 있다!!


그리고 좌석 선택 뷰에서 좌석 선택 시 결제하기 버튼 활성화 등의 기능을 구현할 수 있도록 하기 위해서 외부에서 좌석의 onClick 이벤트에 대한 동작을 구현할 수 있도록 해주었다.
아래처럼 onClick 이벤트에 대한 내용을 직접 구현해줄 수 있다.

참조
https://dev3m.tistory.com/entry/Android-DataBinding%EC%9D%84-%EC%82%B4%ED%8E%B4%EB%B3%B4%EC%9E%90
Android DataBinding을 살펴보자!
안녕하세요! '개발세끼'의 막내를 담당하고 있는 '세끼'라고 합니다. 이번에는 안드로이드 Data Binding에 대한 글을 적어보려 하는데요, 데이터 바인딩 사용법에 대한 글은 아닙니다. 데이터 바인
dev3m.tistory.com