使用 Android ActionBarCompat 製作導覽列 (2)

ActionBarCompat

接續:使用 Android ActionBarCompat 製作導覽列(1)

第一次使用 ActionBarCompat

1.首先要做前面轉換步驟的步驟1,把 appcompat library project 加入你的專案。

2.再來是修改你的 application theme,請打開 AndroidManifest.xml 修改如下:
<application
  android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
    ...
    >
如果你有用 Action Bar Style 產生器,則可能會是
android:theme="@style/Theme.Myabs"
以上只要做一次,接下來就是製作 App 的內容了,現在你可能不會建立 Activity ,而是 ActionBarActivity (android.support.v7.app.ActionBarActivity),一樣在 onCreate 中指定 layout ,如此就是基本的使用 ActionBar 的 App 了。

3.要建立 Action buttons (選單按鈕),必須在專案的 res 目錄中建立 menu 目錄,並在其中建立一個 xml 檔 (menu/main.xml),內容如下:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_refresh"
        android:title="更新"
        android:icon="@android:drawable/ic_menu_revert"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/action_search"
        android:title="搜尋"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/action_settings"
        android:title="@string/action_settings"
        app:showAsAction="never" />
</menu> 
app:showAsAction 的選項有
["ifRoom" | "never" | "withText" | "always" | "collapseActionView"]
選項說明:
  • ifRoom:如果空間夠用,就顯示。
  • never:永遠不顯示,隱藏在 overflow 裡。
  • withText:顯示文字,可和 ifRoom 並用。
  • always:一定顯示。
  • collapseActionView:折疊顯示。
有了選單 xml 後,在 ActionBarActivity 中覆寫 onCreateOptionsMenu 方法:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}
其中 inflate 的就是剛才建立的 main.xml。這樣在 ActionBar 的右邊就會出現選單了。大概會像這樣:
要讓選單被選擇時執行某些動作,必須覆寫 onOptionsItemSelected,如下:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()){
        case R.id.action_refresh:
            Toast.makeText(this, "Refresh", Toast.LENGTH_SHORT).show();
            return true;
    }

    return false;
}

ActionBar 常用操作

接下來要介紹 ActionBar 的一些常用的操作。

隱藏 ActionBar

某些特殊情況下,可能必須讓 ActionBar 消失,請呼叫 hide() 方法:
ActionBar actionBar = this.getSupportActionBar();
actionBar.hide();
重要!這裡必須注意你的 import 套件是否正確,如果是用 support library 的,必須是 android.support.v7.app.ActionBar,如果不是,則為 android.app.ActionBar,而且是使用 getActionBar(),這在之後很多地方,例如 Fragment 等,都要注意 import 是否正確。

返回箭號

要讓 ActionBar 左邊的 App icon 出現返回的箭號,可以設定
actionBar.setDisplayHomeAsUpEnabled(true);
true 表示出現箭號,false 則沒有。沒指定就是不顯示。

當出現箭號時,使用者點選 App icon 可以觸發事件,這時候要在 onOptionsItemSelected 方法裡,添加項目 id,如下:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()){
        case android.R.id.home:
            this.finish();
            break;

        case R.id.action_refresh:
            Toast.makeText(this, "Refresh", Toast.LENGTH_SHORT).show();
            return true;
    }

    return false;
}
注意!android.R.id.home。
另外有一個設定是
actionBar.setHomeButtonEnabled(false);
這個方法和 setDisplayHomeAsUpEnabled 會互相影響。setHomeButtonEnabled 若為 true 則 App icon 可以被點選,若為 false 則無任何事件觸發。

簡單的說,只要顯示返回箭號,就一定可以被點選,反之,則點選無效;但可以透過強制把 setHomeButtonEnabled 設為 true 讓 App icon 即使沒有顯示箭號,仍可以被點選。

圖示

在未指定的情況下,ActionBar 上的圖示是 App 的啟動圖示,如果要換掉,可以指定:
actionBar.setIcon(R.drawable.ic_launcher);

標題

當然也可指定 ActionBar 的標題:
actionBar.setTitle("ABC");
通常會用在切換不同的 Fragment 或 Activity 時,特別設定和該頁面相關的標題名稱。

切換 Fragment

使用 ActionBar 的用途之一就是將原本由許多 Activity 組成的 App 改成由 Fragment 組成,必須把原本的 Activity 換成 ActionBarActivity。原本的 layout 換成類似這個 fragment_content.xml:
<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">

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.DrawerLayout>
就只有一個空的 FrameLayout,之後會用來換成不同的 Fragment。

MainFragment.java

建立主要的 Fragment,叫做 MainFragment,接著覆寫 onCreateView:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    return initView(inflater, container);
}

private View initView(LayoutInflater inflater, ViewGroup container) {
    View view = inflater.inflate(R.layout.fragment_main, container, false);
    return view;
}
和原本 Activity 在 onCreate 初始化 view 的地方不同,Fragment 是在 onCreateView 初始化。inflate 的功能等同於 setContentView 。

接著建立一個產生實體的方法,方便產生實體:
public static MainFragment create(){
    MainFragment mainFragment = new MainFragment();
    return mainFragment;
}

MainActivity.java

接下來回到 MainActivity 中,把 MainFragment 加入:
private void initView(){
    setContentView(R.layout.fragment_content);
    initFragment();
}

private void initFragment() {
    MainFragment mainFragment = (MainFragment) MainFragment.create();

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.content_frame, mainFragment, "main");
    fragmentTransaction.commit();
}
以前的做法是 [A] Activity -> [B] Activity -> [C] Activity

現在的做法是 [A] Activity == [A] Fragment -> [B] Fragment -> [C] Fragment
Activity 是個容器,用來裝其他的 Fragment 。

Back

要讓 [B] Fragment 的 ActionBar 的 App icon 仍夠執行 back 的動作,必須在 Activity 中設定選單行為:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()){
        case android.R.id.home:
            FragmentManager fm = getSupportFragmentManager();
            if(fm.getBackStackEntryCount() > 0){
               fm.popBackStack();
            }
            break;
    }

    return false;
}
只要堆疊數量大於 0 ,就執行 popBackStack() 把目前的 Fragment 給丟掉,自然就會顯示前一個 Fragment 了。

續:使用 Android ActionBarCompat 製作導覽列(3)
本文網址:http://blog.tonycube.com/2014/02/android-actionbarcompat-2.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

9 則留言

  1. 不好意思請問一下...
    private void initView(){
    setContentView(R.layout.fragment_content);
    initFragment();
    }

    private void initFragment() {
    MainFragment mainFragment = (MainFragment) MainFragment.create();

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.content_frame, mainFragment, "main");
    fragmentTransaction.commit();
    }
    這兩個方法是要用在哪裡??
    今年一月的初學者,剛好遇到ADT改版fragment改版,基礎學習上有很大的困難
    大家都說先把FRAGMENT刪掉先用Activity寫,但我爬文找到這篇,排名也很前面
    就進來看到前輩你的文章,超讚的!,可是就這邊看不太懂= ="
    可否麻煩前輩稍微講解一下!!?

    回覆刪除
    回覆
    1. initView()會用來初始化view,先建立layout,再建立fragment。
      實際上你把這兩行直接寫在 onCreate 裡面也是可以。

      initFragment() 裡面只是建立 Fragment 而已,建立 MainFragment 後,透過管理器(FragmentManager)置換掉 layout 中的 R.id.content_frame,

      一個 Activity 中可以有多個 Fragment ,但是一次只會顯示一個。
      在 Fragment 式的程式中,可能會不斷的 replace Fragment 來顯示不同個 Fragment。

      刪除
  2. 版主你好
    我在跟著你的步驟做的時候發現在
    app:showAsAction 出現問題
    eclipse會罵我 沒有 showAsAction這個 attribute
    然後我將app 改為 android 問題解決了
    是否sdk 版本的 問題 還是 版主錯誤得示範?

    回覆刪除
    回覆
    1. 相容性問題,在API Level 11 之前沒有這個屬性,
      所以加了 「xmlns:app="http://schemas.android.com/apk/res-auto"」這行
      讓舊版本可以用 app: 開頭來使用這個屬性。

      11 之後的版本可以使用內建的 android: 來取用該屬性。

      刪除
  3. 請問版主
    MainActivity 中

    FragmentManager fragmentManager = getSupportFragmentManager();
    eclipse 顯示The method getSupportFragmentManager() is undefined for the type MainActivity

    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    顯示 Type mismatch: cannot convert from android.app.FragmentTransaction to android.support.v4.app.FragmentTransaction

    請問我是不是忘了 import 什麼或是 extend錯誤?

    回覆刪除
    回覆
    1. getSupportFragmentManager() 如果 undefined,表示你不是用 support.v4 的版本,所以不需要 Support
      用 getFragmentManager() 就可以了。


      第二個錯誤
      android.app.FragmentTransaction 無法轉換成 android.support.v4.app.FragmentTransaction
      和前一個錯誤是同樣原因,因為你不是用 support.v4 所以當然無法轉換

      請確認是否使用相同版本的類別及方法。


      刪除
  4. 版主你好
    可以請問一下嗎?怎麼將別的activity放入fragment裡做整合的動作。
    EX:將一個MapActivity放入其中一個fragment
    還有怎麼在fragment裡增加原本就存在的toolbar的iconButton?
    已經卡關很久了,跪求方法~

    回覆刪除
    回覆
    1. Activity 是母體,不能放進子體 Fragment 中,有 MapFragment 可用。

      「怎麼在fragment裡增加原本就存在的toolbar的iconButton?」
      不太明白你的意思,如果你是指toolbar 左邊的 Home icon,
      可以用getsupportactionbar()去指定 setDisplayShowHomeEnabled(true)來顯示。
      (https://developer.android.com/intl/zh-tw/reference/android/support/v7/app/ActionBar.html#setDisplayShowHomeEnabled%28boolean%29)

      刪除
  5. 版主你好 我是APP初學者 但一學就有難題了
    所以想請教你 要用什麼工具可以開發 多個使用者一起計劃的功能? 功能類似google doc
    Android studio可以嗎?如果可以 要怎麼做?
    謝謝

    回覆刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。