반응형

 

이번 글에서는 데이터바인딩에 대한 2번째 실습으로 버튼을 클릭하였을때 TextView의 글씨가 바뀌도록 해보겠습니다.

실제 앱개발 과정에서 가장 빈번하게 작업하는 유형입니다. 특정 이벤트가 발생하였을때 뷰의 콘텐츠를 제어하는 작업이지요.

 

이전 포스트에서 잠시 소개하였듯이 데이터 바인딩은 기존에 앱개발에서 이벤트를 처리하는 방법과는 메카니즘 자체가 다릅니다. 

기존방식은 자바에서 뷰를 참조하고 이 뷰객체를 제어하여 다른 UI를 보여주는 방식이었습니다.

그에반해,  데이터바인딩은 뷰객체를 제어하는 것이 아니라 뷰객체가 보여주는 데이터를 가진 변수를 제어하여 화면이 갱신되도록 하는 방법을 사용합니다. 뷰에게 특정변수를 설정(연결)해 놓고 그 변수의 값을 바꾼다는 것이지요. 

단, 이때 우리가 일반적으로 사용하는 String이나 int같은 자료형의 변수를 이용하면 연결되어 뷰에 보여지기는 하지만 변수값을 바꾸어도 뷰가 이를 자동으로 인지하여 화면을 갱신하지 않습니다. 그래서 일반적인 자료형의 변수가 아니라 관찰(Observe) 이 가능한 자료형의 변수를 만들어 이를 뷰들에 설정(연결)합니다. 이런 자료형의 변수들을 Observable 변수라고 합니다.

 

코드를 보면서 더 소개하겠습니다.

이전 글(DataBinding #1)에서 소개했던 실습예제를 수정하면서 이번 실습을 진행하겠습니다.

 

먼저, 이전 실습에서 TextView과 연결되어 있는 데이터 클래스인 User클래스를 수정하겠습니다.

일반적인 자바 자료형인 String, int, boolean을 각각 관찰가능한(Observable) 자료형으로 변경하겠습니다.

Observable 자료형도 자바의 자료형에 대응되어 만들어져 있습니다. 설명없이 그냥 보기만 해도 어렵지 않게 구분하여 변경하실 수 있습니다.

 

값 변경이 관찰되는 자료형은 크게 3종류로 구분됩니다.

1) 자바의 primitive 자료형(기본 자료형) 인 boolean ~ double에 대응되는 ObservableBoolean ~ ObservableDouble

2) List 또는 Map 컬렉션에 대응되는 ObservableList or ObservableMap

3) String이나 Item같은 자바의 참조형 타입들에 대응되는 ObservableField<XXX> : <>제네릭안에 참조형 타입명 지정

 

# User
import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();
    

    //constructor
    public User(String name, int age, boolean fav) {
    	//파라미터로 받은 일반적인 자료형을 ObservableXXX 변수에 대입해주기 위해 .set()메소드 이용 - 자료형이 다르기에 대입연산자(=)로 값 저장불가
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

}

 

레이아웃 xml파일이나 액티비티 자바 파일에서는 ObservableXXX 변수로 바꿨다고 달라지는 코드는 없습니다. 그냥 기존에 String, int, boolean 처럼 생각하고 코딩하셔도 됩니다. 이전 포스트의 레이아웃 xml 파일 코드와 다른것은 없지만  다시 코드를 확인해 보겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

    </LinearLayout>

</layout>

 

액티비티 자바 코드도 다른 것은 없지만 다시 살펴보겠습니다.

# MainActivity.java
mport androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.kitesoft.databindingjava.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        //DataBindingUtil클래스를 통해 이 MainActivity에게 activit_main.xml 레이아웃을 ContentView()로 설정하고 연결하여 제어하는 클래스객체를 리턴
        //클래스의 이름의 xml레이아웃 파일명을 기준으로 자동으로 설계되어 만들어짐. [ex. activity_main.xml 이면 ActivityMainBinding,  activity_intro.xml 이면 ActivityIntroActivityBinding ]
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //xml문서의 <data>요소 안에 있는 <variable>의 type에 지정한 클래스 객체를 만들어 set()해주면 레이아웃파일의 뷰들에 설정한 객체의 값들이 반영됨
        binding.setUser(new User("sam", 20, true)); //초기값으로 지정할 User객체 생성 및 바인딩객체에게 설정

        //참고로 뷰들에 id가 지정되어 있다면 binding객체의 멤버변수로 id명과 같은 뷰의 참조변수들이 자동으로 만들어져 있음 [ ViewBinding 기능 ]
    }
}

 

데이터클래스인 User클래스이 name, age, fav 필드(멤버변수)들의 자료형을 ObservableXXX 로 바꾼것 말고는 똑같습니다. 수정을 하셨다면 앱을 실행시켜보시기 바랍니다. 화면에 User객체의 초기 값인 "sam", 20 이 보이고 체크박스가 체크된 상태로 보이면 데이터들이 잘 연결된 상태입니다. 

 

이제 버튼을 하나 추가해서 버튼 클릭시에 User클래스이 name변수값을 변경해보겠습니다. 단지 name변수의 값만 바꿀 겁니다. TextView를 참조하여 setText()로 설정하는 등의 작업을 전혀 하지 않을 겁니다. 뷰가 보여주는 변수의 값만 바꿈으로서 자동으로 UI가 갱신되도록 하는 기법입니다.

 

대략 메카니즘이 이해 되었나요? 이제 중요한 부분입니다.

 

버튼 클릭이벤트에 반응하기 위해 클릭리스너를 버튼에 설정해 주거나 onClick속성값으로 MainActivity.java에서 자동으로 실행될 콜백메소드명을 설정해 주고 액티비티 자바에서 원하는 작업코드를 하였을 겁니다. 아마 User객체의 참조변수를 만들고 그 멤버인 name 변수값을 변경하는 코드를 액티비티에서 작성하셨을 겁니다. 여기서 좀 생각해볼까요? 객체지향언어의 특징은 객체의 값설정이나 변경은 객체가 스스로 하도록 하는 것을 권장합니다. 니껀니가!! 라는 것이 객체지향의 가장 중요한 특징 중에 하나이지요. 근데 그동안 해오던 방법은 액티비티에서 User객체를 참조하여 User객체의 멤버값을 변경하는 방식이었습니다.  User객체의 값 변경을 왜 액티비티가 해야하는거죠? User객체 스스로 값을 변경하는 기능이 있어야 하는 거 아닐까요? 더 나아가 결국 버튼이 클릭되었을때 User클래스의 멤버변수인 name변수를 제어해야 하는데 그 코드를 왜 액티비티에서 하고 있는 었을까요? 그 변수가 선언된 User.java에서 안에서는 값 변경 처리를 하는 것이 더 자연스럽고 나중에 관리하기도 더 좋지 않을까요? name변수가 선언된 클래스안에만 name변수의 변경 코드를 작성해 놓으면 코드의 유지보수가 더 수월하지 않을까요? 이런 버튼이 한두개 라면 액티비티에서 클릭이벤트에 반응하는 메소드를 만들고 그 안에서 데이터객체의 값을 변경해도 크게 복잡해 보이지 않겠지만 만약, 버튼이 여러개이고 체크박스처럼 이벤트를 발생하여 데이터를 변경해야 하는 것이 여러개라면 액티비티 안에서 너무 많은 작업을 해야하고 정작 데이터를 저장하는 변수가 선언된 데이터클래스안에는 아무 기능이 없는 비효율적인 코딩이 될겁니다. 데이터 바인딩은 바로 이 부분을 개선하였습니다. 버튼 클릭에 반응하는 콜백 메소드를 액티비티 말고 데이터클래스에서도 만들 수 있게 한 것입니다.

Button 뷰의 onClick속성에 @{user::메소드명} 으로 설정하면 해당 메소드가 클릭이벤트에 반응하여 자동 호출됩니다.  user.name 으로 User객체의 멤버필드를 연결하였듯이 user::메소드명  으로 User클래스 안에 있는 메소드를 콜백메소드로 연결해 주는 겁니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>

    </LinearLayout>

</layout>

 

새로 추가된 <Button>의 onClick속성 값으로 User클래스 안에 만든 'changeName' 이라는 메소드를 지정해 준겁니다. MainActivity의 메소드가 아니라 User클래스의 멤버 메소드 입니다. 그럼 User 클래스안에 changeName 이라는 이름의 메소드를 만들겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

}

 

원래 액티비티 자바파일에서 하던 작업을 데이터클래스 안에서 작성한 것만 다른겁니다. MainActivity.java는 아무런 작업도 할 필요가 없는 겁니다. 이제 실행해 보시고 버튼을 눌렀을때 "sam" 이라는 글씨가 "robin"으로 변경되면 성공입니다.

결국 이벤트 처리도 데이터를 가지고 있는 데이터 클래스에서 직접 하는 겁니다. 액티비티와 분리되어 서로의 역할에 충실한 클래스들로 구조를 만들 수 있게되는 겁니다. 

이런식으로 파일의 역할별로 구분하여 프로젝트 구조를 만들어 놓으면 나중에 프로젝트를 유지보수할 때도 해당 역할의 파일만 살펴보면 되기에 보다 쉽게 관리가 가능하고 개발인력의 변경으로 인한 인수인계도 아주 수월하게 됩니다. 대략적인 점만 말했지만 이런 역할 구조로 나누어 패턴화 시킨 것을 아키텍처 패턴이라고 하고 구분기준을 조금씩 다르게 하여 이름을 명명하였으며 MVC, MVP, MVVM 등의 대표적인 패턴이 있습니다. 데이터 바인딩은 이 패턴들 중 MVVM(Model -View - ViewModel)에서 가장 기본적으로 사용됩니다. 나중에 시간을 들여 이 패턴에 대한 소개도 할 예정입니다. 지금은 그런게 있구나. 정도로 받아들여 주시기 바랍니다.

 

이제 데이터 바인딩의 클릭이벤트 처리와 Observable변수의 변경을 통한 자동 UI갱신 방식이 대략적으로 느껴지시나요?

연습을 위해 추가로 age 값도 변경하는 버튼을 추가해 보고, 마찬가지로 boolean 형 값인 fav 체크박스도 변경하는 버튼을 만들어 보겠습니다.

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

    </LinearLayout>

</layout>

 

이제 User클래스 안에 클릭콜백메소드를 추가해 보겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

}

 

3개정도 버튼 클릭 처리를 직접 코딩해보시면 눈으로 보는 것보다 더 쉽게 이해될 겁니다.

 

이번에는 클릭이벤트 말고, 체크박스의 체크상태 변경 이벤트에 반응하는 것도 해보겠습니다. 

레이아웃 xml 파일의 <CheckBox>에 onClick속성처럼 onCheckedChanged 속성을 사용하여 콜백메소드를 지정할 수있습니다. 다만, 주의할 점은 onClick속성을 제외한 OnCheckedChanged나 OnLongClick 같은 이벤트들은 안드로이드 스튜디오에서 자동완성을 지원하지 않습니다. 그래서 처음 해보실때 자동완성이 안되서 잘못 하고 있다고 오해하여 시도를 안하는 경우도 있습니다. 자동완성이 안될 뿐 동작은 문제없이 되니까 걱정말고 따라서 써보시기 바랍니다. 

 

기존 레이아웃 xml에서 <CheckBox>뷰에 android:onCheckedChanged속성만 추가했습니다. 다른 곳은 건드리지 않았습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>


    </LinearLayout>

</layout>

 

이제 User클래스 자바 파일에서 android:onCheckedChanged 에 지정한 콜백 메소드를 추가하여 테스트 해보겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들기
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }

}

실습으로는 onCheckedChange 이벤트만 처리했지만 다른 이벤트의 리스너 콜백 메소드들도 모두 이런식으로 처리 할 수 있습니다.

 

마지막으로, 이 콜백메소드는 당연히 기존 이벤트 리스너의 콜백메소드를 만들때 정해져 있던 파라미터와 리턴타입을 똑같이 했을때만 발동됩니다. 하지만 경우에 따라서는 일반 메소드를 이벤트 콜백으로 반응하게 할 필요가 있을 수도 있습니다. 그때는 xml 에서 익명메소드를 람다식으로 만들어 지정하고 이벤트가 생겼을때 그 익명메소드가 호출되면 User클래스의 일반메소드를 호출함으로서 같은 효과를 볼 수 있습니다. 코드로 소개하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

        <!-- onclick속성에 발동하는 메소드 사용하지 않고 xml에서 바로 람다식으로 콜백메소드를 만들어 일반 메소드 호출가능 -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:backgroundTint="#FFFF3300"
            android:text="change name by lamda functioin"
            android:onClick="@{ (v)-> user.changeName() }"/>

    </LinearLayout>

</layout>

 

이제 User클래스에 일반메소드를 추가합니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }


    //callback메소드가 아닌 일반 메소능 - 람다식을 이용하여 onClick에 반응
    public void changeName(){
        this.name.set("HONG");
    }

}

버튼클릭했을대 "HONG"으로 글씨가 변경되면 성공입니다.

 

 

이정도 실습을 같이 따라 코딩해보시면 데이터 바인딩이 꽤 와닿을 겁니다.

다음 포스트에서는 가장 기본이면서 중요한 실습으로 데이터 바인딩을 연습해 보겠습니다.

사용자가 EditText에 글씨를 입력하고 버튼을 클릭하여 TextView에 그 글씨가 보이도록 하는 예제입니다. 쉽고 간단하지만 가장 중요한 예제라고 생각합니다. 꼭 따라서 해보시길 권해드립니다.

반응형

+ Recent posts