데이터 바인딩 라이브러리 1

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

데이터 바인딩 라이브러리는 지원 라이브러리로 Android 2.1(API 7) 이상의 모든 버전에서 사용 가능.

데이터 바인딩을 사용하려면 Android Plugin for Gradle 1.5.0-alpha1 이상이 필요.

빌드 환경

  • Android SDK Manager의 Support 저장소에 라이브러리 다운로드.

  • build.gradle 파일에 dataBinding 요소 추가

    android { ... dataBinding { enabled = true
    } }

데이터 바인딩 레이아웃 파일

데이터 바인딩 레이아웃 파일은 layout 루트 태그로 시작되고 그 뒤에 data 요소와 view 루트 요소가 나옴.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

data 내에 있는 user 변수는 레이아웃 내에서 사용할 수 있는 속성에 대한 설명임

   <data>
       <variable name="user" type="com.example.User"/>
   </data>

"@{}" 구문을 사용하여 특성 속성에 기록됨.

       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>

데이터 객체

User에 대한 POJO(plain-old java project) 예시

(결코 변경되지 않는 데이터가 있는 경우)

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

JavaBeans 객체 예시

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

데이터 바인딩 관점에서 위 두 클래스는 동일함.

TextView의 android:text 특성에 사용된 @{user.firstName} 은 전자의 firstName 필드와 후자의 getFirstName() 메서드에 액세스함. 또는 firstName() 메서드가 존재할 경우 메서드로 해석하기도 함.

데이터 바인딩

BInding 클래스는 레이아웃 파일명 기준으로 생성되어 파일 이름을 파스칼 표기법(Pascal Case : 합성어 첫 글자를 대문자로 표기)으로 변환하고 그 뒤에 "Binding"을 접미사로 붙임.

main_activity.xml일 경우 생성되는 클래스는 MainActivityBinding 임.

이 클래스는 레이아웃 속성(예: user 변수)에서 레이아웃 View까지 모든 바인딩을 유지하고 바인딩 식에 대해 값을 할당하는 방법을 알고 있음.

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

다음을 통해 뷰를 얻을 수도 있음

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

ListView 어댑터나 RecyclerView 어댑터 내에서 데이터 바인딩 항목 사용시 다음을 선호하기도 함.

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

이벤트 처리

데이터 바인딩을 사용하여 뷰에서 발소오디는 이벤트 처리식을 작성할 수 있으며, 이벤트 특성 이름은 리스너 메서드명에 따라 결정됨.

예를 들면 View.OnLongClickListener 의 onLongClick() 이벤트의 특성은 android:onLongClick

  • 메서드 참조 : 식에서 리스너 메서드의 서명을 준수하는 메서드를 참조함.
  • 리스너 바인딩 : 이벤트 발생 시 계산되는 람다 식. 뷰에서 설정하는 리스너를 항상 생성하며, 이벤트가 발송될 때 람다 식을 계산함.

메서드 참조

이벤트를 핸들러 메서드에 직접 바인딩함.

컴파일 시 처리되므로, 메서드가 존재하지 않거나 메서드 서명이 올바르지 않을 경우 컴파일 시 오류 발생

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

바인딩 식이 View 에 대해 click 리스너를 할당할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

리스너 바인딩

이벤트 발생시 실행되는 바인딩 식.

임의의 데이터 바인딩 식을 실행할 수 있으며, Android Gradle Plugin for Gradle 2.0 이상에서 사용 가능.

반환 값만 리스너의 예상 반환 값과 일치해야 함.

public class Presenter {
    public void onSaveClick(Task task){}
}

click 이벤트를 클래스에 바인딩

  <?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

리스너는 식의 루트 요소로만 허용되는 람다 식으로 표현됨.

식에서 콜백이 사용될 때 데이터 바인딩이 필요한 리스너를 자동으로 생성하고 이벤트에 등록함.

뷰가 이벤트를 발생시키면 데이터 바인딩이 주어진 식을 계산.

정규 바인딩 식에서처럼 리스너 식이 평가되는 동안 여전히 데이터 바인딩의 null 및 스레드의 안전이 보장됨.

위의 예시에서는 onClick(android.view.View)로 전달되는 view 매개변수를 정의 안함.

리스너 바인딩에서 리스너 매개변수로 두 가지 중 선택 가능.

메서드에 대한 모든 매개변수를 무시하거나 모든 매개변수의 이름을 지정하는 것

매개변수의 이름을 지정하기로 선택하면 매개변수를 사용할 수 있음.

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"

또는 식에 매개변수를 사용할 경우

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

두 개 이상의 매개변수를 포함한 람다 식 사용 가능

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

수신 중인 이벤트가 void 형식이 아닌 값을 반환하는 경우

식도 그와 동일한 형식의 값을 반환해야 함

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

null 객체로 인해 식의 계산이 불가능할 경우, 데이터 바인딩은 그 형식에 대한 기본 java 값을 반환.

예를 들어, 참조 형식에는 null, int 에는 0, boolean 에는 false 등을 반환함.

조건자(예:ternary)가 있는 식을 사용해야 할 경우 void를 기호로 사용 가능.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

복잡한 리스너 방지

리스너 식은 UI에서 콜백 메서드로 사용 가능한 데이터를 전달하는 것만큼이나 단순해야 함.

비즈니스 로직은 리스너 식에서 호출한 콜백 메서드 내에 구현해야 함.

몇 가지 특화된 click 이벤트 핸들러 중 충돌 방지를 위해 android:onClick 이외의 특성이 필요함.

클래스리스너 Setter특성
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

레이아웃 세부정보

가져오기

data 요소 내에서 import 요소가 사용될 수도 있음.

Java에서처럼 이러한 요소를 사용하여 레이아웃 파일 내에 클래스를 쉽게 참조할 수 있음.

<data>
    <import type="android.view.View"/>
</data>
...
<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

클래스 이름 간에 충돌이 발생할 때 해당 클래스 중 하나의 이름을 alias 속성으로 바꿀 수 있음

그리고 import 요소로 가져온 타입을 variable 요소에서 사용할 수 있음.

<data>
    <import type="android.view.View"/>
    <import type="com.example.real.estate.View" alias="Vista"/>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>

Android Studio는 아직 가져오기를 처리하지 않으므로 자동 완선 기능이 작동하지 않을 수 있음.

그래도 컴파일은 제대로 되므로, 변수 정의에서 정규화된 이름을 사용하여 IDE 문제를 해결할 수 있음.

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

식에서 정적 필드와 메서드를 참조할 때 가져온 형식을 사용 가능

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Java에서처럼 java.lang.* 을 자동으로 가져오게 됨.

변수

data 요소 내에서 사용할 수 있는 variable 요소의 개수 제한은 없음.

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

변수 형식은 컴파일 시에 검사되므로, 변수가 android.databinding.Observable을 구현하거나 변수가 Observable 컬렉션인 경우 그 점이 형식에 반영되어야 함. 변수가 기본 클래스거나 Observable* 인터페이스를 구현하지 않은 경우 변수가 식별되지 않음.

다양한 구성(예: 가로/세로 모드)에 대한 다양한 레이아웃 파일이 있을 경우 변수들이 결합하므로 충동하는 변수 정의가 없어야 함.

생성되는 바인딩 클래스는 설명되어 있는 변수마다 각각 setter와 getter가 있음. 변수는 setter가 호출될 때까지 기본 Java 값을 취함. 즉, 참조 형식에는 null, int에는 0, boolean 에는 false 값을 취함.

필요에 따라 바인딩 식에 사용하기 위해 context 로 명명된 별도의 변수가 생성됨. context는 루트 View의 getContext()에서 가져온 값임. context 변수는 그 이름을 가진 명시적 변수 선언으로 재정의 됨.

사용자 지정 바인딩 클래스 이름

기본적인 Binding 클래스 : 레이아웃 파일명을 바탕으로 생성되며 파스칼 표기법으로 바꾼 후 "Binding"이 접미사로 붙음. 이 클래스는 모듈 패키지 아래 데이터 바인딩 패키지에 배치됨.

예를 들면

  • 레이아웃 파일 : contact_item.xml
  • 바인딩 클래스 : ContactItemBinding
  • 모듈 패키지 : com.example.my.app
  • 배치 위치 : com.example.my.app.databinding

data 요소의 class 특성을 조정하여 바인딩 클래스명을 바꾸거나 다른 패키지에 배치할 수 있음.

<!-- 바인딩클래스명 변경 -->
<data class="ContactItem">
    ...
</data>

or

<!-- 모듈 패키지 내에서 다른 패키지에 배치할 경우 -->
<data class=".ContactItem">
    ...
</data>

or

<!-- 전체 패키지가 제공될 경우 어떤 패키지든 사용 가능 -->
<data class="com.example.ContactItem">
    ...
</data>

Include

특성에 앱 네임스페이스와 변수 이름을 사용하여 포함하는 레이아웃에서 포함되는 레이아웃의 바인딩으로 변수를 전달할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </LinearLayout>
</layout>

name.xml 및 contract.xml 모두 user 변수가 있어야 함.

데이터 바인딩은 include를 병합 요소의 직접 하위 요소로 지원하지 않음.

지원하지 않는 레이아웃의 예

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </merge>
</layout>


+ Recent posts