效果图:
说明在源码里有注释------>
使用方式:(这里有个坑注意了,不坐下面设置在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)
}
}