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

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

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

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

DrawerLayout

DrawerLayout 是谷歌推出的一款用于實(shí)現(xiàn)側(cè)滑菜單效果的控件,集成在 support library 里面,使用時(shí)需要加載 android-support-v4.jar 包。
DrawerLayout 分為側(cè)邊菜單和主內(nèi)容區(qū)兩部分,側(cè)邊菜單可以根據(jù)手勢(shì)展開或隱藏,主內(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)容(抽屜未打開的默認(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,這樣打開抽屜后,依然可以看到部分內(nèi)容布局。

代碼

布局文件寫好之后,側(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));//給左邊菜單寫入數(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(),那打開抽屜的操作就是 DrawerLayout .openDrawer(int gravity),gravity 為抽屜開啟方向,應(yīng)與布局文件中定義的方向一致,不然會(huì)報(bào)錯(cuò)。下面在代碼中實(shí)現(xiàn)按鈕控制抽屜的開啟操作:

布局:

在默認(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="開啟左邊抽屜"/>
?
       <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="開啟全部抽屜"/>
?
       <Button
           android:id="@+id/btn_open_right_drawer"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="right"
           android:text="開啟右邊抽屜"/>
   </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è)滑菜單的布局都是我們自己寫的,雖然邏輯并不復(fù)雜,各種樣式也可定義無(wú)限制,但總是要耗費(fèi)些許時(shí)間。而且各家 APP 最終實(shí)現(xià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)的按鈕,并寫好邏輯:

<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)單,就是打開抽屜之后,點(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 太多,需要將其按類劃分,設(shè)置分割線以及類標(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 屬性??葱Ч?/p>

分割線

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

上面代碼中已經(jīng)介紹了 menu 的點(diǎn)擊事件,只需要在代碼中獲取到 menu 菜單,然后重寫 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è)滑菜單

寫到這里,滑動(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)菜單啊?。N业囊馑际?,即誒岸上沒(méi)有一個(gè)提示功能讓人知道滑動(dòng)菜單的存在。Material Design 的建議是在 Toolbar 的左邊添加一個(gè)導(dǎo)航按鈕,點(diǎn)擊按鈕抽屜就會(huì)出現(xiàn)。這相當(dāng)于給用戶兩種打開抽屜的方式,防止一些用戶不知道抽屜的存在。
接下來(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ò)重寫 onOptionsItemSelected 方法,根據(jù) item 的 id 獲取到返回按鈕并對(duì)其進(jìn)行處理,HomeAsUp 的按鈕 id 是 android.R.id.home。
到此,我們就完成了 Toolbar 添加導(dǎo)航按鈕進(jìn)行抽屜打開的操作了??匆幌滦Ч?/p>

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

抽屜不遮擋 Toolbar

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


抽屜不遮擋 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ù),可供大家選擇。

參考

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容