Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix drag & drop issues in SimpleDragCallback #985

Merged
merged 5 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
<activity
android:name="com.mikepenz.fastadapter.app.DiffUtilActivity"
android:label="@string/sample_diff_util" />
<activity
android:name="com.mikepenz.fastadapter.app.DragAndDropActivity"
android:label="Drag &amp; Drop Sample" />

<!-- USED FOR THE MopubAdsActivity sample-->
<activity
Expand Down
208 changes: 208 additions & 0 deletions app/src/main/java/com/mikepenz/fastadapter/app/DragAndDropActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package com.mikepenz.fastadapter.app

import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.GenericItem
import com.mikepenz.fastadapter.IAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter.Companion.items
import com.mikepenz.fastadapter.app.databinding.ActivitySampleBinding
import com.mikepenz.fastadapter.app.items.DragHandleTouchEvent
import com.mikepenz.fastadapter.app.items.DraggableSingleLineItem
import com.mikepenz.fastadapter.app.items.SectionHeaderItem
import com.mikepenz.fastadapter.app.items.SmallIconSingleLineItem
import com.mikepenz.fastadapter.app.view.DraggableFrameLayout
import com.mikepenz.fastadapter.app.view.RecyclerViewBackgroundDrawable
import com.mikepenz.fastadapter.drag.ItemTouchCallback
import com.mikepenz.fastadapter.drag.SimpleDragCallback
import com.mikepenz.fastadapter.utils.DragDropUtil
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.materialdesigniconic.MaterialDesignIconic

private const val STATE_LIST_ORDER = "com.mikepenz.fastadapter.app.LIST_ORDER"

class DragAndDropActivity : AppCompatActivity(), ItemTouchCallback {
private lateinit var binding: ActivitySampleBinding

private lateinit var fastAdapter: FastAdapter<GenericItem>
private lateinit var itemAdapter: ItemAdapter<GenericItem>

private lateinit var touchCallback: SimpleDragCallback
private lateinit var touchHelper: ItemTouchHelper

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySampleBinding.inflate(layoutInflater).also {
setContentView(it.root)
}

setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(false)

// Create empty ItemAdapter
itemAdapter = items()

// Create FastAdapter instance that will manage the whole list
fastAdapter = FastAdapter.with(itemAdapter).apply {
// Add an event hook that manages touching the drag handle
addEventHook(
DragHandleTouchEvent { position ->
binding.rv.findViewHolderForAdapterPosition(position)?.let { viewHolder ->
// Start dragging
touchHelper.startDrag(viewHolder)
}
}
)
}

// Handle clicks on our list items
fastAdapter.onClickListener = { v: View?, _: IAdapter<GenericItem>, item: GenericItem, _: Int ->
if (v != null) {
// Perform an action depending on the type of the item
val message = when (item) {
is SmallIconSingleLineItem -> item.name.getText(v.context)
is DraggableSingleLineItem -> item.name.getText(v.context)
is SectionHeaderItem -> null
else -> "Unknown item type: $item"
}
if (message != null) {
Toast.makeText(v.context, message, Toast.LENGTH_SHORT).show()
}
}
false
}

// Set up our RecyclerView
binding.rv.layoutManager = LinearLayoutManager(this)
binding.rv.itemAnimator = DefaultItemAnimator()
binding.rv.adapter = fastAdapter

// Set a custom background on the RecyclerView. It avoids filling the area without list items at the bottom of
// the RecyclerView with our background color.
val recyclerViewBackgroundColor = ResourcesCompat.getColor(resources, R.color.behindRecyclerView, theme)
RecyclerViewBackgroundDrawable(recyclerViewBackgroundColor).attachTo(binding.rv)

// Create our list and set it on the adapter
val items = buildSampleItemList(savedInstanceState)
itemAdapter.add(items)

// Add drag and drop functionality to the RecyclerView
touchCallback = SimpleDragCallback(itemTouchCallback = this).apply {
// Disable drag & drop on long-press
isDragEnabled = false
}
touchHelper = ItemTouchHelper(touchCallback)
touchHelper.attachToRecyclerView(binding.rv)

// Restore the adapter state (this has to be done after adding the items)
fastAdapter.withSavedInstanceState(savedInstanceState)
}

private fun buildSampleItemList(savedInstanceState: Bundle?): ArrayList<GenericItem> {
val items = ArrayList<GenericItem>()
items.add(
SmallIconSingleLineItem(
IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_settings),
getString(R.string.list_item_general_settings)
)
)

items.add(SectionHeaderItem(getString(R.string.list_item_accounts_section)))

val accountIcon = IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_account)

val accountItems = (1..5).map { i ->
val name = getString(R.string.list_item_account, i)
DraggableSingleLineItem(accountIcon, name).apply {
identifier = (100 + i).toLong()
}
}

// Use the saved state (if available) to sort the list of accounts
val sortedAccountItems = if (savedInstanceState != null) {
val listOrder = savedInstanceState.getLongArray(STATE_LIST_ORDER) ?: error("Missing saved state")
val orderById = listOrder.withIndex().associate { it.value to it.index }
accountItems.sortedBy { orderById[it.identifier] }
} else {
accountItems
}

items.addAll(sortedAccountItems)

items.add(SectionHeaderItem(getString(R.string.list_item_misc_section)))
items.add(SmallIconSingleLineItem(
IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_info),
getString(R.string.list_item_about)
))
items.add(
SmallIconSingleLineItem(
IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_code),
getString(R.string.list_item_licenses)
)
)
return items
}

override fun onSaveInstanceState(outState: Bundle) {
// Save the adapter's state
val newOutState = fastAdapter.saveInstanceState(outState)

// Save the current account order
val itemIdentifiers = itemAdapter.adapterItems
.filterIsInstance<DraggableSingleLineItem>()
.map { it.identifier }
.toLongArray()
newOutState.putLongArray(STATE_LIST_ORDER, itemIdentifiers)

super.onSaveInstanceState(newOutState)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}

override fun itemTouchStartDrag(viewHolder: RecyclerView.ViewHolder) {
// Add visual highlight to the dragged item
(viewHolder.itemView as DraggableFrameLayout).isDragged = true
}

override fun itemTouchStopDrag(viewHolder: RecyclerView.ViewHolder) {
// Remove visual highlight from the dropped item
(viewHolder.itemView as DraggableFrameLayout).isDragged = false
}

override fun itemTouchOnMove(oldPosition: Int, newPosition: Int): Boolean {
// Determine the "drop area"
val firstDropPosition = itemAdapter.adapterItems.indexOfFirst { it is DraggableSingleLineItem }
val lastDropPosition = itemAdapter.adapterItems.indexOfLast { it is DraggableSingleLineItem }

// Only move the item if the new position is inside the "drop area"
return if (newPosition in firstDropPosition..lastDropPosition) {
// Change the item's position in the adapter
DragDropUtil.onMove(itemAdapter, oldPosition, newPosition)
true
} else {
false
}
}

override fun itemTouchDropped(oldPosition: Int, newPosition: Int) {
// Save the new item order, e.g. in your database
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class SampleActivity : AppCompatActivity() {
PrimaryDrawerItem().withName(R.string.sample_collapsible_multi_select_delete).withDescription(R.string.sample_collapsible_multi_select_delete_descr).withSelectable(false).withIdentifier(18).withIcon(MaterialDesignIconic.Icon.gmi_check_all),
PrimaryDrawerItem().withName(R.string.sample_sticky_header_mopub).withDescription(R.string.sample_sticky_header_mopub_descr).withSelectable(false).withIdentifier(19).withIcon(MaterialDesignIconic.Icon.gmi_accounts_list),
PrimaryDrawerItem().withName(R.string.sample_diff_util).withDescription(R.string.sample_diff_util_descr).withSelectable(false).withIdentifier(20).withIcon(MaterialDesignIconic.Icon.gmi_refresh),
PrimaryDrawerItem().withName(R.string.sample_drag_and_drop).withDescription(R.string.sample_drag_and_drop_descr).withSelectable(false).withIdentifier(22).withIcon(MaterialDesignIconic.Icon.gmi_reorder),
DividerDrawerItem(),
PrimaryDrawerItem().withName(R.string.open_source).withSelectable(false).withIdentifier(100).withIcon(MaterialDesignIconic.Icon.gmi_github)
)
Expand All @@ -114,6 +115,7 @@ class SampleActivity : AppCompatActivity() {
19L -> Intent(this@SampleActivity, StickyHeaderMopubAdsActivity::class.java)
20L -> Intent(this@SampleActivity, DiffUtilActivity::class.java)
21L -> Intent(this@SampleActivity, PagedActivity::class.java)
22L -> Intent(this@SampleActivity, DragAndDropActivity::class.java)
100L -> LibsBuilder()
.withFields(R.string::class.java.fields)
.withActivityTitle(getString(R.string.open_source))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.mikepenz.fastadapter.app.items

import android.graphics.drawable.Drawable
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.app.R
import com.mikepenz.fastadapter.drag.IDraggable
import com.mikepenz.fastadapter.items.AbstractItem
import com.mikepenz.fastadapter.listeners.TouchEventHook
import com.mikepenz.fastadapter.ui.utils.ImageHolder
import com.mikepenz.fastadapter.ui.utils.StringHolder

class DraggableSingleLineItem(icon: Drawable, name: String) : AbstractItem<DraggableSingleLineItem.ViewHolder>(), IDraggable {
val icon: ImageHolder = ImageHolder(icon)
val name: StringHolder = StringHolder(name)

override val isDraggable: Boolean = true

override val type: Int
get() = R.id.fastadapter_draggable_single_line_item

override val layoutRes: Int
get() = R.layout.draggable_single_line_item

override fun bindView(holder: ViewHolder, payloads: List<Any>) {
super.bindView(holder, payloads)
ImageHolder.applyToOrSetGone(icon, holder.icon)
name.applyTo(holder.name)
}

override fun unbindView(holder: ViewHolder) {
holder.icon.setImageDrawable(null)
holder.name.text = null
}

override fun getViewHolder(v: View): ViewHolder = ViewHolder(v)

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.icon)
var name: TextView = view.findViewById(R.id.name)
var dragHandle: View = view.findViewById(R.id.drag_handle)
}
}

class DragHandleTouchEvent(val action: (position: Int) -> Unit) : TouchEventHook<DraggableSingleLineItem>() {
override fun onBind(viewHolder: RecyclerView.ViewHolder): View? {
return if (viewHolder is DraggableSingleLineItem.ViewHolder) viewHolder.dragHandle else null
}

override fun onTouch(
v: View,
event: MotionEvent,
position: Int,
fastAdapter: FastAdapter<DraggableSingleLineItem>,
item: DraggableSingleLineItem
): Boolean {
return if (event.action == MotionEvent.ACTION_DOWN) {
action(position)
true
} else {
false
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.mikepenz.fastadapter.app.items

import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.app.R
import com.mikepenz.fastadapter.items.AbstractItem
import com.mikepenz.fastadapter.ui.utils.StringHolder

class SectionHeaderItem(text: String) : AbstractItem<SectionHeaderItem.ViewHolder>() {
val text: StringHolder = StringHolder(text)

override val type: Int
get() = R.id.fastadapter_section_header_item

override val layoutRes: Int
get() = R.layout.section_header_item

override fun bindView(holder: ViewHolder, payloads: List<Any>) {
super.bindView(holder, payloads)
text.applyTo(holder.text)
}

override fun unbindView(holder: ViewHolder) {
holder.text.text = null
}

override fun getViewHolder(v: View): ViewHolder = ViewHolder(v)

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var text: TextView = view.findViewById(R.id.text)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mikepenz.fastadapter.app.items

import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.app.R
import com.mikepenz.fastadapter.items.AbstractItem
import com.mikepenz.fastadapter.ui.utils.ImageHolder
import com.mikepenz.fastadapter.ui.utils.StringHolder

class SmallIconSingleLineItem(icon: Drawable, name: String) : AbstractItem<SmallIconSingleLineItem.ViewHolder>() {
val icon: ImageHolder = ImageHolder(icon)
val name: StringHolder = StringHolder(name)

override val type: Int
get() = R.id.fastadapter_small_icon_single_line_item

override val layoutRes: Int
get() = R.layout.small_single_line_item

override fun bindView(holder: ViewHolder, payloads: List<Any>) {
super.bindView(holder, payloads)
ImageHolder.applyToOrSetGone(icon, holder.icon)
name.applyTo(holder.name)
}

override fun unbindView(holder: ViewHolder) {
holder.icon.setImageDrawable(null)
holder.name.text = null
}

override fun getViewHolder(v: View): ViewHolder = ViewHolder(v)

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.icon)
var name: TextView = view.findViewById(R.id.name)
}
}
Loading