RP2 week3 - ListView) 2021.01.15

Star·2021년 1월 16일
0

📝 Daily Report

⏳ Time Schedule

밤 9시반 ~ 새벽 3시

📋 Plan

  1. Drag & Drop 수정 -> 예상시간) 1시간

    • 현재 버전) 드롭을 해야 위치가 바뀐다.
    • 수정될 버전) 아이템을 드래그 하는 도중에도 위치가 바뀐다. <- 일반적으로 기대하는 드래그 동작
  2. ListView -> RecyclerView -> 예상시간) 1시간

    • 지금까지 ListView로 구현했던 것을 모두 RecyclerView로 바꾼다.
  3. 내부 DB 구축 -> 예상시간) 2시간 30분

📚 결과

>> Drag & Drop 수정 -> 1시간

드래그중에 리스트 순서를 변경하기 위해
DragEvent.ACTION_DRAG_ENTERED을 사용하였다.

  • Adapter 코드: setOnDragListener
 // 드래그 도중에 순서 변경
 DragEvent.ACTION_DRAG_ENTERED -> {
 	// 드래그되는 item이 다른 item을 침범하면 액티비티에 콜백
 	if(position > fromPosition){    // 위에서 아래로 드래그할 때
    		onItemEventCallback.onFromTopDrop(fromPosition, position)
        }else{                          // 아래에서 위로 드래그할 때,
        	onItemEventCallback.onFromBottomDrop(fromPosition, position)
            }
        fromPosition = position // 드래그 해서 바뀐 position을 기억한다.
        true
  }
  • Activity 코드: 콜백 함수
// 위에서 아래로 드래그 도중 다른 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()
    }
  • 수정 전)
  • 수정 후)

>> ListView -> RecyclerView --> 총 2시간 20분

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() // 편집모드임을 알 수 있도록 새로고침해서 메뉴바를 보여준다.
            }
        }
  • Viewholder에서 리스너 메소드를 정의하고 omBindViewHolder에서 리스너를 달아주었다. editMode 플래그를 사용하여 각 모드에서 터치 이벤트를 다르게 설정하였다.
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
                }
            }
        }
  • 결과

✂ Reflection

  1. 코틀린 문법까진 따로 보지 못했다.
  2. 구현 예상 시간을 더 길게 잡아야 할 것 같다.
  3. 개발 시간을 늘려야겠다.
profile
To be Developer

0개의 댓글