位于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>
这个布局展示了一些重要的布局特点:
抽屉视图指定宽度的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需要注意以下几个参数:
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,下一篇就是它啦,献上预告片就下班哒: