Skip to content

Commit 68f84b1

Browse files
authored
Merge pull request #106 from AlanCheen/feature/swipe-drag
Feature/swipe drag
2 parents 8775f17 + 3f1a81f commit 68f84b1

File tree

6 files changed

+312
-5
lines changed

6 files changed

+312
-5
lines changed

app/src/main/java/me/yifeiyuan/flapdev/MainActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ class MainActivity : AppCompatActivity() {
8585
subtitle = "LayoutAdapterDelegate DSL"
8686
replace(LayoutDelegateDSLTestcase::class.java)
8787
}
88+
R.id.nav_dismiss -> {
89+
subtitle = "滑动删除&拖放"
90+
replace(SwipeAndDragTestcase::class.java)
91+
}
8892
R.id.nav_github_demo -> {
8993
subtitle = "GitHub Demo"
9094
replace(GitHubDemoFragment::class.java)

app/src/main/java/me/yifeiyuan/flapdev/testcases/BaseTestcaseFragment.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ open class BaseTestcaseFragment : Fragment(), Scrollable {
4747

4848
lateinit var emptyView: View
4949

50+
lateinit var spaceItemDecoration: SpaceItemDecoration
51+
5052
lateinit var linearItemDecoration: RecyclerView.ItemDecoration
5153
lateinit var gridItemDecoration: RecyclerView.ItemDecoration
5254
lateinit var currentItemDecoration: RecyclerView.ItemDecoration
@@ -148,6 +150,8 @@ open class BaseTestcaseFragment : Fragment(), Scrollable {
148150

149151
swipeRefreshLayout.isRefreshing = true
150152

153+
// FlapItemTouchHelper(adapter).attachToRecyclerView(recyclerView)
154+
151155
Handler().postDelayed({
152156
adapter.setData(createRefreshData())
153157
swipeRefreshLayout.isRefreshing = false
@@ -185,6 +189,8 @@ open class BaseTestcaseFragment : Fragment(), Scrollable {
185189
// .withLastItemBottomEdge(false)
186190

187191
gridItemDecoration = SpaceItemDecoration(requireActivity().toPixel(6))
192+
193+
spaceItemDecoration = SpaceItemDecoration(requireActivity().toPixel(6))
188194
}
189195

190196
open fun isClickEnable() = true
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package me.yifeiyuan.flapdev.testcases
2+
3+
import android.graphics.Color
4+
import android.graphics.drawable.ColorDrawable
5+
import android.util.Log
6+
import android.view.View
7+
import androidx.recyclerview.widget.ItemTouchHelper
8+
import me.yifeiyuan.flap.FlapAdapter
9+
import me.yifeiyuan.flap.ext.SwipeDragHelper
10+
11+
12+
private const val TAG = "ItemClicksTestcase"
13+
14+
/**
15+
* 测试滑动删除&拖放
16+
*
17+
* Created by 程序亦非猿 on 2022/8/17.
18+
*/
19+
class SwipeAndDragTestcase : BaseTestcaseFragment() {
20+
21+
override fun onInit(view: View) {
22+
super.onInit(view)
23+
24+
val swipeDragHelper = SwipeDragHelper(adapter)
25+
.withDragEnable(true)
26+
.withSwipeEnable(true)
27+
.withDragFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN)
28+
.withSwipeFlags(ItemTouchHelper.START or ItemTouchHelper.END)
29+
.withSwipeBackground(ColorDrawable(Color.parseColor("#ff0000")))
30+
.onItemDismiss {
31+
toast("滑动删除了一个 item , position=$it")
32+
}
33+
.onItemMove { fromPosition, toPosition ->
34+
toast("移动交换了 $fromPosition to $toPosition")
35+
}
36+
.attachToRecyclerView(recyclerView)
37+
38+
recyclerView.addItemDecoration(spaceItemDecoration)
39+
}
40+
41+
override fun createAdapter(): FlapAdapter {
42+
return super.createAdapter().apply {
43+
doOnItemClick { recyclerView, childView, position ->
44+
Log.d(TAG, "doOnItemClick called with: childView = $childView, position = $position")
45+
toast("点击了 position = $position")
46+
}
47+
48+
doOnItemLongClick { recyclerView, childView, position ->
49+
Log.d(TAG, "doOnItemLongClick called with: childView = $childView, position = $position")
50+
toast("长按了 position = $position")
51+
false
52+
}
53+
}
54+
}
55+
}

app/src/main/res/menu/activity_main_drawer.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
android:id="@+id/nav_layout_delegate_dsl"
4848
android:icon="@drawable/ic_menu_slideshow"
4949
android:title="LayoutAdapterDelegate DSL" />
50+
<item
51+
android:id="@+id/nav_dismiss"
52+
android:icon="@drawable/ic_menu_camera"
53+
android:title="Swipe And Drag" />
5054
<item
5155
android:id="@+id/nav_item_decorations"
5256
android:icon="@drawable/ic_menu_camera"

flap/src/main/java/me/yifeiyuan/flap/FlapAdapter.kt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import me.yifeiyuan.flap.ext.*
1616
import me.yifeiyuan.flap.hook.AdapterHook
1717
import me.yifeiyuan.flap.hook.PreloadHook
1818
import me.yifeiyuan.flap.pool.ComponentPool
19+
import java.util.*
1920
import me.yifeiyuan.flap.service.AdapterService
2021
import me.yifeiyuan.flap.service.IAdapterServiceManager
2122
import me.yifeiyuan.flap.service.AdapterServiceManager
@@ -31,7 +32,7 @@ import me.yifeiyuan.flap.service.AdapterServiceManager
3132
* @since 2020/9/22
3233
* @since 3.0.0
3334
*/
34-
open class FlapAdapter : RecyclerView.Adapter<Component<*>>(), IRegistry, IAdapterServiceManager {
35+
open class FlapAdapter : RecyclerView.Adapter<Component<*>>(), IRegistry, IAdapterServiceManager, SwipeDragHelper.Callback {
3536

3637
companion object {
3738
private const val TAG = "FlapAdapter"
@@ -541,9 +542,31 @@ open class FlapAdapter : RecyclerView.Adapter<Component<*>>(), IRegistry, IAdapt
541542
return serviceManager.getAdapterService(serviceName)
542543
}
543544

544-
/**
545-
* 当 Adapter.data 中存在一个 Model 没有对应的 AdapterDelegate.delegate()==true 时抛出
546-
*/
547-
internal class AdapterDelegateNotFoundException(errorMessage: String) : Exception(errorMessage)
545+
fun removeDataAt(position: Int, notify: Boolean = true) {
546+
data.removeAt(position)
547+
if (notify) {
548+
notifyItemRemoved(position)
549+
}
550+
}
551+
552+
fun swapData(fromPosition: Int, toPosition: Int, notify: Boolean = true) {
553+
Collections.swap(data, fromPosition, toPosition)
554+
if (notify) {
555+
notifyItemMoved(fromPosition, toPosition)
556+
}
557+
}
558+
559+
override fun onItemDismiss(position: Int) {
560+
removeDataAt(position)
561+
}
562+
563+
override fun onItemMoved(fromPosition: Int, toPosition: Int) {
564+
swapData(fromPosition, toPosition)
565+
}
548566
}
549567

568+
/**
569+
* 当 Adapter.data 中存在一个 Model 没有对应的 AdapterDelegate.delegate()==true 时抛出
570+
*/
571+
internal class AdapterDelegateNotFoundException(errorMessage: String) : Exception(errorMessage)
572+
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package me.yifeiyuan.flap.ext
2+
3+
import android.graphics.Canvas
4+
import android.graphics.Color
5+
import android.graphics.drawable.ColorDrawable
6+
import android.graphics.drawable.Drawable
7+
import androidx.recyclerview.widget.*
8+
9+
/**
10+
*
11+
* 封装处理 滑动删除 和 长按拖放排序 功能
12+
*
13+
* Created by 程序亦非猿 on 2022/8/17.
14+
*
15+
* @since 3.0.6
16+
*/
17+
class SwipeDragHelper(private val callback: Callback) : ItemTouchHelper.Callback() {
18+
19+
/**
20+
* 拖动是否可用
21+
*/
22+
private var isDragEnable = true
23+
24+
/**
25+
* 滑动删除是否可用
26+
*/
27+
private var isSwipeEnable = true
28+
29+
private var dragFlags = -1
30+
private var swipeFlags = -1
31+
32+
private var swipeThreshold = 0.5f
33+
private var dragThreshold = 0.5f
34+
35+
private var onMove: ((fromPosition: Int, toPosition: Int) -> Unit)? = null
36+
private var onDismiss: ((position: Int) -> Unit)? = null
37+
38+
private var swipeBackground: Drawable? = null
39+
40+
private val itemTouchHelper: ItemTouchHelper = ItemTouchHelper(this)
41+
42+
override fun isLongPressDragEnabled(): Boolean {
43+
return isDragEnable
44+
}
45+
46+
override fun isItemViewSwipeEnabled(): Boolean {
47+
return isSwipeEnable
48+
}
49+
50+
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
51+
val finalDragFlags = if (dragFlags != -1) dragFlags else genDefaultDragFlags(recyclerView, viewHolder)
52+
val finalSwipeFlags = if (swipeFlags != -1) swipeFlags else genDefaultSwipeFlags(recyclerView, viewHolder)
53+
return makeMovementFlags(finalDragFlags, finalSwipeFlags)
54+
}
55+
56+
private fun genDefaultDragFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
57+
return when (recyclerView.layoutManager) {
58+
is StaggeredGridLayoutManager -> {
59+
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
60+
}
61+
is GridLayoutManager -> {
62+
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
63+
}
64+
is LinearLayoutManager -> {
65+
ItemTouchHelper.UP or ItemTouchHelper.DOWN
66+
}
67+
else -> {
68+
ItemTouchHelper.UP or ItemTouchHelper.DOWN
69+
}
70+
}
71+
}
72+
73+
private fun genDefaultSwipeFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
74+
return when (recyclerView.layoutManager) {
75+
is StaggeredGridLayoutManager -> {
76+
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
77+
}
78+
is GridLayoutManager -> {
79+
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
80+
}
81+
is LinearLayoutManager -> {
82+
ItemTouchHelper.START or ItemTouchHelper.END
83+
}
84+
else -> {
85+
ItemTouchHelper.START or ItemTouchHelper.END
86+
}
87+
}
88+
}
89+
90+
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
91+
if (viewHolder.itemViewType == target.itemViewType) {
92+
onMove?.invoke(viewHolder.adapterPosition, target.adapterPosition)
93+
return true
94+
}
95+
return false
96+
}
97+
98+
fun onItemMove(block: (fromPosition: Int, toPosition: Int) -> Unit): SwipeDragHelper {
99+
onMove = block
100+
return this
101+
}
102+
103+
//Item 被滑动删除了调用
104+
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
105+
onDismiss?.invoke(viewHolder.adapterPosition)
106+
callback.onItemDismiss(viewHolder.adapterPosition)
107+
}
108+
109+
fun onItemDismiss(block: (position: Int) -> Unit): SwipeDragHelper {
110+
onDismiss = block
111+
return this
112+
}
113+
114+
fun attachToRecyclerView(recyclerView: RecyclerView): SwipeDragHelper {
115+
itemTouchHelper.attachToRecyclerView(recyclerView)
116+
return this
117+
}
118+
119+
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
120+
super.onSelectedChanged(viewHolder, actionState)
121+
}
122+
123+
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
124+
return swipeThreshold
125+
}
126+
127+
override fun getMoveThreshold(viewHolder: RecyclerView.ViewHolder): Float {
128+
return dragThreshold
129+
}
130+
131+
/**
132+
* onMove return true 后会调用
133+
*/
134+
override fun onMoved(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, fromPos: Int, target: RecyclerView.ViewHolder, toPos: Int, x: Int, y: Int) {
135+
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
136+
callback.onItemMoved(fromPos, toPos)
137+
}
138+
139+
/**
140+
* 当用户交互完成后调用,此时动画也结束了,例如滑动删除后、拖放完毕
141+
* 在 onViewDetachedFromWindow 之后
142+
*/
143+
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
144+
super.clearView(recyclerView, viewHolder)
145+
}
146+
147+
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
148+
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
149+
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
150+
//滑动的时候可以绘制背景
151+
swipeBackground?.let {
152+
val itemView = viewHolder.itemView
153+
if (dX > 0) {
154+
it.setBounds(itemView.left, itemView.top, itemView.left + dX.toInt(), itemView.bottom)
155+
it.draw(c)
156+
} else {
157+
it.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom)
158+
it.draw(c)
159+
}
160+
}
161+
} else if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
162+
//拖动
163+
}
164+
}
165+
166+
override fun onChildDrawOver(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder?, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
167+
super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
168+
}
169+
170+
/**
171+
* 拖动是否可用
172+
*/
173+
fun withDragEnable(enable: Boolean): SwipeDragHelper {
174+
isDragEnable = enable
175+
return this
176+
}
177+
178+
/**
179+
* 滑动删除是否可用
180+
*/
181+
fun withSwipeEnable(enable: Boolean): SwipeDragHelper {
182+
isSwipeEnable = enable
183+
return this
184+
}
185+
186+
fun withDragFlags(dragFlags: Int): SwipeDragHelper {
187+
this.dragFlags = dragFlags
188+
return this
189+
}
190+
191+
fun withSwipeFlags(swipeFlags: Int): SwipeDragHelper {
192+
this.swipeFlags = swipeFlags
193+
return this
194+
}
195+
196+
fun withSwipeThreshold(swipeThreshold: Float): SwipeDragHelper {
197+
this.swipeThreshold = swipeThreshold
198+
return this
199+
}
200+
201+
fun withDragThreshold(dragThreshold: Float): SwipeDragHelper {
202+
this.dragThreshold = dragThreshold
203+
return this
204+
}
205+
206+
fun withSwipeBackground(swipeBackground: Drawable): SwipeDragHelper {
207+
this.swipeBackground = swipeBackground
208+
return this
209+
}
210+
211+
interface Callback {
212+
fun onItemDismiss(position: Int)
213+
fun onItemMoved(fromPosition: Int, toPosition: Int)
214+
}
215+
}

0 commit comments

Comments
 (0)