效果圖:
說明在源碼裏有註釋------>
使用方式:(這裏有個坑注意了,不坐下面設置在RecyclerView中會出現空白的Item)
這裏的listitem 需要用一個容器包裹,並且設置 android:layout_width android:layout_height 爲 "wrap_content"
這是RecyclerView的bug 我大家都這樣說>.<
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.example.cehua.SlideLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:background="#2000"
android:text="RecycleView"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_height="match_parent">
<TextView
android:id="@+id/delete"
android:layout_width="100dp"
android:layout_height="60dp"
android:gravity="center"
android:background="#55ff"
android:text="DELETE"/>
<TextView
android:id="@+id/menu2"
android:layout_width="100dp"
android:layout_height="60dp"
android:gravity="center"
android:background="#5f0f"
android:text="Menu2"/>
</LinearLayout>
</com.example.cehua.SlideLayout>
</RelativeLayout>
控件源碼:
/**
* Imitating SwipeItemLayout Totally
* 1.側滑
* 2.點擊關閉側滑 <-> 未側滑時內容點擊生效,否則菜單點擊生效
* 3.
*
* 事件總結
* 1.onInterceptTouchEvent ACTION_DOWN 一定會執行
* 2.如果子View 有點擊事件(其他事件不清楚),onTouchEvent ACTION_DOWN 不會執行,否則執行
* 3.onInterceptTouchEvent ACTION_MOVE 一定會執行,但是攔截後 return true; 就不在執行,交給 onTouchEvent ACTION_MOVE 執行
* 4.
*
* 事件總結 .II onInterceptTouchEvent onTouchEvent
* 1.ACTION_DOWN 一定會執行 如果子View有點擊事件(其他事件不清楚)不會執行,否則執行
*
* 2.ACTION_MOVE 一定會執行,但是攔截後,就不在執行, onInterceptTouchEvent攔截後執行
* 交給onTouchEvent執行
*
* 3.ACTION_UP 子View有事件監聽(點擊)會執行,否則不執行 與onInterceptTouchEvent相反
*/
class SlideLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
private var scroller: Scroller = Scroller(context)
private var contentWidth = 0
private var contentHeight = 0
private var menuWidth = 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
contentWidth = measuredWidth
contentHeight = measuredHeight
menuWidth = getChildAt(1).measuredWidth
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (childCount != 2) {
throw RuntimeException("SlideLayout 必須有兩個子View")
}
getChildAt(0).layout(0, 0, right, bottom)
getChildAt(1).layout(right, 0, right + menuWidth, bottom)
}
var downX = 0f
var downY = 0f
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
var intercept = false
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
Log.w("SlideLayout", "--------------------------------------------------")
Log.w("SlideLayout", "onInterceptTouchEvent ACTION_DOWN")
downX = ev.rawX
downY = ev.rawY
startX = ev.rawX
startY = ev.rawY
closeOtherSlideLayout(this)
}
MotionEvent.ACTION_MOVE -> {
Log.w("SlideLayout", "onInterceptTouchEvent ACTION_MOVE")
val endX = ev.rawX
val endY = ev.rawY
val dX = downX - endX
val dY = downY - endY
if (Math.abs(dX) > Math.abs(dY) && Math.abs(dX) > validDistance) {
Log.w("SlideLayout", "onInterceptTouchEvent ACTION_MOVE intercept")
intercept = true
}
validDistance
}
MotionEvent.ACTION_UP -> {
//這個方法後 子View可以響應點擊事件
//跑到這裏來,說明 ACTION_MOVE 沒有被 子View 和 自己 消費
val viewX = ev.x
if (scrollX > 0) {
//item 被滑開
if (contentWidth - scrollX > viewX) {
//點擊的是內容部分
intercept = true
closeMenu()
} else {
//點擊的是菜單部分
//如果事件導致 adapter notifyDataSetChanged(),調用下面的事件才 ->>>>合適<<<<-
//否則調用closeMenu() 更合適
//如果要確認事件結果,就需要關係到 RecycleView 或 adapter
closeMenuS()
// closeMenu()
}
} else {
//點擊的是內容部分
}
Log.w("SlideLayout", "onInterceptTouchEvent ACTION_UP")
}
}
return intercept
}
private var startX: Float = 0f
private var startY: Float = 0f
private var isSlide = false//是否是滑動事件
override fun onTouchEvent(event: MotionEvent?): Boolean {
super.onTouchEvent(event)
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
isSlide = false
//子View 點擊事件 這個方法不會被執行
Log.w("SlideLayout", "onTouchEvent ACTION_DOWN")
touchDown(this)
startX = event.rawX
startY = event.rawY
downX = event.rawX
downY = event.rawY
}
MotionEvent.ACTION_MOVE -> {
Log.w("SlideLayout", "onTouchEvent ACTION_MOVE")
val endX = event.rawX
val endY = event.rawY
val distanceX = endX - startX
var toX = scrollX - distanceX
if (toX < 0) {
toX = 0f
}
if (toX > menuWidth) {
toX = menuWidth.toFloat()
}
// Log.w("SlideLayout", "ACTION_MOVE toX:$toX")
scrollTo(toX.toInt(), 0)
//在X軸和Y軸滑動的距離
val dX = Math.abs(endX - downX)
val dY = Math.abs(endY - downY)
if (dX > dY && dX > 8) {
//水平方向滑動
//響應側滑
//反攔截-事件給SlideLayout
isSlide = true
parent.requestDisallowInterceptTouchEvent(true)
}
//重置
startX = endX
startY = endY
}
MotionEvent.ACTION_UP -> {
Log.w("SlideLayout", "onTouchEvent ACTION_UP")
if (scrollX > menuWidth / 2) {
if (isSlide) {
openMenu()
} else {
if (event.x < contentWidth - scrollX) {
//點擊的內容部分執行
closeMenu()
}
}
} else {
closeMenu()
}
}
}
return true
}
private fun openMenu() {
// scroller.startScroll(scrollX, 0, menuWidth, 0)
openSlideLayout(this)
scroller.startScroll(scrollX, scrollY, menuWidth - scrollX, 0 - scrollY, Math.abs(menuWidth - scrollX))
invalidate()
}
/**
* 直接滑動至初始狀態,
* ->avoiding listView do animation while removing item,you will see the animation be executed on other item
* ->but,if listView does not remove item,the item would not be recycle,
* so the function will makes the item show unreasonable animation(動畫別溜,但也解決了,跑到其他 position 去執行動畫)
*/
private fun closeMenuS() {
scrollTo(0, 0 - scrollY)
}
private fun closeMenu() {
closedSlideLayout()
scroller.startScroll(scrollX, scrollY, 0 - scrollX, 0 - scrollY, Math.abs(scrollX) * 2)
invalidate()
}
override fun computeScroll() {
super.computeScroll()
if (scroller.computeScrollOffset()) {
//滑動中
scrollTo(scroller.currX, scroller.currY)
invalidate()
}
}
companion object {
private val validDistance = 8
private var openedSlideLayout: SlideLayout? = null
fun touchDown(slideLayout: SlideLayout) {
if (openedSlideLayout != slideLayout) {
openedSlideLayout?.closeMenu()
}
}
fun closeOtherSlideLayout(slideLayout: SlideLayout) {
if (openedSlideLayout != null && openedSlideLayout != slideLayout) {
openedSlideLayout?.closeMenu()
}
}
fun closedSlideLayout() {
openedSlideLayout = null
}
fun openSlideLayout(slideLayout: SlideLayout) {
openedSlideLayout?.closeMenu()
openedSlideLayout = slideLayout
}
}
init {
scroller = Scroller(context)
}
}