Fragment를 사용해서 구현했습니다. 구현 내용은 선택과제 3_1까지 구현(좋아요 기능 구현 실패)
필수 구현
1단계 - 메인페이지
강의에서 전부 진행했던 내용이라 딱히 정리할 내용은 없는 것같아서
뒤로가기버튼 클릭시 종료, 알림에 대해 코드만 올리자면,
뒤로가기 버튼 시 AlertDialog
더보기
//뒤로가기 눌렀을 때 종료 물어보기
private fun finishAlert() {
requireActivity().onBackPressedDispatcher.addCallback(requireActivity(), object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
AlertDialog.Builder(requireContext())
.setIcon(R.drawable.chat_num_svg)
.setTitle("종료")
.setMessage("정말 종료하시겠습니까?")
.setPositiveButton("확인") { _, _ ->
requireActivity().finish()
}
.setNegativeButton("취소", null)
.show()
}
})
}
Fragment의 경우
DetailFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
binding.btnBack.setOnClickListener {
goBack()
}
}
private fun goBack() {
requireActivity().supportFragmentManager.popBackStack()
}
알림 울리기
더보기
권한이 없을 경우 바로 설정의 알림 권한으로 이동하게 만들었다.
//알림 울리기(권한 요청)
private fun showNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!NotificationManagerCompat.from(requireContext()).areNotificationsEnabled()) {
// 알림 권한이 없다면, 사용자에게 권한 요청
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
}
startActivity(intent)
} else {
setUpNotification()
}
}
}
//알림 울리기 기초 설정
private fun setUpNotification() {
val builder = NotificationCompat.Builder(requireContext(), "notification")
.setSmallIcon(R.drawable.icon_a)
.setContentTitle("키워드 알림")
.setContentText("설정한 키워드에 대한 알림이 도착했습니다.")
val notificationManager: NotificationManager =
requireActivity().getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "notification"
val channelName = "채널이름"
val descriptionText = "설명글"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(channelId, channelName, importance).apply {
description = descriptionText
}
notificationManager.createNotificationChannel(channel)
notificationManager.notify(1002, builder.build())
} else {
notificationManager.notify(1002, builder.build())
}
}
2단계 - Detail 페이지
Main -> Detail
더보기
Main - 보내기
adapter.itemClick = object : DataAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val selectedData = dataSource.getDataList()[position]
//Detail에 데이터 전달
val detailFragment = DetailFragment().apply {
arguments = Bundle().apply {
putParcelable("selectedData", selectedData)
}
}
requireActivity().supportFragmentManager.beginTransaction().apply {
replace(R.id.fragmentContainer, detailFragment)
addToBackStack(null)
commit()
}
}
}
Detail - 받기
onViewCreated(...){
...
arguments?.getParcelable("selectedData", Data::class.java)?.let {
inputEachData(it)
}
private fun inputEachData(data: Data) {
binding.apply {
ivDetailImage.setImageResource(data.image)
tvDetailName.text = data.name
tvDetailLocation.text = data.location
tvDetailMannerTemperature.text = data.mannerTemperature.toString()
tvDetailTitle.text = data.title
tvDetailDescription.text = data.description
val decimal = DecimalFormat("#,###")
tvDetailPrice.text = decimal.format(data.price.toString().toInt())
}
}
선택 구현
데이터 삭제
더보기
DataSource.kt
fun removeItem(position: Int) {
if (position in 0 until dataList.size) {
dataList.removeAt(position)
}
}
Adapter
interface ItemLongClick {
fun onLongClick(view: View, position: Int)
}
var itemLongClick: ItemLongClick? = null
...
override fun onBindViewHolder(...){
...
holder.itemView.setOnLongClickListener {
itemLongClick?.onLongClick(it, position)
true
}
}
Main
//아이템 삭제
adapter.itemLongClick = object : DataAdapter.ItemLongClick {
override fun onLongClick(view: View, position: Int) {
AlertDialog.Builder(requireContext())
.setIcon(R.drawable.chat_num_svg)
.setTitle("상품 삭제")
.setMessage("상품을 정말로 삭제하시겠습니까?")
.setPositiveButton("확인") { _, _ ->
dataSource.removeItem(position)
adapter.notifyItemRemoved(position) //todo IndexOutOfBoundsException ?
adapter.notifyItemRangeChanged(position, adapter.itemCount - position) //todo 2개 다 사용 ?
//notifyItemRangeChanged(삭제된 아이템의 위치, 업데이트할 아이템의 개수(삭제 후속 아이템들의 개수))
Snackbar.make(view, "선택한 상품을 삭제했습니다.", Snackbar.LENGTH_SHORT).show()
}
.setNegativeButton("취소", null)
.show()
}
}
스크롤
더보기
//스크롤 최상단 올리기
private fun scrollEvent() {
val btnUp = binding.btnUp
//fadeIn, fadeOut 투명 애니메이션
val fadeIn = AlphaAnimation(0f, 1f).apply { duration = 300 }
val fadeOut = AlphaAnimation(1f, 0f).apply { duration = 300 }
//스크롤 이벤트 감지
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) { //OnScrolled vs onScrollStateChange
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) { // SCROLL_STATE_SETTLING, SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING
if (!recyclerView.canScrollVertically(-1)) { //최상단일 경우 -1 <> 최하단일 경우 0
btnUp.startAnimation(fadeOut)
btnUp.visibility = View.GONE
} else {
if (btnUp.visibility == View.GONE) {
btnUp.startAnimation(fadeIn)
}
btnUp.visibility = View.VISIBLE
}
}
}
})
btnUp.setOnClickListener {//최상단으로 이동
binding.recyclerView.smoothScrollToPosition(0)
}
}
코드설명은 어제 TIL에 작성 됨
오늘의 문제-해결
문제 1
문제
삭제하면 데이터가 다시 생성됨
원인
내가 작성한 코드는 dataList라는 더미데이터를 따로 만들어 줬는데
Main코드에서 바로 DataSource.dummydata()를 가져왔다.
이렇게 되면
Main 상에서 삭제/데이터 변경해도 괜찮지만
다른 Activity나 Fragment로 갔다가 돌아올 경우
삭제한 데이터는 저장되지 않고 다시 바로 dataList에서 값을 가져오게 된다.
해결
SingTon을 위해 만든 DataSource.kt에서 삭제 메서드 생성
fun removeItem(position: Int) { if (position in 0 until dataList.size) { dataList.removeAt(position) } }
문제2
문제
삭제한 후 다음 item을 누르면 다른 item의 position이 나옴
RecyclerView의 선택한 아이템을 삭제한 뒤 그 자리에 있는 아이템을 선택하면 다음 item이 선택 됨
원인
RecyclerView의 아이템 위치가 업데이트 X
해결
adapter.notifyItemRemoved(position)
이렇게 아이템이 삭제된 후 삭제되었음을 알려주는 메서드를 사용했다.
실제로 해당 아이템을 RecyclerView에서 삭제하고 업데이트한다.
여러개의 아이템을 삭제하는 경우는 notifyItemRangeRemoved를 사용해서 다음 아이템들의 위치를 업데이트한다.
notifyItemRangeChanged(position, itemCount)
(삭제된 아이템의 위치, 업데이트할 아이템의 개수(삭제 후속 아이템들의 개수)
이 부분도 추가해서 문제 해결adapter.notifyItemRangeChanged(position, adapter.itemCount - position)
'Kotlin > TIL' 카테고리의 다른 글
TIL (04.23) (0) | 2024.04.23 |
---|---|
TIL (04.22) (0) | 2024.04.22 |
TIL (04.17) (0) | 2024.04.17 |
TIL (04.16) (0) | 2024.04.16 |
TIL (04.15) (0) | 2024.04.15 |