Android - Swipe layout
Bài đăng này đã không được cập nhật trong 7 năm
Chẳng là mình đang tập tành code kotlin cho android, app của mình cho phép người dùng swipe item trái phải các kiểu và thực hiện các action ứng với mỗi trường hợp người dùng vuốt trái, phải.
Sau một cả ngày mò mẫm thì mình tìm được một thư viện hỗ trợ khá tốt và đáp ứng được nhu cầu của mình - AndroidSwipeLayout

Cài đặt
Gradle: các bạn có thể thay đổi version của recyclerview theo như mình mong muốn
dependencies {
compile 'com.android.support:recyclerview-v7:21.0.0'
compile 'com.android.support:support-v4:20.+'
compile "com.daimajia.swipelayout:library:1.2.0@aar"
}
Maven
<dependency>
<groupId>com.google.android</groupId>
<artifactId>support-v4</artifactId>
<version>r6</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>recyclerview-v7</artifactId>
<version>21.0.0</version>
</dependency>
<dependency>
<groupId>com.daimajia.swipelayout</groupId>
<artifactId>library</artifactId>
<version>1.2.0</version>
<type>apklib</type>
</dependency>
Sử dụng
Nguyên lý cơ bản của thư viện này là nó sẽ có 2 layer phục vụ cho việc hiển thị, SurfaceView chính là layer ở phía trên mà bạn vuốt, BottomView là layer ở phía dưới sẽ được hiển thị khi bạn vuốt

<com.daimajia.swipe.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="80dp">
<!-- Bottom View Start-->
<LinearLayout
android:background="#66ddff00"
android:id="@+id/bottom_wrapper"
android:layout_width="160dp"
android:weightSum="1"
android:layout_height="match_parent">
<!--What you want to show-->
</LinearLayout>
<!-- Bottom View End-->
<!-- Surface View Start -->
<LinearLayout
android:padding="10dp"
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--What you want to show in SurfaceView-->
</LinearLayout>
<!-- Surface View End -->
</com.daimajia.swipe.SwipeLayout>
Đoạn code ở trên mô tả cấu trúc cơ bản của SwipeLayout, ở phần BottomView, bạn có thể đặt bao nhiêu item cũng được, chỉ cần chú ý rằng SurfaceView chính là layer cuối cùng của SwipeLayout.
Các bạn có thể tham khảo thêm ở đây: swipe_item.xml
Thư viện này hỗ trợ 2 kiểu swipe: LayDown hoặc PullOut, đồng thời hỗ trợ xác định hướng vuốt: LEFT, RIGHT, TOP, BOTTOM
-
LayDown

-
PullOut

Sau khi có SwipeLayout, chúng ta cần cài đặt cho nó.
Dưới đây là một ví dụ sử dụng SwipeLayout cho RecyclerView, chúng ta có một adapter đơn giản có sử dụng callback. Nếu các bạn chỉ sử dụng nó cho một item nào đó thì chỉ cần addSwipeListener và set showMode cho nó là xong
class RecyclerSwipeViewAdapter(
private val items: List<Item>,
private val mListener: OnSwipeItem
) : RecyclerSwipeAdapter<RecyclerSwipeViewAdapter.SimpleViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.swipe_item, parent, false)
return SimpleViewHolder(view)
}
override fun onBindViewHolder(viewHolder: SimpleViewHolder, position: Int) {
val item = items[position]
viewHolder.bindItem(item, position)
}
override fun getItemCount(): Int {
return items.size
}
override fun getSwipeLayoutResourceId(position: Int): Int {
return R.id.swipe
}
inner class SimpleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val text: TextView = itemView.text_view
private val swipeLayout: SwipeLayout = itemView.swipe
init {
swipeLayout.addDrag(SwipeLayout.DragEdge.Right, itemView.findViewById(R.id.bottom_right))
swipeLayout.addDrag(SwipeLayout.DragEdge.Left, itemView.findViewById(R.id.bottom_left))
}
fun close() {
swipeLayout.close()
}
fun bindItem(item: Item, position: Int) {
text.text = item.text
// Set swipe style
swipeLayout.showMode = SwipeLayout.ShowMode.PullOut
// Set listener
swipeLayout.addSwipeListener(object : SwipeLayout.SwipeListener {
override fun onClose(layout: SwipeLayout) { }
override fun onUpdate(layout: SwipeLayout, leftOffset: Int, topOffset: Int) { }
override fun onStartOpen(layout: SwipeLayout) {}
override fun onOpen(layout: SwipeLayout) { }
override fun onStartClose(layout: SwipeLayout) { }
override fun onHandRelease(layout: SwipeLayout, xvel: Float, yvel: Float) {
val edge = layout.dragEdge.name
if (layout.openStatus.toString() !== "Close") {
when (edge) {
SwipeLayout.DragEdge.Right.name -> {
// Drag RIGHT
mListener.onSwipeRight(item)
}
SwipeLayout.DragEdge.Left.name -> {
// Drag LEFT
mListener.onSwipeLeft(item)
}
SwipeLayout.DragEdge.Top.name -> {
// Drag TOP
mListener.onSwipeTop(item)
}
SwipeLayout.DragEdge.Bottom.name -> {
// Drag BOTTOM
mListener.onSwipeBottom(item)
}
}
}
}
})
text.setOnClickListener {
mListener.onClickItem(item)
}
}
}
interface OnSwipeItem {
fun onSwipeLeft(item: Item)
fun onSwipeRight(item: Item)
fun onSwipeTop(item: Item)
fun onSwipeBottom(item: Item)
fun onClickItem(item: Item)
}
}
Trong Activity/Fragment:
private fun setAdapter(items: List<Item>) {
swipeAdapter = RecyclerSwipeViewAdapter(items, object: RecyclerSwipeViewAdapter.OnSwipeItem {
override fun onSwipeLeft(item: Item) {}
override fun onSwipeRight(item: Item) {}
override fun onSwipeTop(item: Item) {}
override fun onSwipeBottom(item: Item) {}
override fun onClickItem(item: Item) {}
})
recycler_view.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
adapter = swipeAdapter
}
}
Note: nếu ứng dụng của bạn chỉ cần nhận diện việc người dùng vuốt theo hướng nào mà ko cần phải hiển thị phần BottomView như ở trên thì bạn có thể tham khảo qua đoạn code dưới đây
abstract class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector: GestureDetector
init {
gestureDetector = GestureDetector(context, GestureListener())
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
private val SWIPE_THRESHOLD = 100
private val SWIPE_VELOCITY_THRESHOLD = 100
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
var result = false
try {
val diffY = e2.y - e1.y
val diffX = e2.x - e1.x
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight()
} else {
onSwipeLeft()
}
result = true
}
} else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom()
} else {
onSwipeTop()
}
result = true
}
} catch (exception: Exception) {
exception.printStackTrace()
}
return result
}
}
abstract fun onSwipeRight()
abstract fun onSwipeLeft()
abstract fun onSwipeTop()
abstract fun onSwipeBottom()
}
Ở đoạn code trên, chúng ta sử dụng GestureDetector.SimpleOnGestureListener để xác định hướng mà người dùng vuốt.
Tiếp theo trong Activity/Fragment chúng ta chỉ cần implement 4 phương thức abstract là xong
text_view.setOnTouchListener(object : OnSwipeTouchListener(this) {
override fun onSwipeRight() {}
override fun onSwipeLeft() {}
override fun onSwipeTop() {}
override fun onSwipeBottom() {}
})
All rights reserved