2024.01.08(월) TIL

quinones·2024년 1월 8일

recyclerView를 사용해서 화면에 데이터를 띄울때, 데이터가 많이 들어가서 스크롤을 너무 많이 내렸을때 누르면 위로 올라오는 버튼을 만드는 법을 정리하려한다.

플로팅 버튼

플로팅 버튼은 예를들어 스크롤뷰와 같이 한 액티비티에서 화면이 움직이더라도, 같은자리에 그대로 위차하는 버튼이다.
이를 이용해서 상품 데이터를 recyclerView를 활용해서 띄워주는 main화면에서 한번에 스크롤의 최상단으로 이동해주는 플로팅버튼을 만들어보려고 한다.

미리보는 결과물

위와같은 결과물을 만들거다.

xml

<com.google.android.material.floatingactionbutton.FloatingActionButton
	android:id="@+id/floatingBtn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_alignParentEnd="true"
	android:layout_alignParentBottom="true"
	android:layout_marginEnd="20dp"
	android:layout_marginBottom="20dp"
	android:backgroundTint="@color/white" //버튼의 배경색 설정
	android:hapticFeedbackEnabled="true" //소리나 진동같은 사용자경험 피드백 활성화
	android:src="@drawable/floating_btn"
	android:visibility="invisible"
	android:elevation="0dp" //표면높이를 0dp로 설정해서 그림자제거
	app:borderWidth="0dp" //테두리너비 0dp로 설정해서 테두리 제거
	app:elevation="0dp" //표면높이를 0dp로 설정해서 그림자제거
	app:fabCustomSize="40dp" //사용자정의 크기 지정
	app:fabSize="normal" //버튼의 크기 지정
	app:layout_constraintBottom_toBottomOf="parent"
	app:layout_constraintEnd_toEndOf="parent"
	app:maxImageSize="40dp" //이미지의 최대크기 지정
	app:tint="@null" //색조 비활성화
	tools:ignore="ContentDescription" />
    -----------------------------------------------
    플로팅버튼 테두리 색 변경
    app:backgroundTint="@drawable/floating_border" //이거한줄 추가
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    	<item android:color="@color/floating" />
	</selector>
    drawable파일에 floating_border.xml selector파일 추가.

여기서 사진의 이미지는 @drawable 안에있는 floating_btn이라는 xml 파일로 설정해뒀다. 이는 selector를 이용해서 클릭시 사진이 바뀌게 하기 위해서다.

floating_btn.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <layer-list>
            <item android:drawable="@drawable/pressedarrowup" />
        </layer-list>
    </item>

    <item>
        <layer-list>
            <item android:drawable="@android:color/transparent" />
            <item android:drawable="@drawable/arrowup" />
        </layer-list>
    </item>

</selector>

android:state_pressed="true"설정을 이용해서 해당 버튼이 눌렸을시에는 다른 이미지를 보여주게 하였다.

이렇게 두가지 이미지를 사용해서 눌렸을때와 차별화되는 이미지를 사용했다. 기본이미지에서 배경색및 선의 색은
이미지편집 이 사이트를 이용해서 바꿔주었다.

액티비티에서 사용법

fade애니메이션

먼저 버튼이 서서히 나타나고, 사라지는 애니메이션을 추가해주었다.

val fadeIn = AlphaAnimation(0f,1f).apply { duration=1000 }
val fadeOut = AlphaAnimation(1f,0f).apply { duration=1000 }
var isTop = true

duration을 줄이거나 늘려서 버튼이 나타나고 사라지는 시간을 조절해줄 수 있다.

최상단으로 이동하기

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.floatingBtn.startAnimation(fadeOut)
            binding.floatingBtn.visibility = View.GONE
            isTop = true
        } else {
            if(isTop) {
                binding.floatingBtn.visibility = View.VISIBLE
                binding.floatingBtn.startAnimation(fadeIn)
                isTop = false
            }
        }
    }
})
...
floatingBtn.setOnClickListener {
    searchRecyclerView.smoothScrollToPosition(0)
}

onScrollStateChanged를 오버라이딩 해주었다.
여기서 if문을 사용해줬는데
canScrollVertically(-1) -> 최상단일경우 false를 return
canScrollVertically(1) -> 최하단일경우 false를 return
이를 활용해주면 된다.
그 뒤에 RecyclerView.SCROLL_STATE_IDLE은 현재 스크롤되지 않는 상태임을 나타내며, 이를 조건에 추가해주는 이유는 스크롤에 인한 중복 발생을 방지하기 위해서 넣어줬다.

리사이클러뷰 롱클릭이벤트

리사이클러뷰에서 롱클릭 이벤트로 dialog화면을 띄워서 해당상품 삭제여부를 묻는로직을 추가해줬다.

미리보는 결과물

사용법

리사이클러뷰를 만들때 사용했던 adapter를 수정해주면 된다.
override fun onBindViewHolder(holder: Holder, position: Int) {}안에다가 setOnLongClickListener를 추가해주면 된다.

holder.itemView.setOnLongClickListener {
    val builder = AlertDialog.Builder(holder.itemView.context)
    builder.setTitle("상품 삭제")
    builder.setMessage("상품을 정말로 삭제하시겠습니까?")
    builder.setIcon(R.drawable.chat)
    builder.setCancelable(false)

    val listener = object : DialogInterface.OnClickListener {
        override fun onClick(p0: DialogInterface?, p1: Int) {
            val adapterPosition = holder.adapterPosition
            when (p1) {
                DialogInterface.BUTTON_POSITIVE -> {
                    // 확인 버튼을 눌렀을 때
                    mItems.removeAt(adapterPosition)
                    notifyItemRemoved(adapterPosition)
                }

                DialogInterface.BUTTON_NEGATIVE -> {
                    // 취소 버튼을 눌렀을 때
                    p0?.dismiss()
                }
            }
        }
    }

    builder.setPositiveButton("확인", listener)
    builder.setNegativeButton("취소", listener)

    builder.show()
    true
}

기본 dialog를 만들때와 달라진 코드 한줄이 있는데,
val adapterPosition = holder.adapterPosition
이 부분이 추가되었다.
그냥 리스트에서 position사용해서 제거해주면 오류가 발생해서 holder를 이용해서 어뎁터의 position을 다시 받아와주고, 확인버튼이 눌리면 리스트에서 해당상품을 제거하고, 리사이클러뷰를 갱신시켜줬다.

profile
이우진

0개의 댓글