밤 9시반 ~ 새벽 3시
Drag & Drop 수정 -> 예상시간) 1시간
ListView -> RecyclerView -> 예상시간) 1시간
내부 DB 구축 -> 예상시간) 2시간 30분
드래그중에 리스트 순서를 변경하기 위해
DragEvent.ACTION_DRAG_ENTERED을 사용하였다.
// 드래그 도중에 순서 변경
DragEvent.ACTION_DRAG_ENTERED -> {
// 드래그되는 item이 다른 item을 침범하면 액티비티에 콜백
if(position > fromPosition){ // 위에서 아래로 드래그할 때
onItemEventCallback.onFromTopDrop(fromPosition, position)
}else{ // 아래에서 위로 드래그할 때,
onItemEventCallback.onFromBottomDrop(fromPosition, position)
}
fromPosition = position // 드래그 해서 바뀐 position을 기억한다.
true
}
// 위에서 아래로 드래그 도중 다른 item의 view에 진입할 때
override fun onFromTopDrop(fromIdx: Int, toIdx: Int) {
musicList.add(toIdx+1, musicList[fromIdx]) // 진입한 위치(toIdx)로 드래그중인 아이템을 옮기고,
musicList.removeAt(fromIdx) // 변경 전의 toIdx 포함해서 그 아래에 있는 나머지는 한칸씩 내린다
// musicListAdapter.notifyDataSetChanged() --> ListView 버전
musicRecyclerAdapter.notifyDataSetChanged()
}
// 아래에서 위로 드래그 도중 다른 item의 view에 진입할 때
override fun onFromBottomDrop(fromIdx: Int, toIdx: Int) {
val temp = musicList[toIdx]
musicList.add(toIdx, musicList[fromIdx]) // 진입한 위치(toIdx)로 드래그중인 아이템을 옮기고,
musicList.removeAt(fromIdx+1) // 변경 전의 toIdx 포함해서 그 위에 있는 나머지는 위로 옮긴다
// musicListAdapter.notifyDataSetChanged() --> ListView 버전
musicRecyclerAdapter.notifyDataSetChanged()
}
1) 리사이클러 뷰 어댑터에 대한 개념을 먼저 이해하려고 했다. --> 1시간 20분
https://juyeop.tistory.com/7
위 사이트가 설명이 자세하고 코드도 kotlin, java 모두 있어서 많은 도움이 됐다.
Adapter란?) 리사이클러뷰를 관리하는 클래스
리사이클러뷰에 나타낼 View객체를 생성하고 거기에 사용자에게 보여줄 데이터를 연결하는 것이 주요 역할이며, 각 아이템에 발생하는 이벤트를 처리할 수 있다.
알아야 할 개념은 크게 3가지였다: Viewholder, ,
1. Viewholder:
말그대로 View의 holder라고 생각할 수 있다. RecyclerView.ViewHolder
를 상속하며, RecyclerView에서 보여주는 리사이클러뷰 아이템(View)을 담는 클래스이다. 아이템이 보여줄 데이터의 변수를 선언하여 접근할 수 있다.
inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.music_title
val artist: TextView = itemView.music_artist
val runtime: TextView = itemView.music_runtime
val editBar: ImageView = itemView.music_edit_bar
fun setItem(musicData: MusicData){ // ---> 안에서는 상수 View의 속성을 변경할 수 있는데, 왜 바깥에서는 안될까??
title.text = musicData.title
artist.text = musicData.artist
runtime.text = musicData.runtime
if(editMode){ // 편집모드
editBar.visibility = View.VISIBLE // 편집모드임을 알 수 있도록 메뉴바를 보여준다
runtime.visibility = View.GONE
}else{ // 기본모드
editBar.visibility = View.GONE // 메뉴바를 지우고 런타임을 보여준다
runtime.visibility = View.VISIBLE
}
}
Viewholder 안에서 데이터를 연결하는 메서드를 선언하고 onBindViewHolder에서 그것을 호출하는 방식으로 구현했다.
2. onCreateViewHolder: 아이템 레이아웃 XML 파일을 View 객체로 inflation 한 후 그것을 담는 Viewholder 인스턴스를 생성하여 반환하는 역할을 한다. Viewholder가 재활용될 때마다 이곳에서 생성된다.
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): MusicRecyclerAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val itemView = inflater.inflate(R.layout.music_recycler_item, parent, false)
return ViewHolder(itemView)
}
3. onBindViewHolder: onCreateViewHolder에서 Viewholer가 생성되면 Viewholder에서 선언한 변수들을 통해 아이템(View)에 데이터를 연결해주는 역할을 한다.
override fun onBindViewHolder(holder: MusicRecyclerAdapter.ViewHolder, position: Int) {
holder.setItem(musicArrayList[position])
holder.musicLongClickListener(position)
}
2) 이전에 만들었던 추가,삭제,업데이트 기능 RecyclerView로 다시 구현
--> 1시간
처음 일정엔 없던 부분인데, 아이템 터치 이벤트가 2가지라서 아래와 같이 분리하였다.
기본 모드: 롱클릭 -> 아이템 삭제 팝업 화면
편집 모드: 편집버튼 -> 아이템 터치 -> 드래그&드랍으로 순서 변경 가능
편집 버튼을 클릭하면 편집모드라는 것을 알 수 있게 메뉴바가 나오도록 했다.
edit.setOnClickListener { // 편집버튼을 클릭하면 편집모드로 변경 -> 드래그 & 드랍
// 편집모드인 상태에서 편집버튼을 누르면 동작하지 않는다. 뒤로가기 버튼으로 해제 가능
if(!musicRecyclerAdapter.editMode) {
musicRecyclerAdapter.editMode = true
musicRecyclerAdapter.notifyDataSetChanged() // 편집모드임을 알 수 있도록 새로고침해서 메뉴바를 보여준다.
}
}
fun musicClickListener(position: Int){
itemView.setOnLongClickListener {
if(!editMode) { // 편집 버튼 누르지 않은 평소상태
onItemEventCallback.onMusicLongClick(position) // 롱클릭으로 음악을 삭제할 수 있다.
}
true
}
// list item을 터치했을 때
itemView.setOnTouchListener { v, motionEvent ->
// 편집모드일 때만 터치에 반응해서 드래그 가능
if(editMode) {
if (motionEvent?.action == MotionEvent.ACTION_DOWN) {
fromPosition = position // 처음 터치해서 드래그가 시작된 위치
val dragShadowBuilder =
View.DragShadowBuilder(v) // 시스템은 위 객체를 통해 드래그 이미지를 그린다.
//시스템에 드래그가 시작됐음을 알린다,
//시스템은 그 응답으로 dragShadowBuilder에서 콜백 메소드 호출하여 drag shadow를 그린다.
v?.startDrag(null, dragShadowBuilder, v, 0)
true
} else {
false
}
// 일반모드에서는 드래그 안됨
}else{
false
}
}
// 시스템이 View에 보내는 드래그 이벤트를 수신하는 리스너
itemView.setOnDragListener { v, event ->
when (event.action) {
// 드래그 시작 이벤트에서 true를 반환한 리스너만 현재 드래그가 끝날 때까지 드래그 이벤트를 계속 수신한다.
DragEvent.ACTION_DRAG_STARTED -> {
true
}
//drop했을 때 drag shadow를 drop 자리에서 사라지도록 함
DragEvent.ACTION_DROP -> {
true
}
// 드래그 도중에 순서 변경
DragEvent.ACTION_DRAG_ENTERED -> {
// 드래그되는 item이 다른 item을 침범하면 액티비티에 콜백
if(position > fromPosition){ // 위에서 아래로 드래그할 때,
onItemEventCallback.onFromTopDrop(fromPosition, position)
}else{ // 아래에서 위로 드래그할 때,
onItemEventCallback.onFromBottomDrop(fromPosition, position)
}
fromPosition = position // 드래그 해서 바뀐 position을 기억한다.
true
}
else -> false
}
}
}