在 iOS 的設定裡面,有一種編輯的效果,進入編輯狀態後,列表左邊推出圓形的刪除按鈕,點選後再出現右邊確認刪除按鈕,相當於給使用者二次確認。看下在 Android 上如何實現。
iOS 的效果如下:
我實現的效果是這樣的:
下面說說我是怎麼做的吧。
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 方法,所以我們可以這樣賦值:
@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 方法來擺放控制元件位置的。這裡有兩種擺放方式,編輯狀態和非編輯狀態,程式碼如下:
@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,這是一個抽象類,我們這裡只關注它的三個方法:
//返回值決定 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 程式碼如下:
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 方法:
@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);
}複製程式碼
當滑動列表的時候,先判斷是否有向右展開項,有的話就將它變回向左展開。
這樣就完成啦,妥妥的。