개인과제의 시간이 돌아오면서 이번에는 리사이클러뷰를 이용한 당근마켓 클론코딩을 진행한다. 심화과정으로 넘어오면서 상당부분의 사용 빈도를 차지하는 👀뷰바인딩, 어댑터뷰, 프래그먼트, 다이얼로그, 알림👀 등에 대한 내용을 학습한다.
🔗 사과마켓 github
🔗 사과마켓 최종
나는 UI 화면을 구상할때 레이아웃이 제일 어려웠다. html이나 flutter처럼 부모가 자식을 감싸서 위젯 형식으로 만들어 주는데 익숙해져있어 이 또한 비슷한 개념으로 생각했다. 하지만 알고보니 앱은 달랐다. 레이아웃이 중첩되면 메모리에 많은 영향을 끼치기 때문에 최소한으로 사용하는 것이 좋다고 한다. 처음에는 constraint레이아웃안에 Linear레이아웃으로 텍스트들을 쪼갰는데 알고보니 constraint레이아웃 하나로 끝낼 수 있는게 많았다. 지금은 페이지가 적어서 잘 티가 안나지만 나중에 많은 양의 페이지를 만들게되면 유지보수와 수정사항에 용이할 수 있도록 constraint레이아웃 사용을 권장한다.
객체를 다른 액티비티나 프래그먼트 등 간에 전달할 때 사용되는 인터페이스로 객체를 주고 받을 때 복잡한 작업을 할 필요 없이 간단하게 정보를 주고 받을 수 있습니다.
@Parcelize
data class MyItem(
val listImage: Int,
val listTitle: String,
val listAddress: String,
val listPrice: String,
val chatCount: Int,
var likeCount: Int,
val nickname: String,
val detailContent: String
) : Parcelable
다이얼로그 형식의 선택 목록을 표시하는 UI로, 터치하면 선택할 수 있는 항목들이 리스트로 표시되며 이 중 하나의 항목을 선택할 수 있습니다. 당근동에서 사과동으로 동네 설정을 변경했습니다.
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
val adList = resources.getStringArray(R.array.spinnerArray)
val adAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, adList)
binding.spinner.adapter = adAdapter
작업이나 동작을 강조하는 데 사용되며, 화면의 오른쪽 또는 왼쪽 하단에 부착되어 사용자가 쉽게 접근할 수 있습니다. 버튼을 누르면 화면 최상단으로 이동합니다.
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_margin"
android:layout_marginBottom="@dimen/layout_margin"
android:clickable="true"
android:visibility="invisible"
android:src="@drawable/up_arrow"
style="@style/CustomFloatingButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/recyclerView" />
val fadeIn = AlphaAnimation(0f, 1f).apply { duration = 700 }
val fadeOut = AlphaAnimation(1f, 0f).apply { duration = 700 }
var isTop = true
binding.recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!binding.recyclerView.canScrollVertically(-1)
&& newState == RecyclerView.SCROLL_STATE_IDLE) {
binding.floatingButton.startAnimation(fadeOut)
binding.floatingButton.visibility = View.INVISIBLE
isTop = true
} else {
if(isTop) {
binding.floatingButton.visibility = View.VISIBLE
binding.floatingButton.startAnimation(fadeIn)
isTop = false
}
}
}
})
binding.floatingButton.setOnClickListener {
binding.recyclerView.smoothScrollToPosition(0)
}
기본적으로 화면에서 뒤로 가기 버튼을 누르면 onBackPressed() 메서드가 호출되어 현재 화면을 종료하고 이전 화면으로 돌아가게 됩니다. 이 앱에서는 종료 다이얼로그 메세지와 함께 앱 종료 여부를 묻습니다.
override fun onBackPressed() {
val alertDialog = AlertDialog.Builder(this)
.setIcon(R.drawable.chat)
.setTitle("종료")
.setMessage("정말로 종료하시겠습니까?")
.setPositiveButton("확인") { dialog, which ->
finish()
}
.setNegativeButton("취소", null)
.create()
alertDialog.show()
}
앱에서 사용자에게 알림을 보여주는 기능입니다. 종 모양 아이콘을 누르면 상태바에 설정해놓은 아이콘 표시가 뜨며, 알림을 확인할 수 있습니다.
private val myNotificationID = 1
private val channelID = "default"
val notiButton = findViewById<ImageButton>(R.id.notiButton)
notiButton.setOnClickListener {
showNotification()
}
createNotificationChannel()
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelID, "default channel",
NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "description text of this channel."
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun showNotification() {
val builder = NotificationCompat.Builder(this, channelID)
.setSmallIcon(R.drawable.apple)
.setContentTitle("키워드 알림")
.setContentText("설정한 키워드에 대한 알림이 도착했습니다!!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
}
문제점:
1) Spinner로 셀렉트박스 구현시 원하는 위치에 맞게 조절할 수 없던 점
2) floating 버튼 커스텀이 내맘같지 않았던 점
3) 각 아이템 리스트 하단에 stroke가 말썽피운 점
원인:
1) spinner모드 중에서는 '드롭다운'옵션이 있는데, 각각의 끝에서부터 시작되는데 border라던지 background등의 구분해줄만한 부분이 없어 영역을 벗어난 것처럼 보였다.
2) 기존의 background drawable 속성으로 버튼을 커스텀 하려고 했는데 전혀 먹질 않았다. 찾아보니 플로팅 버튼과 같이 특이사항이 있는 버튼은 되지 않을 수 있어 style로 적용해야 한다는 것을 알게되었다.
3) 레이아웃간 관계성을 지정해 주지 않아서 레이아웃 영역 밖에서는 뭍혀있었다..?라는 표현이 맞는지 모르겠지만 감춰져있었다. 그래서 2dp를 줬을땐 아주 약간 빼꼼 보여서 마치 1dp가 정상적으로 적용된것처럼 보였던 것
해결책:
1) dropdwon이 아닌 dialog 속성을 선택하여 깔끔하게 가운데 뜰 수 있도록 변경해 주었다.
// xml 속성
<Spinner
android:spinnerMode="dialog"/>
2) vlaues의 styles.xml을 만들어서 resources로 커스텀 해주었다.
3) 레이아웃 간의 관계성을 재정의하고, background drawable 속성이 아닌 컬러만 넣어주니 거짓말같이 1dp의 선이 나왔다. ㅠㅠㅠㅠㅠㅠㅠ 진짜 이번에 한 것 중에 성취도 최고다.
느낀점:
1) 다음에는 다이얼로그 속성으로 타협하는게 아닌 드롭다운 모드에서 어떻게 예쁘게 꾸밀까 고민해 봐야겠다.
2) 원래는 버튼 테두리 색상과 아이콘 색상을 통일하고 배경은 흰색으로 채워진 선으로된 버튼을 만들고 싶었는데 style에서 옵션은 오류가 나지 않는데 적용이 되지 않았다. 다시한번 적용할 수 있는 방법을 알아봐야겠다.
3) 관계성의 중요성을 다시한번 느끼게되었다.
블로그 다 정리하고 갑자기 알게된 사실인데요. 하단 영역 선을 xml의 view로 만들면 성능에 좋지않다고 하네요? RecyclerView ItemDecoration 이라는 키워드를 알게되었는데 아직 구현하지 못한 나머지 기능들과 함께 자세히 알아보도록 하겠습니다.
오 깔끔하게 구현 잘하셨네요 ! ItemDecoration도 빨리 구현하셔서 블로그에 정리해주세요 !!