1. 程式人生 > >使用ViewDragHelper打造屬於自己的DragLayout(抽屜開關 )

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

true header 限制 open() flat 重寫 support 重要 red

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


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

轉載請註明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51935896

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

技術分享圖片

首先我們先來看一下我們要如何使用它

事實上只須要兩個 步驟,使用起來 非常方便

1.在XML文件

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

<com.xujun.drawerLayout.drag.DragLayout
    android:id="@+id/dl"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height
="match_parent" android:background="@drawable/bg" app:range="480" >
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="50dp" android:paddingLeft="10dp"
android:paddingRight="50dp" android:paddingTop="50dp">
<ImageView android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/head"/> <ListView android:id="@+id/lv_left" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout> <com.xujun.drawerLayout.drag.MyLinearLayout android:id="@+id/mll" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#18B6EF" android:gravity="center_vertical"> <ImageView android:id="@+id/iv_header" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginLeft="15dp" android:src="@drawable/head"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="Header"/> </RelativeLayout> <ListView android:id="@+id/lv_main" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </com.xujun.drawerLayout.drag.MyLinearLayout> </com.xujun.drawerLayout.drag.DragLayout>

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

分別能夠監聽打開的 時候,關閉的時候,拖動的時候,能夠在裏面做對應的處理,同一時候我還增加了 自己定義屬性能夠通過 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)的時候處理要做的事情,包含更新狀態, 伴隨動畫, 重繪界面等。這裏就不列出代碼了,有興趣的話下載源代碼看看

轉載請註明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51935896

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

關於很多其它自己定義View的樣例,可查看以下我的一些博客。同一時候假設大家認為還能夠的。歡迎在github上面 star或者fork,謝謝。

經常使用的自己定義View樣例一(FlowLayout)

自己定義View經常使用樣例二(點擊展開隱藏控件,九宮格圖片控件)

經常使用的自己定義View樣例三(MultiInterfaceView多界面處理)

經常使用的自己定義控件四(QuickBarView)

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