仿 iOS 列表的編輯功能 - 刪除篇

在 iOS 的設置裏面,有一種編輯的效果,進入編輯狀態後,列表左邊推出圓形的刪除按鈕,點擊後再出現右邊確認刪除按鈕,至關於給用戶二次確認。看下在 Android 上如何實現。java

iOS 的效果以下:git

我實現的效果是這樣的:github

下面說說我是怎麼作的吧。ide

EditLayout

咱們自定義了一個 EditLayout 繼承 FrameLayout。
能夠看出,這個控件由左中右三部分組成,對應的,我在 EditLsyout 裏建立了如下成員變量:佈局

private View mContentView;  //內容部分
private View mLeftView;     //左邊圓形刪除按鍵
private View mRightView;    //右邊刪除按鍵
private int mWidth;         //內容部分寬度
private int mHeight;        //內容部分高度
private int mLeftWidth;     //左邊部分寬度
private int mRightWidth;    //右邊部分寬度複製代碼

獲取控件及寬高

當 View 中全部的子控件 均被映射成 xml 後,會觸發 onFinishInflate 方法,當 view 的大小發生變化時,會觸發 onSizeChanged 方法,因此咱們能夠這樣賦值:post

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mLeftView = getChildAt(0);
    mContentView = getChildAt(1);
    mRightView = getChildAt(2);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    mHeight = h;
    mRightWidth = mRightView.getMeasuredWidth();
    mLeftWidth = mLeftView.getMeasuredWidth();
}複製代碼

擺放控件位置

獲取到控件和寬高,咱們就能夠擺放它們的位置了。咱們知道,View 是經過 onLayout 方法來擺放控件位置的。這裏有兩種擺放方式,編輯狀態和非編輯狀態,代碼以下:this

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    //判斷是否爲編輯模式,擺放每一個子View的位置
    if (EditAdapter.isEdit) {
        mContentView.layout(mLeftWidth, 0, mLeftWidth + mWidth, mHeight);
        mRightView.layout(mWidth + mLeftWidth, 0, mRightWidth + mWidth + LeftWidth, mHeight);
        mLeftView.layout(0, 0, mLeftWidth, mHeight);
    } else {
        mContentView.layout(0, 0, mWidth, mHeight);
        mRightView.layout(mWidth, 0, mRightWidth + mWidth, mHeight);
        mLeftView.layout(-mLeftWidth, 0, 0, mHeight);
    }
}複製代碼

滑動效果

滑動效果,我交給了 ViewDragHelper 處理。要使用 ViewDragHelper ,須要實現一個 ViewDragHelper.Callback,這是一個抽象類,咱們這裏只關注它的三個方法:spa

//返回值決定 child 是否可拖拽
public boolean tryCaptureView(View child, int pointerId) //限定移動範圍,返回值爲對應控件的左邊位置 public int clampViewPositionHorizontal(View child, int left, int dx) //當 changedView 發生移動時的回調(能夠用來更新其餘子 View 的位置) public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)複製代碼

我實現的 Callback 代碼以下:3d

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        return false;
    }

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        if (child == mContentView) {
            if (left < -mRightWidth) {
                left = -mRightWidth;
            } else if (left > mLeftWidth) {
                left = mLeftWidth;
            }
        } else if (child == mRightView) {
            if (left < mWidth - mRightWidth) {
                left = mWidth - mRightWidth;
            } else if (left > mWidth) {
                left = mWidth;
            }
        } else if (child == mLeftView) {
            if (left < mWidth - mRightWidth) {
                left = mWidth - mRightWidth;
            } else if (left > -mLeftWidth) {
                left = 0 - mLeftWidth;
            }
        }
        return left;
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        if (changedView == mContentView) {
            mRightView.offsetLeftAndRight(dx);
            mLeftView.offsetLeftAndRight(dx);
        } else if (changedView == mRightView) {
            mContentView.offsetLeftAndRight(dx);
            mLeftView.offsetLeftAndRight(dx);
        } else if (changedView == mLeftView) {
            mContentView.offsetLeftAndRight(dx);
            mRightView.offsetLeftAndRight(dx);
        }
        invalidate();
    }
};

mDragHelper = ViewDragHelper.create(this, mCallback);複製代碼

對了,實現滑動還須要重寫 computeScroll 方法:code

@Override
public void computeScroll() {
    super.computeScroll();
    if (mDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}複製代碼

三種狀態

咱們這個控件存在三種狀態,分別是左邊展開,右邊展開,還有關閉。相應的,咱們定義三個方法,用於滑動到不一樣的狀態:

/** * 展開左側 */
public void openLeft() {
    if (mOnStateChangeListener != null) {
        mOnStateChangeListener.onLeftOpen(this);
    }
    mDragHelper.smoothSlideViewTo(mContentView, mLeftWidth, 0);
    invalidate();
}

/** * 展開右側 */
public void openRight() {
    if (mOnStateChangeListener != null) {
        mOnStateChangeListener.onRightOpen(this);
    }
    mDragHelper.smoothSlideViewTo(mContentView, -mRightWidth, 0);
    invalidate();
}

/** * 關閉 */
public void close() {
    if (mOnStateChangeListener != null) {
        mOnStateChangeListener.onClose(this);
    }
    mDragHelper.smoothSlideViewTo(mContentView, 0, 0);
    invalidate();
}複製代碼

mOnStateChangeListener 是一個監聽器,會在 EditLayout 狀態改變的時候調用。我在回調方法裏保存了當前向右展開的 EditLayout。

到這裏,EditLayout 就完成了。

EditAdapter

接下來看下適配器 EditAdapter。

item 佈局

item 的 xml 文件裏面,最外層用咱們的 EditLayout 包裹,而後裏面的三個子佈局,按順序,對應咱們左中右三個部分。

切換編輯模式

這裏須要定義一個 EditLayout 的集合 allItems,在 onBindViewHolder 的時候將佈局添加進去。
而後咱們定義兩個公開方法,用於切換全部 item 的狀態,在切換編輯模式的時候調用:

/** * 關閉全部 item */
public void closeAll() {
    for (EditLayout layout : allItems) {
        editLayout.close();
    }
}

/** * 將全部 item 向左展開 */
public void openLeftAll() {
    for (EditLayout layout : allItems) {
        editLayout.openLeft();
    }
}複製代碼

EditRecyclerView

當列表有某一項是右邊展開了,我但願在滑動列表的時候能將它關閉,變回向左展開的狀態,因此我自定義了一個 RecyclerView。

能夠重寫了 onTouchEvent 方法,實現上面說的效果:

@Override
public boolean onTouchEvent(MotionEvent e) {
    switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if (getAdapter() instanceof EditAdapter) {
                rightOpenItem = ((EditAdapter) getAdapter()).getRightOpenItem();
            }
            if (EditAdapter.isEdit && rightOpenItem != null) {
                rightOpenItem.openLeft();
            }
    }
    return super.onTouchEvent(e);
}複製代碼

當滑動列表的時候,先判斷是否有向右展開項,有的話就將它變回向左展開。

這樣就完成啦,妥妥的。

源碼地址

相關文章
相關標籤/搜索