[내일배움캠프][TIL] 24.01.10 (수) - Android 앱개발 숙련 실습과제 3일차
1. Android 앱개발 숙련 실습과제 3일차
과제를 시작한 지도 벌써 3일이 지났다.
이제 어느 정도 데이터를 보내고 받아내거나 특정 기능을 사용하는 기본 골자는 완성한 상태다.
마지막으로 중간에 빠졌던 부분이나 UI의 디테일들을 잡아서 필수 구현 부분을 끝마치고, 시간이 나면 선택 구현 항목도 추가해볼 예정이다.
LinearLayout -> ConstraintLayout으로 교체
이제 기본적인 골자는 다 만들었으니 UI 부분을 완성…
… …시키려 했는데 문제가 하나 발생했다.
ScrollView 안에 있는 Layout이 전부 LinearLayout로 되어 있어서 몇 가지 디자인이 구현되지 않는 것.
그리하여 지금까지 만든 UI 중에서 LinearLayout으로 되어 있는 것들을 몇 가지 빼고 전부 ConstraintLayout으로 교체했다.
꽤나 LinearLayout을 많이도 집어넣어서 이 작업만 대략 2시간은 넘게 소모된 것 같다. (단순 노가다 작업 뿐인데도)
item_recyclerview.xml (activity_main에 있는 recyclerView에 들어가는 부분)
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:id="@+id/iconItem"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@drawable/rounded_background"
android:clipToOutline="true"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/sample1" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:paddingStart="10dp"
app:layout_constraintStart_toEndOf="@+id/iconItem"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textItem_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/item_name"
android:textColor="@color/black"
android:textSize="16.5sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textItem_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/item_address"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textItem_name" />
<TextView
android:id="@+id/textItem_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/item_price"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textItem_address" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/iv_chat"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/chat"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textItem_chat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:text="@string/item_chat"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_chat"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_good"
android:layout_width="20dp"
android:layout_height="20dp"
android:scaleType="centerCrop"
android:layout_marginStart="5dp"
android:src="@drawable/heart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/textItem_chat"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textItem_good"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/item_good"
android:layout_marginStart="3dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_good"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_detail.xml
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DetailActivity">
<ScrollView
android:id="@+id/ScrollView3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="100dp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingConstraints">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/sample1"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_user"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="10dp"
android:src="@drawable/iv_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_item" />
<TextView
android:id="@+id/txtdetail_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:text="@string/item_nickname"
android:textColor="@color/black"
android:textStyle="bold"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/iv_user"
app:layout_constraintTop_toBottomOf="@id/iv_item" />
<TextView
android:id="@+id/txtdetail_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/item_address"
android:textColor="@color/black"
app:layout_constraintStart_toEndOf="@id/iv_user"
app:layout_constraintTop_toBottomOf="@id/txtdetail_nickname" />
<TextView
android:id="@+id/txt_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="320dp"
android:layout_marginBottom="10dp"
android:text="@string/txt_temp"
android:textColor="#1264A3"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/view1"
app:layout_constraintEnd_toStartOf="@+id/ic_temp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@+id/iv_item" />
<ImageView
android:id="@+id/ic_temp"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:src="@drawable/ic_midtem"
app:layout_constraintTop_toTopOf="@id/txt_temp"
app:layout_constraintBottom_toBottomOf="@id/txt_temp"
app:layout_constraintStart_toEndOf="@+id/txt_temp" />
<TextView
android:id="@+id/txt_manner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/txt_mannertemper"
app:layout_constraintEnd_toEndOf="@id/ic_temp"
app:layout_constraintTop_toBottomOf="@id/ic_temp" />
<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="70dp"
android:background="#858181"
app:layout_constraintTop_toBottomOf="@id/iv_item" />
<TextView
android:id="@+id/txtdetail_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:padding="10dp"
android:text="@string/item_name"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/view1" />
<TextView
android:id="@+id/txtdetail_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:padding="10dp"
android:text="@string/item_post"
android:textColor="@color/black"
android:textSize="19sp"
app:layout_constraintTop_toBottomOf="@id/txtdetail_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"
android:padding="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/heart" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<View
android:layout_width="1dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/black"
android:paddingLeft="10dp"
android:paddingRight="10dp" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Price"
android:textSize="30dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/rounded_background_button"
android:clipToOutline="true"
android:text="@string/txt_usechat"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<ImageView
android:id="@+id/iv_back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:src="@drawable/iv_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
참고로 detail 부분은 원래 LinearLayout으로 만들다보니 LinearLayout 안에 다시 LinearLayout을 집어넣어야 해서 나중에는 겹치는 LinearLayout 부분이 4겹은 되더라.
(덕분에 ConstraintLayout이 좋다는 걸 다시 한 번 깨달았다.)
메인페이지에서 2줄 넘어갈 시 생략처리
다음으로 메인페이지 부분에서 제목이 2줄 넘어가면 끝에 ...을 남기고 생략처리를 하는 기능을 추가해보도록 하자.
이건 레이아웃에서 TextVIew에 다음을 추가하면 된다.
<TextView
android:id="@+id/textItem_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/item_name"
android:textColor="@color/black"
ellipsize를 end로 하고, maxLines를 2로 설정하면 다음과 같이 출력된다.
TextView에 밑줄추가
상세페이지의 매너온도 부분을 잘 보면 매너온도 텍스트 밑에 밑줄이 쳐져있다.
이걸 구현해보자.
매너온도는 상세페이지에 존재하므로, DetailActivity에 들어가서 다음 문구를 추가한다.
binding.txtManner.paintFlags = Paint.UNDERLINE_TEXT_FLAG
(txtManner : 매너온도의 id)
여기까지 해서 필수 구현 항목들은 전부 끝마쳤다.
아래는 사과마켓의 필수 구현 항목들을 시연한 영상이다.
(나중에 확인해보니 상세페이지 부분에서 가격이 출력되지 않는 실수를 저질렀다. 지금은 수정했지만 피드백 차원에서 수정하기 전 영상을 그대로 사용했다.)
선택 과제 - 스크롤 상단 이동
이제부터는 선택 과제 부분이다.
해도 그만 안 해도 그만이지만, 그래도 이왕 해보는 것이 지식과 경험을 쌓는 데에 도움이 되지 않나 싶어서 시작하였다.
그 첫 번째 항목은 바로 '스크롤 상단 이동' 이다.
우측 하단에 보이는 플로팅 버튼을 누르면 스크롤 상단으로 바로 이동시키는 버튼을 구현시키면 된다.
여기서 조건이 몇 가지가 있다.
- 플로팅 버튼은 스크롤을 아래로 내릴 때 나타나며, 스크롤이 최상단일때 사라집니다.
- 플로팅 버튼을 누르면 스크롤을 최상단으로 이동시킵니다.
- 플로팅 버튼은 나타나고 사라질때 fade 효과가 있습니다.
- 플로팅 버튼을 클릭하면(pressed) 아이콘 색이 변경됩니다.
이 조건들을 다 만족하고
우선, 플로팅 버튼을 만들어보자.
activity_main.xml
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:src="@drawable/ic_uparrow"
android:backgroundTint="@color/selector_floating_button"
app:fabCustomSize="48dp"
app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:maxImageSize="32dp"
android:importantForAccessibility="no" />
이 때, 버튼을 클릭하면 색이 바뀌는 설정을 구현하기 위해, res/color 폴더에 xml을 추가했다.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#8D0C71E8" android:state_pressed="true" />
<item android:color="@color/white" android:state_enabled="true" />
</selector>
MainActivity.kt
여기서는 우선 플로팅 버튼을 누르면 이벤트가 발생하는 코드부터 만들었다.
binding.fabUp.setOnClickListener {
binding.recyclerView.smoothScrollToPosition(0)
}
그 다음, 나타나고 사라질 때 fade 효과를 주기 위한 함수를 만들었다.
private fun createScrollListener(): RecyclerView.OnScrollListener {
return object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
with(binding.fabUp) {
if (!binding.recyclerView.canScrollVertically(-1)) {
animate().alpha(0f).duration = 200
visibility = GONE
} else {
visibility = VISIBLE
animate().alpha(1f).duration = 200
}
}
}
}
}
마지막으로 fade 효과가 나타나도록 recyclerView에 위의 함수를 집어넣었다.
binding.recyclerView.addOnScrollListener(createScrollListener())
나머지 부분은 '상품 항목 삭제하기'와 '좋아요 기능'인데 이건 아직 구현하지 못했다.
이건 내일 가능하면 다시 도전할 예정이다.
트러블슈팅 & 피드백
1. 트러블슈팅
- LinearLayout -> ConstraintLayout으로 교체 -> 바꾸는 도중에 굉장히 많은 시간이 소요되었다. 배치도 새로 설정해야 하고, 불필요한 부분도 추려야 해서. 하지만, 난이도가 어렵다기보단 작업량이 많은 쪽이어서 다행히 교체는 전부 완료했다.
- 알림 버튼이 클릭은 되는데 알림이 뜨지 않는 오류가 생겼다. 도통 봐도 뭐가 문제인지 발견하지 못했다.
- 스크롤을 내릴 때 플로팅 버튼의 fade 효과는 잘 되는데 최상단에 위치했을 때 플로팅 버튼의 fade 효과가 나타나지 않는다. 정확하게는 사라지기는 사라지는데 점차 흐릿해지면서 사라지는 것이 아니고 깜빡이듯이 훅 사라진다. 이것도 추후에 뭐가 원인인지 파악할 예정.
2. 피드백
- 처음부터 ScrollView 안에 ConstraintLayout으로 만들었으면 이런 수고를 덜했을 텐데 왜 LinearLayout으로 만들어서 이런 개고생을 했는지 모르겠다. 실제로 LinearLayout으로 작업한 작업물 배치가 굉장히 이상해지더라.
- 중간에 상세페이지에서 가격 부분이 출력되지 않는 오류를 뒤늦게 발견해서 고쳤다.