天天看點

仿微信、QQ左滑删除RecycleView的Item

效果圖:

仿微信、QQ左滑删除RecycleView的Item

 湊合着看吧,達到的效果就是測劃出現Delete後,觸碰任何地方都是會自動的滑回去

是一個自定義控件和RecycleView合用。

activity:

public class TestActivity2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);

        RecyclerView recyclerView = findViewById(R.id.RecyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        SlideAdapter slideAdapter = new SlideAdapter();
        recyclerView.setAdapter(slideAdapter);
        slideAdapter.setNewInstance(getDatas());

        findViewById(R.id.rel).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                slideAdapter.mSlideHelper.closeAllUpSide();
                return false;
            }
        });

    }

    private List<Bean> getDatas() {
        List<Bean> list = new ArrayList<>();
        for (int i = 0; i < 6; i++) {
            Bean b = new Bean("" + i, false);
            list.add(b);
        }
        return list;
    }

}
           

Activity的布局檔案:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rel"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

</RelativeLayout>
           

用到的recycleview的adapter我用的是BRVAH的BaseQuickAdapter

public class SlideAdapter extends BaseQuickAdapter<Bean, BaseViewHolder> {
    public final SlideHelper mSlideHelper = new SlideHelper();

    public SlideAdapter() {
        super(R.layout.adapter_slide);
    }

    @Override
    protected void convert(@NotNull BaseViewHolder holder, Bean item) {
        holder.setText(R.id.tv_content, item.content);

        final SlideLayout sl_slide = holder.getView(R.id.sl_slide);
        sl_slide.setOpen(false, false);
        sl_slide.setOnStateChangeListener(new SlideLayout.OnStateChangeListener() {
            @Override
            public boolean onInterceptTouchEvent(SlideLayout layout) {
                boolean result = mSlideHelper.closeAll(layout);
                return false;
            }

            @Override
            public void onStateChanged(SlideLayout layout, boolean open) {
                mSlideHelper.onStateChanged(layout, open);
            }
        });
    }
}
           

recycleview的item的布局檔案:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <!-- Just contain two view -->
    <com.example.fragmentadaptertest.slide.SlideLayout
        android:id="@+id/sl_slide"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- Content view -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="65dp"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@mipmap/ic_launcher" />

            <TextView
                android:id="@+id/tv_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:text="CONTENT"
                android:textColor="#000000"
                android:textSize="14dp" />
        </LinearLayout>

        <!-- Slide view -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_stick"
                android:layout_width="80dp"
                android:layout_height="match_parent"
                android:background="#C7C7CD"
                android:gravity="center"
                android:text="STICK"
                android:textColor="#000000"
                android:textSize="14dp" />

            <TextView
                android:id="@+id/tv_delete"
                android:layout_width="80dp"
                android:layout_height="match_parent"
                android:background="#FF3A30"
                android:gravity="center"
                android:text="DELETE"
                android:textColor="#000000"
                android:textSize="14dp" />
        </LinearLayout>
    </com.example.fragmentadaptertest.slide.SlideLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#e3e4e5" />
</LinearLayout>
           

然後用到的自定義View:

public class SlideLayout extends ViewGroup {
    private int mWidth;
    private int mHeight;

    private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
    private Scroller mScroller;
    private int mLeftBorder;
    private int mRightBorder;
    private int mTouchSlop;
    private int mSlideSlop;
    private int mDuration;

    // TouchEvent_ACTION_DOWN coordinates (x, y)
    private float mTouchX, mTouchY;

    // TouchEvent last coordinate (x, y)
    private float mLastTouchX;
    private boolean mIsDragging;
    private boolean mIsOpen;
    private boolean mIsEnable;
    private OnStateChangeListener mOnStateChangeListener;

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

    public SlideLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initTypedArray(context, attrs);
        init(context);
    }

    private void initTypedArray(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SlideLayout);
        mSlideSlop = (int) typedArray.getDimension(R.styleable.SlideLayout_sl_slideSlop,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics()));
        mDuration = typedArray.getInteger(R.styleable.SlideLayout_sl_duration, 250);
        mIsEnable = typedArray.getBoolean(R.styleable.SlideLayout_sl_enable, true);
        typedArray.recycle();
    }

    private void init(Context context) {
        mScroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

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

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        return new LayoutParams(lp);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final SlideLayout.LayoutParams lp = (SlideLayout.LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                    childState = combineMeasuredStates(childState, child.getMeasuredState());
                }

                if (measureMatchParentChildren) {
                    if (lp.width == SlideLayout.LayoutParams.MATCH_PARENT ||
                            lp.height == SlideLayout.LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // Check against our foreground's minimum height and width
            final Drawable drawable = getForeground();
            if (drawable != null) {
                maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
                maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
            }
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT));
        } else {
            setMeasuredDimension(mWidth, mHeight);
        }

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                if (lp.width == SlideLayout.LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == SlideLayout.LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }

        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        if (count <= 0) {
            return;
        }

        final int parentLeft = getPaddingLeft();
        final int parentRight = r - l - getPaddingRight();

        final int parentTop = getPaddingTop();
        final int parentBottom = b - t - getPaddingBottom();

        int left = 0, top = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                childTop = parentTop + lp.topMargin;
                childLeft = parentLeft + lp.leftMargin;

                // Layout horizontally for each child view in the ViewGroup
                child.layout(left + childLeft, childTop, left + childLeft + width, childTop + height);

                left += childLeft + width + lp.rightMargin + getPaddingRight();
            }
        }
        // Initialize left and right boundary values
        mLeftBorder = getChildAt(0).getLeft();
        mRightBorder = getChildAt(count - 1).getRight();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            final boolean intercepted = mOnStateChangeListener != null
                    && mOnStateChangeListener.onInterceptTouchEvent(this);
            if (intercepted) {
                return false;
            }

            final float x = ev.getRawX();
            final float y = ev.getRawY();
            mLastTouchX = mTouchX = x;
            mTouchY = y;
            mIsDragging = false;
            super.dispatchTouchEvent(ev);
            return true;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (!mIsEnable) {
            return super.onInterceptTouchEvent(ev);
        }
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            final float x = ev.getRawX();
            final float y = ev.getRawY();
            // Intercept child event when horizontal ACTION_MOVE value is greater than TouchSlop
            if (Math.abs(x - mTouchX) > mTouchSlop
                    && Math.abs(x - mTouchX) > Math.abs(y - mTouchY)) {
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mIsEnable) {
            return super.onTouchEvent(event);
        }

        final float x = event.getRawX();
        final float y = event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (!mIsDragging
                        && Math.abs(x - mTouchX) > mTouchSlop
                        && Math.abs(x - mTouchX) > Math.abs(y - mTouchY)) {
                    // Disable parent view interception events
                    requestDisallowInterceptTouchEvent(true);
                    mIsDragging = true;
                    mLastTouchX = x;
                    return super.onTouchEvent(event);
                }
                if (mIsDragging) {
                    final int offset = (int) (mLastTouchX - x);
                    if (getScrollX() + offset < 0) {
                        setOpen(false, false);
                        mTouchX = x; // Reset touch x
                    } else if (getScrollX() + offset > mRightBorder - mWidth) {
                        setOpen(true, false);
                        mTouchX = x; // Reset touch x
                    } else {
                        scrollBy(offset, 0);
                    }
                    mLastTouchX = x;
                    return true;
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mIsDragging) {
                    if (x - mTouchX < -mSlideSlop) {
                        setOpen(true, true);
                    } else if (x - mTouchX > mSlideSlop) {
                        setOpen(false, true);
                    } else {
                        setOpen(mIsOpen, true);
                    }
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(event);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    private void smoothScrollTo(int dstX, int duration) {
        int offset = dstX - getScrollX();
        mScroller.startScroll(getScrollX(), 0, offset, 0, duration);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    public boolean isEnable() {
        return mIsEnable;
    }

    public void setEnable(boolean enable) {
        this.mIsEnable = enable;
    }

    public boolean isOpen() {
        return mIsOpen;
    }

    public void open() {
        setOpen(true, true);
    }

    public void close() {
        setOpen(false, true);
    }

    /**
     * Set on or off status
     *
     * @param open     Open or close
     * @param withAnim Whether with animation effect
     */
    public void setOpen(boolean open, boolean withAnim) {
        if (mIsOpen != open && mOnStateChangeListener != null) {
            mOnStateChangeListener.onStateChanged(this, open);
        }
        mIsOpen = open;
        int x = mIsOpen ? mRightBorder - mWidth : 0;
        int y = 0;
        if (withAnim) {
            smoothScrollTo(x, mDuration);
        } else {
            scrollTo(x, y);
        }
    }

    public void setOnStateChangeListener(OnStateChangeListener listener) {
        this.mOnStateChangeListener = listener;
    }

    public abstract static class OnStateChangeListener {

        /**
         * Implement this method to intercept all touch screen motion events.
         *
         * @param layout This layout
         */
        public boolean onInterceptTouchEvent(SlideLayout layout) {
            return false;
        }

        public abstract void onStateChanged(SlideLayout layout, boolean open);
    }

    public static class LayoutParams extends MarginLayoutParams {

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

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

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

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

和自定義屬性:放在values下的attrs.xml中,沒有就建立一個

<declare-styleable name="SlideLayout">
        <attr name="sl_enable" format="boolean" />
        <attr name="sl_slideSlop" format="dimension" />
        <attr name="sl_duration" format="integer" />
    </declare-styleable>
           

會用到一個工具類:

public class SlideHelper {
    private final List<SlideLayout> mSlides = new ArrayList<>();

    public SlideHelper() {
    }

    public void onStateChanged(SlideLayout layout, boolean open) {
        if (open) {
            mSlides.add(layout);
        } else {
            mSlides.remove(layout);
        }
    }

    public boolean closeAll(SlideLayout layout) {
        if (mSlides.size() <= 0) {
            return false;
        }
        boolean result = false;
        for (int i = 0; i < mSlides.size(); i++) {
            SlideLayout slide = mSlides.get(i);
            if (slide != null && slide != layout) {
                slide.close();
                mSlides.remove(slide); // Unnecessary
                result = true;
                i--;
            }
        }
        return result;
    }


    public boolean closeAllUpSide() {
        if (mSlides.size() <= 0) {
            return false;
        }
        boolean result = false;
        for (int i = 0; i < mSlides.size(); i++) {
            SlideLayout slide = mSlides.get(i);
            if (slide != null) {
                slide.close();
                mSlides.remove(slide); // Unnecessary
                result = true;
                i--;
            }
        }
        return result;
    }
}