Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 使用ViewDragHelper打造屬於自己的DragwLayout(抽屜開關 )

使用ViewDragHelper打造屬於自己的DragwLayout(抽屜開關 )

編輯:關於Android編程

DrawLayout這個自定義的空間很常見,qq,網易新聞,知乎等等,都有這種效果,那這種效果是怎樣實現的呢?本篇博客將帶你來怎樣實現它。

廢話不多說,先來看一下 效果

\

首先我們先來看一下我們要怎樣使用它

其實只需要兩個 步驟,使用起來 非常方便

1.在XML文件

DragLayout至少要有兩個孩子,且都是 ViewGroup或者ViewGroup的實現類



    

        

        
        
    

    

        

            

            
        

        
        
    

在代碼中若想為其設置監聽器,

分別可以監聽打開的 時候,關閉的時候,拖動的時候,可以在裡面做相應的處理,同時我還加入了 自定義屬性可以通過 app:range=”480”或者setRange()方法,即可設置打開抽屜的范圍。

 mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {

            @Override
            public void onOpen() {
                Utils.showToast(MainActivity.this, "onOpen");
                // 左面板ListView隨機設置一個條目
                Random random = new Random();
                Log.i(TAG, "onOpen:=" +mDragLayout.getRange());
                int nextInt = random.nextInt(50);
                mLeftList.smoothScrollToPosition(nextInt);

            }

            @Override
            public void onDraging(float percent) {
                Log.d(TAG, "onDraging: " + percent);// 0 -> 1
                // 更新圖標的透明度
                // 1.0 -> 0.0
                ViewHelper.setAlpha(mHeaderImage, 1 - percent);
            }

            @Override
            public void onClose() {
                Utils.showToast(MainActivity.this, "onClose");
                // 讓圖標晃動
                ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
                mAnim.setInterpolator(new CycleInterpolator(4));
                mAnim.setDuration(500);
                mAnim.start();
            }
        });

實現方式

關於ViewDragHelper的一些 討論

DrawLayout在網上的 實現方式很多,千奇百怪,有一些是直接監聽 onTouchEvent事件,處理Activon_Move,Action_Down,Action_up等動作,這樣實現的話稍微有點復雜。本篇博客是使用ViewDragHelper來 處理觸摸事件和拖拽事件的的,ViewDragHelper是2013Google IO大會推出的,目的是為了給開發者提供一個處理觸摸事件,節省開發者的時間。

關於Google官方 關於ViewDragHelper的解釋,簡單來說就是處理ViewGroup的 觸摸事件和拖拽事件

ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.

實現思路

1) 我是通過繼承FrameLayout來實現的,相比較於繼承ViewGroup來實現,這樣有一個好處就是省去了自己重寫 onMeasure (),onLayout ()方法

2)在構造方法裡面初始化mDragHelper,mSensitivity代表打開抽屜的 難易程度,是Float類型,至於mCallback是什麼,下面會詳細講,這裡先不著急。

public DragLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initAttars(context, attrs);
    // a.初始化 (通過靜態方法)
    mDragHelper = ViewDragHelper.create(this, mSensitivity, mCallback);
}
3)重寫 onInterceptTouchEvent和onTouchevent 方法 ,將事件交給
// b.傳遞觸摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 傳遞給mDragHelper
    return mDragHelper.shouldInterceptTouchEvent(ev);
}

/***
 * 將事件交給mDragHelper處理
 *
 * @param event
 * @return
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    try {
        mDragHelper.processTouchEvent(event);
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 返回true, 持續接受事件
    return true;
}
4)重寫onFinishInflate方法,在裡面拿到 我們的側滑菜單mLeftContent和主菜單mMainContent
@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    // 容錯性檢查 (至少有倆子View, 子View必須是ViewGroup的子類)
    if (getChildCount() < 2) {
        throw new IllegalStateException("布局至少有倆孩子. Your ViewGroup must have 2 children at " +
                "least.");
    }
    if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
        throw new IllegalArgumentException("子View必須是ViewGroup的子類. Your children must be an " +
                "instance of ViewGroup");
    }

    mLeftContent = (ViewGroup) getChildAt(0);
    mMainContent = (ViewGroup) getChildAt(1);
}


下面我們一起來看一下這個mCallBack是什麼東西

看之前我們需要了解Status和OnDragStatusChangeListener這兩個東西。

Status代表DrawLayout 當前的狀態,是否是打開,關閉還是拖拽。 OnDragStatusChangeListener是個監聽器,在DrawLayout狀態改變的時候會回調相關的方法,方便與外界進行通訊。 我們可以通過 setDragStatusListener(OnDragStatusChangeListener mListener);這個方法設置監聽
/**
 * 狀態枚舉
 */
public static enum Status {
    Close, Open, Draging;
}

/**
 * 抽屜開關的監聽器
 */
public interface OnDragStatusChangeListener {
    void onClose();

    void onOpen();

    void onDraging(float percent);
}

接下來我們來看ViewDragHelper.Callback幾個主要的方法


tryCaptureView(View child, int pointerId)
Called when the user’s input indicates that they want to capture the given child view with the pointer indicated by pointerId.
onViewCaptured(View capturedChild, int activePointerId)
Called when a child view is captured for dragging or settling.

getViewHorizontalDragRange(View child)
Return the magnitude of a draggable child view’s horizontal range of motion in pixels.

clampViewPositionHorizontal(View child, int left, int dx)
Restrict the motion of the dragged child view along the horizontal axis.

onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
Called when the captured view’s position changes as the result of a drag or settle.

onViewReleased(View releasedChild, float xvel, float yvel)
Called when the child view is no longer being actively dragged.

谷歌官方的連接;https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.Callback.html


下面的代碼有關於這幾個方法的中文解釋,這裡就不詳細講解了

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
    // d. 重寫事件

    // 1. 根據返回結果決定當前child是否可以拖拽
    // child 當前被拖拽的View
    // pointerId 區分多點觸摸的id
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        Log.d(TAG, "tryCaptureView: " + child);
        return mToogle;
    }

    @Override
    public void onViewCaptured(View capturedChild, int activePointerId) {
        Log.d(TAG, "onViewCaptured: " + capturedChild);
        // 當capturedChild被捕獲時,調用.
        super.onViewCaptured(capturedChild, activePointerId);
    }

    @Override
    public int getViewHorizontalDragRange(View child) {
        // 返回拖拽的范圍, 不對拖拽進行真正的限制. 僅僅決定了動畫執行速度
        Log.i(TAG, "getViewHorizontalDragRange:mRange=" +mRange);
        return mRange;
    }

    // 2. 根據建議值 修正將要移動到的(橫向)位置   (重要)
    // 此時沒有發生真正的移動
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        // child: 當前拖拽的View
        // left 新的位置的建議值, dx 位置變化量
        // left = oldLeft + dx;
        Log.d(TAG, "clampViewPositionHorizontal: "
                + "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " + left);

        if (child == mMainContent) {
            left = fixLeft(left);
        }
        return left;
    }

    // 3. 當View位置改變的時候, 處理要做的事情 (更新狀態, 伴隨動畫, 重繪界面)
    // 此時,View已經發生了位置的改變
    @Override
    public void onViewPositionChanged(View changedView, int left, int top,
                                      int dx, int dy) {
        // changedView 改變位置的View
        // left 新的左邊值
        // dx 水平方向變化量
        super.onViewPositionChanged(changedView, left, top, dx, dy);
        Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);

        int newLeft = left;
        if (changedView == mLeftContent) {
            // 把當前變化量傳遞給mMainContent
            newLeft = mMainContent.getLeft() + dx;
        }

        // 進行修正
        newLeft = fixLeft(newLeft);

        if (changedView == mLeftContent) {
            // 當左面板移動之後, 再強制放回去.
            mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
            mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
        }
        // 更新狀態,執行動畫
        dispatchDragEvent(newLeft);

        // 為了兼容低版本, 每次修改值之後, 進行重繪
        invalidate();
    }

    // 4. 當View被釋放的時候, 處理的事情(執行動畫)
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        // View releasedChild 被釋放的子View
        // float xvel 水平方向的速度, 向右為+
        // float yvel 豎直方向的速度, 向下為+
        Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
        super.onViewReleased(releasedChild, xvel, yvel);

        // 判斷執行 關閉/開啟
        // 先考慮所有開啟的情況,剩下的就都是關閉的情況
        if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
            open();
        } else if (xvel > 0) {
            open();
        } else {
            close();
        }

    }

    @Override
    public void onViewDragStateChanged(int state) {
        // TODO Auto-generated method stub
        super.onViewDragStateChanged(state);
    }

};

其實主要思路就是

1)在方法public boolean tryCaptureView(View child, int pointerId)處理那些child可以被捕捉,這裡我們返回true表示所有的都可以被捕捉

2)在public int clampViewPositionHorizontal(View child, int left, int dx)方法中根據child返回將要移動的水平位置的偏移量

3)在 void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)方法中處理要做的事情 包括更新狀態, 伴隨動畫, 重繪界面等

public void onViewPositionChanged(View changedView, int left, int top,  int dx, int dy) {

      // 進行修正
   newLeft = fixLeft(newLeft);

  if (changedView == mLeftContent) {
    // 當左面板移動之後, 再強制放回去.
    mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
    mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 +    mHeight);

    if (changedView == mLeftContent) {
    // 把當前變化量傳遞給mMainContent
    newLeft = mMainContent.getLeft() + dx;
   }

}


    // 更新狀態,執行動畫
    dispatchDragEvent(newLeft);

    // 為了兼容低版本, 每次修改值之後, 進行重繪
    invalidate();
}

protected void dispatchDragEvent(int newLeft) {
    float percent = newLeft * 1.0f / mRange;
    //0.0f -> 1.0f
    Log.d(TAG, "percent: " + percent);

    if (mListener != null) {
        mListener.onDraging(percent);
    }

    // 更新狀態, 執行回調
    Status preStatus = mStatus;
    mStatus = updateStatus(percent);
    if (mStatus != preStatus) {
        // 狀態發生變化
        if (mStatus == Status.Close) {
            // 當前變為關閉狀態
            if (mListener != null) {
                mListener.onClose();
            }
        } else if (mStatus == Status.Open) {
            if (mListener != null) {
                mListener.onOpen();
            }
        }
    }

    //    * 伴隨動畫:
    animViews(percent);

}
4)在void onViewReleased(View releasedChild, float xvel, float yvel)的時候處理要做的事情,包括更新狀態, 伴隨動畫, 重繪界面等,這裡就不列出代碼了,有興趣的話下載源碼看看

源碼下載地址: https://github.com/gdutxiaoxu/drawLayout.git

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved