[UI]抽屜菜單DrawerLayout分析(一)


      側拉菜單作為常見的導航交互控件,最開始在沒有沒有android官方控件時,很多時候都是使用開源的SlidingMenu,一直沒機會分析側拉菜單的實現機理,本文將分析android.support.v4.widget.DrawerLayout的使用及實現。

Device 2014 04 16 191818

    官方介紹

DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled out from the edge of the window.

Drawer positioning and layout is controlled using the android:layout_gravity attribute on child views corresponding to which side of the view you want the drawer to emerge from: left or right. (Or start/end on platform versions that support layout direction.)

To use a DrawerLayout, position your primary content view as the first child with a width and height of match_parent. Add drawers as child views after the main content view and set the layout_gravity appropriately. Drawers commonly use match_parent for height with a fixed width.

DrawerLayout.DrawerListener can be used to monitor the state and motion of drawer views. Avoid performing expensive operations such as layout during animation as it can cause stuttering; try to perform expensive operations during the STATE_IDLE state. DrawerLayout.SimpleDrawerListener offers default/no-op implementations of each callback method.

As per the Android Design guide, any drawers positioned to the left/start should always contain content for navigating around the application, whereas any drawers positioned to the right/end should always contain actions to take on the current content. This preserves the same navigation left, actions right structure present in the Action Bar and elsewhere

DrawerLayout直譯的事抽屜布局的意思,作為視窗內的頂層容器,它允許用戶通過抽屜式的推拉操作,從而把視圖視窗外邊緣拉到屏幕內,如右圖:

抽屜菜單的擺放和布局通過android:layout_gravity屬性來控制,可選值為left、right或start、end。通過xml來布局的話,需要把DrawerLayout作為父容器,組界面布局作為其第一個子節點,抽屜布局則緊隨其后作為第二個子節點,這樣就做就已經把內容展示區和抽屜菜單區獨立開來,只需要分別非兩個區域設置內容即可。android提供了一些實用的監聽器,重載相關的回調方法可以在菜單的交互過程中書寫邏輯業務。下面是一個demo布局:

 

<android.support.v4.widget.DrawerLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/drawer_layout"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="com.aven.myapplication2.app.MainActivity">

 

    <FrameLayout

        android:id="@+id/container"

        android:layout_width="match_parent"

        android:layout_height="match_parent"/>

 

    <fragmentandroid:id="@+id/navigation_drawer"

        android:layout_width="@dimen/navigation_drawer_width"

        android:layout_height="match_parent"

        android:layout_gravity="start"

        android:name="com.aven.myapplication2.app.NavigationDrawerFragment"

        tools:layout="@layout/fragment_navigation_drawer"/>

 

</android.support.v4.widget.DrawerLayout>

 
 
所以DrawerLayout的使用非常簡單,和很多容器類布局一樣,它本身也繼承自ViewGroup,只是在內部實現中會默認將第一個子節點作為內容區,第二個作為抽屜菜單,所以寫布局的事后必須牢記,好在現在的IDE已經非常智能,通過引導來創建Drawerlayout時,會自動生成Activity和xml layout布局,比如使用AndroidStudio就非常方便。
 

源碼分析

DrawerLayout實例化相關輔助類

既然DrawerLayout使用是作為頂層布局layout,那先看看他的構造函數:

public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {

    super(context, attrs, defStyle);

    //根據屏幕分辨率密度計算最小的邊距

    final float density = getResources().getDisplayMetrics().density;

    mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);

    final float minVel = MIN_FLING_VELOCITY * density;

    //實例化視圖滑動的回調接口,包括左右兩邊

    mLeftCallback = new ViewDragCallback(Gravity.LEFT);

    mRightCallback = new ViewDragCallback(Gravity.RIGHT);

    //創建滑動手勢的的輔助類,負責具體的滑動監聽實現

    mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);

    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

    mLeftDragger.setMinVelocity(minVel);

    mLeftCallback.setDragger(mLeftDragger);

 

    mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);

    mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);

    mRightDragger.setMinVelocity(minVel);

    mRightCallback.setDragger(mRightDragger);

 

    // So that we can catch the back button

    setFocusableInTouchMode(true);

 

    ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());

    ViewGroupCompat.setMotionEventSplittingEnabled(this,false);

}

從構造函數中,我們發現有兩個關鍵的類ViewDragCallback, ViewDragHelper,命名上來看前者和滑動的回調相關,后者和view的滑動操作實現有關,所以先看ViewDragHelper。

 

ViewDragHelper負責實現drag操作

從它的類注釋信息中可以看到,這個helper是個輔助類,里面封裝了一些便於用戶拖動ViewGroup內子view的操作及狀態記錄方法。

/**

 * 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.

 */

 
現在來看看這個helper到底是怎么封裝的滑動操作,從上面的實例化我們知道這個helper通過工廠方法來構造實例,工廠方法有兩個如下:

/**

 * Factory method to create a new ViewDragHelper.

 *

 * @param forParent Parent view to monitor

 * @param cb Callback to provide information and receive events

 * @return a new ViewDragHelper instance

 */

public static ViewDragHelper create(ViewGroup forParent, Callback cb) {

    return new ViewDragHelper(forParent.getContext(), forParent, cb);

}

 

/**

 * Factory method to create a new ViewDragHelper.

 *

 * @param forParent Parent view to monitor

 * @param sensitivity Multiplier for how sensitive the helper should be about detecting

 *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.

 * @param cb Callback to provide information and receive events

 * @return a new ViewDragHelper instance

 */

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {

    final ViewDragHelper helper = create(forParent, cb);

    helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));

    return helper;

}

 

這第二個工廠方法create就是剛才看到的上層調用來創建helper實例的,我們傳入了一個viewgroup,也就是說helper將持有我們的DrawerLayout實例引用,第二是一個浮點數,和drag操作的敏感性相關,數值越大表示drag操作更易被監聽,最后是一個Callback,即ViewDragCallback實例,它本身繼承自ViewDragHelper.Callback,現在來看helper的構造方法:

/**

 * Apps should use ViewDragHelper.create() to get a new instance.

 * This will allow VDH to use internal compatibility implementations for different

 * platform versions.

 *

 * @param context Context to initialize config-dependent params from

 * @param forParent Parent view to monitor

 */

private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {

    if (forParent == null) {

        throw new IllegalArgumentException("Parent view may not be null");

    }

    if (cb == null) {

        throw new IllegalArgumentException("Callback may not be null");

    }

 

    mParentView = forParent;

    mCallback = cb;

 

    final ViewConfiguration vc = ViewConfiguration.get(context);

    finalfloat density = context.getResources().getDisplayMetrics().density;

    mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);

 

    mTouchSlop = vc.getScaledTouchSlop();

    mMaxVelocity = vc.getScaledMaximumFlingVelocity();

    mMinVelocity = vc.getScaledMinimumFlingVelocity();

    mScroller = ScrollerCompat.create(context, sInterpolator);

}

首先需要檢測我們傳入的DrawerLayout和回調Callback,不允許為空。接下來從ViewConfiguration中獲取一些view的默認配置,

vc.getScaledTouchSlop是獲取一個pix為單位的距離,代表view在滑動的值;

vc.getScaledMaximumFlingVelocity獲取觸發view fling的最大每秒滾動的距離,也是pix為單位;

獲取view fling的最小每秒滾動距離,同樣pix為單位;

這里有scroll和fling,我的理解是scroll表示手沒有離開屏幕產生的滑動效果,二fling則是用力一划,然后view自己開始滾動的效果。

最后實例化一個Scroller,這是專門用來處理滾動的一個類,這里用的是擴展包里的campact類做版本兼容。

到此DrawerLayout已經准備好所有資源,接下來就是手勢分發時候的各種調用,這一部分留到下一篇文章在做分析

 

Source:

git clone https://github.com/avenwu/DrawerDemo.git

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM