ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Basic Activity 분석 - 1.뷰 결합
    android 2021. 12. 10. 23:00
    728x90

    1. 뷰 결합

    private ActivityMainBinding binding;

    뷰 결합은 findViewById를 대체하는 방법이다.

    1.1. 설정

    뷰 결합은 모듈별로 사용 설정된다.

    - 모듈에서 뷰 결합을 사용 설정하려면 "viewBinding" 요소를 "build.gradle"에 추가해야한다.

    android {
    	...
        viewBinding {
        	enable = true
    	}
    }

    결합 클래스를 생성하는 동안 레이아웃 파일을 무시하려면 "tools:viewBindingIgnore="true"" 속성을 레이아웃 파일의 루트 뷰에 추가한다.

    <LinearLayout
    	...
        tools:viewBindingIgnore="true">
        ...
    </LinearLayout>

    1.2. 사용

    모듈에 뷰 결합을 사용하도록 설정되면 모듈에 포함된 각 XML 레이아웃 파일의 결합 클래스가 생성됩니다. 각 결합 클래스에는 루트 뷰 및 ID가 있는 모든 뷰의 참조가 포함됩니다. 결합 클래스의 이름은 XML 파일의 이름을 카멜 표기법으로 변환하고 끝에 "Binding"을 추가하여 생성됩니다.

     

    예를 들어 레이아웃 파일 이름이 "activity_main.xml"인 경우 생성된 결합 클래스 이름은 "ActivityMainBinding"이 됩니다. 이 클래스에는 "name"이라는 "TextView"와 "button"이라는 "Button" 등 두 필드가 있습니다. 레이아웃의 "ImageView"에는 ID가 없으므로 결합 클래스에 참조가 없습니다.

    [Example]
    <LinearLayout ...>
    	<TextView android:id="@+id/name" />
    	<ImageView android:cropToPadding="true" />	// id가 없으므로 결합 클래스에 참조가 없음
    	<Button android:id="@+id/button"
    		android:background="@drawable/rounded_button" />
    </LinearLayout>

     

    1.3. 엑티비티에서 뷰 결합 사용

    엑티비티에 사용할 결합 클래스 인스턴스를 설정하려면 엑티비티의 "onCreate()" 메서드에서 다음 단계를 따릅니다.

    1. 생성된 결합 클래스에 포함된 정적 "inflate()" 메서드를 호출합니다. 그러면 엑티비티에 사용할 결합 클래스 인스턴 스가 생성됩니다.
    2. "getRoot()" 메서드를 호출하거나 Kotlin 속성 구문을 사용하여 루트 뷰 참조를 가져옵니다.
    3. 루트 뷰를 "setContentView()"에 전달하여 화면상의 엑티비티 뷰로 만듭니다.
    private ActivityMainBinding binding;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    
    	binding = ActivityMainBinding.inflate(getLayoutInflater());
    	setContentView(binding.getRoot());
    
    	...
    }
    
    [Kotlin]
    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle) {
    	super.onCreate(savedInstanceState)
        
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        ...
    }
    
    [이전의 방식]
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.activity_main)
    	...
    }

    이제 결합 클래스 인스턴스를 사용하여 뷰를 참조할 수 있습니다.

    [Example]
    binding.getName().setText(viewModel.getName());
    binding.button.setOnClickListener(new View.OnClickListener() {
    	viewModel.userClicked()
    });
    
    [Kotlin]
    binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }

     

    1.4. 프래그먼트에서 뷰 결합 사용

    프래그먼트에 사용할 결합 클래스 인스턴스를 설정하려면 프래그먼트의 "onCreateView()" 메서드에서 다음 단계를 따릅니다.

    1. 생성된 결합 클래스에 포함된 정적 "inflate()" 메서드를 호출합니다. 그러면 프래그먼트에서 사용할 결합 클래스 인스턴스가 생성됩니다.
    2. "getRoot()" 메서드를 호출하거나 Kotlin 속성 구문을 사용하여 루트 뷰 참조를 가져옵니다.
    3. "onCreateView()" 메서드에서 루트 뷰를 반환하여 화면상의 엑티비티 뷰로 만듭니다.
    [Example]
    private ResultProfileBinding binding;
    
    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
    	binding = ResultProfileBinding.inflate(inflater, container, false);
    	View view = binding.getRoot();
    	return view;
    }
    
    @Override
    public void onDestroyView() {
    	super.onDestroyView();
    	binding = null;
    }
    
    [Kotlin]
    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    
    override fun onCreateView(
    	inflater: LayoutInflater,
    	container: ViewGroup?,
    	savedInstanceState: Bundle?
    ): View? {
    	_binding = ResultProfileBinding.inflate(inflater, container, false)
    	val view = binding.root
    	return view
    }
    
    override fun onDestroyView() {
    	super.onDestroyView()
    	_binding = null
    }

    이제 결합 클래스 인스턴스를 사용하여 뷰를 참조할 수 있습니다.

    [Example]
    binding.getName().setText(viewModel.getName());
    binding.button.setOnClickListener(new View.OnClickListener() {
    	viewModel.userClicked()
    });
    
    [Kotlin]
    binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }

     

    1.5. findViewById와의 차이점

    뷰 결합에는 "findViewById"를 사용하는 것에 비해 다음과 같은 중요한 장점이 있습니다.

    • Null 안전: 뷰 결합은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없습니다.
    • 유형 안전: 각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 일치합니다. 즉, 클래스 변환 예외가 발생할 위험이 없습니다.

    이러한 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미합니다.

     

    1.6. 데이터 결합과 비교

    뷰 결합과 데이터 결합은 모두 뷰를 직접 참조하는 데 사용할 수 있는 결합 클래스를 생성합니다. 하지만 뷰 결합은 보다 단순한 사용 사례를 처리하기 위한 것이며 데이터 결합에 비해 다음과 같은 이점을 제공합니다.

    • 더 빠른 컴파일: 뷰 결합에는 주석 처리가 필요하지 않으므로 컴파일 시간이 더 짧습니다.
    • 사용 편의성: 뷰 결합에는 특별히 태그된 XML 레이아웃 파일이 필요하지 않으므로 앱에서 더 신속하게 채택할 수 있습니다. 모듈에서 뷰 결합을 사용 설정하면 모든 레이아웃에 뷰 결합이 자동으로 적용됩니다.

    반대로 뷰 결합에는 데이터 결합과 비교할 때 다음과 같은 제한사항이 있습니다.

    위 사항을 고려할 때 일부 사례에서 프로젝트에 뷰 결합과 데이터 결합을 모두 사용하는 것이 가장 좋습니다. 고급 기능이 필요한 레이아웃에는 데이터 결합을, 고급 기능이 필요 없는 레이아웃에는 뷰 결합을 사용할 수 있습니다.

     

    LoginActivity.java

    package com.example.loginactivity2.ui.login;
    
    import android.app.Activity;
    
    import androidx.lifecycle.Observer;
    import androidx.lifecycle.ViewModelProvider;
    
    import android.os.Bundle;
    
    import androidx.annotation.Nullable;
    import androidx.annotation.StringRes;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.text.Editable;
    import android.text.TextWatcher;
    import android.view.KeyEvent;
    import android.view.View;
    import android.view.inputmethod.EditorInfo;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.example.loginactivity2.R;
    import com.example.loginactivity2.ui.login.LoginViewModel;
    import com.example.loginactivity2.ui.login.LoginViewModelFactory;
    import com.example.loginactivity2.databinding.ActivityLoginBinding;
    
    public class LoginActivity extends AppCompatActivity {
    
        private LoginViewModel loginViewModel;
        private ActivityLoginBinding binding;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            binding = ActivityLoginBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
    
            loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory())
                    .get(LoginViewModel.class);
    
            final EditText usernameEditText = binding.username;
            final EditText passwordEditText = binding.password;
            final Button loginButton = binding.login;
            final ProgressBar loadingProgressBar = binding.loading;
    
            loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
                @Override
                public void onChanged(@Nullable LoginFormState loginFormState) {
                    if (loginFormState == null) {
                        return;
                    }
                    loginButton.setEnabled(loginFormState.isDataValid());
                    if (loginFormState.getUsernameError() != null) {
                        usernameEditText.setError(getString(loginFormState.getUsernameError()));
                    }
                    if (loginFormState.getPasswordError() != null) {
                        passwordEditText.setError(getString(loginFormState.getPasswordError()));
                    }
                }
            });
    
            loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
                @Override
                public void onChanged(@Nullable LoginResult loginResult) {
                    if (loginResult == null) {
                        return;
                    }
                    loadingProgressBar.setVisibility(View.GONE);
                    if (loginResult.getError() != null) {
                        showLoginFailed(loginResult.getError());
                    }
                    if (loginResult.getSuccess() != null) {
                        updateUiWithUser(loginResult.getSuccess());
                    }
                    setResult(Activity.RESULT_OK);
    
                    //Complete and destroy login activity once successful
                    finish();
                }
            });
    
            TextWatcher afterTextChangedListener = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    // ignore
                }
    
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    // ignore
                }
    
                @Override
                public void afterTextChanged(Editable s) {
                    loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
                            passwordEditText.getText().toString());
                }
            };
            usernameEditText.addTextChangedListener(afterTextChangedListener);
            passwordEditText.addTextChangedListener(afterTextChangedListener);
            passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    if (actionId == EditorInfo.IME_ACTION_DONE) {
                        loginViewModel.login(usernameEditText.getText().toString(),
                                passwordEditText.getText().toString());
                    }
                    return false;
                }
            });
    
            loginButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    loadingProgressBar.setVisibility(View.VISIBLE);
                    loginViewModel.login(usernameEditText.getText().toString(),
                            passwordEditText.getText().toString());
                }
            });
        }
    
        private void updateUiWithUser(LoggedInUserView model) {
            String welcome = getString(R.string.welcome) + model.getDisplayName();
            // TODO : initiate successful logged in experience
            Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
        }
    
        private void showLoginFailed(@StringRes Integer errorString) {
            Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
        }
    }

     

    https://developer.android.com/topic/libraries/view-binding?hl=ko#kotlin 

     

    뷰 결합  |  Android 개발자  |  Android Developers

    뷰 결합 뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정된 뷰 결합은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성합니다. 바인딩

    developer.android.com

    반응형

    'android' 카테고리의 다른 글

    Basic Activity 분석 - 2.앱 바  (0) 2021.12.11
    Java/Android Google Sheets 사용하기  (0) 2021.12.05
    android Ripple Effect  (0) 2021.03.24
    화면 2등분된 레이아웃  (0) 2021.03.22
    안드로이드 라운드 버튼 만들기  (0) 2021.03.22

    댓글

Designed by Tistory.