Android開發筆記(一百一十九)工具欄ToolBar

湖前琴亭發表於2016-08-18

Toolbar

在前面的博文《Android開發筆記(二十)頂部導航欄》中,我們學習了ActionBar的用法,可是ActionBar著實是不怎麼好用,比如文字風格不能定製、圖示不能定製,而且還存在低版本的相容性問題,所以實際開發中大家還是不傾向使用ActionBar。為此,Android提供了加強版的工具欄控制元件即Toolbar,因為Toolbar繼承自ViewGroup,而且可在佈局檔案中像其它佈局檢視一樣使用,所以靈活性大大的提高了。既然Android都與時俱進了,那我們也不能落後,現在就來好好學學Toolbar的用法。


匯入android-support-v7-appcompat

Toolbar包含在android-support-v7-appcompat.jar包中,但app工程還不能直接使用這個jar包,因為v7-appcompat是一個完整的工程,jar包裡面大量引用了工程中的圖片資源,所以我們要先把v7-appcompat匯入為一個庫工程,然後app工程再引用這個庫工程。具體步驟如下所示:
1、SDK的Extra元件中的“Android Support Library”要更新到最新版本。
2、把v7-appcompat匯入為一個庫工程,v7-appcompat的源路徑是sdk\extras\android\support\v7\appcompat。
3、把project.properties中的target改為23(注意庫工程和app工程都要改),不然會出現如下錯誤:
Error:Error retrieving parent for item: No resource found that matches the given name 'android:TextAppearance.Material.Widget.Button.Inverse'.  
Error:Error retrieving parent for item: No resource found that matches the given name 'android:Widget.Material.Button.Colored'.  
4、刪除values-v11與values-v14下面的styles.xml(注意庫工程和app工程都要刪),不然編譯報錯:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.exmtoolbar/com.example.exmtoolbar.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.


在專案中引入Toolbar

因為Toolbar與ActionBar都佔著頂部導航欄的位置,所以要想引入Toolbar就得先關閉ActionBar啦,具體步驟如下所示:
1、在styles.xml中定義一個不包含ActionBar的風格樣式
    <style name="AppBaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/blue_light</item>
        <item name="colorPrimaryDark">@color/blue_light</item>
        <item name="colorAccent">@color/blue_light</item>
    </style>
2、修改AndroidManifest.xml,把application節點的android:theme屬性值改為第一步定義的風格,如android:theme="@style/AppBaseTheme"
3、頁面佈局檔案的根節點改為LinearLayout,且為vertical垂直方向;然後增加一個Toolbar元素,因為Toolbar本質是個ViewGroup,所以也可在它下面新增別的控制元件。下面是個佈局例子片段:
    <android.support.v7.widget.Toolbar
        android:id="@+id/tl_head"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
4、Activity程式碼改為繼承AppCompatActivity,注意早期的v7包是沒有AppCompatActivity的,所以前面才說要先把v7包更新到最新版本。
5、同樣修改AndroidManifest.xml,給該activity節點補充屬性值android:theme="@style/AppBaseTheme"
6、回到Activity程式碼,獲取一個Toolbar物件,並呼叫setSupportActionBar方法設定預設的導航欄為當前的Toolbar。


Toolbar的常用方法

Toolbar比ActionBar靈活,主要便是它提供了多個方法來修改控制元件風格,下面是Toolbar的常用方法:
setLogo : 設定工具欄圖示。
setTitle : 設定標題文字。
setTitleTextAppearance : 設定標題的文字風格。
setTitleTextColor : 設定標題的文字顏色。
setSubtitle : 設定副標題文字。副標題在標題下方。
setSubtitleTextAppearance : 設定副標題的文字風格。
setSubtitleTextColor : 設定副標題的文字顏色。
setNavigationIcon : 設定導航圖示。導航圖示在工具欄圖示左邊。
setNavigationOnClickListener : 設定導航圖示的點選監聽器。
setOverflowIcon : 設定溢位選單的按鈕圖示。
showOverflowMenu : 顯示溢位選單圖示。
hideOverflowMenu : 隱藏溢位選單圖示。
dismissPopupMenus : 關閉已彈出的選單。


SearchView

v7包在帶來Toolbar的同時,也帶來了一個加強版的SearchView。有關原SearchView的使用說明參見《Android開發筆記(二十)頂部導航欄》,新舊兩個SearchView的用法其實大同小異,當然新版的功能會更強大些,下面是android.widget.SearchView與android.support.v7.widget.SearchView的主要區別:

二者在呼叫時的區別:
1、選單佈局檔案中,舊SearchView的寫法是android:actionViewClass="android.widget.SearchView",而新SearchView的寫法是app:actionViewClass="android.support.v7.widget.SearchView"
2、程式碼中獲取SearchView物件,新控制元件還可通過v7類MenuItemCompat的getActionView方法來獲取。
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuItem);

二者在功能上的區別:
1、編輯框其實是個SearchAutoComplete控制元件,該控制元件在舊SearchView中是隱藏的,在新SearchView中是開放的,所以我們可隨意修改v7編輯框的顯示風格。
2、基於上一點,新控制元件可取到SearchAutoComplete的物件,因此我們可給該物件註冊自動完成的字串介面卡,在使用者輸入文字時,介面會自動彈出符合搜尋條件的關鍵詞列表;
3、setAppSearchData方法在舊SearchView中是隱藏的,在新SearchView中是開放的,所以舊控制元件只能傳遞搜尋文字給結果頁面,而新控制元件允許傳遞其他的額外資訊給搜尋結果頁面。


Toolbar執行問題處理集錦

更換導航欄還是存在一些相容問題的,下面是博主發現的幾個情況及其解決辦法:

1、溢位選單的選單項已經設定為android:showAsAction="ifRoom",但即使工具欄上還有空間,該選單項也不會顯示在工具欄上。解決辦法:
在選單佈局檔案的menu根節點增加屬性xmlns:app="http://schemas.android.com/apk/res-auto",然後把android:showAsAction="ifRoom"改為app:showAsAction="ifRoom"。

2、溢位選單列表在選單文字左側顯示圖示的方法,使用ActionBar時正常,使用Toolbar時反而不會顯示圖示了。解決辦法:
ActionBar的featureId是8,Toolbar的featureId是108,所以在圖示顯示方法內部,要同時判斷這兩個數值,而不能像以前那樣僅僅判斷Window.FEATURE_ACTION_BAR。修改之後的圖示顯示方法如下:
    //顯示OverflowMenu的Icon 
	public static void setOverflowIconVisible(int featureId, Menu menu) {
		//ActionBar的featureId是8,Toolbar的featureId是108
        if (featureId%100 == Window.FEATURE_ACTION_BAR && menu != null) {
            if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
                try {
                    Method m = menu.getClass().getDeclaredMethod(
                            "setOptionalIconsVisible", Boolean.TYPE);
                    m.setAccessible(true);
                    m.invoke(menu, true);
                } catch (Exception e) {
                    Log.d(TAG, e.getMessage());
                }
            }
        }
    }

3、程式碼中呼叫getActionView方法獲取SearchView物件時,發現取到的SearchView為空。解決辦法:
把選單佈局檔案裡的android:actionViewClass="android.support.v7.widget.SearchView"改為app:actionViewClass="android.support.v7.widget.SearchView"。


下面是新版Toolbar與SearchView的使用截圖:



下面是新版Toolbar與SearchView的使用程式碼示例:
import java.util.Date;

import com.example.exmtoolbar.util.Utils;

import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity  {

	private final static String TAG = "MainActivity";
	private TextView tv_desc;
	private String[] mFormatArray = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd",
			"yyyy年MM月dd日HH時mm分ss秒", "yyyy年MM月dd日"};
	private String mFormat = mFormatArray[0];
	private Date mNowTime = new Date();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tv_desc = (TextView) findViewById(R.id.tv_desc);
		
		Toolbar tl_head = (Toolbar) findViewById(R.id.tl_head);
		tl_head.setBackgroundResource(R.color.blue_light);
		tl_head.setLogo(R.drawable.ic_launcher);
		tl_head.setTitle("標題");
		tl_head.setSubtitle("副標題");
		tl_head.setNavigationIcon(R.drawable.ic_back);
		setSupportActionBar(tl_head);
	}

	private void initSearchView(Menu menu) {
		MenuItem menuItem = menu.findItem(R.id.menu_search);
		SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuItem);
	    if(searchView == null){
	        Log.d(TAG, "Fail to get SearchView.");
	    } else {
	    	//新舊SearchView公用程式碼開始
	    	searchView.setIconifiedByDefault(true);
	    	searchView.setSubmitButtonEnabled(true);
	        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
	        ComponentName cn = new ComponentName(this, SearchResultActvity.class);
	        SearchableInfo info = searchManager.getSearchableInfo(cn); 
	        if(info == null){ 
	            Log.d(TAG, "Fail to get SearchResultActvity."); 
	        }      
	        searchView.setSearchableInfo(info);
	    	//新舊SearchView公用程式碼結束
	        
	        sac_text = (SearchView.SearchAutoComplete) searchView.findViewById(R.id.search_src_text);
	        sac_text.setTextColor(Color.WHITE);
	        sac_text.setHintTextColor(Color.WHITE);

			searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
				@Override
				public boolean onQueryTextSubmit(String query) {
					return false;
				}

				@Override
				public boolean onQueryTextChange(String newText) {
					doSearch(newText);
					return true;
				}
			});
	        
	        Bundle bundle = new Bundle();
	        bundle.putString("hi", "hello");
	        searchView.setAppSearchData(bundle);
	    }
	}
	
	private SearchView.SearchAutoComplete sac_text;
	private String[] hintArray = {"ab", "abc", "abcde", "abHtp", "aaeet", "aab"};
	private void doSearch(String text) {
		if (text.indexOf("a") == 0) {
			ArrayAdapter<String> adapter = new ArrayAdapter<String>(
					this, R.layout.list_auto, hintArray);
			sac_text.setAdapter(adapter);
	        sac_text.setOnItemClickListener(new OnItemClickListener() {
				@Override
				public void onItemClick(AdapterView<?> parent, View view,
						int position, long id) {
					TextView tv_item = (TextView) view;
					sac_text.setText(tv_item.getText());
				}
	        });
		}
	}

    @Override  
    public boolean onMenuOpened(int featureId, Menu menu) {
    	//顯示選單項左側的圖示
        Utils.setOverflowIconVisible(featureId, menu);  
        return super.onMenuOpened(featureId, menu);  
    }  
  
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		//對搜尋框做初始化
		initSearchView(menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == android.R.id.home) {
			finish();
		} else if (id == R.id.menu_refresh) {
			mNowTime = new Date();
			tv_desc.setText("當前重新整理時間: "+Utils.getFormatDateTime(mNowTime, mFormat));
			return true;
		} else if (id == R.id.menu_about) {
			Toast.makeText(this, "這個是工具欄的演示demo", Toast.LENGTH_LONG).show();
			return true;
		} else if (id == R.id.menu_quit) {
			finish();
		}
		return super.onOptionsItemSelected(item);
	}

}


下面是搜尋結果頁面的程式碼:
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class SearchResultActvity extends AppCompatActivity {
	private static final String TAG = "SearchResultActvity";
	private TextView tv_search_result;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_search_result);
		tv_search_result = (TextView) findViewById(R.id.tv_search_result);
		doSearchQuery(getIntent()); 
		
		Toolbar tl_result = (Toolbar) findViewById(R.id.tl_result);
		tl_result.setBackgroundResource(R.color.blue_light);
		tl_result.setLogo(R.drawable.ic_launcher);
		tl_result.setTitle("搜尋結果頁");
		tl_result.setNavigationIcon(R.drawable.ic_back);
		setSupportActionBar(tl_result);
	}

    private void doSearchQuery(Intent intent){  
        if(intent == null) {
            return; 
        } else {
            //如果是通過ACTION_SEARCH來呼叫,即如果通過搜尋呼叫
            if(Intent.ACTION_SEARCH.equals(intent.getAction())){
            	//獲取額外資訊
            	Bundle bundle = intent.getBundleExtra(SearchManager.APP_DATA);
            	String value = bundle.getString("hi");
            	//獲取搜尋內容
                String queryString = intent.getStringExtra(SearchManager.QUERY);
                tv_search_result.setText("您輸入的搜尋文字是:"+queryString+", 額外資訊:"+value);
            }  
        }
    }  

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.null_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == android.R.id.home) {
			Log.d(TAG, "finish activity");
			finish();
		}
		return super.onOptionsItemSelected(item);
	}
}



點選下載本文用到的工具欄Toolbar的工程程式碼



點此檢視Android開發筆記的完整目錄

相關文章