Android 筆記 —— 側(cè)滑菜單(抽屜)

側(cè)滑菜單這種設(shè)計(jì)份氧,在很多 APP 上面都有看到同辣,例如大版本3.0之前的知乎、網(wǎng)易新聞匀们、滴滴打車(chē)等缴淋。有些熱衷于 Android Material Design 的開(kāi)發(fā)者,甚至將一些 IOS 化的 Android 應(yīng)用改頭換面泄朴,將其 MD 化重抖。這過(guò)程中,基本上都會(huì)給這些“改版”應(yīng)用裝上“抽屜”祖灰,例如 Xposed 上面的經(jīng)典插件——“WechatUI ”钟沛、Github 上面的“Material Design 豆瓣客戶(hù)端——豆芽”,以及一些第三方微博客戶(hù)端局扶。
既然側(cè)滑菜單如此受大家歡迎恨统,那我們就趕緊去了解一下它吧

側(cè)滑菜單的實(shí)現(xiàn)方式

側(cè)滑菜單主要有兩種實(shí)現(xiàn)方式,一是使用開(kāi)源庫(kù)三妈,如 SlidingMenu畜埋、MaterialDrawer 等,二是使用 Android 官方推薦的 DrawerLayout 實(shí)現(xiàn)畴蒲。
我們就先以官方推薦的為基礎(chǔ)來(lái)進(jìn)行學(xué)習(xí)悠鞍,如果能把官方推薦的知識(shí)掌握了,再使用開(kāi)源庫(kù)就更不是問(wèn)題了模燥。

DrawerLayout

DrawerLayout 是谷歌推出的一款用于實(shí)現(xiàn)側(cè)滑菜單效果的控件咖祭,集成在 support library 里面,使用時(shí)需要加載 android-support-v4.jar 包蔫骂。
DrawerLayout 分為側(cè)邊菜單和主內(nèi)容區(qū)兩部分么翰,側(cè)邊菜單可以根據(jù)手勢(shì)展開(kāi)或隱藏,主內(nèi)容區(qū)可以根據(jù)側(cè)邊菜單的變換而改變辽旋。

使用方法

先看代碼

布局:
<?xml version="1.0" encoding="utf-8"?>
<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_main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.ginkwang.drawertest.MainActivity">
?
   <FrameLayout
       android:id="@+id/fl_content"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
?
   <!--左邊抽屜菜單-->
   <RelativeLayout
       android:id="@+id/rl_left"
       android:layout_width="200dp"
       android:layout_height="match_parent"
       android:layout_gravity="left"
       >
       <ListView
           android:id="@+id/lv_left"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>
   </RelativeLayout>
?
   <!--右邊抽屜菜單-->
   <RelativeLayout
       android:id="@+id/rl_right"
       android:layout_width="200dp"
       android:layout_height="match_parent"
       android:layout_gravity="right"
       >
       <ListView
           android:id="@+id/lv_right"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>
   </RelativeLayout>
</android.support.v4.widget.DrawerLayout>

從布局文件可以看出硬鞍,DrawerLayout 是根布局,然后緊跟著的是第一個(gè)子元素是默認(rèn)內(nèi)容(抽屜未打開(kāi)的默認(rèn)內(nèi)容,即本例中的 FrameLayout)固该,之后的是抽屜內(nèi)容锅减。
抽屜菜單的擺放通過(guò)抽屜布局的 android:layout_gravity="" 的屬性來(lái)控制,可填入 "left"伐坏、"right"怔匣、"start"、"end"桦沉。此屬性必須設(shè)置每瞒,要不然側(cè)滑抽屜時(shí)會(huì)報(bào)錯(cuò)。
抽屜菜單寬度單位為 dp纯露,大小一般不超過(guò) 320dp剿骨,這樣打開(kāi)抽屜后,依然可以看到部分內(nèi)容布局埠褪。

代碼

布局文件寫(xiě)好之后浓利,側(cè)滑效果按理說(shuō)就已經(jīng)實(shí)現(xiàn)了。但是此時(shí)你滑動(dòng)菜單時(shí)钞速,會(huì)發(fā)現(xiàn)左邊拉出來(lái)什么內(nèi)容都沒(méi)有贷掖,這是因?yàn)槲覀儧](méi)有給菜單布局注入數(shù)據(jù)。

public class MainActivity extends AppCompatActivity {
?
   private static final String TAG = "MainActivity";
?
   private Context mContext;
   private DrawerLayout mDlMain;
   private FrameLayout mFlContent;
   private RelativeLayout mRlLeft, mRlRight;
   private ListView mLvLeft;
   private TextView mTvRight;
?
   private String[] leftMenuNames = {"left_item1", "left_item2",
           "left_item3", "left_item4"};
?
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mContext = this;
       initView();
   }
?
   private void initView() {
       mDlMain = (DrawerLayout) findViewById(R.id.dl_main);
       mFlContent = (FrameLayout) findViewById(R.id.fl_content);
       mRlLeft = (RelativeLayout) findViewById(R.id.rl_left);
       mRlRight = (RelativeLayout) findViewById(R.id.rl_right);
       mLvLeft = (ListView) findViewById(R.id.lv_left);
       mLvLeft.setAdapter(new ArrayAdapter<String>(mContext,
               android.R.layout.simple_list_item_1, leftMenuNames));//給左邊菜單寫(xiě)入數(shù)據(jù)
       mTvRight = (TextView) findViewById(R.id.tv_right);
       mTvRight.setText("right_content");//給右邊菜單內(nèi)容賦值
   }
}

效果

左邊菜單
右邊菜單

菜單添加點(diǎn)擊事件

菜單做好之后,自然就會(huì)給菜單賦予點(diǎn)擊事件,來(lái)控制主內(nèi)容區(qū)顯示的內(nèi)容间驮。給菜單添加點(diǎn)擊事件,其實(shí)就是給菜單布局里面的 listview 添加點(diǎn)擊事件牙甫。這個(gè)簡(jiǎn)單得很,就是 listview.setOnItemClickListener(this) 即可调违。點(diǎn)擊之后窟哺,我們之前在主界面布局里定義的 FrameLayout 就會(huì)顯示相應(yīng)的 Fragment。代碼如下:

mLvLeft.setOnItemClickListener(this);
?
...
?
//左側(cè)菜單點(diǎn)擊事件
   @Override
   public void onItemClick(AdapterView<?> pAdapterView, View pView, int pI, long pL) {
       Fragment lFragment = new MenuFragment();
       Bundle lBundle = new Bundle();
       lBundle.putString("menu_str", "item_" + (pI + 1));
       lFragment.setArguments(lBundle);
       FragmentManager fragmentManager = getFragmentManager();
       fragmentManager.beginTransaction().replace(R.id.fl_content, lFragment).commit();
       mLvLeft.setItemChecked(pI, true);
       mDlMain.closeDrawers();//關(guān)閉抽屜
   }

MenuFragment 代碼如下:

public class MenuFragment extends Fragment {
?
   private View mView;
   private Context mContext;
   private TextView mTvMenuFragment;
?
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       mView = inflater.inflate(R.layout.layout_fragment_menu, container, false);
       mContext = getActivity();
       Bundle lBundle = getArguments();
       mTvMenuFragment = (TextView) mView.findViewById(R.id.tv_menu_fragment);
       if (lBundle != null) {
           mTvMenuFragment.setText(lBundle.getString("menu_str"));
       }
       return mView;
   }
}

上面代碼中翰萨,涉及到一個(gè)關(guān)閉抽屜的操作 DrawerLayout .closeDrawers(),那打開(kāi)抽屜的操作就是 DrawerLayout .openDrawer(int gravity)糕殉,gravity 為抽屜開(kāi)啟方向亩鬼,應(yīng)與布局文件中定義的方向一致,不然會(huì)報(bào)錯(cuò)阿蝶。下面在代碼中實(shí)現(xiàn)按鈕控制抽屜的開(kāi)啟操作:

布局:

在默認(rèn)顯示布局的 FrameLayout 中添加操作按鈕:

<FrameLayout
       android:id="@+id/fl_content"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <Button
           android:id="@+id/btn_open_left_drawer"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="開(kāi)啟左邊抽屜"/>
?
       <Button
           android:id="@+id/btn_open_all_drawer"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center|top"
           android:text="開(kāi)啟全部抽屜"/>
?
       <Button
           android:id="@+id/btn_open_right_drawer"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="right"
           android:text="開(kāi)啟右邊抽屜"/>
   </FrameLayout>
代碼:

在代碼中初始化按鈕雳锋,并實(shí)現(xiàn)按鈕點(diǎn)擊事件:

private Button mBtnOpenLeftDrawer, mBtnOpenAllDrawer, mBtnOpenRightDrawer;
?
...
 
mBtnOpenLeftDrawer = (Button) findViewById(R.id.btn_open_left_drawer);
       mBtnOpenLeftDrawer.setOnClickListener(this);
       mBtnOpenAllDrawer = (Button) findViewById(R.id.btn_open_all_drawer);
       mBtnOpenAllDrawer.setOnClickListener(this);
       mBtnOpenRightDrawer = (Button) findViewById(R.id.btn_open_right_drawer);
       mBtnOpenRightDrawer.setOnClickListener(this);
?
...
?
@Override
   public void onClick(View pView) {
       switch (pView.getId()) {
           case R.id.btn_open_left_drawer:
               mDlMain.openDrawer(Gravity.LEFT);
               break;
?
           case R.id.btn_open_all_drawer:
               mDlMain.openDrawer(Gravity.LEFT);
               mDlMain.openDrawer(Gravity.RIGHT);
               break;
?
           case R.id.btn_open_right_drawer:
               mDlMain.openDrawer(Gravity.RIGHT);
               break;
       }
   }

效果:

按鈕控制側(cè)滑菜單

去除抽屜劃出后主內(nèi)容頁(yè)背景的灰色

DrawerLayout.setScrimColor(Color.TRANSPARENT);

填充抽屜劃出后與屏幕邊緣之間的內(nèi)容

DrawerLayout.setDrawerShadow();

常見(jiàn)問(wèn)題

  • 在點(diǎn)擊DrawerLayout中的空白處的時(shí)候,底部的content會(huì)獲得事件
    由于Google的demo是一個(gè)ListView羡洁,所以ListView會(huì)獲得焦點(diǎn)玷过,事件就不會(huì)傳遞了,看不出來(lái)問(wèn)題。但是如果用的include加載的布局辛蚊,會(huì)出現(xiàn)這個(gè)情況粤蝎,那么如何解決?
    解決辦法:在include進(jìn)的那個(gè)布局里面袋马,添加clickable=true
  • 除了抽屜的布局視圖之外的視圖究竟放哪里
    左初澎、右抽屜和中間內(nèi)容視圖默認(rèn)是不顯示的,其他布局視圖都會(huì)直接顯示出來(lái)虑凛,但是需要將其放在 DrawerLayout 內(nèi)部才能正常使用(不要放在外面)碑宴,否則要么是相互覆蓋,或者就是觸屏事件失效桑谍,滾動(dòng)等效果全部失效延柠。

使用 NavigationView 實(shí)現(xiàn)側(cè)滑菜單的布局

前文中涉及到的側(cè)滑菜單的布局都是我們自己寫(xiě)的,雖然邏輯并不復(fù)雜锣披,各種樣式也可定義無(wú)限制贞间,但總是要耗費(fèi)些許時(shí)間。而且各家 APP 最終實(shí)現(xiàn)的樣式也五花八門(mén)盈罐,風(fēng)格不統(tǒng)一榜跌。
終于,谷歌在 Android 大版本5.0時(shí)推出了 MD 風(fēng)格的導(dǎo)航菜單 —— NavigationView 盅粪。NavigationView 整體分為兩部分钓葫,上部分叫 HeaderLayout,下部分的點(diǎn)擊項(xiàng)為 Menu票顾。
我們就一起來(lái)看看 NavigationView 的使用方式吧础浮。

NavigationView 使用方法

NavigationView 是 Design Support 中的控件,所以如果想要使用它奠骄,首先要先引入 Design 庫(kù)豆同。

引入 Design 庫(kù)

在項(xiàng)目的 build.gradle 中添加如下代碼:

dependencies {
 
   ...
     
   compile 'com.android.support:design:26.0.0-alpha1'
}

然后我們還要準(zhǔn)備好構(gòu)成 NavigationView 的兩個(gè)部分,Menu 和 HeaderLayout含鳞。

新建 Menu

在 xml 下新建 menu 文件夾影锈,然后在 menu 文件夾下再新建 nav_manu.xml 文件,menu 文件代碼為:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <group android:checkableBehavior="single">
       <item
           android:id="@+id/item_nav_menu_news"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_news"/>
?
       <item
           android:id="@+id/item_nav_menu_star"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_star"/>
?
       <item
           android:id="@+id/item_nav_menu_share"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_share"/>
?
       <item
           android:id="@+id/item_nav_menu_set"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_set"/>
   </group>
</menu>

可以看到蝉绷,menu 文件夾下是一個(gè) group 組鸭廷,其中 android:checkableBehavior 屬性設(shè)置為 single,代表 menu 項(xiàng)為單選熔吗。
然后每個(gè)菜單項(xiàng)是由一個(gè)個(gè)的 item 構(gòu)成的辆床,item 的幾個(gè)屬性的簡(jiǎn)單易懂,這里就不詳述桅狠。

新建 HeaderLayout

在 xml 下的 layout 文件夾中新建 layout_nav_header.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="200dp"
             android:background="?attr/colorPrimary"
             android:gravity="center"
             android:orientation="vertical"
             android:padding="15dp">
?
   <ImageView
       android:id="@+id/iv_nav_img"
       android:layout_width="50dp"
       android:layout_height="50dp"
       android:src="@mipmap/ic_launcher"/>
?
   <TextView
       android:id="@+id/tv_nav_txt"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="15dp"
       android:text="我知道你 的憂郁是為了什么"
       android:textColor="@android:color/white"/>
?
</LinearLayout>

這段代碼也很好懂讼载,就是定義了一個(gè) ImageView轿秧,下面是一行說(shuō)明文字。
OK咨堤,我們正式進(jìn)入主題菇篡,對(duì) NavigationView 進(jìn)行操作。

使用 Navigationview

此示例還是在前文介紹 DrawerLayout 的項(xiàng)目中運(yùn)行的吱型,為了不破壞之前的代碼逸贾,我們新建一個(gè)活動(dòng),在另一個(gè)活動(dòng)中使用 Navigationview津滞。
新建一個(gè)跳轉(zhuǎn)到新活動(dòng)的按鈕铝侵,并寫(xiě)好邏輯:

<Button
   android:id="@+id/btn_goto_nav"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="center"
   android:text="NavigationView"/>

case R.id.btn_goto_nav:
               startActivity(new Intent(MainActivity.this, NavActivity.class));
               break;

NavActivity 就是我們新建的活動(dòng)。
然后在這個(gè)活動(dòng)中加入 Navigationview 控件触徐,布局中其他還是和前文一樣咪鲜,只是將我們之前自定義的側(cè)滑菜單換成 Navigationview。

<android.support.v4.widget.DrawerLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/dl_nav"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.ginkwang.drawertest.NavActivity">
?
   <FrameLayout
       android:id="@+id/fl_nav_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >
       <TextView
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:gravity="center"
           android:text="NavigationView 抽屜菜單"
           android:textColor="@android:color/black"
           android:textSize="25sp"/>
   </FrameLayout>
   
   <android.support.design.widget.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/nav_menu"
       app:headerLayout="@layout/layout_nav_header"/>
</android.support.v4.widget.DrawerLayout>

然后在代碼中獲取到 Navigationview撞鹉,并設(shè)置其 menu 的點(diǎn)擊事件:

private NavigationView mNavView;
?
...
 
mNavView = (NavigationView) findViewById(R.id.nav_view);
mNavView.setCheckedItem(R.id.item_nav_menu_news);
mNavView.setNavigationItemSelectedListener(this);
?
...
 
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
   Toast.makeText(NavActivity.this, item.getTitle(), Toast.LENGTH_LONG).show();
   mDlNav.closeDrawers();
   return true;
}

邏輯也十分簡(jiǎn)單疟丙,就是打開(kāi)抽屜之后,點(diǎn)擊某一項(xiàng) menu鸟雏,會(huì)將抽屜關(guān)閉享郊,然后提示你當(dāng)前操作哪項(xiàng)的吐司。其中

mNavView.setCheckedItem(R.id.item_nav_menu_news);

表示設(shè)置 menu 的默認(rèn)選中狀態(tài)孝鹊。

最后效果圖:

NavigationView 抽屜菜單

到這里炊琉,不知道大家看出一個(gè)問(wèn)題沒(méi)有,之前我們定義的 menu item 的 icon 是項(xiàng)目中自帶的 ic_launcher又活。它的本身面目是這樣的:

ic_launcher.png

但是在效果圖中苔咪,卻顯示成了灰色的。
對(duì)于這個(gè)問(wèn)題柳骄,有兩個(gè)解決辦法:
在 Navigationview 的布局中添加 app:itemIconTint="@color/colorPrimary" 屬性团赏,顏色值自定義。

menu顏色1

但是這樣設(shè)置后耐薯,還只是能顯示純色舔清,那如果非要顯示原本的圖片該怎么設(shè)置呢?讓我們來(lái)看第二個(gè)解決方法曲初;

在代碼中獲取到 Navigationview 后体谒,設(shè)置其屬性

mNavView.setItemIconTintList(null);
menu 顏色2

NavigationView 添加分割線

有時(shí)候,側(cè)滑菜單的 item 太多复斥,需要將其按類(lèi)劃分营密,設(shè)置分割線以及類(lèi)標(biāo)注以作明示械媒。
這時(shí)目锭,我們只需要將相應(yīng)的 item 規(guī)劃在一個(gè) group 內(nèi)即可评汰。看代碼:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <group android:checkableBehavior="single">
       <item
           android:id="@+id/item_nav_menu_news"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_news"/>
?
       <item
           android:id="@+id/item_nav_menu_star"
           android:icon="@mipmap/ic_launcher"
           android:title="@string/nav_menu_star"/>
   </group>
?
   <item android:title="@string/nav_menu_title_common">
       <menu>
           <group android:checkableBehavior="single">
               <item
                   android:id="@+id/item_nav_menu_share"
                   android:icon="@mipmap/ic_launcher"
                   android:title="@string/nav_menu_share"/>
?
               <item
                   android:id="@+id/item_nav_menu_set"
                   android:icon="@mipmap/ic_launcher"
                   android:title="@string/nav_menu_set"/>
           </group>
       </menu>
   </item>
?
   <item android:title="@string/nav_menu_title_other">
       <menu>
           <group android:checkableBehavior="single">
               <item
                   android:id="@+id/item_nav_menu_send"
                   android:icon="@mipmap/ic_launcher"
                   android:title="@string/nav_menu_send"/>
           </group>
       </menu>
   </item>
</menu>

如果只要分割線的話痢虹,只需要將 item 放在 group 內(nèi)即可被去。如果想要副標(biāo)題指示的話,就需要將其中的 item 規(guī)劃在一個(gè)新的 item 中奖唯,并指定其 title 屬性惨缆。看效果:

分割線

NavigationView 頭布局(HeaderLayout)點(diǎn)擊事件

上面代碼中已經(jīng)介紹了 menu 的點(diǎn)擊事件丰捷,只需要在代碼中獲取到 menu 菜單坯墨,然后重寫(xiě) onNavigationItemSelected 即可。
頭布局的點(diǎn)擊事件的獲取和 menu 的一樣病往,也是在代碼中先獲取到頭布局的 view捣染,然后調(diào)用頭布局 view 的 findViewById() 獲取頭布局里面的控件,然后獲取到控件的點(diǎn)擊事件停巷,看代碼:

private View mHeaderView;
private ImageView mIvHeaderImg;
?
...
?
mHeaderView = mNavView.getHeaderView(0);//獲取頭布局
mIvHeaderImg = (ImageView) mHeaderView.findViewById(R.id.iv_nav_img);//獲取頭布局內(nèi) imageview 控件
mIvHeaderImg.setOnClickListener(this);
?
...
?
@Override
public void onClick(View pView) {
   Toast.makeText(NavActivity.this, "點(diǎn)擊 ImageView", Toast.LENGTH_LONG).show();
}

整體邏輯也十分簡(jiǎn)單耍攘,就是其中獲取頭布局的代碼 getHeaderView(0) ,可能會(huì)有人不理解畔勤。
其實(shí) Navigationview 本質(zhì)上是一個(gè) RecyclerView蕾各,頭布局通常是第一個(gè)元素,所以獲取頭布局的方式自然是 getHeaderView(0)庆揪。

注意

如果你的 buildToolsVersion 版本是 23.1.0式曲,那獲取頭布局的代碼為:

View headerLayout =
navigationView.inflateHeaderView(R.layout.navigation_header);

參考:

NavigationView獲取Header View的問(wèn)題

結(jié)合 ToolBar,一同構(gòu)建側(cè)滑菜單

寫(xiě)到這里嚷硫,滑動(dòng)菜單的實(shí)現(xiàn)的大部分知識(shí)點(diǎn)都已經(jīng)涵蓋在內(nèi)了检访,但是還有一個(gè)問(wèn)題,就是前文中的例子滑動(dòng)菜單的出現(xiàn)都是我們用手滑動(dòng)屏幕邊緣才出來(lái)的(廢話仔掸!所以叫滑動(dòng)菜單按喙蟆!)起暮。我的意思是卖氨,即誒岸上沒(méi)有一個(gè)提示功能讓人知道滑動(dòng)菜單的存在。Material Design 的建議是在 Toolbar 的左邊添加一個(gè)導(dǎo)航按鈕负懦,點(diǎn)擊按鈕抽屜就會(huì)出現(xiàn)筒捺。這相當(dāng)于給用戶(hù)兩種打開(kāi)抽屜的方式,防止一些用戶(hù)不知道抽屜的存在纸厉。
接下來(lái)我們就來(lái)實(shí)現(xiàn)這個(gè)功能系吭。

添加 Toolbar

我們還是在 NavigationView 的示例項(xiàng)目中操作,還不了解 Toolbar 的朋友請(qǐng)先看一下這篇文章 Android 筆記 —— Toolbar 使用總結(jié)颗品,布局中添加 Toolbar 的代碼就不貼了肯尺,直接看 Activity 中修改的邏輯沃缘。

mToolbar = (Toolbar) findViewById(R.id.tb_nav_bar);
setSupportActionBar(mToolbar);//添加 Toolbar
?
...
?
ActionBar lActionBar = getSupportActionBar();
if (lActionBar != null) {
   lActionBar.setDisplayHomeAsUpEnabled(true);//顯示返回按鈕
   lActionBar.setHomeAsUpIndicator(R.mipmap.ic_toolbar_menu);//替換返回按鈕圖標(biāo),改為導(dǎo)航按鈕
}
?
...
?
@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case android.R.id.home:
           mDlNav.openDrawer(Gravity.START);
           break;
   }
   return true;
}

我們先是將 Toolbar 添加進(jìn)來(lái)并獲取则吟,然后通過(guò) getSupportActionBar() 獲取到 ActionBar槐臀,這里 ActionBar 的具體實(shí)現(xiàn)是由 Toolbar 來(lái)完成的。
之后通過(guò) ActionBar.setDisplayHomeAsUpEnabled(true) 顯示 Toolbar 的返回按鈕氓仲,再替換但會(huì)按鈕的圖標(biāo)樣式水慨,改為導(dǎo)航圖標(biāo)。
這時(shí)敬扛,你點(diǎn)擊導(dǎo)航按鈕的話晰洒,菜單還是不會(huì)出現(xiàn),而是返回到上一個(gè)活動(dòng)啥箭,本質(zhì)上它現(xiàn)在還是一個(gè)返回按鈕欢顷。
接著我們?cè)偬鎿Q它的功能,通過(guò)重寫(xiě) onOptionsItemSelected 方法捉蚤,根據(jù) item 的 id 獲取到返回按鈕并對(duì)其進(jìn)行處理抬驴,HomeAsUp 的按鈕 id 是 android.R.id.home。
到此缆巧,我們就完成了 Toolbar 添加導(dǎo)航按鈕進(jìn)行抽屜打開(kāi)的操作了布持。看一下效果:

Toolbar 導(dǎo)航按鈕打開(kāi)抽屜.gif

抽屜不遮擋 Toolbar

前面我們介紹與演示的都是抽屜拉出后遮擋住 Toolbar 的模式陕悬,那相信你也見(jiàn)到或使用過(guò)一些 APP 题暖,就是抽屜拉出后,不遮擋 Toolbar捉超,然后 Toolbar 上的導(dǎo)航按鈕隨著抽屜的拉出狀態(tài)會(huì)變成返回按鈕胧卤。類(lèi)似下面這樣:


抽屜不遮擋 Toolbar

接下來(lái)我們就一起來(lái)實(shí)現(xiàn)這樣的效果吧!
首先拼岳,

布局:

<LinearLayout
   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:orientation="vertical">
?
   <android.support.v7.widget.Toolbar
       android:id="@+id/tb_nav_buttom"
       android:layout_width="match_parent"
       android:layout_height="?attr/actionBarSize"
       android:background="?attr/colorPrimary"
       app:theme="@style/ToolbarTheme"/>
?
   <android.support.v4.widget.DrawerLayout
       android:id="@+id/dl_nav_buttom"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
?
       <FrameLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent">
?
           <TextView
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:gravity="center"
               android:text="抽屜不遮擋 Toolbar"
               android:textColor="@android:color/black"
               android:textSize="25sp"/>
       </FrameLayout>
?
       <android.support.design.widget.NavigationView
           android:id="@+id/nv_buttom"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:layout_gravity="start"
           app:headerLayout="@layout/layout_nav_header"
           app:menu="@menu/nav_menu"/>
   </android.support.v4.widget.DrawerLayout>
</LinearLayout>

整個(gè)布局的實(shí)現(xiàn)邏輯也很簡(jiǎn)單枝誊,就是將 Toolbar 從 DrawerLayout 中隔離出去,放在 DrawerLayout 上面惜纸,最外層是一個(gè)相對(duì)布局叶撒,方向是從上至下的。
然后 Toolbar 中有一個(gè)設(shè)置布局的屬性耐版,我們來(lái)看一下里面定義了什么:

<style name="ToolbarTheme" parent="AppTheme">
   <!--左邊導(dǎo)航按鈕顏色-->
   <item name="android:textColorSecondary">@color/colorNav</item>
</style>

只是設(shè)置了一個(gè)導(dǎo)航按鈕的顏色祠够,然后將項(xiàng)目父布局的屬性都繼承下來(lái)。
代碼:
來(lái)看一下代碼中都有什么:

public class NavButtomActivity extends AppCompatActivity {
?
   private DrawerLayout mDlNavButtom;
   private Toolbar mToolbar;
   private ActionBarDrawerToggle mActionBarDrawerToggle;
?
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.layout_nav_buttom_toolbar);
       initView();
   }
?
   private void initView() {
       mToolbar = (Toolbar) findViewById(R.id.tb_nav_buttom);
       mToolbar.setTitle("抽屜不遮擋 Toolbar");//設(shè)置標(biāo)題
       mToolbar.setTitleTextColor(getResources().getColor(R.color.colorText));//標(biāo)題顏色
       setSupportActionBar(mToolbar);
?
       mDlNavButtom = (DrawerLayout) findViewById(R.id.dl_nav_buttom);
       mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDlNavButtom, mToolbar, 0, 0);
       mDlNavButtom.setDrawerListener(mActionBarDrawerToggle);
       mActionBarDrawerToggle.syncState();//ActionBarDrawerToggle與DrawerLayout的狀態(tài)同步粪牲,
       // 并將ActionBarDrawerToggle中的drawer圖標(biāo)古瓤,設(shè)置為ActionBar的Home-Button的icon
   }
}

可以看到,我們沒(méi)有按照前文示例中那樣腺阳,將 Toolbar 的返回按鈕替換為我們自己設(shè)置的 icon落君,而是使用 ActionBarDrawerToggle 的默認(rèn)圖標(biāo)滴须。
并調(diào)用 ActionBarDrawerToggle.syncState() 方法將 ActionBarDrawerToggle 與DrawerLayout 的狀態(tài)同步,最終實(shí)現(xiàn)的效果是這樣:

抽屜不遮擋 Toolbar.gif

結(jié)束

至此叽奥,側(cè)滑菜單(抽屜)的大部分用法已經(jīng)介紹完了。當(dāng)然痛侍,如果想要在項(xiàng)目中使用抽屜的話朝氓,遠(yuǎn)沒(méi)有這樣復(fù)雜,只需要構(gòu)建工程時(shí)選擇 Navigation Drawer Activity 即可主届。另外赵哲,Github 上面也有更多功能更強(qiáng)大的關(guān)于抽屜的開(kāi)源庫(kù),可供大家選擇君丁。

參考

郭霖 《第一行代碼》
使用DrawerLayout實(shí)現(xiàn)側(cè)拉菜單
android官方側(cè)滑菜單DrawerLayout詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枫夺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绘闷,更是在濱河造成了極大的恐慌橡庞,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件印蔗,死亡現(xiàn)場(chǎng)離奇詭異扒最,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)华嘹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)吧趣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耙厚,你說(shuō)我怎么就攤上這事强挫。” “怎么了薛躬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵俯渤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我型宝,道長(zhǎng)稠诲,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任诡曙,我火速辦了婚禮臀叙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘价卤。我一直安慰自己劝萤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布慎璧。 她就那樣靜靜地躺著床嫌,像睡著了一般跨释。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厌处,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天鳖谈,我揣著相機(jī)與錄音,去河邊找鬼阔涉。 笑死缆娃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瑰排。 我是一名探鬼主播贯要,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼椭住!你這毒婦竟也來(lái)了崇渗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤京郑,失蹤者是張志新(化名)和其女友劉穎宅广,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體些举,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乘碑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了金拒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兽肤。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绪抛,靈堂內(nèi)的尸體忽然破棺而出资铡,到底是詐尸還是另有隱情,我是刑警寧澤幢码,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布笤休,位于F島的核電站,受9級(jí)特大地震影響症副,放射性物質(zhì)發(fā)生泄漏店雅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一贞铣、第九天 我趴在偏房一處隱蔽的房頂上張望闹啦。 院中可真熱鬧,春花似錦辕坝、人聲如沸窍奋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琳袄。三九已至江场,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窖逗,已是汗流浹背址否。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碎紊,地道東北人佑附。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像矮慕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啄骇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容