본문 바로가기
안드로이드

[안드로이드] layout <include> 속성 정의 및 이벤트 구현하기

by algosketch 2022. 2. 10.

이 글에서 설명하는 내용은 싱글 Activity 프로젝트에 적절합니다.

  • 22.02.27. 설명 추가 : variable 로 listener 혹은 viewModel 등을 넘겨주는 방식으로도 이벤트를 연결할 수 있다. listener 를 interface 로 만들어 ViewModel 등에서 구현하여 속성값으로 넘겨주면 된다. 하지만 ViewModel 마다 직접 구현해야 하고, 코드 중복이 생기는 것은 불가피하다.

 

View 의 재사용이 안드로이드에서 가장 아쉬운 부분인 것 같다. React 나 Flutter 같은 경우 매우 쉽게 사용중인 ui 의 일부를 떼어내어 컴포넌트화(재사용)할 수 있다.

떼어낸 ui 가 툴바라고 해 보자. 대략 위와 같은 모습일 것이다. 페이지에 따라서 BackButton 이 없을 수도 있다. 백 버튼이나 설정 버튼을 누르면 이벤트가 발생해야 하고, 툴바의 제목은 페이지마다 다르다. 안드로이드에서는 다른 프레임워크보다 이런 코드를 재사용하기 어렵다. (ui 구현을 Anko 라이브러리로 한다면 쉬울지도... 하지만 안코를 쓰는 곳은 없으니)

참고로 커스텀뷰로 구현하는 방법도 있다. 사실 컴포넌트를 정의하는 방법이 이쪽이 더 정석인 것 같지만 커스텀뷰를 만드는 과정이 너무 복잡하다. (한 번 만들어두면 사용하는 것은 좀 더 깔끔하다.)

 

1. 구현할 View

미리보기 화면은 위와 같다. 간단하게 백버튼과 제목이 들어간다. 백버튼은 백스택이 없는 경우엔 보이지 않아야 한다. 또한 제목은 사용하는 곳마다 다르다.

 

2. 구현

// MainActivity.kt
fun onClickBack(view: View) {
    view.findNavController().navigateUp()
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools">
    <data>
        <import type="android.view.View"/>
        <variable
            name="disableBackButton"
            type="Boolean" />
        <variable
            name="title"
            type="String" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:elevation="4dp"
        android:background="@color/white">

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/toolbar_back"
            android:layout_width="24dp"
            android:layout_height="24dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="@dimen/horizontal_margin"
            android:background="@drawable/ic_arrow_back"
            android:backgroundTint="@color/black"
            android:visibility="@{disableBackButton ? View.GONE : View.VISIBLE }"
            android:onClick="onClickBack"/>

        <TextView
            android:id="@+id/toolbar_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@id/toolbar_back"
            android:layout_marginStart="20dp"
            style="@style/header3"
            android:text="@{title}"
            tools:text="제목"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

모든 프로젝트에 적용할 수 있도록 데이터바인딩도 시도해봤으나 보기좋게 실패했다. 이벤트를 구현하는 부분은 싱글 액티비티에서만 사용할 수 있다. 이 코드는 (Navigation Component 를 사용중인) 프로젝트의 코드를 발췌하여 가져왔다. (참고로 코드가 비공개되어 있는 프로젝트이다.)

 

2-1. 변수 선언

<data>
    <import type="android.view.View"/>
    <variable
        name="disableBackButton"
        type="Boolean" />
    <variable
        name="title"
        type="String" />
</data>

필요한 자료구조를 임포트하고 변수들을 선언한다. variable 로 선언한 변수들은 이 layout 을 include 하는 곳에서 주입시켜줄 수 있다. hasBackButton 처럼 긍정을 사용하는 편이 가독성에 더 좋으나 disable 과 같이 부정을 사용한 이유는 null 값이 들어오면 false 로 취급되기 때문이다. (null 값이 들어오면 기본적으로 백버튼을 보여주기 위함)

 

2-2. 변수 사용

android:text="@{title}"

제목 대입

 

android:onClick="onClickBack"

이벤트 함수 대입

 

android:visibility="@{disableBackButton ? View.GONE : View.VISIBLE }"

백버튼의 Visibility 설정

 

2-3 layout 사용

<include
    android:id="@+id/home_toolbar"
    layout="@layout/toolbar"
    app:title='@{"홈"}'
    app:disableBackButton="@{true}"/>

※ title 은 resource 로 대입하는 게 더 좋다.

@{ 여기에 값을 넣어주면 된다. } title 은 String 값을 넘겨줘야 하는데 큰 따옴표(")가 \ 로 escape 이 안되어서 작은따옴표(')로 감싸주었다.

 

이벤트 연결하는 방법을 구글링 해봤지만 마음에 드는 결과를 발견 못 하던 중 여러 가지를 실험하다가 발견한 방법이다. 사실 더 좋은 방법이 있을 것이다.