Android开发之DrawerLayout

位于v4包下面的DrawerLayout控件,用于充当一个顶级窗口内容的容器,允许交互式“抽屉”方式从左右两侧边缘拖拽出视图,控制抽屉方向通过属性android:layout_gravity,视图的子视图对应于哪一边的抽屉里出现:向左或向右.

DrawerLayoutd调用实例及Demo

先上效果图,看会儿妹子哈

添加一个导航抽屉,声明你的用户界面DrawerLayout对象作为根视图的布局。DrawerLayout内添加一个视图,其中包含屏幕的主要内容(该布局视图在抽屉隐藏时显示),另一个视图,其中包含的内容导航的抽屉视图。实例如下:

<android.support.v4.widget.DrawerLayout  xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout  android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

这个布局展示了一些重要的布局特点:

  • contentView布局必须是第一个子View
  • contentView内容视图设置为匹配父视图的宽度和高度,因为它代表了整个UI、导航抽屉的隐藏。
  • 抽屉布局view,android:layout_gravity属性控制抽屉的方向,可选值left(start)、right(end)
  • 抽屉视图指定宽度的dp单位和高度匹配父视图。抽屉的宽度应不超过320 dp的用户可以看到部分的主要内容。

    在Activity里面使用时先初始化DrawerLayout和抽屉ListView

public class MainActivity extends Activity {
    private String[] mPlanetTitles;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPlanetTitles = getResources().getStringArray(R.array.planets_array);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set the adapter for the list view
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mPlanetTitles));
        // Set the list's click listener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        ...
    }
}

setOnItemClickListener()来接收点击事件在导航抽屉的名单。下面将展示如何实现这个接口,当用户选择某一项时,改变视图的内容

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
        selectItem(position);
    }
}

/** Swaps fragments in the main content view */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Highlight the selected item, update the title, and close the drawer
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

监听抽屉打开和关闭事件,调用setDrawerListener(),DrawerLayout将其传递给DrawerLayout.DrawerListener的实现。这个接口提供了抽屉等事件回调onDrawerOpened()和onDrawerClosed()。

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

用户可以打开和关闭导航抽屉的滑动手势或屏幕的左边缘,但如果你使用标题栏,您还应该允许用户通过点击应用图标,打开和关闭它。和应用程序图标也应该表明导航抽屉的存在与一个特殊的图标。您可以实现所有这些行为通过使用ActionBarDrawerToggle,ActionBarDrawerToggle需要注意以下几个参数:

  • Activity
  • DrawerLayout
  • 控制抽屉的图标资源,官方提供的图标资源下载地址:http://developer.android.com/downloads/design/Android_Design_Icons_20130926.zip
  • DrawerLayout抽屉打开是的显示标题文字
  • DrawerLayout抽屉关闭时显示的标题文字

    创建了一个子类ActionBarDrawerToggle作为你的抽屉侦听器,同时还需要控制ActionBarDrawerToggle在一些地方在你的活动生命周期:

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...

    public void onCreate(Bundle savedInstanceState) {
        ...

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }

    ...
}

上图效果的源码就是根据官方文档,按照这个流程开发的,源码地址:http://download.csdn.net/detail/analyzesystem/9417240,ListView的适配器就没写了(有了适配器影响我们看妹子),必须的说的是,DrawerArrowDrawable、ActionBarDrawerToggle都是源于开源项目,而非系统v4自带的,开源地址:https://github.com/keklikhasan/LDrawer

compile 'com.ikimuhendis:ldrawer:0.1'

沉浸式状态栏由于我并没有用AppCompatActivity,而是使用的兼容4.4的开源库https://github.com/jgilfelt/SystemBarTint:

dependencies { compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' }

基于ActionBar开发应用对于我来说是一件令我很不开心的事(曾经ActionBar的适配让我伤痛了,就彻底反感了),强迫症的我不得不另寻他法,所幸的是我找到了他ActionView,地址:https://github.com/markushi/android-ui,效果图:

通过这个开源项目的帮助,我完成了一个小demo,没用actionbar,效果图就不贴了,差别不大,demo下载地址:http://download.csdn.net/detail/analyzesystem/9417382,这是一个我曾经写的有点挫的代码,效果还是实现了,当初用的ec没用as,只能下源码导入。

DrawerLayout源码浅析

通过DrawerLayout源码不难发现,自定义ViewGroup通过上一篇提到的ViewDragHelper辅助实现的(上篇提到的自定义右侧侧滑代码就不贴了,DrawerLayout已实现,自己写的又不完善),我们现在来了解DrawerLayout源码。

 public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        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.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);

        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
        if (ViewCompat.getFitsSystemWindows(this)) {
            IMPL.configureApplyInsets(this);
            mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
        }
    }

在构造函数里我们发现他已经预先初始化了左右侧的ViewDragHelper的实现,方便与抽屉布局的layout:gravity的属性匹配选择调用helper,今天之前我还想代码动态控制抽屉方向,结果发现LayoutParam并没有发现gravity的属性代码设置。在我看过源码后发现了它

    /** * Enable or disable interaction with the given drawer. * * <p>This allows the application to restrict the user's ability to open or close * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, * {@link #closeDrawer(int)} and friends if a drawer is locked.</p> * * <p>Locking a drawer open or closed will implicitly open or close * that drawer as appropriate.</p> * * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. * @param edgeGravity Gravity.LEFT, RIGHT, START or END. * Expresses which drawer to change the mode for. * * @see #LOCK_MODE_UNLOCKED * @see #LOCK_MODE_LOCKED_CLOSED * @see #LOCK_MODE_LOCKED_OPEN */
    public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
        final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
                ViewCompat.getLayoutDirection(this));
        if (absGravity == Gravity.LEFT) {
            mLockModeLeft = lockMode;
        } else if (absGravity == Gravity.RIGHT) {
            mLockModeRight = lockMode;
        }
        if (lockMode != LOCK_MODE_UNLOCKED) {
            // Cancel interaction in progress
            //这里,我认为还可以换一种写法,类似之前设计模式中提到的策略,初始化两个策略,根据传入不同对齐方式选择不同策略(不过有点大材小用)
            final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
            helper.cancel();
        }
        switch (lockMode) {
            case LOCK_MODE_LOCKED_OPEN:
                final View toOpen = findDrawerWithGravity(absGravity);
                if (toOpen != null) {
                    openDrawer(toOpen);
                }
                break;
            case LOCK_MODE_LOCKED_CLOSED:
                final View toClose = findDrawerWithGravity(absGravity);
                if (toClose != null) {
                    closeDrawer(toClose);
                }
                break;
            // default: do nothing
        }
    }

从这个方法不难看出,通过Right、Left属性控制抽屉方向,通过LOCK_MODE_LOCKED_CLOSED、LOCK_MODE_LOCKED_OPEN、LOCK_MODE_UNLOCKED控制抽屉的是否锁定不可触摸。有了这个方法我们可以做很多种实现效果,例如:左侧抽屉点击了某一项需要登录权限,立即remove()左侧抽屉,切换到右侧登陆抽屉。

    /** * Close all currently open drawer views by animating them out of view. */
    public void closeDrawers() {
        closeDrawers(false);
    }

    /** * Open the specified drawer view by animating it into view. * * @param drawerView Drawer view to open */
    public void openDrawer(View drawerView) {

        //...........略..............
        invalidate();
    }

DrawerLayout还提供了了抽屉的开关方法,多个方法迭代,当你不想使用ActionBarDrawerToggle等控制开关,只是通过简单的控件点击事件切换,就可以调用以上方法。如果你想获取当前DrawerLayout的状态,可以调用isDrawerOpen方法:

public boolean isDrawerOpen(View drawer) {
        if (!isDrawerView(drawer)) {
            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
        }
        return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
    }

对于抽屉这一块,我最喜欢的是开源项目还是FlowingDrawer,下一篇就是它啦,献上预告片就下班哒:

Android开发之DrawerLayout_第1张图片

你可能感兴趣的:(android)