自定义侧滑菜单 - 与非原生XRecyclerView配合使用效果更佳

效果图:

说明在源码里有注释------>

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值