自定義側滑菜單 - 與非原生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)
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章