[THE SOPT] Android 1차 세미나 과제

44647 단어 SOPTandroidSOPT

SOPT 30기 Android 파트 세미나 과제입니다.
github: https://github.com/KINGSAMJO/iOS_Seunghyeon
해당 주차 브랜치(ex. seminar/1)에서 각 세미나별 과제 코드를 확인할 수 있습니다.
github를 통해 코드를 보시는 것을 추천드립니다.

목차

  • 들어가며
  • 과제 단계별 개요 & 구현 설명
  • 마치며

들어가며

SOPT의 Android 파트에서는 어떤 식으로 세미나 과제가 이루어지나요?

SOPT에서는 매주 세미나가 진행되며, 각 세미나 내용과 관련한 요구사항의 과제가 마찬가지로 매주 있습니다. 파트별로 구체적인 과제 내용은 파트별로 다르지만, 현재 30기 Android 파트에서는 매주 배운 내용을 기반으로 하여 필수과제, 성장과제, 도전과제 3가지로 난이도가 분류되어 과제가 나옵니다.

SOPT Android 파트의 1주차 과제는 어떤지, 제 코드를 기반으로 함께 공부해보는 시간을 가져보겠습니다. 저는 3기수 연속으로 SOPT에서 Android를 공부하고 있기 때문에 과제에서 요구하는 사항 이상의, 평소 공부해보고 싶었던 내용을 세미나 과제에 접목시켜 구현해 보았습니다. 과제의 요구사항 이상의 내용들은 별도의 포스트를 통해 리뷰해보는 시간을 가져보겠습니다.

과제 단계별 개요 & 구현 설명

필수과제

필수과제란 해당 주차 세미나의 내용을 기반으로 한, 모든 파트원들이 "최소한" 필수적으로 구현해야 하는 과제입니다. 그런만큼 난이도는 세미나 자료만으로도 구현할 수 있는 정도의 난이도입니다. 1주차 세미나 과제의 필수과제를 이제 함께 짚어보겠습니다.

필수과제 1.


필수과제 1은 로그인 화면(SignInActivity)를 구현하는 것입니다. 이 때, 과제의 요구사항은 다음과 같습니다.

  1. 아이디, 비밀번호 모두 입력이 되어있을 때만 로그인 버튼 누를 시 HomeActivity로 이동하게 처리한다(Intent를 사용한 startActivity)
  2. 아이디, 비밀번호 둘 중 하나라도 비어있을 경우 "아이디/비밀번호를 확인해주세요" 라는 토스트 메시지를 출력한다(Toast)
  3. 비밀번호 EditText는 입력 내용이 가려져야 한다(EditText의 inputType)
  4. 모든 EditText는 미리보기 글씨가 있어야 한다(EditText의 hint)
  5. 회원가입 버튼 클릭 시 SignUpActivity로 이동한다(Intent를 사용한 startActivity)

제가 구현한 화면을 먼저 첨부하겠습니다.


먼저, EditText 관련한 요구사항인 필수과제 1 3번, 4번부터 보겠습니다. EditText 뿐만 아니라 Android의 다양한 View들은 사용할 수 있는 여러 속성들이 있습니다. 그 중에서 EditText의 inputType이라는 속성과 hint라는 속성에 대해 아는지 점검할 수 있는 과제입니다.

<EditText
	android:id="@+id/et_user_id"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:hint="@string/text_user_id_hint"
    android:importantForAutofill="no"
    android:inputType="text"
    android:text="@={viewmodel.userId}"
    app:layout_constraintBottom_toTopOf="@id/tv_user_password"
	app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/tv_user_id" />

위 코드는 제가 작성한 activity_sign_in.xml의 EditText 중 아이디를 입력하는 View입니다. 보시면 android:hint 속성에 string값을 넣은 것을 확인할 수 있습니다. Android Project의 res/values/strings.xml에 문자열을 등록하고 사용할 수 있기 때문에 저는 strings.xml에 name이 text_user_id_hintLOGIN이라는 문자열을 등록해두었고 이를 사용했습니다.

hint 속성을 사용할 경우, EditText에 text가 비어있는 상황, 즉 아무것도 입력되지 않은 상황에서 hint에 할당한 문자열이 EditText 필드에 보이게 됩니다. 그래서 hint 속성은 주로 이 EditText에 어떤 값을 입력해야 하는지에 대해 사용자에게 알려주는 역할을 합니다.

<EditText
	android:id="@+id/et_user_password"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:hint="@string/text_user_password_hint"
    android:importantForAutofill="no"
    android:inputType="textPassword"
    android:text="@={viewmodel.userPassword}"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/tv_user_password" />

위 코드는 제가 작성한 activity_sign_in.xml의 EditText 중 비밀번호를 입력하는 View입니다. 보시면 android:inputType 속성에 textPassword를 넣은 것을 확인할 수 있습니다. inputType 속성의 경우, EditText의 입력 타입을 결정합니다. 대표적으로 textPassword를 사용하면, EditText의 입력 타입을 비밀번호로 간주하여 문자열을 보여주지 않고 가려줍니다. 비밀번호로 abcd를 입력한다면, textPassword로 인해 **** 처럼 보입니다.

다음으로 Intent를 사용한 Activity 이동 관련한 요구사항인 필수과제 1 1번, 5번을 보겠습니다. Android에서는 Activity 전환 시 Intent라는 것을 이용합니다. Intent는 메시징 객체입니다. Intent를 이용해 다른 앱 구성요소(ex. Activity)로부터 작업을 요청합니다.

val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)

위 코드는 SignInActivity.kt의, HomeActivity를 시작하는 코드입니다. Intent 객체를 생성하고, 그 Intent 객체를 startActivity() 메서드에 넘겨주는 방식으로 구현할 수 있습니다.

Intent 객체를 생성할 때는 첫 번째 파라미터로 Context를, 두 번째 파라미터로 Class 객체를 제공해야 합니다. Activity에서 Intent를 이용해 다른 Activity 이동하고 싶은 경우 Activity는 자신의 context를 가지고 있기 때문에 context로 this를 넣어줄 수 있습니다. 두 번째 파라미터인 Class 객체에는 이동할 Activity의 이름 뒤에 ::class.java를 붙여주면 됩니다. ::class.java가 궁금하다면 Kotlin Reflection을 검색해보시는 것을 추천드립니다.

회원가입 화면으로 이동하는 코드는 startActivity()가 아닌 다른 메서드를 사용했습니다. 이 내용은 성장과제 1. 화면 이동 + @에서 다루도록 하겠습니다.


마지막으로 Android의 Toast Message 관련한 요구사항인 필수과제 1 2번입니다. Android에서는 Toast Message라는 개념이 있습니다. Toast Message는 화면 아래에서 올라오는 메시지로, 마치 토스트 기계에서 식빵이 올라오는 모습과 비슷하다고 해서 Toast라고 불립니다.

Toast.makeText(this, "아이디/비밀번호를 확인해주세요", Toast.LENGTH_SHORT).show()

Toast Message의 사용 방법은 다음과 같습니다. Toast.makeText().show()라는 메서드를 활용하는데, makeText() 메서드 안에 3개의 파라미터를 전달해야 합니다. 첫 번째는 Context입니다. context는 위에서 설명한 것처럼 모든 Activity는 자신의 context를 가지기 때문에 this를 넣으면 됩니다. 두 번째 파라미터는 Toast Message로 표시하고 싶은 메시지 문자열입니다. 마지막으로 세 번째 파라미터는 이 Toast Message를 얼마나 띄우고싶은지에 해당하는 표시 시간입니다. Toast Class에는 사전에 정의된 LENGTH_SHORTLENGTH_LONG이라는 값이 있습니다. 짧게 보여주고 싶다면 전자를, 오래 보여주고 싶다면 후자를 넣으면 됩니다.

아이디, 비밀번호 미입력 검사는 ViewModel에서 수행하도록 구현했습니다. 따라서 아이디, 비밀번호 미입력 검사는 도전과제 2. MVVM에서 다루도록 하겠습니다.


필수과제 2.

필수과제 2는 회원가입 화면(SignUpActivity)를 구현하는 것입니다. 과제의 요구사항은 다음과 같습니다.

  1. 이름, 아이디, 비밀번호 모두 입력이 되었을 때만 회원가입 버튼을 누를 시 다시 SignUpActivity로 이동한다(finish()를 통한 Activity 종료)
  2. 이름, 아이디, 비밀번호 셋 중 하나라도 비어있을 경우 "입력되지 않은 정보가 있습니다"라는 토스트 메시지를 출력한다(Toast)
  3. 비밀번호 EditText는 입력 내용이 가려져야 한다(EditText의 inputType)
  4. 모든 EditText는 미리보기 글씨가 있어야 한다(EditText의 hint)

제가 구현한 화면을 먼저 첨부하겠습니다.


필수과제 2의 2번, 3번, 4번은 필수과제 1과 겹칩니다. 따라서 여기서는 필수과제 2 1번만 확인하겠습니다. Activity를 startActivity() 메서드를 통해 새로 시작해 이동하는 방법이 있다면, 반대로 Activity를 종료시키고 이전 화면으로 복귀하는 메서드 또한 존재합니다.

finish()

Android에서는 새로운 Activity를 시작할 때마다 기존의 Activity를 BackStack이라는 공간에 저장합니다. 그리고 현재 보여지는 Activity가 종료될 때마다 BackStack의 가장 상단에 있는 Activity를 꺼내와 다시 화면에 보여줍니다. finish() 메서드를 사용하면, 현재 Activity를 종료시키고 BackStack을 활용해 이전 화면으로 복귀합니다.

회원가입 시 이름, 아이디, 비밀번호 미입력 검사는 ViewModel에서 수행하게 구현했습니다. 따라서 이름, 아이디, 비밀번호 미입력 검사는 도전과제 2. MVVM에서 다루도록 하겠습니다.


필수과제 3.

필수과제 3은 자기소개 페이지(HomeActivity)를 구현하는 것입니다. 과제의 요구사항은 다음과 같습니다.

  1. ImageView, TextView 활용
  2. 이름, 나이, MBTI 등 자기소개 적기

제가 구현한 화면을 먼저 첨부하겠습니다.


필수과제 3의 1번을 fragment_home.xml을 보며 확인하겠습니다. 원래는 Activity만 사용해서 구현해도 되지만, 저는 별도로 추가 구현해보고 싶은 내용이 있어 Fragment로 구현하게 되었습니다.

<ImageView
	android:id="@+id/iv_profile_image"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:contentDescription="@string/description_profile_image"
	setProfileImage="@{viewmodel.userImage}"
    app:layout_constraintBottom_toTopOf="@id/tv_profile_name"
	app:layout_constraintDimensionRatio="1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.33" />

0dp를 적극적으로 활용한 ImageView입니다. width를 0dp로 설정하고 startend 제약조건을 parent에 건 후, app:layout_constraintWidth_percent 속성을 활용해 width를 33%로 설정했습니다.

위 ImageView에는 src를 설정하는 부분이 없습니다. 이 이유는 도전과제 1. DataBinding과 관련이 있습니다. 이 부분은 추후 도전과제 설명에서 다루도록 하겠습니다.


<TextView
	android:id="@+id/tv_profile_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    android:text="@{`이름: ` + viewmodel.user.userName}"
    android:textColor="@color/black"
    android:textSize="20sp"
    app:layout_constraintBottom_toTopOf="@id/tv_profile_age"
	app:layout_constraintEnd_toEndOf="@id/iv_profile_image"
	app:layout_constraintStart_toStartOf="@id/iv_profile_image"
	app:layout_constraintTop_toBottomOf="@id/iv_profile_image" />

별 다를 것 없는 TextView입니다. 이름을 표시하는 TextView입니다.

위 TextView에는 text를 설정하는 부분이 @{}로 감싸져 있습니다. 이 또한 도전과제 2. DataBinding과 관련이 있습니다. 이 부분 역시 추후 도전과제 설명에서 다루도록 하겠습니다.


성장과제

성장과제란 해당 주차의 세미나 내용을 기반으로 하지만 한 걸음 더 성장하기 위해 고민하며 공부한 후 구현하는 과제입니다. 필수과제보다는 조금 더 어렵지만 조금만 검색하고 공부해보면 구현할 수 있는 과제이기 때문에 개발자로서 성장하기 위해 구현해 보시는 것을 추천드립니다.

성장과제 1.

성장과제 1은 단순히 startActivity()로 화면을 이동하는 것을 넘어, 특정 결과를 위해 새 화면으로 이동하고, 이동한 화면에서 작업을 한 뒤, 복귀할 때 이동한 화면에서의 작업 결과를 기존 화면에서 활용하는 것입니다. 과제의 요구사항은 다음과 같습니다.

  1. 회원가입(SignUpActivity)이 성공한다면 이전 로그인 화면으로 돌아온다
  2. 이 때 회원가입 화면에서 입력한 아이디와 비밀번호가 로그인 화면의 아이디, 비밀번호 입력칸에 채워져 있어야 한다(registerForActivityResult)

제가 구현한 화면을 먼저 첨부하겠습니다.

먼저, 성장과제 1을 이해하기 위해서는 콜백(Callback)이라는 개념에 대한 이해가 필요합니다.

콜백이란?

콜백의 사전적 정의는 이렇습니다.

콜백이란,
1. 다른 함수의 인자로써 이용되는 함수
2. 어떤 이벤트에 의해 호출되는 함수

이렇게 보면 무슨 말인가 싶습니다. 최대한 이해하기 쉽게, 일상 속의 예제로 한 번 들어보겠습니다.

승민이는 보현이와 팀 프로젝트를 합니다. 승민이는 자료조사와 발표를, 보현이는 발표자료 제작을 맡았습니다.
승민이는 자료를 조사하고 보현이한테 자료를 보내줬습니다. 자료를 보내며 승민이는 말합니다.

네가 PPT를 다 만들면 그 때 내가 그 PPT를 가지고 발표를 준비할테니 PPT 다 만들면 알려줘(보내줘)

그러면 시간이 흐른 뒤 보현이가 말합니다.

내가 PPT 다 만들어서 메일로 보냈어, 이제 발표 준비해줘

보현이의 말을 들은 승민이는 이제 발표를 준비합니다.

이해가 가시나요? 일상 속의 모습을 코드로 비유해보겠습니다.

A 함수와 B 함수는 엮여 있습니다. A가 a, c 작업을 수행하고, B는 b 작업을 수행합니다.
A 함수는 a 작업을 마친 뒤 B 함수에게 a 작업의 결과를 전달하고 콜백함수(c)를 등록합니다.

A: B 너가 a를 가지고 b 작업을 다 수행하면 내가 b 작업의 결과를 가지고 c 작업을 할게. 그러니 B 너의 작업이 다 끝나면 알려줘(콜백해줘)

그러면 B 함수가 b 작업을 마친 뒤 A 함수에게 알립니다(콜백).

B: 나 b 작업 끝나서 너한테 알려주는거야(콜백), 너 c 작업 해

B 함수가 콜백하면 A 함수는 콜백함수를 수행합니다.

registerForActivityResult란?

register: 등록하다
ActivityResult:Activity의 Result, 즉 결과

registerForActivityResult() 메서드를 사용하면, 해당 Activity의 Result를 활용하는 콜백을 등록할 수 있습니다. 위에서는 함수로 예시를 들었지만 Activity간에도 콜백을 등록할 수 있습니다.

세미나 과제를 위의 예시에 적용해볼까요? 그렇다면 이런 상황이라고 할 수 있습니다.

SignInActivity: 얘, SignUpActivity야. 너 회원가입 성공하면 그 때의 ID랑 비밀번호를 나한테 알려줄래? 너가 알려주면 내가 그 ID랑 비밀번호를 내 EditText 필드에 채워넣을게!

SignUpActivity: 내가 방금 한승현이라는 사람을 회원가입 성공시켰어(콜백), 얘의 ID는 qwer이고 비밀번호는 1234야!

SignInActivity: 오 그래? 그러면 내 EditText 필드에 너가 알려준 qwer, 1234를 채울게!

개념을 확실하게 잡았으니 이제 코드를 이해해봅시다.

private val activityResultLauncher =
	registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
		if (it.resultCode == RESULT_OK) {
        	val userId = it.data?.getStringExtra("userId") ?: ""
            val userPassword = it.data?.getStringExtra("userPassword") ?: ""
            binding.etUserId.setText(userId)
            binding.etUserPassword.setText(userPassword)
		}
	}

하나하나 분석해보겠습니다.

  1. registerForActivityResult: ActivityResult에 대한 콜백을 등록하고 Launcher를 생성.
  2. ActivityResultContracts: ActivityResult에 대한 Contract(계약) 여러 개(s)가 포함된 클래스(ActivityResultContracts)입니다. ActivityResultContract 클래스는 Result를 생성하는 데 필요한 입력 유형과 Result의 출력 유형을 정의함.
  3. StartActivityForResult: 새 Activity를 열어주되, 기존의 startActivity와 달리 데이터를 쌍방향으로 교류함을 의미.
  4. resultCode: 새 Activity는 작업을 수행한 후 Result, 즉 결과가 성공 후 종료로 나온 결과인지 혹은 실패 후 종료로 나온 결과인지에 대한 값을 RESULT_OK 또는 RESULT_CANCELED 로 반환함. 그러면 결과를 받은 Activity, 즉 콜백을 받은 Activity는 resultCodeRESULT_OK인 경우에는 성공한 것으로 간주해 성공 시에는 무엇을 수행할 것인지, RESULT_CANCELED인 경우에는 실패한 것으로 간주해 실패 시에는 무엇을 수행할 것인지 결정할 수 있음.

위의 코드에서는 콜백이 등록된 런처를 생성만 했고, 이를 activityResultLancher라는 변수에 할당했습니다. 그렇다면 이 런처를 가지고 어떻게 SignUpActivity를 실행시키고 결과를 받을 수 있을까요?

binding.btnSignUp.setOnClickListener {
	val intent = Intent(this, SignUpActivity::class.java)
    activityResultLauncher.launch(intent)
}

SIGNUP 버튼을 누르면 SignUpActivity로 이동할 수 있는 Intent를 생성합니다. 그리고 생성한 Intent 객체를 아까 생성한 런처 activityResultLauncherlaunch() 메서드에 인자로 실어서 실행시킵니다.

그 결과로, SignUpActivity가 실행됩니다. 단순 startActivity()와 다른 점은, startActivity()는 그저 새 Activity를 시작하는 것뿐이었다면, 이제는 새 Activity로부터 콜백을 받을 수 있게 되었습니다!

Result(결과) 전달하기

위 과정을 통해 SignUpActivity가 실행되었습니다. 그렇다면 회원가입 후 아이디와 비밀번호를 다시 SignInActivity에게 돌려주는 코드도 작성해야 합니다. 이 과정은 IntentsetResult()를 사용합니다.

회원가입 시 이름, 아이디, 비밀번호 미입력 검사는 ViewModel에서 수행합니다. 검사 로직에 대해서는 별도로 언급하지 않습니다. 해당 내용이 궁금하신 분들은 추후 도전과제 2. MVVM을 확인해주시면 감사하겠습니다.

val intent = Intent(this, SignInActivity::class.java)
intent.putExtra("userId", binding.etUserId.text.toString())
intent.putExtra("userPassword", binding.etUserPassword.text.toString())
setResult(RESULT_OK, intent)
if (!isFinishing) {
	finish()
}

SignInActivity에 대한 Intent 객체를 생성하고, putExtra() 메서드를 사용해 전달할 데이터를 담습니다(put). putExtra()는 인자 2개를 받는 메서드입니다. 첫 번째 인자로 전달할 값의 name을, 두 번째 인자로 전달할 값을 넣어줍니다. 우리는 아이디와 비밀번호를 전달해야 하므로, 아이디와 비밀번호를 putExtra를 통해 Intent 객체에 담아줍니다.

그 후, 이 결과(Result)가 성공한 케이스(RESULT_OK)인지, 취소된(실패된) 케이스인지(RESULT_CANCELED) 설정해야 합니다(set). 그래서 setResult() 메서드를 통해 첫 번째 인자로 resultCode를, 두 번째 인자로 Intent 객체를 넣습니다. setResult() 메서드가 수행되는 순간, SignInActivity에서는 콜백함수가 수행됩니다.

하지만 SignInActivity가 콜백함수를 수행하면 뭐합니까, 회원가입을 했으면 회원가입 화면을 종료시켜야 합니다. 그래서 finish() 메서드를 통해 SignUpActivity를 종료시키고 로그인 화면으로 돌아갑니다.

콜백함수 분석

콜백함수는 registerForActivityResult()의 중괄호 부분입니다. 해당 콜백함수만 살펴보도록 하겠습니다. (왜 중괄호 안에 쓰는지 궁금하신 분은 Android 람다를 검색해보시는 것을 추천드립니다)

if (it.resultCode == RESULT_OK) {
	val userId = it.data?.getStringExtra("userId") ?: ""
    val userPassword = it.data?.getStringExtra("userPassword") ?: ""
    binding.etUserId.setText(userId)
    binding.etUserPassword.setText(userPassword)
}

resultCodeRESULT_OK라면, 즉 정상적으로 결과를 반환했다는 콜백을 받으면 우리는 Result에 해당하는 it에서 보낸 결과를 꺼내서 사용해야 합니다. it.data 에는 SignUpActivity가 Intent가 들어있습니다. 우리는 Intent에서 이번엔 Extra를 가져와 보겠습니다.
getExtra() 메서드는 putExtra() 메서드와 달리, 타입을 지정해줘야 합니다. String Extra를 가져오려면 getStringExtra()를, Int Extra를 가져오려면 getIntExtra()를 사용하면 됩니다. getExtra() 메서드의 인자로는 putExtra() 시 입력한 name을 전달해주면 됩니다.

이렇게 SignUpActivity로부터 결과(Result)를 전달받았으니, 이제 화면의 EditText의 필드에 그 값을 넣습니다. EditText의 경우 setText() 메서드를 이용해 String을 text로 set 할 수 있습니다.

성장과제 2.

성장과제 2의 요구사항은 다음과 같습니다.

  1. 화면에 표시될 내용이 많아서 내용이 화면 밖으로 넘어가는 경우를 대비해 스크롤이 가능하게끔 화면을 변경한다(ScrollView)
  2. ImageView의 가로세로 비율을 1:1로 만든다(constraintDimensionRatio)

제가 구현한 화면을 먼저 첨부하겠습니다.

성장과제 2의 1번은 ScrollView를 사용해 화면을 스크롤 가능하게 만들어보는 과제입니다. ScrollView를 사용하면 ScrollView 안의 View들이 차지하는 영역이 ScrollView가 차지하는 영역보다 더 클 때, 스크롤을 통해 더 표시할 수 있습니다. Android 공식문서의 ScrollView를 한 번 보겠습니다.

이 공식문서에는 가장 중요한 사실 하나가 들어있습니다.

ScrollView may have only one direct child placed within it ScrollView는 그 안에 하나의 직계 자식(View)만 가질 수 있다.

이것도 말로만 하니까 잘 와닿지 않습니다. 우리는 ScrollView 안에 ImageView와 여러 TextView를 넣어야 하는데, 하나의 직계 자식만 가질 수 있다니. 이 문제를 어떻게 해결해야 할까요?

<ScrollView
	android:id="@+id/sv_profile"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_marginBottom="20dp"
    app:layout_constraintBottom_toTopOf="@id/btn_edit"
    app:layout_constraintTop_toTopOf="parent">

	<androidx.constraintlayout.widget.ConstraintLayout
    	android:id="@+id/layout_profile"
    	android:layout_width="match_parent"
        android:layout_height="wrap_content">
		
      	<!-- ImageView와 TextView들-->
      
	</androidx.constraintlayout.widget.ConstraintLayout>

</ScrollView>

fragment_home.xml의 일부입니다. ScrollView 안에 하나의 직계 자식인 ConstraintLayout만 들어 있습니다. 우리가 ScrollView 안에 넣어야 할 ImageView와 TextView들은 ConstraintLayout 안에 들어있습니다.

공식문서에 적힌 단 하나의 직계 자식만 가진다는 조건을 만족시켰습니다. ScrollView의 직계 자식은 ConstraintLayout 뿐입니다. 즉, 하위 View들을 모두 포함하는 ViewGroup을 만들고, ScrollView가 그 ViewGroup을 하나의 직계 자식으로 삼도록 구현하면 됩니다.

성장 과제 2의 2번은 ImageView의 width(가로), height(세로) 비율을 1:1로 설정하라는 요구사항입니다. 이 요구사항은 constraintDimensionRatio 속성을 사용해 충족시킬 수 있습니다.

<ImageView
	android:id="@+id/iv_profile_image"
    setProfileImage="@{viewmodel.userImage}"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:contentDescription="@string/description_profile_image"
	android:scaleType="centerCrop"
    app:layout_constraintBottom_toTopOf="@id/tv_profile_name"
	app:layout_constraintDimensionRatio="1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.33" />

constraintDimensionRatio 속성을 사용하면 widthheight 값을 비율에 맞게 설정할 수 있습니다. 이 속성을 사용하려면, 조건이 있습니다.

적어도 한 면(가로 또는 세로)은 크기가 정해져야 하며,
그 정해진 크기에 맞춰 비율로 크기를 정할 나머지 면은 0dp로 설정되어야 한다.

제가 짠 ImageView에서는 위 조건을 충족할까요? 충족합니다.

  1. width는 0dp로 설정되어 있으나, layout_constraintWidth_percent="0.33"에 의해 가로 폭의 33%만큼 width가 고정됩니다.
  2. 고정된 width에 맞춰, 1:1의 비율로 height가 자동으로 계산됩니다.

그런데, 만약 width도 0dp고 height도 0dp이며 둘 다 화면에 꽉 제약이 걸려있다면, constraintDimensionRatio로 1:1을 지정하면 어떻게 될까요?

이렇게, 짧은 쪽을 기준으로 1:1 비율을 맞춥니다!

도전과제

도전과제는 세미나에서 따로 학습하지는 않지만 혼자, 또 파트원들과 함께 따로 공부하며 도전해볼 수 있는 난이도의 과제입니다. 필수과제와 성장과제 내용을 이해한 후 도전과제에도 도전해보시면 더욱 좋을 것 같습니다.

도전과제 1.

과제의 요구사항은 ViewBinding이 아닌 DataBinding으로 구현하는 것입니다.

ViewBinding? DataBinding?

ViewBinding이란 뭘까요? DataBinding이란 뭘까요? 1차 세미나에 대해서는 ViewBinding에 대해 다룹니다. 그렇지만 한 번 다시 짚고 넘어가보겠습니다.

findViewById

ViewBinding 이전에는 findViewById라는 메서드를 이용해 View에 접근했습니다. 예를 들면 이렇습니다. id가 "button_login"이라는 Button이 존재하고, 이 Button에 대한 ClickEvent를 구현한다면

val button = findViewById<Button>(R.id.buttonLogin)
button.setOnClickListener {
	// 클릭 시 수행할 코드
}

하지만 이 방법에는 단점이 있습니다.

  1. 느리다.
  2. null을 return할 수 있어 NullPointerException으로 인해 앱이 비정상 종료될 수 있다.

ViewBinding

위의 이유로 1차 세미나에서는 findViewById가 아닌 ViewBinding을 배웠습니다.

< 작 성 중 . . . >

좋은 웹페이지 즐겨찾기