Kotlin Drap & Drop 지원하는 RecyclerView (이미지 부분 눌러서 드래그)

|

item_todo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <ImageView
    android:id="@+id/drag_handle"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_marginEnd="20dp"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:src="@drawable/drag_handle"/>

  <TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="15dp"
    android:layout_marginBottom="15dp"
    android:layout_marginStart="10dp"
    android:layout_marginEnd="20dp"
    android:layout_centerVertical="true"
    android:layout_toStartOf="@id/drag_handle"
    android:textAppearance="@style/TextAppearance.AppCompat.Body2"
    android:textSize="18sp"/>

</RelativeLayout>


ItemTouchHelperViewHolder.kt

interface ItemTouchHelperViewHolder {
    fun onItemSelected()
    fun onItemClear()
}


ItemTouItemTouchHelperAdapter.kt

interface ItemTouchHelperAdapter {
    fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
    fun onItemRemove(position: Int)
}


##

OnRecyclerAdapterEventListener.kt

interface OnRecyclerAdapterEventListener {
    fun onItemClicked(position: Int)
    fun onItemLongClicked(position: Int)
    fun onDragStarted(viewHolder: RecyclerView.ViewHolder)
}


SimpleItemTouchHelperCallback.kt

import android.graphics.Canvas
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper

class SimpleItemTouchHelperCallback(val adapter: ItemTouchHelperAdapter) :
        ItemTouchHelper.Callback() {
    private val MAX_ALPHA = 1.0f

    override fun isItemViewSwipeEnabled(): Boolean {
        return false
    }

    override fun isLongPressDragEnabled(): Boolean {
        return false
    }


    override fun getMovementFlags(recyclerView: RecyclerView,
                                  viewHolder: RecyclerView.ViewHolder): Int {
        var dragFlags: Int
        var swipeFlags: Int

        if (recyclerView.layoutManager is GridLayoutManager) {
            dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or
                    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
            swipeFlags = 0
        } else {
            dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
            swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        }

        return makeMovementFlags(dragFlags, swipeFlags)
    }

    override fun onMove(recyclerView: RecyclerView,
                        source: RecyclerView.ViewHolder,
                        target: RecyclerView.ViewHolder): Boolean {
        if (source.itemViewType != target.itemViewType) {
            return false
        }

        adapter.onItemMove(source.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, position: Int) {
        adapter.onItemRemove(viewHolder.adapterPosition)
    }

    override fun onChildDraw(c: Canvas, recyclerView: RecyclerView,
                             viewHolder: RecyclerView.ViewHolder,
                             dX: Float, dY: Float, actionState: Int,
                             isCurrentlyActive: Boolean) {

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            val alpha = MAX_ALPHA - Math.abs(dX) / viewHolder.itemView.width
            viewHolder.itemView.alpha = alpha
            viewHolder.itemView.translationX = dX
        } else {
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
        }
    }

    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?,
                                   actionState: Int) {
        if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder is ItemTouchHelperViewHolder) {
                viewHolder.onItemSelected()
            }
        }

        super.onSelectedChanged(viewHolder, actionState)
    }

    override fun clearView(recyclerView: RecyclerView, viewHolder:
    RecyclerView.ViewHolder) {
        super.clearView(recyclerView, viewHolder)

        viewHolder.itemView.alpha = MAX_ALPHA

        if (viewHolder is ItemTouchHelperViewHolder) {
            viewHolder.onItemClear()
        }
    }
}


TodoListAdapter.kt

import android.content.Context
import android.graphics.Paint
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.ImageView
import android.widget.TextView
import com.ran.todolist.R
import com.ran.todolist.common.TodoInfo
import com.ran.todolist.model.ModelManager
import com.ran.todolist.model.OnTodoInfoEventListener
import com.ran.todolist.utils.Log
import com.ran.todolist.utils.recyclerview.ItemTouchHelperAdapter
import com.ran.todolist.utils.recyclerview.OnRecyclerAdapterEventListener
import kotlinx.android.synthetic.main.item_todo.view.*
import java.util.*
import kotlin.collections.ArrayList

class TodoListAdapter(private val ctx: Context, private val categoryId: Long) :
        RecyclerView.Adapter<ViewHolder>(), OnTodoInfoEventListener,
        ItemTouchHelperAdapter {

    private var list: ArrayList<TodoInfo> = ArrayList()

    private var onEventListener: OnRecyclerAdapterEventListener? = null

    fun setOnRecyclerAdapterEventListener(l: OnRecyclerAdapterEventListener) {
        onEventListener = l
    }

    init {
        ModelManager.instance.addOnTodoInfoEventListener(this)
        refresh()
    }

    private fun refresh() {
        list = ModelManager.instance.getList(categoryId)

        for (i in 0 until list.size) {
            Log.i("snowdeer] ${list[i]}")
        }

        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
        val view: View = LayoutInflater.from(ctx).inflate(R.layout.item_todo, parent, false)
        return ViewHolder(view)
    }

    fun getItem(position: Int): TodoInfo {
        return list[position]
    }

    override fun getItemCount(): Int {
        return list.size
    }

    override fun onModelUpdated(info: TodoInfo) {
        refresh()
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        viewHolder.text.text = list[position].text
        viewHolder.text.paintFlags = when (list[position].done) {
            true -> Paint.STRIKE_THRU_TEXT_FLAG
            else -> 0
        }

        val info = list[position]
        viewHolder.text.setOnLongClickListener {
            Log.i("onItemLongClicked: $position")
            onEventListener?.onItemLongClicked(position)
            true
        }

        viewHolder.handle.setOnTouchListener(View.OnTouchListener { _, event ->
            if (event.action == MotionEvent.ACTION_DOWN) {
                onEventListener?.onDragStarted(viewHolder);
            }
            false;
        })
    }

    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
        swap(fromPosition, toPosition)
        return true
    }

    override fun onItemRemove(position: Int) {
        ModelManager.instance.deleteTodoInfo(list[position])
    }

    private fun swap(from: Int, to: Int) {
        ModelManager.instance.swap(list[from], list[to])
        Collections.swap(list, from, to)
        notifyItemMoved(from, to)
    }

}

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val text: TextView = view.text
    val handle: ImageView = view.drag_handle
}


TodoListFragment.kt

import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.ran.todolist.R
import com.ran.todolist.utils.recyclerview.OnRecyclerAdapterEventListener
import com.ran.todolist.utils.recyclerview.SimpleItemTouchHelperCallback
import kotlinx.android.synthetic.main.fragment_todolist.view.*

private const val ARG_TAB_KEY = "ARG_TAB_KEY"

class TodoListFragment : Fragment(), OnRecyclerAdapterEventListener {

    private var categoryId = 0L
    private lateinit var itemTouchHelper: ItemTouchHelper
    private val adapter by lazy {
        TodoListAdapter(activity!!.applicationContext, categoryId)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            categoryId = it.getLong(ARG_TAB_KEY)
        }
    }

    companion object {
        fun newInstance(categoryId: Long): TodoListFragment {
            return TodoListFragment().apply {
                arguments = Bundle().apply {
                    putLong(ARG_TAB_KEY, categoryId)
                }
            }
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_todolist, container, false)

        view.recycler_view.layoutManager = LinearLayoutManager(activity)
        view.recycler_view.adapter = adapter

        adapter.setOnRecyclerAdapterEventListener(this)
        val callback = SimpleItemTouchHelperCallback(adapter)
        itemTouchHelper = ItemTouchHelper(callback)
        itemTouchHelper.attachToRecyclerView(view.recycler_view)

        return view
    }

    override fun onItemClicked(position: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun onItemLongClicked(position: Int) {
        val fragment = TodoDeleteFragment()

        fragment.currentItemId = adapter.getItem(position)?.id
        fragment.show(activity?.supportFragmentManager, "TodoInsertFragment")
    }

    override fun onDragStarted(viewHolder: RecyclerView.ViewHolder) {
        itemTouchHelper.startDrag(viewHolder)
    }
}