{"id":21888507,"url":"https://github.com/pythoncat1024/swipeitemlayout","last_synced_at":"2025-03-22T02:23:55.024Z","repository":{"id":107255621,"uuid":"90521504","full_name":"pythoncat1024/SwipeItemLayout","owner":"pythoncat1024","description":"精心打造的，仅支持左滑的ItemLayout","archived":false,"fork":false,"pushed_at":"2017-05-13T06:54:49.000Z","size":56,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-26T20:32:17.674Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pythoncat1024.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-07T09:19:32.000Z","updated_at":"2017-05-07T09:52:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"0118d6d9-f177-4f74-ae38-5ba38bc2b138","html_url":"https://github.com/pythoncat1024/SwipeItemLayout","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pythoncat1024%2FSwipeItemLayout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pythoncat1024%2FSwipeItemLayout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pythoncat1024%2FSwipeItemLayout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pythoncat1024%2FSwipeItemLayout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pythoncat1024","download_url":"https://codeload.github.com/pythoncat1024/SwipeItemLayout/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244895509,"owners_count":20527902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T11:15:53.199Z","updated_at":"2025-03-22T02:23:55.018Z","avatar_url":"https://github.com/pythoncat1024.png","language":"Java","readme":"# SwipeItemLayout\n**精心打造的，仅支持左滑的ItemLayout**\n\n**前言：**\n- 这个`ItemLayout`出现的的初衷是为了配合`ListView` or `RecyclerView`实现侧滑的。但是我实在是太天真了。这个`ItemLayout`实现侧滑当然没有问题。放在`ListView`or`RecyclerView`里面也可以侧滑。~~但是，凡是都有个但是。~~但是，将这个`ItemLayout`作为`Item`使用的时候，无论是`ListView`还是`RecyclerView`都不好管理。也就是说，很难在`ListView`or`RecyclerView`中，去知道哪个`Item`是侧滑打开的，哪个不是。然后是怎样去关闭已经打开的`Item`.比如：一个常见的使用场景是：当前已经有一个`Item`被打开了。然后，现在想去打开另外一个，就应该先去关闭上一个，再打开当前想要打开的。但是，真的太麻烦了。我能想到的一个方案就是获取所有的`ItemLayout`然后遍历处理。但是，如果这样做，我感觉真的已经失去了意义。如果是这样做，还不如去使用现成的开源库。最起码不会那么蹩脚。\n\n- 但是，~~凡事都有个但是啊。~~\n\n- 虽然这个`SwipeItemLayout`似乎没有使用场景了。可能唯一的使用场景是，一个布局里面有那么一两个`Item`是需要侧滑的。这时候倒是可以使用上。因为只有几个侧滑的`Item`控制起来比较容易。\n\n- 看到网上的很多侧滑菜单，其实是没有考虑内边距`padding`和外边距的`margin`。使用起来就没有办法设置这些。因为设置了也没用。于是准备自己写一个的....\n\n**后语：**\n- 虽然这个`ItemLayout`可能是没有什么使用场景了。但是毕竟是一番心血。况且，里面对`padding`和`margin`都有很好的支持。对滑动边界的检测，对事件冲突的处理，对滑动释放的处理，个人感觉都处理的很完善。当然，一定是有不足之处的。不过就目前而言，里面的，我能想到的，需要处理的，都处理好了。\n\n- 这个`ItemLayout`算是对`ViewDragHelper`的一次实践吧。顺便吐槽一句：`ViewDragHelper`对边界处理是不算友好的。里面的边界处理计算值，我是打了好多`log`才慢慢找到规律，然后正确处理的。\n- add: 自己横向滑动时，不让父容器滑动。2017-05-07 22:07:55\n\u003e 好吧。前言后语就这些了。\n\u003e 顺便安利一个`log`库：[从Log切换到LogUtils](https://github.com/pengwei1024/LogUtils)。\n**这个`log`库满足你对`log`的所有幻想！**\n\n源码如下：\n```java\npackage com.python.cat.animatebutton.view;\n\nimport android.content.Context;\nimport android.support.annotation.IntRange;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.v4.view.ViewCompat;\nimport android.support.v4.widget.ViewDragHelper;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\n\nimport com.python.cat.animatorbutton.BuildConfig;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static com.python.cat.animatebutton.view.SwipeItemLayout.State.Center;\nimport static com.python.cat.animatebutton.view.SwipeItemLayout.State.Right;\n\n\n/**\n * packageName: com.python.cat.animatebutton.view\n * Created on 2017/5/6.\n * 右边有菜单的item\n *\n * @author cat\n */\npublic class SwipeItemLayout extends FrameLayout {\n\n    @SuppressWarnings(\"FieldCanBeLocal\")\n    private int mHeight;\n    private int mWidth;\n    private ViewDragHelper mDragHelper;\n    private List\u003cItemPos\u003e mItemSet;\n    private OnOpenStatedListener mViewStateListener;\n    private DragCallback mDragCallBack;\n\n    public SwipeItemLayout(Context context) {\n        this(context, null);\n    }\n\n    public SwipeItemLayout(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public SwipeItemLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n//        setOrientation(HORIZONTAL); // 验证说明ok\n        initDragHelper();\n    }\n\n    private void initDragHelper() {\n        mDragCallBack = new DragCallback();\n        mDragHelper = ViewDragHelper.create(this, 1, mDragCallBack);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n//        LogUtils.w(\"on measure....\");\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n//        LogUtils.w(\"on finish inflate....\");\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        this.mWidth = w;\n        this.mHeight = h;\n        int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();\n//        LogUtils.i(\"touchSlop =  %d\", touchSlop);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n//        super.onLayout(changed, left, top, right, bottom);\n        layoutChildren(0, 0);\n//        LogUtils.w(\"on layout.........\");\n    }\n\n    private void layoutChildren(int dx, int dy) {\n        mItemSet = new ArrayList\u003c\u003e();\n        mItemSet.clear();\n        int childCount = getChildCount();\n        int cl, ct, cr, cb;\n        int tempX = 0;\n        boolean isFirstView;\n        for (int i = 0; i \u003c childCount; i++) {\n            isFirstView = i == 0;\n            View child = getChildAt(i);\n            MarginLayoutParams params = getChildLayoutParams(child);\n            cl = tempX + params.leftMargin + dx;\n            ct = params.topMargin + get().getPaddingTop() + dy;\n            if (isFirstView) {\n                // first view\n                cl += get().getPaddingLeft();\n            }\n            cr = cl + getChildWidth(child);\n            cb = ct + getChildHeight(child);\n            tempX += cr + params.rightMargin;\n            //noinspection unused\n            int bX = getChildWidth(child)\n                    + params.leftMargin + params.rightMargin\n                    + getPaddingLeft() + getPaddingRight();\n\n            // 当子View完全填充时， ax == bx\n//            LogUtils.w(\"index ---ax= \" + aX + \" , bx=\" + bX);\n//            LogUtils.w(\"index = %d, left= %d, top= %d, right=%d, bottom=%d\", cl, ct, cr, cb);\n            child.layout(cl, ct, cr, cb);\n            ItemPos it = new ItemPos(i, cl, ct, cr, cb);\n            mItemSet.add(it);\n        }\n    }\n\n\n    private class ItemPos {\n        int index;\n        int left;\n        int top;\n        int right;\n        int bottom;\n\n        ItemPos(int index, int left, int top, int right, int bottom) {\n            this.index = index;\n            this.left = left;\n            this.top = top;\n            this.right = right;\n            this.bottom = bottom;\n        }\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent event) {\n\n        return mDragHelper.shouldInterceptTouchEvent(event);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        mDragHelper.processTouchEvent(event);\n        return true;\n    }\n\n    private class DragCallback extends ViewDragHelper.Callback {\n\n        State mCurrentOpen = Center; // default is center\n        ScrollState scrollState = new ScrollState();\n        @IntRange(from = 0, to = 2)\n        private int mDragState = ViewDragHelper.STATE_IDLE; // default is idle\n\n        class ScrollState {\n            static final int LEFT = -1;\n            static final int RIGHT = 1;\n            int scrollDirection; // Left or Right\n\n            @Override\n            public String toString() {\n                return \"ScrollState{\" +\n                        \"scrollDirection=\" + scrollDirection +\n                        '}';\n            }\n        }\n\n        @Override\n        public int getViewHorizontalDragRange(View child) {\n            int dx = getMeasuredWidth() - child.getMeasuredWidth();\n//            LogUtils.w(\"dx = \" + dx);\n            return dx;\n        }\n\n        @Override\n        public boolean tryCaptureView(View child, int pointerId) {\n            return true;\n        }\n\n        /**\n         * @param left 父容器的左侧，到child的左侧的距离\n         */\n        @Override\n        public int clampViewPositionHorizontal(View child, int left, int dx) {\n//            LogUtils.w(\"clampH : \" + left + \" , \" + dx + \" , \" + child);\n            getParent().requestDisallowInterceptTouchEvent(true); // 不给外层布局上下滑动\n            View childAt1 = getChildAt(1);\n            // 滑动第一个view\n            if (child == getChildAt(0)) {\n                // left  [getPaddingLeft() + getChildLayoutParams(child).leftMargin,\n                int maxL = getPaddingLeft() + getChildLayoutParams(child).leftMargin;\n\n                if (left \u003e maxL) {\n                    // 向右 ok\n                    left = maxL;\n                } else {\n                    // 向左 ok\n                    int min = mWidth - getChildWidthWithMargin(child) - getChildWidthWithMargin(childAt1);\n                    if (left \u003c min) {\n                        left = min;\n                    }\n                }\n            }\n            // 滑动第二个view\n            else if (child == getChildAt(1)) {\n                int max = mItemSet.get(1).left;\n                int min = mWidth - getChildWidthWithMargin(child) - getPaddingRight();\n                if (left \u003e max) {\n                    // 向右 ok\n                    left = max;\n                } else if (left \u003c min) {\n                    // 向左 ok\n                    left = min;\n                }\n            }\n            if (dx \u003e 0) {\n                scrollState.scrollDirection = ScrollState.RIGHT;\n            } else if (dx \u003c 0) {\n                scrollState.scrollDirection = ScrollState.LEFT;\n            }\n            return left;\n        }\n\n        @Override\n        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {\n//            LogUtils.w(\"onViewPositionChanged : \" + left + \" , \" + top + \" , \" + dx + \" , \" + dy);\n            View childAt0 = getChildAt(0);\n            View childAt1 = getChildAt(1);\n            if (changedView == childAt0) {\n                ItemPos it = mItemSet.get(1);\n                int moveX = left - mItemSet.get(0).left;\n                childAt1.layout(it.left + moveX, it.top, it.right + moveX, it.bottom + dy);\n            } else if (changedView == childAt1) {\n                ItemPos it = mItemSet.get(0);\n                int moveX = left - mItemSet.get(1).left;\n                childAt0.layout(it.left + moveX, it.top, it.right + moveX, it.bottom + dy);\n            }\n        }\n\n        @Override\n        public int clampViewPositionVertical(View child, int top, int dy) {\n            // 继续保持margin与padding\n            return getPaddingTop() + getChildLayoutParams(child).topMargin;\n        }\n\n        @Override\n        public void onViewDragStateChanged(int state) {\n            super.onViewDragStateChanged(state);\n            this.mDragState = state;\n//            LogUtils.e(\"ViewDragState == \" + state);\n        }\n\n        @Override\n        public void onViewReleased(View releasedChild, float xvel, float yvel) {\n\n            View childAt0 = getChildAt(0);\n            View childAt1 = getChildAt(1);\n\n            int a = getPaddingLeft();\n            int d = getChildLayoutParams(childAt0).leftMargin;\n            int L = getChildAt(0).getLeft();\n            int k = getChildWidth(childAt1);\n            if (BuildConfig.DEBUG) {\n                int b = getWidth();\n                int c = getPaddingRight();\n                int e = getChildLayoutParams(childAt0).rightMargin;\n                int f = getChildWidthWithMargin(childAt0);\n                int g = getChildWidth(childAt0);\n                int h = getChildLayoutParams(childAt1).leftMargin;\n                int i = getChildLayoutParams(childAt1).rightMargin;\n                int j = getChildWidthWithMargin(childAt1);\n//                LogUtils.e(\"release: \" + a + \" , \" + b + \" , \"\n//                        + c + \" , \" + d + \" , \" + e + \" , \"\n//                        + f + \" , \" + g + \" , \" + h + \" , \"\n//                        + i + \" , \" + j + \" , \" + k + \" , \"\n//                        + L);\n//                // L == -34 , d==e==26 , a==c==80 , ---\u003e k/2 == 140\n//                LogUtils.e(\"L-d-a = \" + (L - d - a) + \" k/2 = \" + k / 2); // 中点真难找啊..\n\n            }\n            State temp = this.mCurrentOpen;\n//            LogUtils.e(scrollState);\n            int left = L - d - a;\n            int change; // 释放临界值：不要放中间，放1/4处就好了\n            int absLeft = Math.abs(left);\n            if (scrollState.scrollDirection == ScrollState.LEFT) {\n                // 左滑\n                change = k / 4;\n                if (absLeft \u003e change) {\n                    // 左滑到底\n                    openRight(childAt0, childAt1);\n\n                } else {\n                    // 右滑到底\n                    openCenter(childAt0, childAt1);\n\n                }\n//                LogUtils.e(\"left = \" + left + \" absLeft = \" + absLeft + \" change = \" + change);\n            } else if (scrollState.scrollDirection == ScrollState.RIGHT) {\n                // 右滑\n                change = k * 3 / 4;\n                if (absLeft \u003e change) {\n                    // 左滑到底\n                    openRight(childAt0, childAt1);\n                    this.mCurrentOpen = Right;\n                } else {\n                    // 右滑到底\n                    openCenter(childAt0, childAt1);\n                    this.mCurrentOpen = Center;\n                }\n//                LogUtils.e(\"left = \" + left + \" absLeft = \" + absLeft + \" change = \" + change);\n            }\n\n//            LogUtils.e(\"xv = \" + xvel + \" , yv = \" + yvel);\n//            xvel\u003c0 --\u003e右滑  \u003e0 左滑  | 缓慢滑就 == 0\n\n//          LogUtils.e(\"temp = \" + temp + \" , state = \" + mCurrentOpen);\n            if (mViewStateListener != null) {\n                mViewStateListener.onViewOpen(temp != this.mCurrentOpen, this.mCurrentOpen);\n            }\n            invalidate();\n        }\n\n        // ####################################\n        private void openCenter(View childAt0, View childAt1) {\n            mDragHelper.smoothSlideViewTo(childAt0, mItemSet.get(0).left, mItemSet.get(0).top);\n            mDragHelper.smoothSlideViewTo(childAt1, mItemSet.get(1).left, mItemSet.get(1).top);\n            this.mCurrentOpen = Center;\n        }\n\n        private void openRight(View childAt0, View childAt1) {\n            mDragHelper.smoothSlideViewTo(childAt0,\n                    mItemSet.get(0).left - getChildWidthWithMargin(childAt1),\n                    mItemSet.get(0).top);\n            mDragHelper.smoothSlideViewTo(childAt1,\n                    mItemSet.get(1).left - getChildWidthWithMargin(childAt1),\n                    mItemSet.get(1).top);\n\n            this.mCurrentOpen = Right;\n        }\n    }\n\n    @Override\n    public void computeScroll() {\n        if (mDragHelper.continueSettling(true)) {\n            ViewCompat.postInvalidateOnAnimation(this);\n        }\n    }\n\n    public void setOnViewStatedChangedListener(OnOpenStatedListener listener) {\n        this.mViewStateListener = listener;\n    }\n\n    public interface OnOpenStatedListener {\n        @SuppressWarnings(\"unused\")\n        void onViewOpen(boolean changed, State state);\n    }\n\n    @SuppressWarnings(\"unused\")\n    public enum State {\n        Left, Center, Right,\n    }\n\n    @SuppressWarnings(\"unused\")\n    public State getOpenState() {\n        return mDragCallBack.mCurrentOpen;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public int getDragState() {\n        return mDragCallBack.mDragState;\n    }\n\n    public State openRight() {\n        mDragCallBack.openRight(getChildAt(0), getChildAt(1));\n        return mDragCallBack.mCurrentOpen;\n    }\n\n    public State openCenter() {\n        mDragCallBack.openCenter(getChildAt(0), getChildAt(1));\n        return mDragCallBack.mCurrentOpen;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public State changeState() {\n        switch (getOpenState()) {\n            case Center:\n                openRight();\n                break;\n            case Right:\n                openCenter();\n                break;\n        }\n        return mDragCallBack.mCurrentOpen;\n    }\n\n    // ######################---##########################\n    private MarginLayoutParams getChildLayoutParams(@NonNull View child) {\n        ViewGroup.LayoutParams pa = child.getLayoutParams();\n        if (pa instanceof MarginLayoutParams) {\n            return (MarginLayoutParams) pa;\n        } else {\n            throw new RuntimeException(pa == null ? null : pa.toString());\n        }\n    }\n\n    private int getChildWidth(@NonNull View child) {\n        return child.getMeasuredWidth();\n    }\n\n    private int getChildHeight(@NonNull View child) {\n        return child.getMeasuredHeight();\n    }\n\n    private int getChildWidthWithMargin(@NonNull View child) {\n        MarginLayoutParams params = getChildLayoutParams(child);\n        return getChildWidth(child) + params.leftMargin + params.rightMargin;\n    }\n\n    @SuppressWarnings(\"unused\")\n    private int getChildHeightWithMargin(@NonNull View child) {\n        MarginLayoutParams params = getChildLayoutParams(child);\n        return getChildWidth(child) + params.topMargin + params.bottomMargin;\n    }\n\n    private ViewGroup get() {\n        return this;\n    }\n}\n```\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpythoncat1024%2Fswipeitemlayout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpythoncat1024%2Fswipeitemlayout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpythoncat1024%2Fswipeitemlayout/lists"}