안드로이드3

timothy jeong·2021년 10월 25일
0

Android with Kotlin

목록 보기
3/69

Android3

[LinearLayout using the Layout Editor]

ScrollView 가 가능한 App 을 만들건데, Design Tool 을 이용해서 만들거다.
Android Kotlin Fundamentals: LinearLayout using the Layout Editor

Design Editor 는 Design 창에서 볼 수 있다. View 에 대한 대략적인 개념을 잡을 수 있어서 좋음.

왼쪽 Palette 는 View Component List 를 확인할 수 있고, 해당 View Component 를 화면 부분에 Drag & Drop 해서 배치할 수 있음.

오른쪽 Attribute 에서는 View Componen 에 설정할 수 있는 속성들을 확인할 수 있음.

Design Editor 를 통해서 res/values/strings.xml 파일에 직접 값을 추가하고, 참조시킬 수 있음.

사이즈에 관련된 value 들은 res/values/dimens.xml 파일에서 다루는데, 이러한 처리 자체가 정형화된 처리이기 때문에 layout.xml 파일에 android:textSize 속성을 추가하고 option + enter 하면 Extract dimension resource 를 선택할 수 있고, 그 결과 dimens.xml 파일이 만들어지고, 값이 설정된다.

이전에도 언급했지만, MainActivity.kt 파일의 setContentView(R.layout.activity_main) 함수는 layout file 과 Activity 파일을 연결시킨다. 그래서 findByid() 등은 해당 함수 이후에 나와야 하는 거다.

[Style your TextView]

padding 과 margin

Padding 은 View 의 끝부분과 View 의 Content 사이에 있는 공간을 의미한다. 즉, 엄연히 View 와 외부의 경계 사이에 존재하는 공간이다.

반면 margin 은 View 의 경계 바깥 부분에 공간을 의미한다.

Left, right 그리고 Start, End 가 있어서 헷갈리는데, start, end 는 앱이 사용하는게 left-to-right (LTR) flow or a right-to-left (RTL) flow 인지에 따라서 다르게 적용되는 속성이다.

만약 안드로이드 API 17 이상을 타겟으로 하고 있다면 Start, End 를 사용하는게 좋다.

역시 padding 과 margin 도 dimens.xml 파일에서 관리 되어야 한다.

[Font]

android:fontFamily 속성을 이용해서 폰트를 지정할 수 있음.
왠만하면 design editor 를 이용해서 font 를 지정하는게 좋음. 그렇게 함으로써 추가 font 도 손쉽게 찾을 수 있고, resource 를 관리하는 것도 편해짐

[Extract Style]

Style 은 특정 view 에 대한 attributes 의 집합체이다. 어떤 attributes 든 해당 view 에 관련된 거라면 style 로 묶을 수 있다.

Style 로 만들고자하는 view 를 우클릭하고 defector 를 클릭, style 을 누르면 어떤 attributes 를 style 로 묶을지 지정할 수 있다. Style 을 지정하는 속성은
style=“@style/defaultTextView” 이다.

Style 파일은 다음과 같이 생성이 된다.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="defaultTextView">
        <item name="android:fontFamily">@font/roboto</item>
        <item name="android:padding">@dimen/small_padding</item>
        <item name="android:textSize">@dimen/text_size</item>
        <item name="android:visibility">visible</item>
    </style>
</resources>

[ScrollView]

ScrollView 라는 ViewGroup 은 오로지 하나의 child 만 가질 수 있다. 보통 스크롤시킬 거면 LinearLayout 을 추가한다.

[Let User Enter Text]

TextView 에서 inputType 을 지정하면 Android System 이 들어오는 값을 validate 한다. 유저를 위해 hint 를 적어두는게 좋다.

inputType 은 여러개를 입력할 수 있다. Hint 를 써주는게 좋다.

이렇게만 하면 일단 layout 은 완료임

그리고 이젠 작성된 내용을 제출할 버튼을 넣어줘야함.

Visibility 에 대해서 알아봐야할듯.
Gone 으로 설정하면 app 이 로딩되는 순간에는 안보임.

[ViewModel]

ViewModel 개요  |  Android 개발자  |  Android Developers 이 문서를 제대로 이해한게 맞나?

핸드폰을 세로모드에서 가로모드로 변경하는 경우를 생각해보자, 이때 UI 컨트롤러는 삭제되었다가 새롭게 구성된다. 그렇다면 가지고 있던 데이터들은 사라지는 것이다. 작은량의 데이터라면 onSaveInstanceState() 메서드나
onCreate() 메서드를 이용해서 데이터를 복원할 수 있지만, 데이터량이 많은 경우는 불가능하다.

또한 UI 구성을 비동기적으로 처리하는 경우 leak 방지등을 위해 전체 프로세스 관리에 많은 리소스가 사용되게 된다.

이때 UI 컨트롤러가 재구성되는 것과는 별개로 데이터를 저장하는 역할을 해주는게 ViewModel 이다. 데이터 보관 책임을 ViewModel 에 맡기면 해결된다.
으어어엉ㅇㅇ

[Binding]

바인딩은 컴파일 시점에서 layout.xml 과 activity 혹은 fragment 를 연결시키는 기술(패턴)이다. 이 기술(패턴)을 이용하면 View 를 findById() 를 이용해서 찾을 때 모든 view 상속체계를 탐색하면서 발생하는 시간 지연을 최소화 시킬 수 있다.

컴파일러는 activity 가 만들어질때 binding class 라고 불리우는 helper class 를 생성하는데 activity 는 binding class 를 이용해서 layout.xml 에 접근할 수 있다.

Binding Class 의 이름은 XML 파일의 이름을 카멜 표기법으로 변환하고 끝에 ‘Binding’을 추가하여 생성됩니다.

에를 들어 result_profile.xml 인 경우 ResultProfileBinding 가 된다.

Binding 은 View Binding 과 Data Binding 이 있다. 데이터를 다루는게 아니라면 View Binding 만으로 충분하다.

[View Binding]

뷰 결합  |  Android 개발자  |  Android Developers

대부분의 경우 view Binding 이 findById() 를 대체한다.

뷰 바인딩은 모듈별로 적용할 수 있다. Gradle 스크립트에 다음와 같이 추가해준다.

android {
        ...
        buildFeatures{
			viewBinding = true
        }
    }

레이아웃 파일의 루트뷰에 tools:viewBindingIgnore="true" 를 추가해야한다.

<LinearLayout ... >
        <TextView android:id="@+id/name" />
        <ImageView android:cropToPadding="true" />
        <Button android:id="@+id/button"
            android:background="@drawable/rounded_button" />
    </LinearLayout>

이러한 result_profile.xml 파일이 있을 경우 ResultProfileBinding 클래스에는 name이라는 TextView와 button이라는 Button 등 두 필드가 있습니다. ID 속성이 없는 ImageView는 Binding Class 가 참조할 수 없다.


    private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    }

보통 코틀린 코드에서는 setContentView(R.layout./activity_main/) 코드를 이용해 이상의 과정을 수행한다.
binding = ResultProfileBinding.inflate(layoutInflater) : Binding Class 를 인스턴스한다.
val view = binding.root root 뷰를 가져온다.
setContentView(view) root 뷰를 화면에 그린다(따라서 하위의 모든 view 들이 그려진다.)

binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }

이렇게 해서 view 를 참조할 수 있다.

[Fragment 에서 View Binding]

Fragment 에서 View Binding 을 이용하기 위해서는 onCreateView() 메서드에서 설정을 해줘야한다.

 private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!! // !! 연산은 절대 null 이 아님을 컴파일러에게 알려줌.

    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
    }

findViewById 를 이용하는 것보다 View Binding 을 이용하는 것은 다음의 장점이 있다.

  • Null 안전: 뷰 결합은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없습니다. 또한 레이아웃의 일부 구성에만 뷰가 있는 경우 결합 클래스에서 참조를 포함하는 필드가 @Nullable로 표시됩니다.
  • 유형 안전: 각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 일치합니다. 즉, 클래스 변환 예외가 발생할 위험이 없습니다.
    이러한 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미합니다.

Data Binding 도 비슷하게 작동한다. 하지만 View Binding 은 보다 단순한 사용 사례를 처리하기 위한 것이며 데이터 결합에 비해 다음과 같은 이점을 제공합니다.

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

반대로 View Binding 은 Data Binding 에 비해 다음의 제한사항이 있다.

[Data Binding]

데이터 바인딩은 컴파일 시점에 두개의 서로 다른 정보 소스를 이어주고, 매핑해주고, 묶어주는 객체가 있으면 편하겠다는 아이디어에서 시작되었다.

이렇게 바인딩 해주는 객체를 Binding Object 라고 부른다. module Grade 에서 설정을 해주면 컴파일러가 생성한다.

그레이들에 다음 추가, 그리고 layout 을 으로 감싸기, 이렇게 으로 감싸진 view 컴포넌트에서 id 가 있는 것들만 바인딩 된다.

buildFeatures{
    dataBinding = true
    viewBinding = true
}

findById 버전

class MainActivity : AppCompatActivity() {
    private lateinit var doneButton : Button
    private lateinit var editText : EditText
    private lateinit var nicknameText : TextView


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
        initViews()
        doneButton.setOnClickListener {
            addNickname(it) // 이때 it 은 doneButton View 를 참조한다.
        }

    }

    private fun addNickname(view: View) {
        nicknameText.text = editText.text // EditText View 에 입력받은 값을 TextView 에 전달해줌

// 더이상 보여줄 필요가 없는 값을 Gone 으로, 보여줘야하는 값을 Visible 로 설정.
        editText.visibility = View.GONE
        view.visibility = View.GONE
        nicknameText.visibility = View.VISIBLE
    }

    // findById 를 사용할 때마다 View 치계를 root 에서부터 탐색하기 때문에 한번에 View 를 초기화 시킴.
// 이 방법은 앱이 커질 경우 유저가 사용하지 않는 기능까지 한번에 init 시켜버려서 유저의 사용자 경험을 망칠 수 있지 않을까?
// 뷰 바인딩, 데이터 바인딩이라는 기술을 이용해서 이런 문제점을 개선할 수 있다.
    private fun initViews() {
        doneButton = findViewById(R.id.done_button)
        editText = findViewById(R.id.nickname_edit)
        nicknameText = findViewById(R.id.nickname_text)
    }
}

데이터바인딩 버전

import androidx.databinding.DataBindingUtil
import com.android.example.aboutme.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.doneButton.setOnClickListener {
            addNickName()
        }
    }

    private fun addNickName() {
        binding.apply {
            binding.nicknameText.text = binding.nicknameEdit.text
            invalidateAll() // Invalidates all binding expressions and requests a new rebind to refresh UI.
            binding.nicknameEdit.visibility = View.GONE
            binding.doneButton.visibility = View.GONE
            binding.nicknameText.visibility = View.VISIBLE
        }
    }
}

실제로 데이터를 바인딩 하기 위해 data class 를 만들자
그리고 xml 에서 data class 에 접근할 수 있도록 몇가지 설정을 추가해야한다.

// data class 따로 생성.
data class MyName(var name: String = "", var nickname: String = "")

// main 문에 적용

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val myName = MyName("Timothy Jeong")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.myName = myName
        binding.doneButton.setOnClickListener {
            addNickName()
        }
    }

private fun addNickName() {
    binding.apply {
        binding.myName.nickname = binding.nicknameEdit.toString()
        // ViewBinding 끼리 작업하던걸 Data Class 를 중계헤서 하도록함.
        invalidateAll()
        binding.nicknameEdit.visibility = View.GONE
        binding.doneButton.visibility = View.GONE
        binding.nicknameText.visibility = View.VISIBLE
    }
}

}
<data>
    <variable
        name="myName"
        type="com.android.example.aboutme.MyName" />
</data>

<!-- 이제 XML 파일에서 값을 res 디렉터리에서 참조하는것이 아니라 variable 에 정의된 값을 이용해 참조할 수 있다.  -->

<TextView
    android:id="@+id/name_text"
    style="@style/NameStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/name"
    android:textAlignment="center" />

<!-- 아래와 같이 바뀐다. -->

<TextView
    android:id="@+id/name_text"
    style="@style/NameStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={myName.name}"
    android:textAlignment="center" />

<!-- 똑같이 @id/nickname_text 의 TextView 의 text 를 "@={myName.nickname}" 으로 변경한다. -->

binding.myName= myName 를 통해서 인스턴스화된 MyName data class 가 xml 의 data variable myName 과 바인딩 되도록 하였고, addNickName 메서드에서는 직접 binding 을 호출하여 myName data variable 에 값을 입력하였다.

그런데 myName.nickname = binding.nicknameEdit.toString() 이렇게 하는것도 적용이 되긴 한다.

profile
개발자

0개의 댓글