如何使用DrawerLayout来显示在ActionBar /工具栏和状态栏下?
我在新的材料deviseSide Nav规范中看到,您可以在操作栏和状态栏后面显示抽屉。 我怎样才能实现呢?
框架和支持库中的新function可以完全实现这一点。 有三个难题:
- 使用工具栏,以便您可以将您的操作栏embedded到视图层次结构中。
- 使DrawerLayout适合
fitsSystemWindows
以便它放在系统栏之后。 - 禁用
Theme.Material
的正常状态栏着色,以便DrawerLayout可以在那里绘制。
我假设你将使用新的appcompat。
首先,你的布局应该是这样的:
<!-- The important thing to note here is the added fitSystemWindows --> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/my_drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- Your normal content view --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- We use a Toolbar so that our drawer can be displayed in front of the action bar --> <android.support.v7.widget.Toolbar android:id="@+id/my_awesome_toolbar" android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" /> <!-- The rest of your content view --> </LinearLayout> <!-- Your drawer view. This can be any view, LinearLayout is just an example. As we have set fitSystemWindows=true this will be displayed under the status bar. --> <LinearLayout android:layout_width="304dp" android:layout_height="match_parent" android:layout_gravity="left|start" android:fitsSystemWindows="true"> <!-- Your drawer content --> </LinearLayout> </android.support.v4.widget.DrawerLayout>
然后在你的Activity / Fragment中:
public void onCreate(Bundled savedInstanceState) { super.onCreate(savedInstanceState); // Your normal setup. Blah blah ... // As we're using a Toolbar, we should retrieve it and set it // to be our ActionBar Toolbar toolbar = (...) findViewById(R.id.my_awesome_toolbar); setSupportActionBar(toolbar); // Now retrieve the DrawerLayout so that we can set the status bar color. // This only takes effect on Lollipop, or when using translucentStatusBar // on KitKat. DrawerLayout drawerLayout = (...) findViewById(R.id.my_drawer_layout); drawerLayout.setStatusBarBackgroundColor(yourChosenColor); }
然后,您需要确保DrawerLayout在状态栏后面可见。 你通过改变你的values-v21主题来做到这一点:
值-V21 /的themes.xml
<style name="Theme.MyApp" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowTranslucentStatus">true</item> </style>
注意:如果使用<fragment android:name="fragments.NavigationDrawerFragment">
而不是
<LinearLayout android:layout_width="304dp" android:layout_height="match_parent" android:layout_gravity="left|start" android:fitsSystemWindows="true"> <!-- Your drawer content --> </LinearLayout>
实际的布局,如果在从onCreateView
方法返回的视图上调用fitsSystemWindows(boolean)
,将实现预期的效果。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View mDrawerListView = inflater.inflate( R.layout.fragment_navigation_drawer, container, false); mDrawerListView.setFitsSystemWindows(true); return mDrawerListView; }
编辑:新的devise支持库支持这和以前的方法不再需要。
现在可以使用新的Androiddevise支持库来实现这一点。
你可以看到Chris Banes的Cheesesquare示例应用程序 ,它演示了所有的新function。
先前的方法:
由于没有完整的解决scheme发布,这是我达到预期的结果的方式。
首先在你的项目中包含一个ScrimInsetsFrameLayout 。
/* * Copyright 2014 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A layout that draws something in the insets passed to * {@link #fitSystemWindows(Rect)}, ie the area above UI chrome * (status and navigation bars, overlay action bars). */ public class ScrimInsetsFrameLayout extends FrameLayout { private Drawable mInsetForeground; private Rect mInsets; private Rect mTempRect = new Rect(); private OnInsetsCallback mOnInsetsCallback; public ScrimInsetsFrameLayout(Context context) { super(context); init(context, null, 0); } public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public ScrimInsetsFrameLayout( Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrimInsetsView, defStyle, 0); if (a == null) { return; } mInsetForeground = a.getDrawable( R.styleable.ScrimInsetsView_insetForeground); a.recycle(); setWillNotDraw(true); } @Override protected boolean fitSystemWindows(Rect insets) { mInsets = new Rect(insets); setWillNotDraw(mInsetForeground == null); ViewCompat.postInvalidateOnAnimation(this); if (mOnInsetsCallback != null) { mOnInsetsCallback.onInsetsChanged(insets); } return true; // consume insets } @Override public void draw(Canvas canvas) { super.draw(canvas); int width = getWidth(); int height = getHeight(); if (mInsets != null && mInsetForeground != null) { int sc = canvas.save(); canvas.translate(getScrollX(), getScrollY()); // Top mTempRect.set(0, 0, width, mInsets.top); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Bottom mTempRect.set(0, height - mInsets.bottom, width, height); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Left mTempRect.set( 0, mInsets.top, mInsets.left, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Right mTempRect.set( width - mInsets.right, mInsets.top, width, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); canvas.restoreToCount(sc); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(this); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(null); } } /** * Allows the calling container to specify a callback for custom * processing when insets change (ie when {@link #fitSystemWindows(Rect)} * is called. This is useful for setting padding on UI elements * based on UI chrome insets (eg a Google Map or a ListView). * When using with ListView or GridView, remember to set * clipToPadding to false. */ public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) { mOnInsetsCallback = onInsetsCallback; } public static interface OnInsetsCallback { public void onInsetsChanged(Rect insets); } }
然后创build一个样式,以便可以设置insetForeground
。
值/ attrs.xml
<declare-styleable name="ScrimInsetsView"> <attr name="insetForeground" format="reference|color" /> </declare-styleable>
更新您的活动的xml文件,并确保在DrawerLayout
以及DrawerLayout
android:fitsSystemWindows
设置为true。
布局/ activity_main.xml中
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <!-- The main content view --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- Your main content --> </LinearLayout> <!-- The navigation drawer --> <com.example.app.util.ScrimInsetsFrameLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/scrimInsetsFrameLayout" android:layout_width="320dp" android:layout_height="match_parent" android:layout_gravity="start" android:background="@color/white" android:elevation="10dp" android:fitsSystemWindows="true" app:insetForeground="#4000"> <!-- Your drawer content --> </com.example.app.util.ScrimInsetsFrameLayout> </android.support.v4.widget.DrawerLayout>
在活动的onCreate方法中,在抽屉布局上设置状态栏背景颜色。
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ... mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); mDrawerLayout.setStatusBarBackgroundColor( getResources().getColor(R.color.primary_dark)); }
最后更新你的应用程序的主题,使DrawerLayout
位于状态栏的后面。
值-V21 / styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style>
结果:
随着最新Android支持库(Rev 22.2.0)的发布,我们已经有了一个devise支持库,并作为这个称为NavigationView的新视图的一部分。 所以我们ScrimInsetsFrameLayout
和其他所有东西来完成所有的工作,而只是使用这个视图,一切都为我们完成。
例
步骤1
将Design Support Library
添加到您的build.gradle
文件
dependencies { // Other dependencies like appcompat compile 'com.android.support:design:22.2.0' }
第2步
将NavigationView
添加到DrawerLayout
:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- this is important --> <!-- Your contents --> <android.support.design.widget.NavigationView android:id="@+id/navigation" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/navigation_items" /> <!-- The items to display --> </android.support.v4.widget.DrawerLayout>
第3步
在/res/menu
创build一个新的菜单资源,并添加您想要显示的项目和图标:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_home" android:icon="@drawable/ic_action_home" android:title="Home" /> <item android:id="@+id/nav_example_item_1" android:icon="@drawable/ic_action_dashboard" android:title="Example Item #1" /> </group> <item android:title="Sub items"> <menu> <item android:id="@+id/nav_example_sub_item_1" android:title="Example Sub Item #1" /> </menu> </item> </menu>
步骤4
初始化NavigationView并处理点击事件:
public class MainActivity extends AppCompatActivity { NavigationView mNavigationView; DrawerLayout mDrawerLayout; // Other stuff private void init() { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mNavigationView = (NavigationView) findViewById(R.id.navigation_view); mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { mDrawerLayout.closeDrawers(); menuItem.setChecked(true); switch (menuItem.getItemId()) { case R.id.nav_home: // TODO - Do something break; // TODO - Handle other items } return true; } }); } }
第5步
请务必在values-v21
android:windowDrawsSystemBarBackgrounds
设置android:windowDrawsSystemBarBackgrounds
和android:statusBarColor
,否则您的Drawer将不会显示在“StatusBar
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Other attributes like colorPrimary, colorAccent etc. --> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style>
可选步骤
将一个标题添加到NavigationView。 为此,只需创build一个新的布局,然后在NavigationView中添加app:headerLayout="@layout/my_header_layout"
。
结果
笔记
- 突出显示的颜色使用通过
colorPrimary
属性定义的颜色 - 列表项使用通过
textColorPrimary
属性定义的颜色 - 图标使用通过
textColorSecondary
属性定义的颜色
您还可以通过Chris Banes检查示例应用程序 ,该应用程序突出显示NavigationView以及作为devise支持库的其他新视图(如FloatingActionButton , TextInputLayout , Snackbar , TabLayout等)
让它工作,在值-v21风格或主题xml需要使用此属性:
<item name="android:windowTranslucentStatus">true</item>
这使得魔法!
以上所有的方法是正确的,可能正在工作。 我已经按照上述指南创build了一个工作演示,并在2.x至5.x上进行了testing
你可以从Github克隆
重要的是玩“主要活动”
toolbar = (Toolbar) findViewById(R.id.toolbar); res = this.getResources(); this.setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ScrimInsetsFrameLayout scrimInsetsFrameLayout = (ScrimInsetsFrameLayout) findViewById(R.id.linearLayout); scrimInsetsFrameLayout.setOnInsetsCallback(this); }
和回电
@Override public void onInsetsChanged(Rect insets) { Toolbar toolbar = this.toolbar; ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); lp.topMargin = insets.top; int top = insets.top; insets.top += toolbar.getHeight(); toolbar.setLayoutParams(lp); insets.top = top; // revert }
绝对的主题为V21做魔术
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- API 21 theme customizations can go here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/accent_material_light</item> <item name="windowActionModeOverlay">true</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowTranslucentStatus">true</item> </style>
和ScrimInsetsFrameLayout
现在,新的devise支持库变得更容易了
compile 'com.android.support:design:22.2.0'
来自@Chris Banes的克隆https://github.com/chrisbanes/cheesesquare
这里提到的所有答案都太旧,冗长。最新的Navigationview的最佳和最简短的解决scheme是
@Override public void onDrawerSlide(View drawerView, float slideOffset) { super.onDrawerSlide(drawerView, slideOffset); try { //int currentapiVersion = android.os.Build.VERSION.SDK_INT; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ // Do something for lollipop and above versions Window window = getWindow(); // clear FLAG_TRANSLUCENT_STATUS flag: window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // finally change the color to any color with transparency window.setStatusBarColor(getResources().getColor(R.color.colorPrimaryDarktrans));} } catch (Exception e) { Crashlytics.logException(e); } }
这将打开抽屉时将状态栏颜色更改为透明
现在,当closures抽屉时,需要将状态栏的颜色再次改为黑暗。所以你可以这样做。
public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); try { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ // Do something for lollipop and above versions Window window = getWindow(); // clear FLAG_TRANSLUCENT_STATUS flag: window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // finally change the color again to dark window.setStatusBarColor(getResources().getColor(R.color.colorPrimaryDark));} } catch (Exception e) { Crashlytics.logException(e); } }
然后在主布局中添加一行即
android:fitsSystemWindows="true"
你的抽屉布局看起来就像
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="match_parent">
你的导航视图看起来像
<android.support.design.widget.NavigationView android:id="@+id/navigation_view" android:layout_height="match_parent" android:layout_width="wrap_content" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/navigation_header" app:menu="@menu/drawer" />
我已经testing了它,并且完全正常工作。希望它可以帮助别人。这可能不是最好的方法,但是它工作顺利并且易于实现。 标记它,如果它帮助。快乐的编码:)
我正在使用devise支持库。 而只是通过使用自定义的主题,我打开导航抽屉时,实现了透明的状态栏。
<style name="NavigationStyle" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/primaryColor</item> <item name="colorPrimaryDark">@color/primaryColorDark</item> <!-- To Make Navigation Drawer Fill Status Bar and become Transparent Too --> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style>
最后在Manifest文件中添加主题
<activity ........ ........ android:theme="@style/NavigationStyle"> </activity>
不要忘记在“DrawerLayout”中使用属性android:fitsSystemWindows="true"
”
这是最简单的,它为我工作:
在值-21:
<resources> <style name="AppTheme" parent="AppTheme.Base"> ... <item name="android:windowTranslucentStatus">true</item> </style> <dimen name="topMargin">25dp</dimen> </resources>
在值中:
<resources> <dimen name="topMargin">0dp</dimen> </resources>
并设置到您的工具栏
android:layout_marginTop="@dimen/topMargin"
而不是使用ScrimInsetsFrameLayout
…是不是更容易添加一个固定的高度24dp
和一个24dp
的背景视图?
我明白,这涉及到在层次结构中添加虚拟视图,但对我来说似乎更清洁。
我已经尝试过了,它运作良好。
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_base_drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- THIS IS THE VIEW I'M TALKING ABOUT... --> <View android:layout_width="match_parent" android:layout_height="24dp" android:background="?attr/colorPrimary" /> <android.support.v7.widget.Toolbar android:id="@+id/activity_base_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="2dp" android:theme="@style/ThemeOverlay.AppCompat.Dark" /> <FrameLayout android:id="@+id/activity_base_content_frame_layout" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <fragment android:id="@+id/activity_base_drawer_fragment" android:name="com.myapp.drawer.ui.DrawerFragment" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:elevation="4dp" tools:layout="@layout/fragment_drawer" /> </android.support.v4.widget.DrawerLayout>
试试这个:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drawer_layout" android:fitsSystemWindows="true"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!--Main layout and ads--> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <FrameLayout android:id="@+id/ll_main_hero" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </FrameLayout> <FrameLayout android:id="@+id/ll_ads" android:layout_width="match_parent" android:layout_height="wrap_content"> <View android:layout_width="320dp" android:layout_height="50dp" android:layout_gravity="center" android:background="#ff00ff" /> </FrameLayout> </LinearLayout> <!--Toolbar--> <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/toolbar" android:elevation="4dp" /> </FrameLayout> <!--left--> <ListView android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@null" android:background="@mipmap/layer_image" android:id="@+id/left_drawer"></ListView> <!--right--> <FrameLayout android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="right" android:background="@mipmap/layer_image"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@mipmap/ken2" android:scaleType="centerCrop" /> </FrameLayout>
风格:
<style name="ts_theme_overlay" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/red_A700</item> <item name="colorPrimaryDark">@color/red1</item> <item name="android:windowBackground">@color/blue_A400</item> </style>
主要活动扩展了ActionBarActivity
toolBar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolBar);
现在你可以像使用ToolBar的普通ActionBar一样来创build选项菜单。
这是我的布局
- 顶部:左抽屉 – 右抽屉
- MID:ToolBar(ActionBar)
- 底部:ListFragment
希望你明白!玩得开心!