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() 등은 해당 함수 이후에 나와야 하는 거다.
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 파일에서 관리 되어야 한다.
android:fontFamily 속성을 이용해서 폰트를 지정할 수 있음.
왠만하면 design editor 를 이용해서 font 를 지정하는게 좋음. 그렇게 함으로써 추가 font 도 손쉽게 찾을 수 있고, resource 를 관리하는 것도 편해짐
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 라는 ViewGroup 은 오로지 하나의 child 만 가질 수 있다. 보통 스크롤시킬 거면 LinearLayout 을 추가한다.
TextView 에서 inputType 을 지정하면 Android System 이 들어오는 값을 validate 한다. 유저를 위해 hint 를 적어두는게 좋다.
inputType 은 여러개를 입력할 수 있다. Hint 를 써주는게 좋다.
이렇게만 하면 일단 layout 은 완료임
그리고 이젠 작성된 내용을 제출할 버튼을 넣어줘야함.
Visibility 에 대해서 알아봐야할듯.
Gone 으로 설정하면 app 이 로딩되는 순간에는 안보임.
ViewModel 개요 | Android 개발자 | Android Developers 이 문서를 제대로 이해한게 맞나?
핸드폰을 세로모드에서 가로모드로 변경하는 경우를 생각해보자, 이때 UI 컨트롤러는 삭제되었다가 새롭게 구성된다. 그렇다면 가지고 있던 데이터들은 사라지는 것이다. 작은량의 데이터라면 onSaveInstanceState() 메서드나
onCreate() 메서드를 이용해서 데이터를 복원할 수 있지만, 데이터량이 많은 경우는 불가능하다.
또한 UI 구성을 비동기적으로 처리하는 경우 leak 방지등을 위해 전체 프로세스 관리에 많은 리소스가 사용되게 된다.
이때 UI 컨트롤러가 재구성되는 것과는 별개로 데이터를 저장하는 역할을 해주는게 ViewModel 이다. 데이터 보관 책임을 ViewModel 에 맡기면 해결된다.
으어어엉ㅇㅇ
바인딩은 컴파일 시점에서 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 만으로 충분하다.
뷰 결합 | 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 을 이용하기 위해서는 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 을 이용하는 것은 다음의 장점이 있다.
Data Binding 도 비슷하게 작동한다. 하지만 View Binding 은 보다 단순한 사용 사례를 처리하기 위한 것이며 데이터 결합에 비해 다음과 같은 이점을 제공합니다.
반대로 View 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()
이렇게 하는것도 적용이 되긴 한다.