1. 程式人生 > >Android開發之仿QQ側滑刪除實現(二)

Android開發之仿QQ側滑刪除實現(二)

一、把SlideDelete的簡單樣式先做出來。

SlideDelete繼承自ViewGroup,在引用SlideDelete的xml的位置include進兩個layout,一個是內容,一個是刪除

一、1、準備兩個佈局

內容部分

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
android:background="#97d8da" android:gravity="center" android:orientation="vertical">
<TextView android:id="@+id/mTvContent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="文字" android:gravity="center"
android:textSize="28dp" />
</LinearLayout>

內容部分.png

刪除部分

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="130dp"
    android:layout_height="60dp"
    android:background="#ff0000"
    android:gravity
="center" android:orientation="vertical">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="刪除" android:textSize="22dp" android:textColor="#ffffff" /> </LinearLayout>

刪除部分.png

一.2、在activity_main裡面把放進SlideDelete這個控制元件,然後include進上面兩個佈局

activity_main

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amqr.slidedelete.MainActivity">
    <com.amqr.slidedelete.view.SlideDelete
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <!--文字部分-->
        <include layout="@layout/slide_content"/>

        <!--刪除部分-->
        <include layout="@layout/slide_delete"/>

    </com.amqr.slidedelete.view.SlideDelete>
</RelativeLayout>

一.3、在自定義控制元件SlideDelete裡面把利用onFinishInflate、onMeasure和onLayout讓控制元件顯示出來

public class SlideDelete extends ViewGroup{
    private View mContent; // 內容部分
    private View mDelete;  // 刪除部分
    public SlideDelete(Context context) {
        super(context);
    }
    public SlideDelete(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = getChildAt(0);
        mDelete = getChildAt(1);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 這跟mContent的父親的大小有關,父親是寬填充父窗體,高度是和孩子一樣是60dp
        mContent.measure(widthMeasureSpec,heightMeasureSpec); // 測量內容部分的大小
        LayoutParams layoutParams = mDelete.getLayoutParams();
        int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width,MeasureSpec.EXACTLY);
        int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height,MeasureSpec.EXACTLY);
        // 這個引數就需要指定為精確大小
        mDelete.measure(deleteWidth,deleteHeight); // 測量刪除部分的大小
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int mContentWidth = mContent.getMeasuredWidth();
        int mContentHeight = mContent.getMeasuredHeight();
        mContent.layout(0,0,mContentWidth,mContentHeight); // 擺放內容部分的位置
        int mDeleteWidth = mDelete.getMeasuredWidth();
        mDelete.layout(mContentWidth,0,
                mContentWidth + mDeleteWidth, mContentHeight); // 擺放刪除部分的位置
    }
}

至此,像下面這樣右側其實 刪除部分 已經繪製顯示在手機的右側了,只是螢幕上現在暫時還看不見


在本文中,我們用ViewDragHelper來做動畫效果。

ViewDragHelper簡介

ViewDragHelper是v4包種提供的用來的做滑動的

ViewDragHelper是在android4.3的時候釋出的,所以必須是android4.3以上的v4包才能支援ViewDragHelper

我們這裡的Demo要使用ViewDragHelper,所以我們在當面as的專案的gradle裡面引入v4包。

compile 'com.android.support:support-v4:23.1.1'

ViewDragHelper怎麼用?

簡單說,分兩步

第一步,
利用ViewDragHelper.create(ViewGroup forParent, Callback cb)建立一個ViewDragHelper的例項。

第二步,弄一個類繼承自ViewDragHelper.Callback,作為第一步中create方法的引數,複寫一下這麼幾個方法並且一些邏輯操作

tryCaptureView()
clampViewPositionHorizontal()
clampViewPositionVertical()
onViewPositionChanged()
onViewReleased()

實現動畫效果。

第二步中的複寫的那幾個方法很重要。這幾個方法到底分別的有什麼用呢?可以大概這麼理解:
我們知道TouchEvent大概可以分為三個狀態,Down(按下)、Move(移動)和Up(擡起)。

那麼在這三個不同的狀態裡面,與之關聯的就是上面的幾個方法:

  • Touch的down事件:
    回撥tryCaptureView()

  • Touch的move事件
    回撥
    clampViewPositionHorizontal()
    clampViewPositionVertical()
    onViewPositionChanged()

  • Touch的up事件
    回撥:onViewReleased()

來個程式碼看一下
繼承自ViewDragHelper.Callback的類複寫的幾個方法。

class MyDrawHelper extends ViewDragHelper.Callback {
    /**
     * Touch的down事件會回撥這個方法 tryCaptureView
     *
     * @Child:指定要動的孩子  (哪個孩子需要動起來)
     * @pointerId: 點的標記
     * @return : ViewDragHelper是否繼續分析處理 child的相關touch事件
     */
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        System.out.println("呼叫tryCaptureView");
        System.out.println("contentView : " + (mContent == child));
        return mContent == child || mDelete == child;
    }
    // Touch的move事件會回撥這面這幾個方法
    // clampViewPositionHorizontal
    // clampViewPositionVertical
    // onViewPositionChanged
    /**
     *
     * 捕獲了水平方向移動的位移資料
     * @param child 移動的孩子View
     * @param left 父容器的左上角到孩子View的距離
     * @param dx 增量值,其實就是移動的孩子View的左上角距離控制元件(父親)的距離,包含正負
     * @return 如何動
     *
     * 呼叫完此方法,在android2.3以上就會動起來了,2.3以及以下是海動不了的
     * 2.3不相容怎麼辦?沒事,我們複寫onViewPositionChanged就是為了解決這個問題的
     */
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        Log.d("Slide","增量值:   "+left);
        return left;
    }
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return super.clampViewPositionVertical(child, top, dy);
    }
    /**
     * 當View的位置改變時的回撥
     * @param changedView  哪個View的位置改變了
     * @param left  changedView的left
     * @param top  changedView的top
     * @param dx x方向的上的增量值
     * @param dy y方向上的增量值
     */
    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        invalidate();
        super.onViewPositionChanged(changedView, left, top, dx, dy);
    }
    /**
     * 相當於Touch的up的事件會回撥onViewReleased這個方法
     *
     * @param releasedChild
     * @param xvel x方向的速率
     * @param yvel y方向的速率
     */
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
    }
}

通過上面的詳細的備註,我們這些方法的用途都有了相當的瞭解了。需要注意的是,我們的clampViewPositionHorizontal和clampViewPositionHorizontal所產生的動畫效果在2.3以上才會有效果,如果要達到相容,我們就需要藉助onViewPositionChanged方法。

好啦,現在可以開始做動畫了。

二.1、當一個孩子動起來另外一個孩子也可以跟隨著動起來

        /**
         * 當View的位置改變時的回撥  這個方法的價值是結合clampViewPositionHorizontal或者clampViewPositionVertical
         * @param changedView  哪個View的位置改變了
         * @param left  changedView的left
         * @param top  changedView的top
         * @param dx x方向的上的增量值
         * @param dy y方向上的增量值
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //super.onViewPositionChanged(changedView, left, top, dx, dy);
            invalidate();
            if(changedView == mContent){ // 如果移動的是mContent
                //我們移動mContent的實惠要相應的聯動改變mDelete的位置
                // 怎麼改變mDelete的位置,當然是mDelete的layput方法啦
                int tempDeleteLeft = mContentWidth+left;
                int tempDeleteRight = mContentWidth+left + mDeleteWidth;
                mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight);
            }else{ // touch的是mDelete
                int tempContentLeft = left - mContentWidth;
                int tempContentRight = left;
                mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight);
            }
        }

跟著一起動.gif

孩子是一起跟著動起來了,出現越界的問題

二.2、解決越界問題

這個越界的問題為什麼會產生,是因為現在在clampViewPositionHorizontal方法裡面我們簡單粗暴地返回了left。這樣肯定是不行的。所以我們需要在這個方法上做一些處理

/**
 *
 * 捕獲了水平方向移動的位移資料
 * @param child 移動的孩子View
 * @param left 父容器的左上角到孩子View的距離
 * @param dx 增量值,其實就是移動的孩子View的左上角距離控制元件(父親)的距離,包含正負
 * @return 如何動
 *
 * 呼叫完此方法,在android2.3以上就會動起來了,2.3以及以下是海動不了的
 * 2.3不相容怎麼辦?沒事,我們複寫onViewPositionChanged就是為了解決這個問題的
 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
    Log.d("Slide","增量值:   "+left);
    if(child == mContent){ // 解決內容部分左右拖動的越界問題
        if(left>0){
            return 0;
        }else if(-left>mDeleteWidth){
            return -mDeleteWidth;
        }
    }
    if(child == mDelete){ // 解決刪除部分左右拖動的越界問題
        if(left<mContentWidth - mDeleteWidth){
            return mContentWidth - mDeleteWidth;
        }else if(left > mContentWidth){
            return mContentWidth;
        }
    }
    return left;
}

二、3、釋放時位置的歸正

/**
 * 相當於Touch的up的事件會回撥onViewReleased這個方法
 *
 * @param releasedChild
 * @param xvel  x方向的速率
 * @param yvel  y方向的速率
 */
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
    //super.onViewReleased(releasedChild, xvel, yvel);
    // 方法的引數裡面沒有left,那麼我們就採用 getLeft()這個方法
    int mConLeft = mContent.getLeft();
    // 這裡沒必要分來兩個孩子判斷
    if(-mConLeft>mDeleteWidth/2){
        mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight);
        mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight);
    }else{
        mContent.layout(0,0,mContentWidth,mContentHeight);
        mDelete.layout(mContentWidth,0,mContentWidth+mDeleteWidth,mDeleteHeight);
    }
    super.onViewReleased(releasedChild, xvel, yvel);
}

解決越界.gif

現在效果是實現了,但是鬆開手指的一瞬間位置歸正得有點突兀,我們需要做一些過渡動畫,才顯得自然。

二、4、位置歸正的過渡動畫

ViewDragHelper裡面給我們提供了一個方法,smoothSlideViewTo(View child, int finalLeft, int finalTo), smooth是平滑的意思,這個方法就是幫助我們做平滑滑動的。

smoothSlideViewTo(View child, int finalLeft, int finalTop)

public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
    mCapturedView = child;
    mActivePointerId = INVALID_POINTER;
    boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
    if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
        // If we're in an IDLE state to begin with and aren't moving anywhere, we
        // end up having a non-null capturedView with an IDLE dragState
        mCapturedView = null;
    }
    return continueSliding;
}

三個引數

child,那個孩子動
finalLeft + finalTop: 孩子運動到最後的左上角的點

通過孩子最後左上角的點就可以確定最後的應該到達的位置

上程式碼

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        //super.onViewReleased(releasedChild, xvel, yvel);
        // 方法的引數裡面沒有left,那麼我們就採用 getLeft()這個方法
        int mConLeft = mContent.getLeft();
        // 這裡沒必要分來兩個孩子判斷
        if(-mConLeft>mDeleteWidth/2){
            //mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight);
            //mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight);
            //採用ViewDragHelper的 smoothSlideViewTo 方法讓移動變得順滑自然,不會太生硬
            //smoothSlideViewTo只是模擬了資料,但是不會真正的動起來,動起來需要呼叫 invalidate
            // 而 invalidate 通過呼叫draw()等方法之後最後還是還是會呼叫 computeScroll 這個方法
            // 所以,使用 smoothSlideViewTo 做過渡動畫需要結合  invalidate方法 和 computeScroll方法
            // smoothSlideViewTo的動畫執行時間沒有暴露的引數可以設定,但是這個時間是google給我們經過大量計算給出合理時間
        viewDragHelper.smoothSlideViewTo(mContent,-mDeleteWidth,0);
            viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth-mDeleteWidth,0);
        }else{
            //mContent.layout(0,0,mContentWidth,mContentHeight);
            //mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mDeleteHeight);
            viewDragHelper.smoothSlideViewTo(mContent, 0, 0);
            viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth, 0);
        }
        invalidate();
        super.onViewReleased(releasedChild, xvel, yvel);
    }
}
@Override
public void computeScroll() {
    //super.computeScroll();
    // 把捕獲的View適當的時間移動,其實也可以理解為 smoothSlideViewTo 的模擬過程還沒完成
    if(viewDragHelper.continueSettling(true)){
        invalidate();
    }
    // 其實這個動畫過渡的過程大概在怎麼走呢?
    // 1、smoothSlideViewTo方法進行模擬資料,模擬後就就呼叫invalidate();
    // 2、invalidate()最終呼叫computeScroll,computeScroll做一次細微動畫,
    //    computeScroll判斷模擬資料是否徹底完成,還沒完成會再次呼叫invalidate
    // 3、遞迴呼叫,知道資料noni完成。
}  
}

item的自定義完成

到此為止,我們單個的側滑刪除的話的效果就實現了

三、在ListView裡面嵌入我們的自定義控制元件

新建一個Activity,假設名為MyActivity,並且把這個Activity設定為啟動頁。

MyActivity

public class MyActivity extends Activity{
    private ListView mLv;
    private ArrayList<String> mData;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        mLv = (ListView) findViewById(R.id.mLv);
        mData=new ArrayList<>();
        for(int i=0;i<200;i++){
            mData.add("文字"+i);
        }
        mLv.setAdapter(new MyAdapter());
    }
    class MyAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return mData.size();
        }
        @Override
        public Object getItem(int position) {
            return mData.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if(convertView == null){
                viewHolder = new ViewHolder();
                convertView = View.inflate(MyActivity.this,R.layout.item,null);
                viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete);
                viewHolder.mLlContent = (LinearLayout) convertView.findViewById(R.id.mLlContent);
                viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete);
                convertView.setTag(viewHolder);
            }else{
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() {
                @Override
                public void onOpen(SlideDelete slideDelete) {
                }
                @Override
                public void onClose(SlideDelete slideDelete) {
                }
            });
            return convertView;
        }
    }
    class ViewHolder{
        SlideDelete mSlideDelete;
        LinearLayout mLlContent;
        LinearLayout mLlDelete;
    }
}

關聯的activity_my

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amqr.slidedelete.MainActivity">
    <ListView
        android:id="@+id/mLv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</RelativeLayout>

adapter裡面的item

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.amqr.slidedelete.view.SlideDelete
        android:id="@+id/mSlideDelete"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <!--文字部分-->
        <include layout="@layout/slide_content"/>
        <!--刪除部分-->
        <include layout="@layout/slide_delete"/>
    </com.amqr.slidedelete.view.SlideDelete>
</LinearLayout>

當前效果


當前效果.gif

簡單的效果是實現了,但是這樣不好

實際使用中,我們通常是隻能一個item處於開啟狀態,
即A開啟,B必須處於關閉,不能A和B都處於開啟的狀態

怎麼辦呢。

只讓一個item處於開啟狀態

整理思路:
1、我們給SlideDelete新增介面和回撥,接口裡面有onOpen(SlideDelete slideDelete)和onClose(SlideDelete slideDelete)兩個方法

    // SlideDlete的介面
    public interface OnSlideDeleteListener {
        void onOpen(SlideDelete slideDelete);
        void onClose(SlideDelete slideDelete);
    }

2、暴露一個setOnSlideDeleteListener方法給外部呼叫,把SlideDelete的onViewReleased裡面的開啟和關閉抽取暴露出來,通過引數boolean決定是否顯示delete部分。

然後在onViewReleased裡面,真正呼叫介面的onOpen和onClose方法

private OnSlideDeleteListener onSlideDeleteListener;
    public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){
        this.onSlideDeleteListener = onSlideDeleteListener;
    }
/**
     * 相當於Touch的up的事件會回撥onViewReleased這個方法
     *
     * @param releasedChild
     * @param xvel  x方向的速率
     * @param yvel  y方向的速率
     */
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        //super.onViewReleased(releasedChild, xvel, yvel);
        // 方法的引數裡面沒有left,那麼我們就採用 getLeft()這個方法
        int mConLeft = mContent.getLeft();
        // 這裡沒必要分來兩個孩子判斷
        if(-mConLeft>mDeleteWidth/2){  // mDelete展示起來
            isShowDelete(true);
            if(onSlideDeleteListener != null){
                onSlideDeleteListener.onOpen(SlideDelete.this); // 呼叫介面開啟的方法
            }
        }else{    // mDetele隱藏起來
            isShowDelete(false);
            if(onSlideDeleteListener != null){
                onSlideDeleteListener.onClose(SlideDelete.this); // 呼叫介面的關閉的方法
            }
        }
        super.onViewReleased(releasedChild, xvel, yvel);
    }

3、在MyActivity的Adapter裡面呼叫SlideDelete暴露出來的實現介面的方法。
弄一個集合記錄起來已經開啟的item,每次getView的執行都先關閉已經開啟的item

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder;
    if(convertView == null){
        viewHolder = new ViewHolder();
        convertView = View.inflate(MyActivity.this,R.layout.item,null);
        viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete);
        viewHolder.mTvContent = (TextView) convertView.findViewById(R.id.mTvContent);
        viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete);
        convertView.setTag(viewHolder);
    }else{
        viewHolder = (ViewHolder) convertView.getTag();
    }
    viewHolder.mTvContent.setText(mData.get(position));
    viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() {
        @Override
        public void onOpen(SlideDelete slideDelete) {
            closeOtherItem();
            slideDeleteArrayList.add(slideDelete);
            Log.d("Slide", "slideDeleteArrayList當前數量:" + slideDeleteArrayList.size());
        }
        @Override
        public void onClose(SlideDelete slideDelete) {
            slideDeleteArrayList.remove(slideDelete);
            Log.d("Slide", "slideDeleteArrayList當前數量:" + slideDeleteArrayList.size());
        }
    });
    return convertView;
}

關閉所有已經開啟的item的方法

private void closeOtherItem(){
    // 採用Iterator的原因是for是執行緒不安全的,迭代器是執行緒安全的
    ListIterator<SlideDelete> slideDeleteListIterator = slideDeleteArrayList.listIterator();
    while(slideDeleteListIterator.hasNext()){
        SlideDelete slideDelete = slideDeleteListIterator.next();
        slideDelete.isShowDelete(false);
    }
    slideDeleteArrayList.clear();
}

至此完成。

附上當前完整的SlideDelete的程式碼

public class SlideDelete extends ViewGroup{
    private View mContent; // 內容部分
    private View mDelete;  // 刪除部分
    private ViewDragHelper viewDragHelper;
    private int mContentWidth;
    private int mContentHeight;
    private int mDeleteWidth;
    private int mDeleteHeight;
    public SlideDelete(Context context) {
        super(context);
    }
    public SlideDelete(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    private OnSlideDeleteListener onSlideDeleteListener;
    public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){
        this.onSlideDeleteListener = onSlideDeleteListener;
    }
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = getChildAt(0);
        mDelete = getChildAt(1);
        //public static ViewDragHelper create(ViewGroup forParent, Callback cb)
        viewDragHelper = ViewDragHelper.create(this,new MyDrawHelper());
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 這跟mContent的父親的大小有關,父親是寬填充父窗體,高度是和孩子一樣是60dp
        mContent.measure(widthMeasureSpec,heightMeasureSpec); // 測量內容部分的大小
        LayoutParams layoutParams = mDelete.getLayoutParams();
        int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
        int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
        // 這個引數就需要指定為精確大小
        mDelete.measure(deleteWidth, deleteHeight); // 測量刪除部分的大小
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mContentWidth = mContent.getMeasuredWidth();
        mContentHeight = mContent.getMeasuredHeight();
        mContent.layout(0, 0, mContentWidth, mContentHeight); // 擺放內容部分的位置
        mDeleteWidth = mDelete.getMeasuredWidth();
        mDeleteHeight = mDelete.getMeasuredHeight();
        mDelete.layout(mContentWidth, 0,
                mContentWidth + mDeleteWidth, mContentHeight); // 擺放刪除部分的位置
    }
    class MyDrawHelper extends ViewDragHelper.Callback {
        /**
         * Touch的down事件會回撥這個方法 tryCaptureView
         *
         * @Child:指定要動的孩子  (哪個孩子需要動起來)
         * @pointerId: 點的標記
         * @return : ViewDragHelper是否繼續分析處理 child的相關touch事件
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            System.out.println("呼叫tryCaptureView");
            System.out.println("contentView : " + (mContent == child));
            return mContent == child || mDelete == child;
        }
        // Touch的move事件會回撥這面這幾個方法
        // clampViewPositionHorizontal
        // clampViewPositionVertical
        // onViewPositionChanged
        /**
         *
         * 捕獲了水平方向移動的位移資料
         * @param child 移動的孩子View
         * @param left 父容器的左上角到孩子View的距離
         * @param dx 增量值,其實就是移動的孩子View的左上角距離控制元件(父親)的距離,包含正負
         * @return 如何動
         *
         * 呼叫完此方法,在android2.3以上就會動起來了,2.3以及以下是海動不了的
         * 2.3不相容怎麼辦?沒事,我們複寫onViewPositionChanged就是為了解決這個問題的
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //Log.d("Slide", "增量值:   " + left);
            if(child == mContent){ // 解決內容部分左右拖動的越界問題
                if(left>0){
                    return 0;
                }else if(-left>mDeleteWidth){
                    return -mDeleteWidth;
                }
            }
            if(child == mDelete){ // 解決刪除部分左右拖動的越界問題
                if(left<mContentWidth - mDeleteWidth){
                    return mContentWidth - mDeleteWidth;
                }else if(left > mContentWidth){
                    return mContentWidth;
                }
            }
            return left;
        }
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return super.clampViewPositionVertical(child, top, dy);
        }
        /**
         * 當View的位置改變時的回撥  這個方法的價值是結合clampViewPositionHorizontal或者clampViewPositionVertical
         * @param changedView  哪個View的位置改變了
         * @param left  changedView的left
         * @param top  changedView的top
         * @param dx x方向的上的增量值
         * @param dy y方向上的增量值
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            //super.onViewPositionChanged(changedView, left, top, dx, dy);
            invalidate();
            if(changedView == mContent){ // 如果移動的是mContent
                //我們移動mContent的實惠要相應的聯動改變mDelete的位置
                // 怎麼改變mDelete的位置,當然是mDelete的layput方法啦
                int tempDeleteLeft = mContentWidth+left;
                int tempDeleteRight = mContentWidth+left + mDeleteWidth;
                mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight);
            }else{ // touch的是mDelete
                int tempContentLeft = left - mContentWidth;
                int tempContentRight = left;
                mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight);
            }
        }
        /**
         * 相當於Touch的up的事件會回撥onViewReleased這個方法
         *
         * @param releasedChild
         * @param xvel  x方向的速率
         * @param yvel  y方向的速率
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //super.onViewReleased(releasedChild, xvel, yvel);
            // 方法的引數裡面沒有left,那麼我們就採用 getLeft()這個方法
            int mConLeft = mContent.getLeft();
            // 這裡沒必要分來兩個孩子判斷
            if(-mConLeft>mDeleteWidth/2){  // mDelete展示起來
                isShowDelete(true);
                if(onSlideDeleteListener != null){
                    onSlideDeleteListener.onOpen(SlideDelete.this); // 呼叫介面開啟的方法
                }
            }else{    // mDetele隱藏起來
                isShowDelete(false);
                if(onSlideDeleteListener != null){
                    onSlideDeleteListener.onClose(SlideDelete.this); // 呼叫介面的關閉的方法
                }
            }
            super.onViewReleased(releasedChild, xvel, yvel);
        }
    }
    /**
     * 是否展示delete部分
     * @param isShowDelete
     */
    public void isShowDelete(boolean isShowD