Android Toolbar選單動態切換item的圖示
警示:本文所述的方法過於繁瑣,邏輯混亂,程式碼難以適應複雜情況,並且幾乎不可擴充套件,本文之所以沒刪是我為了記錄自己的踩坑記錄,因此如果有人想參照這方面的知識,請參考我的另一篇文章:
大家都知道,Fragment的啟動速度比Activity快很多,因此在開發中如果每一個介面都使用一個Activity顯然不那麼好,這時候我們一般用Activity來充當管理的角色,介面的內容都放在Fragment中。可是由於每個Fragment都對應一個功能介面,因此每個Fragment的頂部工具欄都應該是不同的,但是ActionBar或者ToolBar都是屬於Activity的,這時候我們就需要在切換Fragment的時候使Toolbar也做出相應的動態切換。
我們來舉一個簡單的例子,打開個人資訊Activity(MyInformationActivity),佈局檔案如下。
佈局很簡單,裡面只放置了一個Toolbar和第一個Fragment (MyInformationFragment),因此,只要這個Activity一啟動,首先展示在使用者眼前並和使用者互動的就是MyInformationFragment,MyInforamtionFragment的佈局檔案我就不放了,直接放效果圖,如下:<LinearLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".myinformation.MyInformationActivity"> <android.support.v7.widget.Toolbar android:id="@+id/my_information_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:theme="@style/WhiteToolBar"> </android.support.v7.widget.Toolbar> <FrameLayout android:id="@+id/alter_my_information_frame" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/my_information" android:name="com.icon.app.myinformation.MyInformationFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> </LinearLayout>
展示的使用者資訊除了頭像以外,主要就是姓名,性別,友好度這三項。大家可以注意到 ,toolbar的標題是“我的資訊”,右上角的MenuItem是一支筆的圖片。我們要實現的是點選右上角的menuitem,建立第二個Fragment (AlterMyInformationFragment) 使其到達Activity的最頂端,覆蓋當前的MyInforamtionFragment。於是,我們在onOptionsItemSelected(MenuItem item)方法中進行監聽,當用戶點選右上角的筆圖示時,啟動新的Fragment,MyInformationActivity的程式碼如下:
public class MyInformationActivity extends AppCompatActivity {
private Toolbar toolbar;
private AlterMyInformationFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_information);
toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
toolbar.setTitle("我的資訊");
setSupportActionBar(toolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
private void setAlterMyInformationFragment() {
fragment = new AlterMyInformationFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.alter_my_information_frame, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_my_information, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
break;
case R.id.alter_information:
setAlterMyInformationFragment();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
注:android.R.id.home是左上角後退按鈕的id。
MyInformationActivity的Menu佈局檔案如下所示:<?xml version="1.0" encoding="utf-8"?>
<menu 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"
tools:context=".myinformation.MyInformationActivity">
<item
android:id="@+id/alter_information"
android:orderInCategory="100"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_create_white_24dp"/>
</menu>
可以看到,MyInformationActivity的Toolbar中只有一個item。
現在,我們在動態變更選單前還要做一個事情,一般toolbar最左邊的後退按鈕相當於back鍵的功能,即點選以後銷燬當前的Activity,但是,我們現在的問題是,如果當前Activity展示的是MyInformationFragment,那自然沒有問題,單擊後退鍵,即銷燬當前活動,但是如果我們當前處在和AlterMyInforamtionFragment的互動狀態,單擊後退鍵把當前Activity銷燬了,顯然是不合理的,正確的情況是應該銷燬AlterMyInforamtionFragment,使使用者回到和MyInformationFragment互動的狀態。因此,我們需要在Activity中設定一個變數status來進行標記,用來反映當前和使用者互動的Fragment到底是哪一個。因此,新增程式碼後Activity的程式碼如下所示:
public class MyInformationActivity extends AppCompatActivity {
private static final int MY_FRAGMENT = 0;
private static final int ALTER_FRAGMENT = 1;
private int status;
private Toolbar toolbar;
private AlterMyInformationFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_information);
toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
toolbar.setTitle("我的資訊");
setSupportActionBar(toolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
status = MY_FRAGMENT;
}
private void setAlterMyInformationFragment() {
fragment = new AlterMyInformationFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.alter_my_information_frame, fragment);
transaction.addToBackStack(null);
status = ALTER_FRAGMENT;
transaction.commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_my_information, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
switch (status) {
case MY_FRAGMENT:
finish();
break;
case ALTER_FRAGMENT:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(fragment);
status = MY_FRAGMENT;
transaction.commit();
break;
default:
break;
}
break;
case R.id.alter_information:
setAlterMyInformationFragment();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
我們用兩個常量來表示當前到底處在哪一個Fragment,MY_FRAGMENT表示MyInformationFragment,ALTER_FRAGMENT表示AlterMyInformationFragment。我們可以看到Activity啟動的時候自動給status賦值MY_FRAGMENT,在setAlterMyInformationFragment執行的時候給status賦值ALTER_FRAGMENT。於是我們在監聽後退鍵的時候就可以進行判斷了,如果當前和使用者互動的是MyInformationFragment,執行finish()銷燬Activity,如果和使用者互動的是AlterMyInformationFragment,則銷燬Fragment,讓使用者回到和MyInformationFragment互動的狀態。
我們處理完了後退鍵的問題,就該來解決動態變更toolbar的問題了。切換了Fragment,Toolbar如何相應的發生改變呢?大家平常使用選單的時候,主要使用兩個方法onCreateOptionView(Menu menu)和onOptionsItemSelected(MenuItem item),前者是載入選單佈局的,只會在Activity啟動的時候呼叫一次,後者是對Menu中的item進行事件監聽。也就是說,我們是不可能在onCreateOptionView(Menu menu)中對menu進行動態變更的。那我們來試試在onOptionsItemSelected(MenuItem item)中進行。於是我在啟動AlterMyInforamtionFragment的程式碼後面加了這麼兩行:
item.setIcon(R.drawable.ic_send_white_24dp);
toolbar.setTitle("修改個人資訊");
這樣啟動AlterMyInforamtionFragment後的介面如下圖所示:
demo的介面還是很簡單,使用者可以自己修改自己的姓名和性別,看起來我們貌似實現了,這時候我們遇到一個問題,現在右上角的item的功能不再是啟動新的Fragment了,而應該是使用者修改好個人資訊後進行提交,於是我們再次修改程式碼,讓onOptionsItemSelected(MenuItem item)中的程式碼變成這樣:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
switch (status) {
case MY_FRAGMENT:
finish();
break;
case ALTER_FRAGMENT:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(fragment);
status = MY_FRAGMENT;
transaction.commit();
break;
default:
break;
}
break;
case R.id.alter_information:
switch (status) {
case MY_FRAGMENT:
setAlterMyInformationFragment();
item.setIcon(R.drawable.ic_send_white_24dp);
toolbar.setTitle("修改個人資訊");
break;
case ALTER_FRAGMENT:
fragment.postAlterInformation();
break;
default:
break;
}
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
我們再次通過status來判斷當前和使用者互動的Fragment,從而決定這個item在不同狀態下的功能。在ALTER_FRAGMENT狀態下呼叫AlterMyInformationFragment類中的postAlterInformation()方法把修改好的資訊通過網路傳送給伺服器。
看起來我們已經實現這個功能了,但是還有很重要的一點沒有做,那就是點選後退鍵的時候我們的toolbar應該變回之前的樣子,但是我們現在還沒有實現。那應該怎麼做呢?修改toolbar的title很容易執行,只要在監聽android.R.id.home的ALTER_FRAGMENT的switch分支下加一條
toolbar.setTitle("我的資訊");
就可以了,但是變更圖示似乎不那麼容易,有些人說,再加一條item.setIcon("R.drawable.ic_create_white_24dp");
不就行了嗎,但是我們仔細看,我們之所以在啟動AlterMyInforamtionFragment的時候能通過這條程式碼改變item的圖示,是因為我們這行程式碼寫在case R.id.alter_information這條switch分支下,此時的item代表的是右上角我們要修改的item。但是如果你要android.R.id.home這條switch分支下使用引數item,這個item代表的是左上角的後退鍵,即使執行上面那條程式碼,也不會有任何效果。但是,我們確實必須在點選後退鍵的時候改變右上角的item的圖示,這就麻煩了,我找了item能呼叫的所有方法,沒有發現能通過什麼方式指定它的id,讓它在非case
R.id.alter_information分支下也代表這個item。後來我在網上查閱後發現,我們應該改變一種思路,因為onCreateOptionView(Menu menu)和onOptionsItemSelected(MenuItem item)這兩個方法可能不夠用了。
我在網上看到很多文章發現,onPrepareOptionsMenu(Menu menu)這個方法也可以被呼叫多次,那我們就通過它來改變item的圖示。於是MyInformationActivity的程式碼就變成了如下這樣
public class MyInformationActivity extends AppCompatActivity {
private static final int MY_FRAGMENT = 0;
private static final int ALTER_FRAGMENT = 1;
private int status;
private Toolbar toolbar;
private AlterMyInformationFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_information);
toolbar = (Toolbar) findViewById(R.id.my_information_toolbar);
toolbar.setTitle("我的資訊");
setSupportActionBar(toolbar);
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
status = MY_FRAGMENT;
}
private void setAlterMyInformationFragment() {
fragment = new AlterMyInformationFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.alter_my_information_frame, fragment);
transaction.addToBackStack(null);
status = ALTER_FRAGMENT;
transaction.commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_my_information, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
switch (status) {
case MY_FRAGMENT:
finish();
break;
case ALTER_FRAGMENT:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(fragment);
status = MY_FRAGMENT;
transaction.commit();
invalidateOptionsMenu();
toolbar.setTitle("我的資訊");
break;
default:
break;
}
break;
case R.id.alter_information:
switch (status) {
case MY_FRAGMENT:
setAlterMyInformationFragment();
item.setIcon(R.drawable.ic_send_white_24dp);
toolbar.setTitle("修改個人資訊");
break;
case ALTER_FRAGMENT:
fragment.postAlterInformation();
break;
default:
break;
}
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.alter_information).setIcon(R.drawable.ic_create_white_24dp);
return super.onPrepareOptionsMenu(menu);
}
}
呼叫invalidateOptionsMenu()方法的地方,onPrepareOptionsMenu(Menu menu)就會被呼叫一次,值得說明的是,網上許多文章的教程寫的是,如果你使用的是系統原生的ActionBar,應呼叫mActivity.getWindow().invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); 如果使用的是ActionBarSherlock就應呼叫invalidateOptionsMenu(),目前大家可能使用ActionBar的逐漸減少,都逐步轉向了Toolbar,Toolbar和ActionBarSherlock一樣,都是呼叫invalidateOptionsMenu()方法。
2016年10月7日最新更新:又經過了兩個月的學習,我又遇到了更為複雜的問題,比如一個activity可能會管理三個甚至更多的Fragment,這時候我這篇部落格寫的解決方法就不太靈了,會讓程式碼既複雜又臃腫,而且難以閱讀。不過我已經找到了新的解決方法,通過沖分利用Fragment的返回棧模擬以及Fragment的生命週期回撥,可以更簡單易懂的解決這個問題,過段時間我會把最新的方法再寫一篇部落格。