Android기능_findViewById개선_ButterKnife_ViewBinding_DataBinding

소정·2023년 3월 9일
0

Android_with_Java

목록 보기
27/33

findViewById() 성능 이슈 개선

성능이슈로 인해 나온 기능들


[1] ButterKnife 외부 Library

  • 지금은 사용하지 않는다
  • 설치형에서만 동작됨
  • 보기에는 깔끔해보이지만 내부적으로 개발자가 할 일을 처리하는 중인것 : 성능이슈는 하나도 해결되지않음

라이브러리 추가

package com.bsj93.ex77butterknife;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {
    
    //버터나이프 라이브러리
    // 편의성은 개선되었지만 성능이슈는 개선되지않음
    // 그래서 등장한 안드로이드의 ViewBinding와 DataBinding

    @BindView(R.id.tv) //find 한것
    TextView tv;


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

        ButterKnife.bind(this);

        tv.setText("aaa");

    }

    @OnClick(R.id.btn)
    void clickBtn() {
        tv.setText("버튼 클릭 이벤트");
    }
}

[2] ViewBinding***

  • 그래들에서 뷰 바인딩 기능 true
  • 뷰 바인딩을 켜면 참조변수를 가진 객체(바인딩 클래스)가 생성되고 멤버변수와 root(가장 밖 layout) 자동으로 가지고 있음
  • 자바 클래스에서 자동으로 만들어진 객체의 root를 써준다
  • 자바 클래스엔 단 하나의 참조변수로 layout에 있는 view들을 제어할 수 있다

사용방법

0. ViewBinding을 사용하기 위한 준비

  1. ViewBinding 기능을 그래이들에서 켠다

  2. main.java에서 할 일
    ① setContentView(R.layout.activity_main) : 바인딩 클래스 객체가 만든 뷰로 액티비티 보여줘야하기에 삭제!

    setContentView가 layoutInfate를 해주고 있었음

② binding 객체 생성, activity_main.xml을 객체로 생성하여 액티비티에 뷰로 설정
-> 메인 함수가 자동으로 하던 inflate를 위임함

③ setContentView에게 바인딩으로 화면 만들라고 하기
setContentView(binding.getRoot());
-> 모든 뷰들을 감싸고 있는 layout을 가진 root(xml의 최상위 레이아웃)

1. 텍스트뷰 제어

2. 버튼 클릭 리스너

2-1) 버튼 롱 클릭 이벤트(by 람다)

3. EditText 제어


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:orientation="vertical"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--1) TextView 제어 -->
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:padding="8dp"
        android:textColor="@color/black"
        />

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

    <!--3) EditText 글씨 입력받아 텍스트뷰에 보이기 -->
    <EditText
        android:id="@+id/et"
        android:layout_marginTop="24dp"
        android:inputType="text"
        android:hint="Enter Text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

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

    <TextView
        android:id="@+id/tv_result"
        android:text="tesult"
        android:textColor="@color/black"
        android:padding="8dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

main.java 클래스

package com.bsj0420.exviewbinding;

import androidx.appcompat.app.AppCompatActivity;

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

import com.bsj0420.exviewbinding.databinding.ActivityMainBinding;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    //ViewBinding 은 라이브러리가 아니고 안드로이드 아키텍쳐 고유의 기능이다
    //따라서 그냥 기능만 On 하면 됨
    // => 그래이들에서!!

    //activity_main.xml과
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //바인딩 클래스 객체가 만든 뷰로 액티비티 보여줘야하기에 삭제!
        //setContentView(R.layout.activity_main); => 삭제

        //binding 객체 생성 
        // activity_main.xml을 객체로 생성하여 액티비티에 뷰로 설정
        //메인 함수가 자동으로 하던 inflate를 위임함
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot()); //모든 뷰들을 감싸고 있는 layout을 가진 root

        //1. 텍스뷰 제어 - 이미 바인딩 객체가 텍스트뷰 침조 하고 있음
        binding.tv.setText("바인딩 완");

        //2. 버튼 클릭 리스너
        binding.btn.setOnClickListener(v->{
            binding.tv.setText("버튼클릭");
        });

        //2-1) 버튼 롱 클릭 이벤트-- 람다식으로
        binding.btn.setOnLongClickListener(view -> {
            Toast.makeText(this, "long - click", Toast.LENGTH_SHORT).show();
            return true;
        });

        //3) EditText 글씨 입력받아 텍스트뷰에 보이기
        binding.btn2.setOnClickListener(view -> {
            binding.tvResult.setText(binding.et.getText().toString());
            binding.et.setText("");
        });


    }



4. Fragment에서 ViewBinding

화면의 일부분을 별도로 설계하여 관리하는 Fragment

플래그먼트를 제어하는 자바에서 플래그먼트의 xml을 바인딩하여 따로 제어하도록 함


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:orientation="vertical"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- 4) 프래그먼트에서 뷰 바인딩 -->
    <!-- 프래그먼트 정적으로 놓기 -->
    <fragment
        android:id="@+id/frag"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:name="com.bsj0420.exviewbinding.MyFragment"
        tools:layout="@layout/fragment_my"/>

</LinearLayout>

Fragment.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:padding="8dp"
        android:textStyle="bold"
        android:textColor="@color/white"
        android:text="마이 프래그먼트"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn"
        android:layout_below="@+id/tv"
        android:text="텍스트 변경"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

Fragment.java

onCreateView()에서 바인딩 작업한다
-> 뷰를 리턴해 주는 기능 메소드 재정의
바인딩으로 레이아웃 inflate 한다
리턴값으로 최상위 root를 준다

onViewCreated()에서 바인딩한 뷰를 참조하여 xml에 있는 뷰를 찾아와 명령

package com.bsj0420.exviewbinding;

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.bsj0420.exviewbinding.databinding.FragmentMyBinding;

public class MyFragment extends Fragment {

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

    //이 프래그먼트가 보여줄 뷰를 리턴해 주는 기능 메소드 재정의
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        binding = FragmentMyBinding.inflate(inflater, container, false);
        //플래그먼트 만들 때 조각 사이즈(container) 미리 같이 정함

        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        binding.btn.setOnClickListener(v -> binding.tv.setText("Good day"));
    }
}



5. Adapter에서 바인딩 하는 2가지 방법


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:orientation="vertical"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--5) RecyclerView에서 viewBinding 사용해보기-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>


</LinearLayout>

main.java
대량의 데이터 준비 & 어답터 연결

//대량의 데이터
ArrayList<ItemVO> items = new ArrayList<>();
MyAdapter adapter; 
    
//리사이클러뷰는 가져올 필요 없음 이미 메인 액티비티 바인디으로 연결되어 있으니까

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

    //임의 데이터 추가
    items.add(new ItemVO("모아나1", R.drawable.moana01));
    items.add(new ItemVO("모아나2", R.drawable.moana02));
    items.add(new ItemVO("모아나3", R.drawable.moana03));
    items.add(new ItemVO("모아나4", R.drawable.moana04));
    items.add(new ItemVO("모아나5", R.drawable.moana05));

    //리사이클러에 아답터 설정
    adapter = new MyAdapter(this,items);
    binding.recycler.setAdapter(adapter);
}

리사이클러 뷰 xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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"
    app:cardElevation="8dp"
    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/moana02"
            android:scaleType="centerCrop"/>

        <TextView
            android:id="@+id/tv"
            android:layout_below="@id/iv"
            android:layout_centerHorizontal="true"
            android:padding="8dp"
            android:textColor="@color/black"
            android:textStyle="bold"
            android:text="모아난"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        
    </RelativeLayout>

</androidx.cardview.widget.CardView>

리사이클러뷰에 쓸 item class

package com.bsj0420.exviewbinding;

public class ItemVO {

    String title; //제목 글씨
    int imgRes; //이미지 리소스 아이딘

    public ItemVO() {
    }

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

1) onCreateViewHolder에서 화면을 기존처럼 inflate 하고 ViewHolder한테 itemView를 리턴 하여 ViewHolder 생성자에서 바인드

adaper.java

package com.bsj0420.exviewbinding;

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.bsj0420.exviewbinding.databinding.RecyclerItemBinding;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH> {
    //바인딩 2가지

    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 VH holder, int position) {

        holder.binding.tv.setText(items.get(position).title);
        holder.binding.iv.setImageResource(items.get(position).imgRes);

    }

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

    class VH extends RecyclerView.ViewHolder {

        // recycler_item.xml과 연결되는 바인딩 클래스 필요
        RecyclerItemBinding binding;

        public VH(@NonNull View itemView) {
            super(itemView);

            //이미 만든 뷰 객체의 안에 있는 자식뷰들과 연결하는 바인딩 객체
            //인플레이트 이미 위에서 되어 있으니 바로 바인드
            binding = RecyclerItemBinding.bind(itemView);
    
        }
    }
}

2) 첨부터 ViewHolder에서 매개변수로 RecyclerItemBinding로 받기

adaper.java

package com.bsj0420.exviewbinding;

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.bsj0420.exviewbinding.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) {
        //여기서 바인딩 만듦!!!!!!!!!!!!!!!!
        return new VH(RecyclerItemBinding.inflate(LayoutInflater.from(context), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        holder.binding.iv.setImageResource(items.get(position).imgRes);
        holder.binding.tv.setText(items.get(position).title);
    }

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

    // ViewHolder에 매개변수를 RecyclerItemBinding로 받기
    class VH extends RecyclerView.ViewHolder {

        RecyclerItemBinding binding;

        public VH(@NonNull RecyclerItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
}




[3] DataBinding 소개

  • 뷰를 참조하지 않고 뷰가 보여주는 값을 가진 변수를 제어하는 방식
  • 데이터를 직접 참조하지 않고 옵저버블 변수를 만들어 변수로 값을 제어한다
  • 안드로이드 아키텍쳐 구성요소 이기에 그래들에 사용설정 필요
  • 뷰 바인딩과 다르게 레이아웃 xml 파일의 최상위 요소(모든 뷰를 감싸고 있는 애)가 반드시 레이아웃(layout)이어야만 바인딩 클래스들이 만들어진다
  • MVVM 패턴에서 많이 씀
  • 요즘 많이 쓰는 이벤트 처리 기법
  • 뷰를 찾아오는 작업을 하지않음
  • 데이터가 있는 곳에서 클릭리스너 같은 것을 한다

사용방법

0. 그래들에 추가

1. 뷰 바인딩과 다르게 레이아웃 xml 파일의 최상위 요소(root)가 반드시 레이아웃이어야만 바인딩 클래스들이 만들어진다

2. layout엔 단 두개 요소 들어옴

① data : 뷰와 데이터 연결하는 곳
② 실질적 화면 부분

3. xml에서 변수와 연결 할 옵저버블 변수를 가진 class 작성

data binding에 기능에 인해 변수값이 바뀌면 화면에 자동갱신되는 특별한 자료형(Observable) 가진 클래스 작성

💡 옵저버 클래스 작성

  • Observablexxx : 기본 자료형, 어레이리스트, 배열
  • ObservableField<자료형> : 기본 자료형 뺀 나머지 참조형 객체

생성자
생성자로 초기화 할 값은 무조건 set으로 넣는다

리스너 메소드
버튼 클릭 시 실행 될 콜백메소드 여기에 만들어야함
why? 바꾸려는 변수가 이 클래스에 있으니까
주의) 원래 리스너의 콜백 메소드에 있는 파라미터가 있다면 반드시 꼭 같이 써야함

package com.bsj0420.ex79databinding;

import android.view.View;

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

public class User {
    //일반 자료형은 값이 변경되어도 화면 갱신을 하지는 않음
    //data binding에 기능에 인해 변수값이 바뀌면 화면에 자동갱신되는 특별한 자효형
    // Observablexxx 클래스 객체 - 기본 자료형, 어레이리스트, 배열을 있음
    // ObservableField<자료형> 나머지 참조형 객체들은 ObservableField<>
    public ObservableField<String> name = new ObservableField<>();
    public ObservableInt age = new ObservableInt();
    public ObservableBoolean fav = new ObservableBoolean();

    public User(String name, int age, boolean fav) {
        this.name.set(name); //무조건 셋으로 넣는다
        this.age.set(age);
        this.fav.set(fav);
    }


    //버튼 클릭 시 실행 될 콜백메소드 여기에 만들어야함
    // why? 바쑤려는 변수가 이 클래스에 있으니까
    //원래 리스너의 콜백 메소드에 있는 파라미터가 반드시 꼭같이 있어야됨
    public void changeName(View view) {
        this.name.set("로빈");
    }

    public void increaseAge(View view){
        this.age.set(this.age.get()+1); //get / set으로 값 넣고 뺸다
    }

}

3-1) xml 에서 변수 연결

기본 뷰 : text에 데이터에 정한 네임을 참조하여 @{데이터네임.옵저버변수이름}
함수를 가진 뷰 : 함수이름=@{데이터네임::함수이름}


<?xml version="1.0" encoding="utf-8"?>
<layout 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"
    >

    <!-- 1. 레이아웃 뷰와 바인딩할 데이터들 명칭과 클래스 지칭 -->
    <!-- 뷰 -->
    <data>

        <variable
            name="userData"
            type="com.bsj0420.ex79databinding.User" />

    </data>
    <!-- 2. 화면에 그려낼 레이아웃 뷰 : 기존의 최상위 뷰그룹-->
    <LinearLayout
        android:padding="16dp"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 뷰 배치용 아님 id 필요 없음 -->
        <TextView
            android:text="@{userData.name}"
            android:textColor="@color/black"
            android:padding="8dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:text="@{String.valueOf(userData.age)}"
            android:textColor="@color/black"
            android:padding="8dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

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

        <Button
            android:text="chageText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{userData::changeName}"/>
        <!-- 온클릭 됐을 때 실행 할 함수 이름 써야함
         원래 클릭리스너는 액티비티에 써야됨 하지만 databinding
         -->


        <Button
            android:text="나이값 증가"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{userData::increaseAge}"/>

    </LinearLayout>

</layout>

4. main.java

① setContentView(R.layout.activity_main); 삭제
② DataBinding 의 기능을 통해 액티비티에 보여질 내용물(뷰) 설정
③ xml문서의 data 요소 안에 있는 variable 의 type에 지정한 클래스를 객체로 만들어서 set() 해주면 그와 연결된 뷰들에 값이 보여짐
=> xml에 쓴 name을 참조하여 setter / getter 만들어짐

package com.bsj0420.ex79databinding;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.bsj0420.ex79databinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    //DataBinding - 뷰를 참조하지 않고 뷰가 보여주는 값을 가진 변수를 제어하는 방식
    //안드로이드 아키텍쳐 구성요소 이기에 사용설정 하면됨
    
    //뷰 바인딩과 다르게 레이아웃 xml 파일의 최상위 요소가
    //반드시 레이아웃이어야만 바인딩 클래스들이 만들어진다

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

        //2. DataBinding 의 기능을 통해 액티비티에 보여질 내용물(뷰) 설정 : ActivityMainBinding을 리턴해줌
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //3.
        //xml문서의 data 요소 안에 있는 variable 의 type에 지정한 
        //클래스를 객체로 만들어서 set() 해주면 그와 연결된 뷰들에 값이 보여짐
        binding.setUserData(new User("sam",13,true)); //

    }
}
profile
보조기억장치

0개의 댓글