반응형

 

이번 글은 데이터바인딩의 3번째 실습예제로서 사용자가 EditText에 글씨를 입력하고 버튼을 클릭하면 입력된 글씨를 TextView에 보여주는 예제입니다. 클릭이벤트 및 뷰의 데이터를 제어하는 가장 기본적이면서 어쩌면 앱 개발과정의 가장 핵심적인 기능구현이라고 생각합니다. 이 기능구현에 데이터바인딩을 적용해 보고자 합니다. 이전 포스트에서 소개했던 데이터바인딩의 특징을 다시한번 글로 정리해 보겠습니다.

 

기존의 방법은 EditText 객체를 찾아와 참조하여 입력된 글씨데이터를 얻어와서 TextView객체 참조변수를 이용하여 setText()로 직접 뷰에 데이터를 설정해주는 방법으로 기능을 구현했습니다. 

데이터 바인딩에서는 뷰객체를 참조하여 제어하지 않습니다. 뷰객체에 연결되어 있는 변수를 제어하여 뷰가 보여주는 값을 갱신하지요.

 

또한, 기존에는 클릭이벤트나 체크상태변경 이벤트, 또는 EditText의 글씨변경 리스너등을 Activity에서 설정하고 이벤트 콜백메소드를 만들어 뷰객체들에게 직접 데이터를 설정하는 등의 코드를 작성하였습니다. 그러다 보니 뷰가 많아지고 이벤트들이 많아지면 액티비티의 코드가 길어지고 거의 모든 코드가 액티비티 자바파일에서 작성되어 나중에는 어디에 어떤 메소드가 있는지 찾는 것도 어렵고 수정할때 그 위치를 찾는 것도 어려워 답답해 지는 경우가 많습니다.

자바의 객체지향을 학습하면서 많이 들어 보셨겠지만 객체지향을 위해 설계되는 class들은 그 이름에 걸맞는 변수와 기능메소드 들로 구성되어야 좋습니다. Random 클래스안에 말도 안되게 키보드 입력같은 기능메소드가 들어가 있거나, String 클래스안에 네트워트 작업 기능 메소드가 들어가 있으면 뭔가 이상하지 않나요? 클래스의 이름과도 어울리지 않고요.

 

안드로이드의 액티비티 class 역시 앱의 화면 UI를 담당하는 목적의 클래스 입니다. 이 안에서 액티비티가 보여주는 뷰를 참조하여 제어하는 코드를 작성하는 것는 것은 특별히 어색하지 않습니다. 그래서 그동안은 그렇게 뷰를 참조하고 뷰에 리스너를 달아주고 이벤트를 처리하였습니다.

하지만 데이터바인딩에서는 뷰를 참조하지 않습니다. xml 레이아웃파일에서 이미 뷰객체에 본인들이 제어할 데이터를 가진 변수와 연결되어 있습니다. 그렇기에 버튼을 클릭하는 콜백메소드를 액티비티가 아니라 데이터를 저장하는 변수가 있는 데이터클래스(User)에서 메소드를 만들었습니다. 데이터를 가진 클래스가 User인데 그 값을 변경하는 코드가 Activity에 있는 것보다는 User클래스에 있는것이 유지보수와 재사용성 면에서 훨씬 효과적일 겁니다. 이전 포스트에서 소개한 내용이지요.

 

이번 실습에서도 마찬가지로 EditText의 글씨가 입력되어 변경되는 것을 듣는 TextWatcher리스너의  콜백메소드인 onTextChanged를 User클래스에 콜백메소드로 만들어 입력된 글씨를 알아내고 그 글씨를 TextView에 곧바로 보여주도록 하겠습니다. 버튼을 클릭했을때 글씨를 얻어오는 코드는 이 다음에 이어서 소개하겠습니다. 즉, EditText에 글씨를 입력할때 마다 그 글씨를 TextView에 보여주도록 하겠습니다. 마치 자동으로 따라 써지는 것처럼요.

 

먼저, User 클래스에 TextView가 보여줄 데이터를 저장할 관찰가능한(Observable) 타입의 String 을 저장하는 msg 라는 이름의 참조변수와 객체를 만들어 주겠습니다. EditText의 글씨를 보여줄 TextView에 연결될 변수입니다.

또한 EditText 의 글씨변경에 반응하는 리스너인 TextWatcher의 콜백메소드인 onTextChanged()에 대응하는 메소드를 추가하겠습니다. 이전 글의 실습에 이어서 작업하겠습니다. 이 클래스의 가장 아래쪽 EditText 주석이 있는 부분부터 보시면 됩니다.

# User.java
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");
    }



    //EditText의 글씨 변경값을 저장하는 관찰되고 있는 멤버값
    public ObservableField<String> msg= new ObservableField<>();

    //EditText의 TextChanged event callback method... [ TextWatcher 클래스의 콜백메소드 - 파라미터 참고 ]
    public void onTextChanged(CharSequence s, int start, int before, int count){
        //변경될때 마다의 글씨를 TextView와 연결되어 있는 msg 변수에 설정
        msg.set(s.toString());
    }
    
}

 

이제, 레이아웃 xml 파일에서 EditText와 TextView를 추가하겠습니다. 이전 실습의 코드에 이어서 작업하겠습니다.

# 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() }"/>


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="enter text"
            android:inputType="text"
            android:onTextChanged="@{user::onTextChanged}"/>
        <!-- onTextChanged=""속성- 자동완성은 안되지만 직접 작성하면 TetWatcher의 콜백메소드 호출 가능함  -->

        <!-- EditText의 입력글씨를 그대로 보여주는 TextView       -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.msg}"/>
            
    </LinearLayout>

</layout>

 

이제, 실행해보면 EditText에 글씨를 입력하여 변경될때 마다의 글씨가 바로 밑에 배치된 TextView에 그대로 보여질 겁니다. 

 

 

이번에는, 입력글씨가 변경될때마다 TextView의 글씨를 바꾸지 말고 버튼을 클릭하였을때 TextView에 글씨를 보여주도록 하겠습니다. 실제로는 더 많이 쓰여지는 상황입니다. EditText 뷰를 참조하지 않기 때문에 버튼을 클릭했을때 입력글씨를 얻어오는 식의 코드는 작성할 수 없습니다. 그럼 어떻게 해야 할까요? 특별한 문법이 있는 것은 아니고 조금 다른 방법으로 그렇게 동작하도록 구현해 낼겁니다.

 

EditText의 글씨가 변경될때 마다 TextView와 연결된 ObservableField<String> 객체인 msg 의 값을 set()하다보니 매번 화면이 갱신되면서 적용이 되니까, 굳이 msg의 매번 값을 설정하지 말고 버튼 눌렀을때 한번만 set()하면 되겠죠. 근데 그 때의 EditText 값을 읽어올 수 없으니 매번 글씨가 변경될 때마다 관찰되지 않는 일반 String 변수에 그 값을 저정할 겁니다. 글씨가 변경될 때 마다 발동하는 콜백메소드에서 EditText에 입력된 글씨를 일반 String 변수에 저장하고 버튼을 눌렀을때 마지막으로 저장된 String 변수값을 TextView가 보여주는 Observable 필드에 설정하면 그 순간 한번만 화면을 갱신하게 됩니다.

 

코드로 바로 소개하겠습니다. 위 실습의 User클래스에 이어서 코드를 작성하겠습니다.

# User.java
mport 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");
    }



    //EditText의 글씨 변경값을 저장하는 관찰되고 있는 멤버값
    public ObservableField<String> msg= new ObservableField<>();

    //EditText의 TextChanged event callback method... [ TextWatcher 클래스의 콜백메소드 - 파라미터 참고 ]
    public void onTextChanged(CharSequence s, int start, int before, int count){
        //변경될때 마다의 글씨
        msg.set(s.toString());
    }
    
    

    //EditText의 값 변경시마다의 글씨를 일반 멤버변수에 저장
    String titleStr="";

    public void changedTitle(CharSequence s){
        titleStr= s.toString();
    }

    //버튼 클릭시에 저장되어 있는 title변수의 값을 ObservableField<>의 변수값으로 설정
    public ObservableField<String> title= new ObservableField<>(""); //""값으로 초기화

    //버튼 클릭 콜백메소드
    public void clickBtn(View v){
        title.set(titleStr);
    }

    // 이렇게 데이터를 가지고 있고 데이터의 값을 변경하는 코드가 있는 클래스가 MVVM에서 MiewModel 의 역할을 하게됨.

}

 

마지막으로, 레이아웃 xml 파일에 EditText, Button, TextView 를 1개씩 추가하겠습니다.

# 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() }"/>


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="enter text"
            android:inputType="text"
            android:onTextChanged="@{user::onTextChanged}"/>
        <!-- onTextChanged=""속성- 자동완성은 안되지만 직접 작성하면 TetWatcher의 콜백메소드 호출 가능함  -->

        <!-- EditText의 입력글씨를 그대로 보여주는 TextView       -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.msg}"/>


        <!-- EditText에 글씨 입력하고 버튼 클릭하면 그 글씨 텍스트뷰에 보이기  -->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="input title"
            android:inputType="text"
            android:onTextChanged="@{(s,start,before,count) -> user.changedTitle(s)}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="OK"
            android:onClick="@{user::clickBtn}"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.title}"/>

    </LinearLayout>

</layout>

EditText의 onTextChanged속성값으로 콜백함수를 바로 지정하지 않고 익명메소드를 람다식으로 지정하여 이벤트가 발생하면 람다식으로 표현된 함수가 호출되고 그 실행내역으로 user객체의 changeTitle()이라는 일반 메소드를 호출하면서 변경된 글씨를 가진 s를 파라미터로 전달하도록 해봤습니다. 바로 위 실습과 같이 콜백에 반응하지만 조금 다른 방식으로 소개해 봤습니다. 

 

보시다 시피 MainActivity.java는 전혀 건드리지 않았습니다. 실무에서는 이렇게 액티비티에 코드가 별로 없는 경우가 많습니다. 역할에 충실하게 코드들의 위치를 구분하여 작성하는 것이지요. 좋긴 하지만 초보분들에게는 오히려 한눈에 코드가 보이지 않아 더 복잡하게 느껴질 수도 있습니다.

 

처음에는 어색하지만 조금 익숙해지면 여러분들의 예상보다 훨씬 매력적인 기법이라는 것을 느끼실 수 있으실 겁니다.

미리 포기하지 말고 도전해 보시길 권해드립니다.

 

다음 데이터바인딩 관련 글로는 Fragment에서 데이터 바인딩을 적용하는 것을 소개하겠습니다.

반응형
반응형

 

이번 글에서는 데이터바인딩에 대한 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에 그 글씨가 보이도록 하는 예제입니다. 쉽고 간단하지만 가장 중요한 예제라고 생각합니다. 꼭 따라서 해보시길 권해드립니다.

반응형
반응형

 

※ findViewById()메소드를 이용한 View참조 방법이 가진 문제를 해결하기 위한 기법들[코드의 번거로움과 메소드의 동작속도 문제]

1. ButterKnife 외부 라이브러리 사용하기 (버터 나이프 라이브러리)
2. ViewBinding 안드로이드 아키텍처 구성요소
3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

 

이번글에서는 findViewById()를 대체하는 기술의 마지막 3번째 기법인 데이터바인딩에 대해 소개하겠습니다.

이 데이터 바인딩기법은 요즘 안드로이드 앱 개발시에 많이 사용되는 프로젝트 아키텍처 패턴 중 MVVM( Medel- View - View Model)을 이해하는데 선행되어야 할 기법 중 하나여서 이 글에서 학습이 끝나지 않는 기법이니 주의깊게 봐 주시기 바랍니다.

 

코드를 보기전에 간략하게 데이터바인딩의 주요특징을 알아보겠습니다.

 

데이터바인딩은 기존에 하던 뷰와의 상호작용 방법과는 완전 그 메카니즘이 다릅니다.  즉, 기존 방법대로 이해하려고 하면 오히려 코드를 보실때 더 알아보기 어려우실 것이라는 겁니다. 아주 큰 개념의 차이점만 먼저 인지하실 필요가 있습니다.

기존에 하던 findViewById()나 ButterKnife 라이브러리, 또는 뷰바인딩은 기본적으로 데이터를 보여주는 뷰(텍스트뷰 또는 이미지뷰 등)들을 xml에서 만들고 이를 .java 언어나 .kt 코틀린언어의 프로그래밍 코드에서 참조하여 참조변수를 이용하여 뷰에게 원하는 데이터를 설정하는 방법으로 뷰의 데이터를 보여줍니다. TextView의 .setText("Hello") 나 ImageView의 .setImageResource() 같은 뷰들의 기능메소드를 이용하는 방법 말이죠. 다시말해, 데이터를 보여주는 View객체들을 참조하여 그 객체들을 제어하는 방식입니다.

 

이에 반해, 데이터바인딩은 뷰객체를 참조하는 것이 아니라 뷰객체가 보여주는 데이터를 가진 변수를 제어합니다. 즉, 뷰들에게 데이터를 가지고 있는 변수를 처음에 설정해주면 그 변수의 값이 보여질 것이고, 그런다음 버튼을 클릭하는 등의 이벤트가 발생하였을때 뷰들을 참조하여 제어하지 말고 뷰에 설정되어 있는 변수의 값을 변경하면 그 뷰가 보여주는 데이터가 바뀌니까 화면도 바뀌도록 하는 방식입니다. 뷰객체를 제어할 필요가 없기에 findViewById()를 할 필요가 없는 것이죠. 사실 기존에 하던 방법과는 완전히 다른 뷰와의 상호작용 기법이라 조금 당황스러울 수 있겠지만 이해가 어려운 것은 아닙니다.  오히려 쉽다면 쉬운겁니다. 그냥 변수값만 바꾸면 화면이 갱신되는 겁니다. 특히, 리스트뷰나 리사이클러뷰 등을 다루어 보신 분들은 더 강하게 와 닿을 겁니다. 리스트뷰나 리사이클러뷰가 보여주는 대량의 데이터에 변경이 있을때 화면을 갱신하기 위해 아답터객체의 notifyDataSetChange()나 notifityItemInsert()등의 화면갱신 코드를 매번 호출해야 하지만 데이터 바인딩은 그 코드를 일일이 신경쓰지 않아도 데이터가 바뀌면 화면이 자동 갱신됩니다. 익숙해지면 코딩이 아주 수월해 지실겁니다. 물론 아무 변수나 값이 바뀌었다고 뷰들이 무조건 갱신되지는 않습니다. 만약, 그렇게 되면 뷰의 갱신이 너무 자주 발생하여 오히려 앱의 성능에 악영향을 미칠테니까요. 그래서 관찰이 가능한(Observable) 자료형의 변수들이 바뀌었을때만 연결된 뷰의 화면이 자동갱신되어 있도록 되어 있어서 원하는 시점에만 화면의 갱신이 되도록 조절할 수 있습니다.

 

데이터 바인딩은 뷰바인딩 기능이 기본적으로 내포되어 있기에 데이터바인딩 사용 설정만으로도 원한다면 뷰바인딩으로 뷰와의 상호작용이 가능합니다. 때문에 프로젝트의 상황에 따라 개발자가 뷰바인딩과 데이터바인딩을 적절히 선택하여 개발하신다면 보다 효율적이고 성능이 개선된 앱을 만드실 수 있으실 겁니다. 

뷰바인딩에 대한 내용을 알지못한다면 이전 뷰바인딩에 대한 글을 먼저 읽어보시고 오시길 권합니다. 조금더 데이터 바인딩의 초기설정을 이해하는 데 도움이 될겁니다.

 

2021.09.15 - [소소한 소스코드] - [안드로이드 Android] 뷰 바인딩 View Binding #1

 

 

너무 서론이 길면 오히려 학습에 방해가 되니 우선 이정도만 소개하고 추후 필요한 시점에 추가적인 특성들을 더 소개하도록 하겠습니다.

 

 

이제 여러분들의 앱에 데이터 바인딩을 적용해보는 실습을 진행해보겠습니다.

 

참고로 데이터 바인딩 역시 뷰바인딩 처럼 안드로이드 아케텍처 구성요소이기에 별도의 라이브러리를 추가하는 것이 아니라 데이터바인딩 기능을 사용설정 해줌으로서 준비가 끝나기에 매우 편하게 적용가능합니다.

 

먼저, 앱 모듈수준의 build.gradle 파일에 데이터바인딩 사용 설정을 하도록 하겠습니다. 안드로이드 스튜디오의 버전에 따라 방법이 다르니 참고하시어 따라하시기 바랍니다. 

※주의. 안드로이드 개발자 공식문서에는 아직 예전 버전의 방법으로 소개되어 있습니다. 이 글을 보고있는 대부분의 분들은 본인의 안드로이드 스튜디오 버전이 최신버전이기에 4.0버전 이상일 겁니다.

# build.gradle (Module)
android {
    
    .....


    //Android Studio 4.0 버전 이상일때,
    buildFeatures {
        dataBinding = true
    }
    
    //Android Studio 4.0 버전 미만일때,
    dataBinding {
    	enabled= true
    }
}

 

데이터 바인딩의 사용설정을 해주면 뷰바인딩과 마찬가지로 레이아웃 xml 파일과 연결되어 자동으로 설계되는 바인딩 클래스가 만들어 집니다. 다만, 뷰바인딩과 다르게 무조건 생기는 것이 아닙니다. 여기서 주의 해야 합니다. 

데이터 바인딩에 의해 자동 연결되고 싶은 레이아웃 xml 파일의 root(최상위) View는 반드시 <layout> 이어야 합니다. 기존의 xml 레이아웃파일을 만들듯이 그냥 RelativeLayout아니 LinearLayout으로 시작하면 바인딩클래스는 만들어 지지 않습니다.

 

즉, 데이터바인딩으로 제어하고 싶은 레이아웃 xml은 기존 코드를 반드시 수정해야 합니다.

참고로 미리 소개하자면, 자동으로 설계되어 만들어지는 바인딩클래스의 이름은 뷰바인딩 명명규칙과 동일합니다.

activity_main.xml 과 연결되는 바인딩 클래스는 ActivityMainBinding 인 것은 뷰바인딩과 똑같습니다. 단, 뷰 바인딩과 다르게 이 바인딩클래스 객체의 뷰참조 멤버변수들을 직접 제어하지 않는 다는 것이 조금 다릅니다.

 

우선 데이터바인딩에서의 레이아웃 xml 은 최상위 root로 <layout>으로 시작하고 그 안에 크게 2개의 요소를 배치합니다.

1. <data>요소 - 뷰들과 연결되어 보여줄 데이터를 가진 데이터 클래스를 지정해주고 레이아웃파일안에서 사용할 식별명을 정해주는 요소입니다. 

2. 레이아웃의 root 뷰 요소 - 기존에 레이아웃 xml 파일에 하던 RelativieLayout 이나 LinearLayout 등이 뷰를 배치합니다.

 

주석으로 해당 요소들에 대한 소개를 하였으니 참고 바랍니다.

# 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>
        
    </data>


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

    </LinearLayout>

</layout>

 

코드에서 보듯이 <layout>태그가 최상위 이고 그 안에 1. 과 2. 의 자식요소가 배치됩니다. 2. 레이아웃뷰는 기존에 레이아웃 xml 파일의 최상위 였던 뷰그룹입니다.

1. <data> 요소가 중요합니다. 데이터바인딩의 핵심이지요. 여기에 뷰들에 보여질 데이터를 가지고 있는 클래스를 지정해주고 이 레이아웃 파일안에서 인식할 명칭을 지정해주는 작업을 합니다.

 

먼저 간단하게 TextView를 하나 추가한 후 그 뷰가 보여줄 데이터를 멤버로 가지는 클래스를 만들어 지정해 주겠습니다.

 

앱에 사용될 데이터를 저장할 변수를 가진 클래스를 설계하는 겁니다. 데이터 바인딩의 연결 특성을 확인해보는 실습이라서 우선 간단히 만들고 다음 실습에서 다시 수정할 겁니다. TextView에 보여줄 String 멤버변수를 하나 가지는 클래스 입니다.

# User.java
public class User{
	
    //TextView에서 보여줄 데이터를 저장하고 있을 변수
    String name;
    
    //생성자메소드
    public User(String name){
    	this.name = name;
    }
    
}

 

이제 위 데이터 클래스를 activity_main.xml 에서 데이터바인딩으로 연결 설정해주기 위해 <data> 태그안에 변수값을 의미하는 태그인 <variable>로 지정해 주겠습니다. 주석으로 부연설명을 하였으니 확인바랍니다.

 

# 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">

    </LinearLayout>

</layout>

 

<variable>태그 속성

 - name속성 :  "user" 라는 이름은 개발자가 임의로 지정하는 값입니다. 이 activity_main.xml에서 이 "user"라는 이름으로 type에 지정한 자료형(User클래스)의 객체를 인식하겠다는 의미입니다. 일종의 xml에서 사용하는 객체 참조변수명 같은 것이라고 쉽게 이해하시면 됩니다. 

- type속성 : 이 activity_main.xml과 연결될 데이터를 가지고 있을 클래스 입니다. 저 위에서 만들었던 User 클래스를 지정해 줍니다.

 

추후 MainActivity.java 에서 이 User타입의 객체를 만들어서 바인딩 해주는 코드를 작성해주게 됩니다. 

 

<data> 태그안에 데이터를 가진 변수 <variable>을 지정해 주었다면 이제 TextView를 하나 추가 하겠습니다. 특별한 점은 텍스트뷰가 보여줄 글씨값을 설정한는 android:text="" 속성안에 {}를 쓰고 위 <data>안에 만든 <variable>을 마치 객체인듯 이 곳에서 직접 연결한다는 겁니다. 주석으로 부연설명했습니다.

 

# 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객체의 멤버값 사용 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

    </LinearLayout>

</layout>

@{user.name} 이 데이터 바인딩의 가장 핵심코드입니다. user라는 이름은 <variable>에서 name 속성안에 개발자가 임의로 지정한 이름입니다.  그리고 그 user는 type에 지정한 User클래스의 참조변수명이기에 그 안에 있는 멤버변수 String name 변수의 값을 텍스트뷰에서 보여주겠다고 설정한 것입니다. 평소에 해오던 안드로이드 코드와 달라서 조금 어색해 보일 수 있으나 생각보다 간단히 이해되는 코드입니다. 

 

근데. 여기까지만 보면 User객체를 만든적도 없고, 그 User 객체의 name 변수에 어떤 값을 저장한 적도 없었죠. 그렇기에 도데체 어떤 글씨가 보여질 것인지 전혀 예측하기 어려울 겁니다. 그 작업은 MainActivity.java 에서 하시는 겁니다.

 

곧바로 자바코드를 확인해 보겠습니다. 주석으로 주요설명을 표시하였으니 잘 살펴보시기 바랍니다.

# MainActivity.java
import 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")); //초기값으로 지정할 User객체 생성 및 바인딩객체에게 설정

    }
}

 

이제 실행해 보시면 텍스트뷰에는 bind.setUser( new User("sam") ); 에 의해 설정된 데이터객체인  User의 멤버값 "sam"이 보여지게 될 겁니다. 

당연히 객체가 name 말고 여러개의 데이터를 추가로 더 가지고 있다면 각각의 값을 보여주는 뷰와 연결하는 작업도 같은 방식으로 할 수 있습니다. 연습을 위해 일부러 String 이 아닌 int나 boolean 같은 다른 자료형을 가지고 있도록 하고 이 값들을 적절한 뷰에 연결하도록 하겠습니다.

 

먼저 User클래스를 조금 수정하겠습니다.

#  User.java
public class User{
	
    //TextView에서 보여줄 데이터를 저장하고 있을 변수
    String name;  //이름 
    int age;      //나이
    boolean fav;  //좋아요 체크박스 여부 [true/false]
    
    //생성자메소드
    public User(String name, int age, boolean fav){
    	this.name = name;
        this.age = age;
        this.fav = fav;
    }
    
}

 

이제 age와 fav 데이터값을 보여줄 TextView와 CheckBox 뷰를 추가하여 데이터와 연결해 놓겠습니다.

# 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>

 

age와 fav 값을 추가로 설정해주도록 자바코드를 수정하겠습니다.

# MainActivity.java
import 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 기능 ]
    }
}

실행해 보았을때 2개의 텍스트뷰에 "sam", 20 이 보이고 체크박스는 true 값에 의해 체크된 상태로 보이면 데이터 바인딩이 잘 동작하고 있는 것입니다.

 

잠시 정리하면, 자바에서 뷰들을 findViewById()와 같은 방법으로 참조하여 값을 설정하는 것이 아니라 데이터를 가진 클래스(User) 객체를 만들고 xml 레이아웃에서 미리 연결만 해 놓으면 알아서 UI에 적용되도록 하는 기법입니다. 글로 쓴 것보다 코드를 보면 더 쉽게 이해가 되실 거라고 생각합니다.

 

대충 데이터 바인딩에 대해 알아보았습니다. 하지만 지금까지 소개한 데이터 바인딩은 큰 개념만 소개하기 위해 실습입니다. 애석하게 지금 만든것처럼 User 클래스를 만들면 클릭같은 이벤트가 발생했을때 User객체의 값을 변경해도 이 객체의 데이터를 보여주도록 설정되어 있는 뷰들의 화면을 자동 갱신되지 않습니다. 

 

다음 포스트에서 데이터를 변경했을때 자동으로 UI 가 갱신되어 뷰들에 변경된 값이 보이도록 하는 코드를 소개하겠습니다. 진짜 데이터바인딩의 코드인 것이지요.

잠시 예고를 해드린다면, User 클래스 멤버들의 자료형을 그냥 String, int, boolean으로 선언하면 값의 변경을 관찰(Observe) 하고 있지 않아 화면이 자동 갱신되지 않기에 관찰가능한 자료형으로 선언해야 합니다. 다음 포스트에서 코드로 소개해 드리겠습니다.

반응형
반응형

Ex17PopupMenu

안드로이드 네이티브 앱 개발 수업 예제#17

주요코드

PopupMenu 추가하기 [이전 컨텍스트메뉴와 다르게 원하는 위치에서 메뉴가 보이게 하고 싶을때 사용하는 메뉴]

  • 컨텍스트메뉴처럼 버튼을 롱~클릭했을때 버튼에 메뉴 보이기
  • OptionMenu나 Context메뉴처럼 액티비티에 create하는 메소드가 존재하지 않음.
  • 메뉴객체가 놓여지길 원하는 위치에 PopupMenu객체 생성하여 붙이고(anchor view 설정) 그 PopupMenu객체안에 있는 Menu객체에 MenuItem을 추가하여 보이도록 함.
  • 다른 메뉴들과 마찬가지로 XML언어로 메뉴항목들을 설계하고 자바언어의 MenuItem객체로 만들어서(부풀리다inflate) Menu에 추가하는 방법을 사용함.res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨] menu폴더에 있는 popup.xml문서를 읽어와서 Menu객체로 만들어주는(부풀려주는inflate) 객체인 MenuInflater사용
  • PopupMenu의 Menu(MenuItem)클릭 반응하기
  • 버튼을 롱~클릭했을때 다른 버튼에 팝업메뉴가 붙어서 보이도록 지정하기

실행모습

 

 

 

실행모습 GIF

 

 

소스코드

버튼의 롱~클릭으로 나타날 팝업 메뉴에 보여줄 메뉴의 항목들 Menu Resource로 설계

 

- res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨 

- 추가된 menu 폴더에서 마우스오른쪽버튼 메뉴에서 [ New > Menu Reource File ]선택하여 file name 칸에 "popup"을 쓰고 [OK]를 누르면 메뉴리소스파일 이 만들어 짐.

# res / menu / popup.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@+id/menu_info"
        android:title="information"/>

    <item android:id="@+id/menu_delete"
        android:title="delete"/>

    <item android:id="@+id/menu_modify"
        android:title="modify"/>

</menu>

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"
        android:onClick="clickBtn"/>

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="button2"/>

</RelativeLayout>

 

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button btn;
    Button btn2;

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

        btn= findViewById(R.id.btn);
        btn2= findViewById(R.id.btn2);

        //버튼이 롱클릭되는 것을 듣는 리스너 생성 및 설정
        btn.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {

                //PopupMenu 객체 생성
                PopupMenu popup= new PopupMenu(MainActivity.this, view); //두 번째 파라미터가 팝업메뉴가 붙을 뷰
                //PopupMenu popup= new PopupMenu(MainActivity.this, btn2); //첫번째 버튼을 눌렀지만 팝업메뉴는 btn2에 붙어서 나타남
                getMenuInflater().inflate(R.menu.popup, popup.getMenu());

                //팝업메뉴의 메뉴아이템을 선택하는 것을 듣는 리스너 객체 생성 및 설정
                popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem menuItem) {

                        switch (menuItem.getItemId()){
                            case R.id.menu_info:
                                Toast.makeText(MainActivity.this, "INFORMATION", Toast.LENGTH_SHORT).show();
                                break;

                            case R.id.menu_delete:
                                Toast.makeText(MainActivity.this, "DELETE", Toast.LENGTH_SHORT).show();
                                break;

                            case R.id.menu_modify:
                                Toast.makeText(MainActivity.this, "MODIFY", Toast.LENGTH_SHORT).show();
                                break;
                        }

                        return false;
                    }
                });

                popup.show();


                return true;
            }
        });


    }

    public void clickBtn(View v){
        Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
    }

    public void clickBtn2(View view) {
    }
}
반응형
반응형

뷰바인딩 소개의 5가지 실습 중 마지막 5번째 실습을 소개하겠습니다.

이번에는 앱 개발에 가장 많이 사용되는 뷰 중에 단연 으뜸인 RecyclerView에서 뷰 바인딩을 사용해 보겠습니다. 정확히 말하면 리사이클러뷰에 뷰 바인딩을 적용하는 것이 아니라 리사이클러뷰가 보여주는 아이템뷰들을 만들어주는 아답터에서 사용하는 ViewHolder의 아이템뷰에 뷰 바인딩을 적용하는 것입니다.

 

기본적으로 리사이클러뷰를 만들어 보신 적이 없다면 조금 이해하기 어려우니 먼저 다른 포스트 글 들을 통해 리사이클러뷰를 다루어본 후 이 포스트의 실습을 참여해 보시길 바랍니다.

 

먼저 리사이클러 뷰가 보여줄 목록 아이템 1개의 모양을 설계하는 레이아웃 xml 파일을 만들어 보겠습니다.

간단한 실습을 위해 ImageView 1개와 TextView 1개를 가진 CardView로 아이템 1개의 모양을 구성하겠습니다.

실습에 사용하는 이미지는 본인들을 가지고 있는 이미지 파일로 대체하여 실습하시기 바랍니다.

 

# res / layout / recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="120dp"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="4dp"
    android:elevation="4dp"
    android:layout_margin="8dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:src="@drawable/newyork"
            android:scaleType="centerCrop"/>
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="newyork"
            android:layout_below="@id/iv"
            android:padding="8dp"
            android:textColor="#FF333333"
            android:layout_centerHorizontal="true"/>

    </RelativeLayout>


</com.google.android.material.card.MaterialCardView>

 

다음 두번째 작업으로, 위 아이템 뷰 1개 모양에 보여질 이미지파일의 res 리소스ID를 저장할 int 변수와 텍스트뷰에 보여질 String변수 1개를 가진 데이터 클래스를 한개 만들겠습니다. 리사이클러뷰가 보여줄 대량의 데이터(배열 or 리스트)의 요소 1개 클래스 입니다.

# ItemVO.java
//리사이클러뷰가 보여줄 Item 뷰 1개안에서 보여줄 데이터를 가진 Value Object(VO) 클래스
public class ItemVO {
    int imgResId; //이미지의 리소스 ID
    String title; //이미지 아래에 있는 제목글씨

    public ItemVO(int imgResId, String title) {
        this.imgResId = imgResId;
        this.title = title;
    }
}

 

이제 세번째로, 대량의 데이터 수 만큼 리사이클러뷰에서 보여줄 아이템 항목뷰를 만들어내는 Adapter 클래스를 만들어 보겠습니다. 이번 실습에서 소개하고자 하는 가장 중요한 코드 입니다. 특히 이너클래스(inner class)인 ViewHolder를 상속한 VH 클래스의 코드를 주석과 함께 주의깊게 살펴보시길 바랍니다.

이전 포스트들에서 강조했듯이 결국 레이아웃파일에 대응되어 자동으로 연결해주는 바인딩클래스를 알아보는 것이 중요합니다.

이 아답터 클래스가 만들 항목뷰들의 레이아웃 xml 파일인 recycler_item.xml 에 대응되어 연결되는 RecyclerItemBinding 바인디 클래스가 자동으로 만들어 져 있다는 것을 이해하시면 됩니다.

 

recycler_item.xml  --- >  RecyclerItemBinding

recycler_board.xml --- >  RecyclerBoardBinding

 

# MyAdapter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

//RecyclerView.Adapter를 상속할때 이너클래스로 설계한 VH 클래스를 <>제네릭으로 지정하면 onBindViewHolder() 에서 다운캐스팅을 안해도 되어 코드가 간결해 짐.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH> {

    Context context;
    ArrayList<ItemVO> items;

    public MyAdapter(Context context, ArrayList<ItemVO> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView= LayoutInflater.from(context).inflate(R.layout.recycler_item, parent, false);
        return new VH(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyAdapter.VH holder, int position) {
        //아이템의 값 연결 작업코드는 홀더안에 메소드를 만들어 처리
        holder.bindItem(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    //아이템 뷰 1개의 자식뷰들 참조값을 저장하고 있는 ViewHolde 클래스 설계 - inner class
    class VH extends RecyclerView.ViewHolder{

        RecyclerItemBinding itemBinding;

        public VH(@NonNull View itemView) {
            super(itemView);
            //이미 recycler_item.xml 모양으로 만들어진 itemView와 연결하는 바인딩클래스객체 연결하여 생성하기!
            // [ recycler_item.xml  --->   RecyclerItemBinding  ]
            itemBinding= RecyclerItemBinding.bind(itemView);
        }

        //아이템뷰 1개 안에 있는 자식뷰들에 전달받은 아이템의 값들을 연결하는 기능메소드
        void bindItem(ItemVO item){
            //뷰 바인딩으로 이미 자식뷰들의 참조값들이 모두 연결되어 있음.
            itemBinding.iv.setImageResource(item.imgResId);
            itemBinding.tv.setText(item.title);
        }

        //다른 방법으로 아답터를 구현할 수도 있음. [ MyAdapter2.java ]

    }
}

마지막 주석으로 써놓은 또 다른 방법의 뷰바인딩 적용방법(MyAdapter2.java)은 이 포스트의 마지막에 소개하겠습니다.

 

이제 마지막단계로 액티비티에서 리사이클러뷰를 보여주는 코드를 추가하겠습니다. 액티비티의 레이아웃 파일에 가로방향 스크롤의 리사이클러뷰를 추가하고 액티비티 자바파일에서 ArrayList, Adapter 를 만들어 리사이클러뷰에 설정해 주도록 하겠습니다. 당연히 액티비티에서 리사이클러뷰를 제어하기 위해 findViewById()를 호출하여 참조하지 않고 바인딩클래스 객체를 통해 제어할 겁니다.

이 실습을 마지막으로 뷰 바인딩에 대해 알아보는 실습을 마무리 하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 1) 기본 TextView  -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="Hello"/>

    <!-- 2) 버튼 클릭이벤트 처리하기 -->
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"/>


    <!-- 3) EditText의 글씨을 얻어와서 TextView에 보이는 예제실습 [뷰참조와 클릭이벤트 종합 simple 예제]-->
    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="enter text"
        android:inputType="text"
        android:layout_marginTop="24dp"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="입력완료"
        android:onClick="clickBtn2"/>
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="RESULT"/>


    <!-- 4) Fragment에서 ViewBinding 사용하기 예제 실습 [name속성에 적용한 패키지명은 본인앱의 패키지명으로 수정하여 적용하시기 바랍니다.]  -->
    <fragment
        android:id="@+id/frag"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:name="com.kitesoft.exviewbinding.MyFragment"
        tools:layout="@layout/fragment_my"
        android:layout_marginTop="24dp"/>
    <!-- tools:layout="@layout/fragment_my" 화면의 한 부분을 담당하는 MyFragment의 모양을 이곳에서 미리보기해주는 속성 -->


    <!-- 5) 앱 개발과정에서 가장 많이 사용되는 뷰 중에 하나인 RecyclerView 에서 뷰바인딩 실습 [ ViewHolder 에서의 사용 ] -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        android:orientation="horizontal"/>

</LinearLayout>

 

지금까지의 모든 실습을 모두 작성한 최종 MainActivity.java 파일

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    // findViewById() 메소드를 이용한 View 참조 방법이 가진 문제를 해결하기 위한 기법들 [ 코드의 번거로움과 메소드의 동작속도 문제 ]
    // 1. ButterKnife 외부 라이브러리 사용하기
    // 2. ViewBinding 안드로이드 아키텍처 구성요소
    // 3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

    // 이번 실습에서는 뷰 참조만 관점으로 볼때 가장 성능이 좋은 ViewBinding 안드로이드 아키텍처 구성요소 라이브러리 예제 소개
    // ButterKnife의 어노테이션을 이용한 BindView 역시 코드가 간결하다고 볼수없음. 이를 개선하였다고 보면 됨.

    // ViewBinding은 외부 라이브러리가 아니고 안드로이의 아키텍처 구성요소이기에 사용설정을 (모듈단위) build.gradle 에서 해주면 됨.

    // 뷰바인딩 기능의 주요특징은 xml 레이아웃파일(activity_main.xml)의 뷰들을 이미 연결(Bind)하고 있는 클래스가 자동으로 만들어져서
    // 개발자가 직접 뷰 참조변수를 만들어서 findViewBy()로 찾아서 연결하는 일련의 모든 작업을 할 필요가 없게 만들어 놓은 기능임.

    // 이때, 자동으로 xml 레이아웃 파일의 뷰들의 id와 같은 이름을 가진 멤버변수를 가진 클래스는 그 이름이 규칙적으로 명명되어 만들어짐.
    // 레이아웃파일의 이름에 맞게 클래스이름이 만들어짐.

    // ex.1) 액티비티 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // activity_main.xml      -->   ActivityMainBinding
    // activity_second.xml    -->   ActivitySecondBinding
    // activity_xxx.xml       -->   ActivityXxxBinding

    // ex.2) 프레그먼트 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // fragment_my.xml        -->   FragmentMyBinding
    // fragment_login.xml     -->   FragmentLoginBinding
    // fragment_xxx.xml       -->   FragmentXxxBinding

    // ex.3) 리사이클러뷰(아답터뷰)의 아이템 1개 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // recycler_item.xml      -->   RecyclerItemBinding
    // recycler_xxx.xml       -->   RecyclerXxxBinding


    // 실습방법은 이전에 실습했던 ButterKnife와 완전히 동일하게 진행해 봄으로서 차이점을 극명하게 확인.

    //1) 먼저 간단하게 TextView 1개를 만들고 이를 뷰 바인딩으로 참조하여 글씨 변경해 보기
    // - ViewBinding 초기화 -
    // 액티비티의 onCreate()에서 액티비티가 직접 setContentView()를 하지 말고 뷰바인딩 클래스가 레이아웃파일을 inflate해서 만들고 참조하여 멤버변수로 가지는 객체를 생성

    // activity_main.xml 파일과 연결되어 있는 바인딩 클래스 참조변수
    ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //액티비티가 직접 setContentView()로 뷰들을 생성하지 않도록... 주석처리
        //setContentView(R.layout.activity_main);

        // xml 레이아웃파일명과 관련되어 뷰바이딩 기능에 의해 자동으로 설계되어진 클래스을 이용하여 뷰들의 id와 같은 이름을 가진 참조변수를 멤버로 가지는 객체 생성
        //activity_main.xml과 연결되는 바인딩클래스인 ActivityMainBinding 클래스의 inflate() static 메소드를 통해 뷰를 바인딩클래스가 직접 생성(inflate)
        // 바인딩 클래스의 멤버로 여러 뷰들의 참조변수들이 있으므로 가급적 바인딩클래스의 참조변수는 이 액티비티클래스의 멤버변수 위치를 만들것을 권장함.
        mainBinding= ActivityMainBinding.inflate(getLayoutInflater()); //파라미터로 LayoutInflater객체 전달

        //이 액티비티가 보여줄 내용물 뷰를 바인딩 클래스 객체의 최상위 뷰(rootView : activity_main.xml의 첫번째 레이아웃 뷰 - 이 예제에서는 LinearLayout)로 설정
        setContentView(mainBinding.getRoot());

        //1) TextView 글씨 변경하기
        //이미 activity_main.xml의 뷰들을 참조하여 연결하고 있는 바인딩 클래스인 ActivityMainBinding의 객체인 mainBinding의 멤버변수로
        //각 뷰들의 id 값과 이름이 완전 똑같은 해당뷰의 참조변수들이 찾아져서 연결되어 있는 상태임.
        mainBinding.tv.setText("Nice to meet you. ViewBinding");

        //2) 버튼클릭 이벤트 처리를 어노테이션으로 연결하던 ButterKnife와 다르게 뷰 바인딩을 이름그대로 뷰만 연결하기에 클릭이벤트 처리는 버튼에 리스너를 설정하거나 onClick속성으로 처리
        //바인딩클래스 객체 안에 id와 같은 이름의 뷰 참조변수가 이미 연결되어 있음.
        mainBinding.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "clicked button", Toast.LENGTH_SHORT).show();
            }
        });

        //2-1)람다식 -> [익명클래스의 콜백메소드 코드를 좀더 간결하게 표현하기 : JDK 1.8버전 이상에서 사용가능 ]
        mainBinding.btn.setOnClickListener(v -> Toast.makeText(this, "clicked button", Toast.LENGTH_SHORT).show());

        //롱~클릭 람다식 표기법 - 리턴있음.
        mainBinding.btn.setOnLongClickListener( v-> {
            Toast.makeText(this, "long~ clicked button", Toast.LENGTH_SHORT).show();
            return true;
        });
    }



    //3) 뷰 참조와 클릭이벤트 처리 종합 simple 예제 실습 - 버튼클릭이벤트 처리는 onClick속성 이용
    //이미 바인딩 클래스객체에 의해 TextView와 EditText의 참조변수들이 연결되어 있기에 곧바로 클릭이벤트 처리 코드만 작성 - 버터나이프의 어노테이션 작업도 필요없음.
    public void clickBtn2(View view) {
        mainBinding.tv2.setText(mainBinding.et.getText().toString());
        mainBinding.et.setText(""); //EditText의 글씨 지우기
    }


    //4) 프레그먼트에 뷰바인딩 실습 [ MyFragemnt.java ]


    //5) 앱 개발과정에서 가장 많이 사용되는 뷰 중에 하나인 RecyclerView 에서 뷰바인딩 실습 [ ViewHolder 에서의 사용 ]
    // - 원래는 onCreate()에서 작업하지만 위 실습과 분리해서 보기위해 onResume()에서 리사이클러뷰 작업코딩
    // RecyclerView 참조변수는 이미 mainBinding 객체의 멤버로 연결되어 있기에 다시 만들필요 없음

    ArrayList<ItemVO> items= new ArrayList<>(); //리사이클러 뷰가 보여줄 대량의 데이터를 가지고 있는 리시트객체
    MyAdapter adapter;   //리사이클러뷰가 보여줄 뷰을 만들어내는 객체참조변수

    @Override
    protected void onResume() {
        super.onResume();

        //임이의 대량의 데이터 추가
        items.add(new ItemVO(R.drawable.newyork, "newyork"));
        items.add(new ItemVO(R.drawable.paris, "paris"));
        items.add(new ItemVO(R.drawable.sydney, "sydney"));
        items.add(new ItemVO(R.drawable.newyork, "뉴욕"));
        items.add(new ItemVO(R.drawable.paris, "파리"));
        items.add(new ItemVO(R.drawable.sydney, "시드니"));

        //아답터생성 및 리사이클러뷰에 설정
        adapter= new MyAdapter(this, items);
        mainBinding.recycler.setAdapter(adapter);

        //*추가** 다른 방법의 바인딩클래스 사용 아답터 예제소개 *****
        //MyAdapter2 adapter2= new MyAdapter2(this, items);
        //mainBinding.recycler.setAdapter(adapter2);
    }
}

여기까지 리사이클러뷰에서 뷰바인딩을 적용하는 코드를 소개하였습니다.

 

※ 추가로. 아답터의 ViewHolder에서 뷰바인딩을 적용하는 조금 다른 방법도 소개해드리겠습니다. 어떤 것이 더 좋다 나쁘다는 것이 아니니 이런방법도 있구나 정도로 참고해 보시기 바랍니다.

# MyAdapter2.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.mrhi2021.ex98viewbinding.databinding.RecyclerItemBinding;

import java.util.ArrayList;

public class MyAdapter2 extends RecyclerView.Adapter<MyAdapter2.VH> {

    Context context;
    ArrayList<ItemVO> items;

    public MyAdapter2(Context context, ArrayList<ItemVO> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // recycler_item.xml의 뷰들과 연결돠는 바인딩 클래스 객체 생성  [ recycler_item.xml  --->   RecyclerItemBinding  ]
        //RecyclerItemBinding itemBinding= RecyclerItemBinding.inflate(LayoutInflater.from(context)); //부모인 parent 없이 inflate하면 아이템뷰의 사이즈가 이상해질 수 있음.
        RecyclerItemBinding itemBinding= RecyclerItemBinding.inflate(LayoutInflater.from(context), parent, false);
        return new VH(itemBinding);
    }

    @Override
    public void onBindViewHolder(@NonNull MyAdapter2.VH holder, int position) {
        //아이템의 값 연결 작업코드는 홀더안에 메소드를 만들어 처리
        holder.bindItem(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    //아이템 뷰 1개의 자식뷰들 참조값을 저장하고 있는 ViewHolde 클래스 설계 - inner class
    //뷰 바인딩으로 이미 자식뷰들의 참조값들이 모두 연결되어 있음.
    class VH extends RecyclerView.ViewHolder{

        RecyclerItemBinding itemBinding;

        //다른 방법으로 아답터를 구현할 수도 있음.

        //기존에 생성자로 inflate한 ItemView 객체를 받아서 부모클래스(ViewHolder)의 생성자로 전달했었으나.[ super(itemView) ]
        //이제는 자식뷰들을 이미 참조하고 있는 바인딩객체를 전달받아 그 root뷰를 부모의 생성자에 전달.
        public VH(RecyclerItemBinding itemBinding) {
            super(itemBinding.getRoot());
            //뷰홀더 안에 멤버변수에 대입
            this.itemBinding= itemBinding;
        }


        //아이템뷰 1개 안에 있는 자식뷰들에 전달받은 아이템의 값들을 연결하는 기능메소드
        void bindItem(ItemVO item){
            itemBinding.iv.setImageResource(item.imgResId);
            itemBinding.tv.setText(item.title);
        }


    }
}

 

 뷰 바인딩은 레이아웃파일에 대응하여 자동으로 만들어지는 바인딩 클래스의 존재를 이해하고 그 바인딩 클래스의 멤버필드로 있는 id와 같은 이름을 가진 참조변수들로 뷰를 제어하는 부분만 이해하시면 명시적으로 findViewById()를 하거나, 외부 라이브러리이면서 @어노테이션과 뷰 참조변수를 직접 작성해야 하는 ButterKnife 라이브러리 보다 더 간결하게 코딩이 가능해지고 개발자의 실수로 다른 뷰의 id를 가지고 find를 하여 null 에러가 발생하는 빈도를 현저히 줄여주며, 성능적인 부분도 가장 우수하게 뷰와 상호작용할 수 있는 방법입니다. 그래서 안드로드이 공식 문서에서도 단순하게 뷰와의 상호작용을 하고자 한다면 뷰 바인딩을 권장하고 있으니 가급적 본인들의 앱 개뱔에 사용해 보실것을 권해드립니다.

 

다음 포스트 글로는 findViewById()를 대체하는 3가지 방법 중 마지막인 Data Binding(데이터 바인딩)에 대해 소개하겠습니다.

반응형
반응형

이전 포스트에 이어서 뷰 바이딘에 대한 4번째 예제를 소개하겠습니다.

이전 버터나이프 포스트에서 소개한 바와 같이 실제 앱을 구현하다보면 액티비티에 직접 뷰들을 배치하여 사용하기 보다 화면의 부분별로 별도의 영역(Fragment)으로 나누어 UI를 작업하는 경우가 더 많습니다. 화면구성이 단순하지 않으면 액티비티에 모든 뷰들을 배치하여 관리하기 어렵고 코드도 복잡하기 때문이지요. 그로인해 Fragment를 사용하는 빈도가 아주 높습니다.

그래서 이번에는 Fragment 에서 뷰 바인딩을 사용하여 뷰들을 제어해보겠습니다. 

 

4) 프레그먼트에 뷰바인딩 실습 [ MyFragment.java ]

예제가 복잡하면 정작 뷰바인딩을 사용하는 방법보다 다른 부분을 이해하는 것에 어려움을 느낄 수도 있기에 이전 ButterKnife 라이브러리 실습에서 소개한 것과 마찬가지로 아주 간단하게 실습을 진행하고자 합니다.

보통 프레그먼트를 액티비티에 추가할때는 자바에서 FragmentManager를 이용하여 add하는 방법이 일반적으로 더 많이 사용되지만 실습의 단순화를 위해 액티비티의 xml에 <fragment>태그로 정적추가 하겠습니다.

 

Fragment를 상속하는 MyFragment클래스를 별도의 .java 파일로 만들어 적용해 보겠습니다.

먼저, MyFragment에서 보여줄 레이아웃 xml 파일을 만들어 보겠습니다. 간단하게 구성하기 위해 TextView 1개와 Button 1개만으로 구성하여 버튼을 글릭했을 때 TextView의 글씨를 변경하도록 만들어 보겠습니다.

 

# res / layout / fragment_my.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="@color/white"
        android:text="This is MyFragment"
        android:textStyle="bold"/>
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv"
        android:backgroundTint="@color/teal_200"
        android:text="chnage text"
        android:textAllCaps="false"/>

</RelativeLayout>

뷰 바인딩 사용이 설정되어 있기이 layout 폴더에 xml 파일을 만들면 자동으로 레이아웃 파일명을 기반으로 그에 상응하는 바인딩 클래스가 만들어져 있습니다.

여기서는 fragment_my.xml 이라는 이름의 레이아웃파일이 있기에 자동으로 만들어진 바인딩 클래스의 이름은 FragmentMyBinding으로 됩니다. 액티비티의 바인딩과 크게 다르지 않습니다. 이 바인딩 클래스의 멤버필드에는 레이아웃 xml 파일안에 만든 id가 있는 뷰들을 참조하고 있는 참조변수가 id와 같은 이름으로 만들어져 있습니다. 우리는 이 바인딩 클래스 객체의 멤버필드들을 이용하여 별도의 뷰를 찾아 참조하는 findViewById()작업 없이 뷰들을 제어할 수 있습니다. 

다신 언급하지만 레이아웃파일명과 바인딩 클래스의 명명 규칙만 이해하면 그리 어렵지 않게 뷰 바인딩을 사용하실 수 있게 됩니다.

 

fragment_my.xml    --- >   FragmentMyBinding

fragment_aa.xml.   --- >.  FragmentAABinging

 

 

이제 위 레이아웃파일을 보여주고 제어하는 MyFragment.java 를 만들어 보겠습니다. 레이아웃과 연결된 바인딩 클래스 인스턴스를 만드는 부분을 주의 깊게 보시기 바랍니다. 주석으로 설명하였으니 참고바랍니다.

# MyFragment.java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.mrhi2021.ex98viewbinding.databinding.FragmentMyBinding;

public class MyFragment extends Fragment {

    //fragment_my.xml 파일과 연결되어 있는 바인딩 클래스 참조변수
    FragmentMyBinding myBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //프레그먼트가 보여줄 뷰의 레아아웃 파일인 fragment_my.xml 문서와 연결되도록 뷰바인딩에서 자동으로 만들어지는 바인딩 클래스를 통해 뷰객체 생성(inflate)
        // fragment_my.xml  ---> FragmentMyBiding   [ 바인딩클래스 참조변수는 가급적 이 프레그먼트클래스의 멤버변수에 선언 - 어디서든 불러서 사용할 수 있도록]
        myBinding= FragmentMyBinding.inflate(inflater);
        return myBinding.getRoot(); //프레그먼트가 보여줄 뷰로서 바인딩클래스 객체의 최상위 뷰(rootView : fragment_my.xml의 첫번째 레이아웃 뷰 - 이 예제에서는 RelativeLayout)를 리턴
    }

    //onCreateView()메소드를 통해 프레그먼트가 보유줄 뷰가 만들어진 후 자동으로 실행되는 라이프사이클 콜백메소드
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        //이미 바인딩클래스객체의 멤버변수안에 id와 같은 이름을 가진 뷰참조변수들의 연결되어 있으므로 원하는 코드를 바로 작성
        myBinding.btn.setOnClickListener( v -> myBinding.tv.setText("Have a good day.") );
    }

    //*참고*
    //프레그먼트가 보여주는 뷰들이 Destroy(파괴)되어도 프레그먼트 객체가 살아있을 수 있기에 뷰들이 메모리에서 없어질때 바인딩클래스의 인스턴스도 파괴함으로서 메모리누수를 방지할 수 있음.
    @Override
    public void onDestroyView() {
        super.onDestroyView();

        myBinding=null; //바인딩 객체를 GC(Garbage Collector) 가 없애도록 하기 위해 참조를 끊기.
    }
}

각 코드마다 작성한 주석을 보시면 전체 코드를 이해하실 수 있으실 겁니다.

 

이제 마지막 단계로 위 MyFragment를 MainActivity에 정적으로 추가하도록 하겠습니다. activity_main.xml 파일에 <fragment>태그로 추가만 하면 되기에 MainActivity.java는 특별히 수정할 부분이 없습니다. 곧바로 액티비티의 레이아웃 파일만 소개하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 1) 기본 TextView  -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="Hello"/>

    <!-- 2) 버튼 클릭이벤트 처리하기 -->
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"/>


    <!-- 3) EditText의 글씨을 얻어와서 TextView에 보이는 예제실습 [뷰참조와 클릭이벤트 종합 simple 예제]-->
    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="enter text"
        android:inputType="text"
        android:layout_marginTop="24dp"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="입력완료"
        android:onClick="clickBtn2"/>
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="RESULT"/>


    <!-- 4) Fragment에서 ViewBinding 사용하기 예제 실습 [name속성에 적용한 패키지명은 본인앱의 패키지명으로 수정하여 적용하시기 바랍니다.]  -->
    <fragment
        android:id="@+id/frag"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:name="com.kitesoft.exviewbinding.MyFragment"
        tools:layout="@layout/fragment_my"
        android:layout_marginTop="24dp"/>
    <!-- tools:layout="@layout/fragment_my" 화면의 한 부분을 담당하는 MyFragment의 모양을 이곳에서 미리보기해주는 속성 -->


</LinearLayout>

만들어 보시면 프레그먼트라고 해서 액티비티와 크게 다르지 않다는 것을 알 수 있으실 겁니다.

 

이제 뷰 바인딩의 마지막 실습으로 실제 앱 구현에 아주 많이 사용되는  RecyclerView 에서 뷰 바인딩을 사용하는 예제를 다음 포스트로 소개하겠습니다. 

반응형
반응형

Ex16ContextMenu

안드로이드 네이티브 앱 개발 수업 예제#16

주요코드

ContextMenu 추가하기 [뷰를 꾹~ 누르고 있으면(long click) 팝업되어 보여지는 메뉴 : 데스크탑에서의 마우스 오른쪽버튼 메뉴 역할]

  • 메뉴을 추가하고 싶은 뷰(이 예제에서는 Button)을 ContextMunu로 등록(registerForContextMenu())
  • ContextMenu를 만들기 위한 메소드 onCreateContextMenu() 오버라이드
  • ContextMenu도 OptionMenu만드는 방식과 동일함
  • XML언어로 메뉴항목들을 설계하고 자바언어의 MenuItem객체로 만들어서(부풀리다inflate) Menu에 추가하는 방법을 사용함.res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨] menu폴더에 있는 context.xml문서를 읽어와서 Menu객체로 만들어주는(부풀려주는inflate) 객체인 MenuInflater사용
  • ContextMenu의 Menu(MenuItem)클릭 반응하기

실행모습

 

 

실행모습 GIF

 

 

소스코드

버튼의 롱~클릭으로 나타날 컨텍스트 메뉴에 보여줄 메뉴의 항목들 Menu Resource로 설계

 

- res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨 

- 추가된 menu 폴더에서 마우스오른쪽버튼 메뉴에서 [ New > Menu Reource File ]선택하여 file name 칸에 "context"을 쓰고 [OK]를 누르면 메뉴리소스파일 이 만들어 짐.

# res / menu / context.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@+id/menu_save"
        android:title="Save"/>

    <item android:id="@+id/menu_delete"
        android:title="Delete"/>
    <!-- ContextMenu는 기본적으로 icon이 적용되지 않음  -->

</menu>

 

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="show Context Menu"
        android:onClick="clickBtn"/>

</LinearLayout>

 

 

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button btn;

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

        btn= findViewById(R.id.btn);

        //액티비티에게 btn객체를 ContextMenu로 등록
        registerForContextMenu(btn);
    }

    //ContextMenu로 등록된 뷰가 롱클릭되면
    //컨텍스트메뉴를 만드는 메소드가 자동으로 실행되는 콜백메소드..
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {

        //OptionMenu와 만드는 방법은 동일함.
        //res폴더>>menu폴더>>context.xml 파일을 만들어서 메뉴항목들 작성
        //context.xml파일을 메뉴객체로 생성(부플리다..)
        MenuInflater inflater= getMenuInflater();
        inflater.inflate(R.menu.context, menu);

        super.onCreateContextMenu(menu, v, menuInfo);
    }

    //콘텍스트메뉴의 아이템을 선택했을때 자동으로 실행되는 콜백메소드..
    @Override
    public boolean onContextItemSelected(MenuItem item) {

        //context.xml파일에 작성한 메뉴항목들의 id를 식별하여 토스트 보여주기
        switch ( item.getItemId() ){
            case R.id.menu_save:
                Toast.makeText(this, "SAVED", Toast.LENGTH_SHORT).show();
                break;

            case R.id.menu_delete:
                Toast.makeText(this, "DELETED", Toast.LENGTH_SHORT).show();
                break;
        }

        return super.onContextItemSelected(item);
    }

    // 기본적인 버튼으로서의 클릭이벤트 동작 확인 콜백메소드...
    public void clickBtn(View v){
        Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
    }
}
반응형
반응형

 

이전 포스트에 이어서 뷰 바인딩에 대한 3번째 예제를 소개하겠습니다.

이번에는 EditText와 Button, TextView를 배치하여, 사용자가 EditText에 글씨를 입력하고 완료버튼을 클릭하면 써있는 글씨를 읽어와서 TextView에 보여주는 간단하면서 앱 개발기술을 가장 중요한 예제를 뷰 바인딩으로 처리해 보겠습니다.

 

3) 뷰 참조와 클릭이벤트 처리 종합 simple 예제 실습

- 버튼클릭이벤트의 처리는 버터나이프처럼 @OnClick 어노테이션이 없기에 기존에 해오던 xml 레이아웃 파일의 뷰에 onClick속성을 지정하여 처리하겠습니다.

 

이전 예제에 이어서 코드를 작성하니 주석에 표시한 실습번호 3)에 해당하는 코드영역을 구분하여 보시기 바랍니다.

 

먼저, 레이아웃파일에 뷰들을 추가하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 1) 기본 TextView  -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="Hello"/>

    <!-- 2) 버튼 클릭이벤트 처리하기 -->
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"/>


    <!-- 3) EditText의 글씨을 얻어와서 TextView에 보이는 예제실습 [뷰참조와 클릭이벤트 종합 simple 예제]-->
    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="enter text"
        android:inputType="text"
        android:layout_marginTop="24dp"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="입력완료"
        android:onClick="clickBtn2"/>
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="RESULT"/>  

</LinearLayout>

 

이제 자바 코드의 버튼 클릭 콜백메소드 ( clickBtn2 )에서 뷰를 제어하는 코스를 작성하겠습니다.

이미 바인딩 클래스(ActivityMainBinding) 객체에 의해 TextView와 EditText의 참조변수들이 연결되어 있기에 별도의 멤버변수(Field) 선언과 findViewById()를 명시적으로 작성할 필요없이 곧바로 클릭이벤트 처리 코드만 작성하면 됩니다. 버터나이프의 어노테이션 작업도 필요없습니다. 정말 빠르고 간결하고 기능이 추가되는 느낌일겁니다.

 

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.mrhi2021.ex98viewbinding.databinding.ActivityMainBinding;


public class MainActivity extends AppCompatActivity {

    // findViewById() 메소드를 이용한 View 참조 방법이 가진 문제를 해결하기 위한 기법들 [ 코드의 번거로움과 메소드의 동작속도 문제 ]
    // 1. ButterKnife 외부 라이브러리 사용하기
    // 2. ViewBinding 안드로이드 아키텍처 구성요소
    // 3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

    // 이번 실습에서는 뷰 참조만 관점으로 볼때 가장 성능이 좋은 ViewBinding 안드로이드 아키텍처 구성요소 라이브러리 예제 소개
    // ButterKnife의 어노테이션을 이용한 BindView 역시 코드가 간결하다고 볼수없음. 이를 개선하였다고 보면 됨.

    // ViewBinding은 외부 라이브러리가 아니고 안드로이의 아키텍처 구성요소이기에 사용설정을 (모듈단위) build.gradle 에서 해주면 됨.

    // 뷰바인딩 기능의 주요특징은 xml 레이아웃파일(activity_main.xml)의 뷰들을 이미 연결(Bind)하고 있는 클래스가 자동으로 만들어져서
    // 개발자가 직접 뷰 참조변수를 만들어서 findViewBy()로 찾아서 연결하는 일련의 모든 작업을 할 필요가 없게 만들어 놓은 기능임.

    // 이때, 자동으로 xml 레이아웃 파일의 뷰들의 id와 같은 이름을 가진 멤버변수를 가진 클래스는 그 이름이 규칙적으로 명명되어 만들어짐.
    // 레이아웃파일의 이름에 맞게 클래스이름이 만들어짐.

    // ex.1) 액티비티 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // activity_main.xml      -->   ActivityMainBinding
    // activity_second.xml    -->   ActivitySecondBinding
    // activity_xxx.xml       -->   ActivityXxxBinding

    // ex.2) 프레그먼트 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // fragment_my.xml        -->   FragmentMyBinding
    // fragment_login.xml     -->   FragmentLoginBinding
    // fragment_xxx.xml       -->   FragmentXxxBinding

    // ex.3) 리사이클러뷰(아답터뷰)의 아이템 1개 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // recycler_item.xml      -->   RecyclerItemBinding
    // recycler_xxx.xml       -->   RecyclerXxxBinding


    // 실습방법은 이전에 실습했던 ButterKnife와 완전히 동일하게 진행해 봄으로서 차이점을 극명하게 확인.

    //1) 먼저 간단하게 TextView 1개를 만들고 이를 뷰 바인딩으로 참조하여 글씨 변경해 보기
    // - ViewBinding 초기화 -
    // 액티비티의 onCreate()에서 액티비티가 직접 setContentView()를 하지 말고 뷰바인딩 클래스가 레이아웃파일을 inflate해서 만들고 참조하여 멤버변수로 가지는 객체를 생성

    // activity_main.xml 파일과 연결되어 있는 바인딩 클래스 참조변수
    ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //액티비티가 직접 setContentView()로 뷰들을 생성하지 않도록... 주석처리
        //setContentView(R.layout.activity_main);

        // xml 레이아웃파일명과 관련되어 뷰바이딩 기능에 의해 자동으로 설계되어진 클래스을 이용하여 뷰들의 id와 같은 이름을 가진 참조변수를 멤버로 가지는 객체 생성
        //activity_main.xml과 연결되는 바인딩클래스인 ActivityMainBinding 클래스의 inflate() static 메소드를 통해 뷰를 바인딩클래스가 직접 생성(inflate)
        // 바인딩 클래스의 멤버로 여러 뷰들의 참조변수들이 있으므로 가급적 바인딩클래스의 참조변수는 이 액티비티클래스의 멤버변수 위치를 만들것을 권장함.
        mainBinding= ActivityMainBinding.inflate(getLayoutInflater()); //파라미터로 LayoutInflater객체 전달

        //이 액티비티가 보여줄 내용물 뷰를 바인딩 클래스 객체의 최상위 뷰(rootView : activity_main.xml의 첫번째 레이아웃 뷰 - 이 예제에서는 LinearLayout)로 설정
        setContentView(mainBinding.getRoot());

        //1) TextView 글씨 변경하기
        //이미 activity_main.xml의 뷰들을 참조하여 연결하고 있는 바인딩 클래스인 ActivityMainBinding의 객체인 mainBinding의 멤버변수로
        //각 뷰들의 id 값과 이름이 완전 똑같은 해당뷰의 참조변수들이 찾아져서 연결되어 있는 상태임.
        mainBinding.tv.setText("Nice to meet you. ViewBinding");

        //2) 버튼클릭 이벤트 처리를 어노테이션으로 연결하던 ButterKnife와 다르게 뷰 바인딩을 이름그대로 뷰만 연결하기에 클릭이벤트 처리는 버튼에 리스너를 설정하거나 onClick속성으로 처리
        //바인딩클래스 객체 안에 id와 같은 이름의 뷰 참조변수가 이미 연결되어 있음.
        mainBinding.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "clicked button", Toast.LENGTH_SHORT).show();
            }
        });

        //2-1)람다식 -> [익명클래스의 콜백메소드 코드를 좀더 간결하게 표현하기 : JDK 1.8버전 이상에서 사용가능 ]
        mainBinding.btn.setOnClickListener(v -> Toast.makeText(this, "clicked button", Toast.LENGTH_SHORT).show());

        //롱~클릭 람다식 표기법 - 리턴있음.
        mainBinding.btn.setOnLongClickListener( v-> {
            Toast.makeText(this, "long~ clicked button", Toast.LENGTH_SHORT).show();
            return true;
        });
    }



    //3) 뷰 참조와 클릭이벤트 처리 종합 simple 예제 실습 - 버튼클릭이벤트 처리는 onClick속성 이용
    //이미 바인딩 클래스객체에 의해 TextView와 EditText의 참조변수들이 연결되어 있기에 곧바로 클릭이벤트 처리 코드만 작성 - 버터나이프의 어노테이션 작업도 필요없음.
    public void clickBtn2(View view) {
        mainBinding.tv2.setText(mainBinding.et.getText().toString());
        mainBinding.et.setText(""); //EditText의 글씨 지우기
    }
    
}

위 자바코드의 마지막 부분의 3) 영역만 추가한 겁니다. 코딩을 거의 안한 느낌이지요. 아주 편하다고 느끼실 겁니다.

 

다음 포스트에서는 액티비티의 UI에서 한 부분을 별도를 만들고 관리하기위한 Fragment에서 뷰 바인딩을 사용하는 방법을 소개하겠습니다. 버터나이프 때처럼 아주 간단한 실습으로 소개할테니 가볍게 살펴보시기를 바랍니다.

반응형
반응형

Ex15OptionMenu

안드로이드 네이티브 앱 개발 수업 예제#15

주요코드

OptionMenu 추가하기 [ 제목줄에 보여지는 메뉴여서 ActionBar Menu라고도 함]

  • OptionMenu를 만들기 위한 메소드 onCreateOptionsMenu() 오버라이드
  • OptionMenu 만들어 추가하는 2가지 방법
    1. 방법1. 자바언어로 메뉴아이템 추가..(잘 사용하지 않음)
    2. 방법 2. XML언어로 메뉴항목들을 설계하고 자바언어의 MenuItem객체로 만들어서(부풀리다inflate) Menu에 추가하는 방법을 사용함.
    res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨] menu폴더에 있는 actionbar_main.xml문서를 읽어와서 Menu객체로 만들어주는(부풀려주는inflate) 객체인 MenuInflater사용
  • OptionMenu의 Menu(MenuItem)클릭 반응하기

실행모습

 

 

실행모습 GIF

 

소스코드

- activity_main.xml은 수정할 것이 없음.

- 제목줄(액션바)에 보여줄 메뉴의 항목들 Menu Resource로 설계

 

- res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨 

- 추가된 menu 폴더에서 마우스오른쪽버튼 메뉴에서 [ New > Menu Reource File ]선택하여 file name 칸에 "actionbar_main"을 쓰고 [OK]를 누르면 메뉴리소스파일 이 만들어 짐.

# res / menu / actionbar_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/menu_search"
        android:title="Search"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always"/>
        <!-- showAsAction="always" : 제목줄(ActionBar)에 항상 아이콘으로 노출됨 -->

    <item
        android:id="@+id/menu_add"
        android:title="Add"
        android:icon="@android:drawable/ic_menu_add"
        app:showAsAction="ifRoom"/>
        <!-- showAsAction="ifRoom" : 제목줄(ActionBar)에 제목글씨를 제외하고 놓여질 공간이 있으면 노출, 아니면 오버플로우메뉴로 들어가는 설정[권장] -->

    <item
        android:id="@+id/menu_helf"
        android:title="Helf"
        android:icon="@android:drawable/ic_menu_help"
        app:showAsAction="never"/>
        <!-- showAsAction="never" : 항상 오버플로우(오른쪽 끝 세로 점3개 아이콘) 메뉴에 숨었다가 눌렀을 때 노출되는 설정 -->
        <!-- 오버플로우(오른쪽 끝 세로 점3개 아이콘) 메뉴에 숨은 메뉴아이템은 기본적으로 아이콘이 보이지 않음 -->

    <item
        android:id="@+id/menu_aaa"
        android:title="aaa"
        app:showAsAction="never"/>

</menu>

 

 

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

    //onCreate()메소드가 실행된 후
    //자동으로 OptionMenu를 만드는 작업을 하는
    //이 콜백메소드가 발동
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        //매개변수로 전달된 Menu객체에게 MenuItem(메뉴항목)을 추가[ 2가지 방법 ]

        // 방법1. 자바언어로 메뉴아이템 추가..(잘 사용하지 않음)
        //menu.add( 0, 1, 0, "aaa" );
        //menu.add( 0, 2, 0, "bbb");


        // 방법 2. XML언어로 메뉴항목들을 설계하고
        //자바언어의 MenuItem객체로 만들어서(부풀리다inflate)
        //Menu에 추가하는 방법을 사용함.

        // res폴더>>menu폴더 생성 [ res폴더에서 마우스오른쪽버튼 메뉴에서 'Android Resource Directory' 선택 후 위에서 2번째 항목의 'Resource Type'에서 'menu'선택하면 menu폴더 추가됨]
        //menu폴더에 있는 actionbar_main.xml문서를 읽어와서
        //Menu객체로 만들어주는(부풀려주는inflate) 객체를 얻어오기
        MenuInflater inflater= getMenuInflater();
        inflater.inflate(R.menu.actionbar_main, menu);


        //* 오버플로우 메뉴들은 기본적으로 아이콘이 보이지 않도록 되어 있지만 코드를 통해 줄수는 있음.- 꼭 필요하지 않음.
//        if( menu instanceof MenuBuilder){
//            MenuBuilder m= (MenuBuilder)menu;
//            m.setOptionalIconsVisible(true);
//        }

        return super.onCreateOptionsMenu(menu);
    }

    //OptionMenu의 메뉴항목(MenuItem)을 선택했을때
    //자동으로 실행되는 콜백메소드
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id= item.getItemId();

        switch ( id ){

            case R.id.menu_search:
                Toast.makeText(this, "Search", Toast.LENGTH_SHORT).show();
                break;

            case R.id.menu_add:
                Toast.makeText(this, "Add", Toast.LENGTH_SHORT).show();
                break;

            case R.id.menu_helf:
                Toast.makeText(this, "Helf", Toast.LENGTH_SHORT).show();
                break;

        }

        return super.onOptionsItemSelected(item);
    }
}
반응형
반응형

이전 포스트에 이어서 뷰 바인딩에 대한 2번째 예제를 소개하겠습니다.

이번에는 버튼과 텍스트뷰를 1개씩 추가하고 버튼을 클릭하였을때 텍스트뷰의 글씨를 변경하는 코드를 작성해 보겠습니다.

 

2) 버튼 클릭 이벤트 처리 

버튼클릭 이벤트 처리를 어노테이션으로 연결하던 ButterKnife와 다르게 뷰 바인딩은 이름그대로 뷰만 연결하기에 클릭이벤트 처리는 버튼에 리스너를 설정하거나 onClick속성으로 처리해야만 합니다.


바인딩클래스 객체 안에 id와 같은 이름의 뷰 참조변수가 이미 연결되어 있기에 findViewById()는 필요하지 않겠지요. 앞 포스트의 코드에 이어서 작성하는 것이니 주석으로 표기한 실습번호 2)에 관련된 부분을 구별하여 보시기 바랍니다. 주석을 통해 각 코드에 대해 설명하였으니 주석을 주의깊게 보시면 이해하기 편하실 겁니다.

 

먼저 액티비티 레이아웃 파일에 버튼 1개를 추가하겠습니다. id를 지정하고 뷰바인딩으로 연결하여 리스너를 설정하고자 합니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <!-- 1) 기본 TextView  -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#FF333333"
        android:text="Hello"/>

    <!-- 2) 버튼 클릭이벤트 처리하기 -->
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"/>

</LinearLayout>

 

Button에 id가 지정되었기에 이 activity_main.xml 레이아웃 파일과 연결되도록 자동으로 만들어지는 바인딩 클래스인 ActivityMainBinding 클래스 객체의 필드(멤버변수)에 btn 이리는 이름의 참조변수가 자동으로 Button과 연결되어 만들어집니다.

그렇기에 별도의 Button 참조변수를 만들거나 findViewById(R.id.btn) 메소드를 호출할 필요가 없습니다. 그냥 바인딩클래스의 멤버에 곧바로 리스너 처리하시면 됩니다.

 

MainActivity 자바코드를 작성해보겠습니다. 주석을 참고하세요

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.mrhi2021.ex98viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // findViewById() 메소드를 이용한 View 참조 방법이 가진 문제를 해결하기 위한 기법들 [ 코드의 번거로움과 메소드의 동작속도 문제 ]
    // 1. ButterKnife 외부 라이브러리 사용하기
    // 2. ViewBinding 안드로이드 아키텍처 구성요소
    // 3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

    // 이번 실습에서는 뷰 참조만 관점으로 볼때 가장 성능이 좋은 ViewBinding 안드로이드 아키텍처 구성요소 라이브러리 예제 소개
    // ButterKnife의 어노테이션을 이용한 BindView 역시 코드가 간결하다고 볼수없음. 이를 개선하였다고 보면 됨.

    // ViewBinding은 외부 라이브러리가 아니고 안드로이의 아키텍처 구성요소이기에 사용설정을 (모듈단위) build.gradle 에서 해주면 됨.

    // 뷰바인딩 기능의 주요특징은 xml 레이아웃파일(activity_main.xml)의 뷰들을 이미 연결(Bind)하고 있는 클래스가 자동으로 만들어져서
    // 개발자가 직접 뷰 참조변수를 만들어서 findViewBy()로 찾아서 연결하는 일련의 모든 작업을 할 필요가 없게 만들어 놓은 기능임.

    // 이때, 자동으로 xml 레이아웃 파일의 뷰들의 id와 같은 이름을 가진 멤버변수를 가진 클래스는 그 이름이 규칙적으로 명명되어 만들어짐.
    // 레이아웃파일의 이름에 맞게 클래스이름이 만들어짐.

    // ex.1) 액티비티 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // activity_main.xml      -->   ActivityMainBinding
    // activity_second.xml    -->   ActivitySecondBinding
    // activity_xxx.xml       -->   ActivityXxxBinding

    // ex.2) 프레그먼트 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // fragment_my.xml        -->   FragmentMyBinding
    // fragment_login.xml     -->   FragmentLoginBinding
    // fragment_xxx.xml       -->   FragmentXxxBinding

    // ex.3) 리사이클러뷰(아답터뷰)의 아이템 1개 레이아웃 파일명과 자동으로 만들어지는 바인딩 클래스명
    // recycler_item.xml      -->   RecyclerItemBinding
    // recycler_xxx.xml       -->   RecyclerXxxBinding


    // 실습방법은 이전에 실습했던 ButterKnife와 완전히 동일하게 진행해 봄으로서 차이점을 극명하게 확인.

    //1) 먼저 간단하게 TextView 1개를 만들고 이를 뷰 바인딩으로 참조하여 글씨 변경해 보기
    // - ViewBinding 초기화 -
    // 액티비티의 onCreate()에서 액티비티가 직접 setContentView()를 하지 말고 뷰바인딩 클래스가 레이아웃파일을 inflate해서 만들고 참조하여 멤버변수로 가지는 객체를 생성

    // activity_main.xml 파일과 연결되어 있는 바인딩 클래스 참조변수
    ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //액티비티가 직접 setContentView()로 뷰들을 생성하지 않도록... 주석처리
        //setContentView(R.layout.activity_main);

        // xml 레이아웃파일명과 관련되어 뷰바이딩 기능에 의해 자동으로 설계되어진 클래스을 이용하여 뷰들의 id와 같은 이름을 가진 참조변수를 멤버로 가지는 객체 생성
        //activity_main.xml과 연결되는 바인딩클래스인 ActivityMainBinding 클래스의 inflate() static 메소드를 통해 뷰를 바인딩클래스가 직접 생성(inflate)
        // 바인딩 클래스의 멤버로 여러 뷰들의 참조변수들이 있으므로 가급적 바인딩클래스의 참조변수는 이 액티비티클래스의 멤버변수 위치를 만들것을 권장함.
        mainBinding= ActivityMainBinding.inflate(getLayoutInflater()); //파라미터로 LayoutInflater객체 전달

        //이 액티비티가 보여줄 내용물 뷰를 바인딩 클래스 객체의 최상위 뷰(rootView : activity_main.xml의 첫번째 레이아웃 뷰 - 이 예제에서는 LinearLayout)로 설정
        setContentView(mainBinding.getRoot());

        //1) TextView 글씨 변경하기
        //이미 activity_main.xml의 뷰들을 참조하여 연결하고 있는 바인딩 클래스인 ActivityMainBinding의 객체인 mainBinding의 멤버변수로
        //각 뷰들의 id 값과 이름이 완전 똑같은 해당뷰의 참조변수들이 찾아져서 연결되어 있는 상태임.
        mainBinding.tv.setText("Nice to meet you. ViewBinding");

        //2) 버튼클릭 이벤트 처리를 어노테이션으로 연결하던 ButterKnife와 다르게 뷰 바인딩을 이름그대로 뷰만 연결하기에 클릭이벤트 처리는 버튼에 리스너를 설정하거나 onClick속성으로 처리
        //바인딩클래스 객체 안에 id와 같은 이름의 뷰 참조변수가 이미 연결되어 있음.
        mainBinding.btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "clicked button", Toast.LENGTH_SHORT).show();
            }
        });

        //2-1)람다식 -> [익명클래스의 콜백메소드 코드를 좀더 간결하게 표현하기 : JDK 1.8버전 이상에서 사용가능 ]
        mainBinding.btn.setOnClickListener(v -> Toast.makeText(this, "clicked button", Toast.LENGTH_SHORT).show());

        //롱~클릭 람다식 표기법 - 리턴있음.
        mainBinding.btn.setOnLongClickListener( v-> {
            Toast.makeText(this, "long~ clicked button", Toast.LENGTH_SHORT).show();
            return true;
        });
    }
    
}

버튼 참조변수를 별도로 만들지 않고 findViewById()를 사용하지 않았다는 것을 말고는 기본적인 버튼 클릭리스너 처리의 코드와 차이가 없습니다. 즉, 뷰 바인딩은 이름에 걸맞게 뷰와 연결하여 상호작용하는 것에만 집중된 기능으로 보셔도 됩니다. 안드로이드 공식 문서에도 단순히 뷰를 참조하는 기능만으로는 유사목적으로 가진 버터나이프나, 데이터바인딩 보다 더 우수하여 뷰와 상호작용만이 필요한 상황에서는 뷰바인딩 사용을 권장하고 있습니다.

 

이번 포스트는 워낙 간단하니 다음 포스트에서는 EditText, Button, TextView 를 각각 추가하여 사용자가 글씨를 입력하고 버튼을 클릭하면 입력된 글씨를 텍스트뷰에 보여주는 가장 기본적으면서도 중요한 예제를 소개하겠습니다.

반응형

+ Recent posts