欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android 左滑删除 一个简单的自定义控件

程序员文章站 2022-06-08 17:34:16
...

转自那里忘记了,并非原创


package com.guo.qlzx.nongji.service.costom;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.Scroller;



/**
 * Created by 李 on 2018/5/29.
 */

public class SwipeItemLayout  extends ViewGroup {
    enum Mode{
        RESET, DRAG, FLING, CLICK
    }
    private Mode touchMode;

    private View mainItemView;

    private boolean mInLayout =false;

    private int scrollOffset;
    private int maxScrollOffset;

    private ScrollRunnable scrollRunnable;

    public SwipeItemLayout(Context context) {
        this(context,null);
    }

    public SwipeItemLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        touchMode = Mode.RESET;
        scrollOffset = 0;

        scrollRunnable = new ScrollRunnable(context);
    }

    public int getScrollOffset(){
        return scrollOffset;
    }

    public void open(){
        if(scrollOffset!=-maxScrollOffset){
            if(touchMode== Mode.FLING)
                scrollRunnable.abort();

            scrollRunnable.startScroll(scrollOffset,-maxScrollOffset);
        }
    }

    public void close(){
        if(scrollOffset!=0){
            if(touchMode== Mode.FLING)
                scrollRunnable.abort();

            scrollRunnable.startScroll(scrollOffset,0);
        }
    }

    void fling(int xVel){
        scrollRunnable.startFling(scrollOffset,xVel);
    }

    void revise(){
        if(scrollOffset<-maxScrollOffset/2)
            open();
        else
            close();
    }

    private void ensureChildren(){
        int childCount = getChildCount();

        for (int i=0;i<childCount;i++){
            View childView = getChildAt(i);
            ViewGroup.LayoutParams tempLp = childView.getLayoutParams();

            if(tempLp==null || !(tempLp instanceof LayoutParams))
                throw new IllegalStateException("缺少layout参数");

            LayoutParams lp = (LayoutParams) tempLp;
            if(lp.itemType==0x01){
                mainItemView = childView;
            }
        }

        if(mainItemView==null)
            throw new IllegalStateException("main item不能为空");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //确定children
        ensureChildren();

        //先测量main
        LayoutParams lp = (LayoutParams) mainItemView.getLayoutParams();

        measureChildWithMargins(
                mainItemView,
                widthMeasureSpec,getPaddingLeft()+getPaddingRight(),
                heightMeasureSpec,getPaddingTop()+getPaddingBottom());

        setMeasuredDimension(
                mainItemView.getMeasuredWidth()+ getPaddingLeft()+getPaddingRight()+ lp.leftMargin+lp.rightMargin
                ,mainItemView.getMeasuredHeight()+getPaddingTop()+getPaddingBottom()+lp.topMargin+lp.bottomMargin);

        //测试menu
        int menuWidthSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        int menuHeightSpec = MeasureSpec.makeMeasureSpec(mainItemView.getMeasuredHeight(),MeasureSpec.EXACTLY);
        for(int i=0;i<getChildCount();i++){
            View menuView = getChildAt(i);
            lp = (LayoutParams) menuView.getLayoutParams();

            if(lp.itemType==0x01)
                continue;

            measureChildWithMargins(menuView,menuWidthSpec,0,menuHeightSpec,0);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mInLayout = true;

        //确定children
        ensureChildren();

        int pl = getPaddingLeft();
        int pt = getPaddingTop();
        int pr = getPaddingRight();
        int pb = getPaddingBottom();

        LayoutParams lp;

        //layout main
        lp = (LayoutParams) mainItemView.getLayoutParams();
        mainItemView.layout(
                pl+lp.leftMargin,
                pt+lp.topMargin,
                getWidth()-pr-lp.rightMargin,
                getHeight()-pb-lp.bottomMargin);

        //layout menu
        int totalLength = 0;
        int menuLeft = mainItemView.getRight()+lp.rightMargin;
        for(int i=0;i<getChildCount();i++){
            View menuView = getChildAt(i);
            lp = (LayoutParams) menuView.getLayoutParams();

            if(lp.itemType==0x01)
                continue;

            int tempLeft = menuLeft+lp.leftMargin;
            int tempTop = pt+lp.topMargin;
            menuView.layout(
                    tempLeft,
                    tempTop,
                    tempLeft+menuView.getMeasuredWidth()+lp.rightMargin,
                    tempTop+menuView.getMeasuredHeight()+lp.bottomMargin);

            menuLeft = menuView.getRight()+lp.rightMargin;
            totalLength += lp.leftMargin+lp.rightMargin+menuView.getMeasuredWidth();
        }

        maxScrollOffset = totalLength;
        scrollOffset = scrollOffset<-maxScrollOffset/2 ? -maxScrollOffset:0;

        offsetChildrenLeftAndRight(scrollOffset);

        mInLayout = false;
    }

    void offsetChildrenLeftAndRight(int delta){
        for(int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            ViewCompat.offsetLeftAndRight(childView,delta);
        }
    }

    @Override
    public void requestLayout() {
        if (!mInLayout) {
            super.requestLayout();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(scrollRunnable);
        touchMode = Mode.RESET;
        scrollOffset = 0;
    }

    //展开的情况下,拦截down event,避免触发点击main事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                View pointView = findTopChildUnder(this,x,y);
                if(pointView!=null && pointView==mainItemView && scrollOffset !=0)
                    return true;
                break;
            }

            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
                break;

            case MotionEvent.ACTION_UP:{
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                View pointView = findTopChildUnder(this,x,y);
                if(pointView!=null && pointView==mainItemView && touchMode== Mode.CLICK && scrollOffset !=0)
                    return true;
            }
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                View pointView = findTopChildUnder(this,x,y);
                if(pointView!=null && pointView==mainItemView && scrollOffset !=0)
                    return true;
                break;
            }

            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
                break;

            case MotionEvent.ACTION_UP:{
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                View pointView = findTopChildUnder(this,x,y);
                if(pointView!=null && pointView==mainItemView && touchMode== Mode.CLICK && scrollOffset !=0) {
                    close();
                    return true;
                }
            }
        }

        return false;
    }

    void setTouchMode(Mode mode){
        if(mode==touchMode)
            return;

        if(touchMode== Mode.FLING)
            removeCallbacks(scrollRunnable);

        touchMode = mode;
    }

    public Mode getTouchMode(){
        return touchMode;
    }

    boolean trackMotionScroll(int deltaX){
        if(deltaX==0)
            return true;

        boolean over = false;
        int newLeft = scrollOffset+deltaX;
        if((deltaX>0 && newLeft>0) || (deltaX<0 && newLeft<-maxScrollOffset)){
            over = true;
            newLeft = Math.min(newLeft,0);
            newLeft = Math.max(newLeft,-maxScrollOffset);
        }

        offsetChildrenLeftAndRight(newLeft-scrollOffset);
        scrollOffset = newLeft;
        return over;
    }

    private static final Interpolator sInterpolator = new Interpolator() {
        @Override
        public float getInterpolation(float t) {
            t -= 1.0f;
            return t * t * t * t * t + 1.0f;
        }
    };

    private class ScrollRunnable implements Runnable{
        private Scroller scroller;
        private boolean abort;
        private int minVelocity;

        ScrollRunnable(Context context){
            scroller = new Scroller(context,sInterpolator);
            abort = false;

            ViewConfiguration configuration = ViewConfiguration.get(context);
            minVelocity = configuration.getScaledMinimumFlingVelocity();
        }

        void startScroll(int startX,int endX){
            if(startX!=endX){
                Log.e("scroll - startX - endX",""+startX+" "+endX);
                setTouchMode(Mode.FLING);
                abort = false;

                scroller.startScroll(startX,0,endX-startX,0, 400);
                ViewCompat.postOnAnimation(SwipeItemLayout.this,this);
            }
        }

        void startFling(int startX,int xVel){
            Log.e("fling - startX",""+startX);

            if(xVel> minVelocity && startX!=0) {
                startScroll(startX, 0);
                return;
            }

            if(xVel<-minVelocity && startX!=-maxScrollOffset) {
                startScroll(startX, -maxScrollOffset);
                return;
            }

            startScroll(startX,startX>-maxScrollOffset/2 ? 0:-maxScrollOffset);
        }

        void abort(){
            if(!abort){
                abort = true;
                if(!scroller.isFinished()){
                    scroller.abortAnimation();
                    removeCallbacks(this);
                }
            }
        }

        @Override
        public void run() {
            Log.e("abort",Boolean.toString(abort));
            if(!abort){
                boolean more = scroller.computeScrollOffset();
                int curX = scroller.getCurrX();
                Log.e("curX",""+curX);

                boolean atEdge = false;
                if(curX!=scrollOffset)
                    atEdge = trackMotionScroll(curX-scrollOffset);

                if(more && !atEdge) {
                    ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
                    return;
                }else{
                    removeCallbacks(this);
                    if(!scroller.isFinished())
                        scroller.abortAnimation();
                    setTouchMode(Mode.RESET);
                }
            }
        }
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams ? (LayoutParams) p : new LayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams && super.checkLayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    public static class LayoutParams extends MarginLayoutParams{
        public int itemType = -1;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SwipeItemLayout_Layout);
            itemType = a.getInt(R.styleable.SwipeItemLayout_Layout_layout_itemType,-1);
            a.recycle();
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            itemType = source.itemType;
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }
    }

    static View findTopChildUnder(ViewGroup parent,int x, int y) {
        final int childCount = parent.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = parent.getChildAt(i);
            if (x >= child.getLeft() && x < child.getRight()
                    && y >= child.getTop() && y < child.getBottom()) {
                return child;
            }
        }
        return null;
    }


    public static class OnSwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
        private SwipeItemLayout captureItem;
        private float lastMotionX;
        private float lastMotionY;
        private VelocityTracker velocityTracker;

        private int activePointerId;

        private int touchSlop;
        private int maximumVelocity;

        private boolean parentHandled;
        private boolean probingParentProcess;

        private boolean ignoreActions = false;

        public OnSwipeItemTouchListener(Context context){
            ViewConfiguration configuration = ViewConfiguration.get(context);
            touchSlop = configuration.getScaledTouchSlop();
            maximumVelocity = configuration.getScaledMaximumFlingVelocity();
            activePointerId = -1;
            parentHandled = false;
            probingParentProcess = false;
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
            if(probingParentProcess)
                return false;

            boolean intercept = false;
            final int action = ev.getActionMasked();

            if(action!=MotionEvent.ACTION_DOWN && ignoreActions)
                return true;

            if(action!=MotionEvent.ACTION_DOWN && (captureItem==null||parentHandled))
                return false;

            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain();
            }
            velocityTracker.addMovement(ev);

            switch (action){
                case MotionEvent.ACTION_DOWN:{
                    ignoreActions = false;
                    parentHandled = false;
                    activePointerId = ev.getPointerId(0);
                    final float x = ev.getX();
                    final float y = ev.getY();
                    lastMotionX = x;
                    lastMotionY = y;

                    boolean pointOther = false;
                    SwipeItemLayout pointItem = null;
                    //首先知道ev针对的是哪个item
                    View pointView = findTopChildUnder(rv,(int)x,(int)y);
                    if(pointView==null || !(pointView instanceof SwipeItemLayout)){
                        //可能是head view或bottom view
                        pointOther = true;
                    }else
                        pointItem = (SwipeItemLayout) pointView;

                    //此时的pointOther=true,意味着点击的view为空或者点击的不是item
                    //还没有把点击的是item但是不是capture item给过滤出来
                    if(!pointOther && (captureItem ==null || captureItem !=pointItem))
                        pointOther = true;

                    //点击的是capture item
                    if(!pointOther){
                        Mode mode = captureItem.getTouchMode();

                        //如果它在fling,就转为drag
                        //需要拦截,并且requestDisallowInterceptTouchEvent
                        boolean disallowIntercept = false;
                        if(mode== Mode.FLING){
                            captureItem.setTouchMode(Mode.DRAG);
                            disallowIntercept = true;
                            intercept = true;
                        }else {//如果是expand的,就不允许parent拦截
                            captureItem.setTouchMode(Mode.CLICK);
                            if(captureItem.getScrollOffset()!=0)
                                disallowIntercept = true;
                        }

                        if(disallowIntercept){
                            final ViewParent parent = rv.getParent();
                            if (parent!= null)
                                parent.requestDisallowInterceptTouchEvent(true);
                        }
                    }else{//capture item为null或者与point item不一样
                        //直接将其close掉
                        if(captureItem !=null &&
                                captureItem.getScrollOffset()!=0) {
                            captureItem.close();
                            ignoreActions = true;
                            return true;
                        }

                        captureItem = null;

                        if(pointItem!=null) {
                            captureItem = pointItem;
                            captureItem.setTouchMode(Mode.CLICK);
                        }
                    }

                    //如果parent处于fling状态,此时,parent就会转为drag。应该将后续move都交给parent处理
                    probingParentProcess = true;
                    parentHandled = rv.onInterceptTouchEvent(ev);
                    probingParentProcess = false;
                    if(parentHandled) {
                        intercept = false;
                        //在down时,就被认定为parent的drag,所以,直接交给parent处理即可
                        if(captureItem !=null && captureItem.getScrollOffset()!=0)
                            captureItem.close();
                    }
                    break;
                }

                case MotionEvent.ACTION_POINTER_DOWN: {
                    final int actionIndex = ev.getActionIndex();
                    activePointerId = ev.getPointerId(actionIndex);

                    lastMotionX = ev.getX(actionIndex);
                    lastMotionY = ev.getY(actionIndex);
                    break;
                }

                case MotionEvent.ACTION_POINTER_UP: {
                    final int actionIndex = ev.getActionIndex();
                    final int pointerId = ev.getPointerId(actionIndex);
                    if (pointerId == activePointerId) {
                        final int newIndex = actionIndex == 0 ? 1 : 0;
                        activePointerId = ev.getPointerId(newIndex);

                        lastMotionX = ev.getX(newIndex);
                        lastMotionY = ev.getY(newIndex);
                    }
                    break;
                }

                //down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
                case MotionEvent.ACTION_MOVE: {
                    final int activePointerIndex = ev.findPointerIndex(activePointerId);
                    if (activePointerIndex == -1)
                        break;

                    final int x = (int) (ev.getX(activePointerIndex)+.5f);
                    final int y = (int) ((int) ev.getY(activePointerIndex)+.5f);

                    int deltaX = (int) (x - lastMotionX);
                    int deltaY = (int)(y- lastMotionY);
                    final int xDiff = Math.abs(deltaX);
                    final int yDiff = Math.abs(deltaY);

                    Mode mode = captureItem.getTouchMode();

                    if(mode== Mode.CLICK){
                        //如果capture item是open的,下拉有两种处理方式:
                        //  1、下拉后,直接close item
                        //  2、只要是open的,就拦截所有它的消息,这样如果点击open的,就只能滑动该capture item
                        if(xDiff> touchSlop && xDiff>yDiff){
                            captureItem.setTouchMode(Mode.DRAG);
                            final ViewParent parent = rv.getParent();
                            parent.requestDisallowInterceptTouchEvent(true);

                            deltaX = deltaX>0 ? deltaX-touchSlop:deltaX+touchSlop;
                        }else/* if(yDiff>touchSlop)*/{
                            probingParentProcess = true;
                            parentHandled = rv.onInterceptTouchEvent(ev);
                            probingParentProcess = false;

                            if(parentHandled && captureItem.getScrollOffset() != 0)
                                captureItem.close();
                        }
                    }

                    mode = captureItem.getTouchMode();
                    if(mode== Mode.DRAG){
                        intercept = true;
                        lastMotionX = x;
                        lastMotionY = y;

                        //对capture item进行拖拽
                        captureItem.trackMotionScroll(deltaX);
                    }
                    break;
                }

                case MotionEvent.ACTION_UP:
                    Mode mode = captureItem.getTouchMode();
                    if(mode== Mode.DRAG){
                        final VelocityTracker velocityTracker = this.velocityTracker;
                        velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
                        int xVel = (int) velocityTracker.getXVelocity(activePointerId);
                        captureItem.fling(xVel);

                        intercept = true;
                    }
                    cancel();
                    break;

                case MotionEvent.ACTION_CANCEL:
                    captureItem.revise();
                    cancel();
                    break;
            }

            return intercept;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
            if(ignoreActions)
                return;

            final int action = ev.getActionMasked();
            final int actionIndex = ev.getActionIndex();

            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain();
            }
            velocityTracker.addMovement(ev);

            switch (action){
                case MotionEvent.ACTION_POINTER_DOWN:
                    activePointerId = ev.getPointerId(actionIndex);

                    lastMotionX = ev.getX(actionIndex);
                    lastMotionY = ev.getY(actionIndex);
                    break;

                case MotionEvent.ACTION_POINTER_UP:
                    final int pointerId = ev.getPointerId(actionIndex);
                    if(pointerId== activePointerId){
                        final int newIndex = actionIndex == 0 ? 1 : 0;
                        activePointerId = ev.getPointerId(newIndex);

                        lastMotionX = ev.getX(newIndex);
                        lastMotionY = ev.getY(newIndex);
                    }
                    break;

                //down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
                case MotionEvent.ACTION_MOVE: {
                    final int activePointerIndex = ev.findPointerIndex(activePointerId);
                    if (activePointerIndex == -1)
                        break;

                    final float x = ev.getX(activePointerIndex);
                    final float y = (int) ev.getY(activePointerIndex);

                    int deltaX = (int) (x - lastMotionX);

                    if(captureItem !=null && captureItem.getTouchMode()== Mode.DRAG){
                        lastMotionX = x;
                        lastMotionY = y;

                        //对capture item进行拖拽
                        captureItem.trackMotionScroll(deltaX);
                    }
                    break;
                }

                case MotionEvent.ACTION_UP:
                    if(captureItem !=null){
                        Mode mode = captureItem.getTouchMode();
                        if(mode== Mode.DRAG){
                            final VelocityTracker velocityTracker = this.velocityTracker;
                            velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
                            int xVel = (int) velocityTracker.getXVelocity(activePointerId);
                            captureItem.fling(xVel);
                        }
                    }
                    cancel();
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if(captureItem !=null)
                        captureItem.revise();

                    cancel();
                    break;

            }
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}

        void cancel(){
            parentHandled = false;
            activePointerId = -1;
            if(velocityTracker !=null){
                velocityTracker.recycle();
                velocityTracker = null;
            }
        }

    }
}

中间的其他资源文件 attrs_swipeitemlayout.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SwipeItemLayout.Layout">
        <attr name="layout_itemType">
            <flag name="main" value="0x01"/>
            <flag name="menu" value="0x02"/>
        </attr>
    </declare-styleable>
</resources>

最后就是例子= = 单个条目的写法,母为平常显示的,子为滑出来的删除

母:

 app:layout_itemType="main"

子:

 app:layout_itemType="menu"
<?xml version="1.0" encoding="utf-8"?>
<自定义地址.SwipeItemLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <LinearLayout
        android:id="@+id/ll_content"
        app:layout_itemType="main"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="44dp"
            android:textColor="@color/textcolor3"
            android:textSize="14sp"
            android:gravity="center_vertical"
            android:lines="1"
            tools:text="123"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/gray_e5"
            android:layout_marginLeft="15dp"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_delete"
        app:layout_itemType="menu"
        android:layout_width="60dp"
        android:layout_height="44dp"
        android:text="删除"
        android:textSize="18sp"
        android:textColor="@color/white"
        android:background="@color/red_e7"/>
</自定义地址.SwipeItemLayout>

无论是recycle还是listview 都需要在加入一句话

recyclerView.addOnItemTouchListener(new SwipeItemLayout.OnSwipeItemTouchListener(this));

这样才会滑动有效,不然是无效的

自然如果已经使用这个,单个条目点击事件会失效,自需要在母布局里价格id变成点击这个id跳转即可代替。

以上转自地址忘记了= =,作者有看到可以删除