Android實現(xiàn)底部導航欄效果
目前網上主流的文章都是用底部的 RadioGroup + 頁面部分的 Fragment 實現(xiàn)導航欄切換頁面效果的。
然而底部的 RadioGroup 是如此麻煩,每個按鈕的圖片和文字部分都要做一個 selector 用于表示選中和非選中兩種狀態(tài)時的樣式。
另外 Fragment 也有很多坑,先不管大家是否已熟練掌握,反正我是看著看著就學不下去了,所以我另辟蹊徑用 Activity 的方式實現(xiàn)了偽 Fragment 的效果。
這里我們就來做一個三個按鈕的底部導航欄。
因為我們這里是用三個 Activity 實現(xiàn)三個頁面,而并非一個 Activity 中的三個 Fragment,所以在此之前,我們需要建立一個管理活動堆棧的類,以便在程序退出時能直接結束堆棧中的所有活動,不至于要依次退出三個 Activity。
在 java 代碼目錄里新建一個 base 包,在包內新建文件 AppManager.java:
/**
?* AppManager: 用于對活動進行管理。
?* 該模塊僅限 base 包內使用。
?* 該模塊為單一實例,您需要調用 AppManager.get() 獲取實例后再調用方法。
?* <p>
?* 為確保應用管理器正常工作,請新建一個繼承 Activity 的抽象類 BaseActivity,
?* 然后重寫 BaseActivity 類的 onCreate() 和 onDestroy() 方法。
?* 請給 BaseActivity 類的 onCreate() 方法添加如下代碼:
?* AppManager.get().addActivity(this);
?* 請給 BaseActivity 類的 onDestroy() 方法添加如下代碼:
?* AppManager.get().removeActivity(this);
?* 最后,確保本 APP 內的所有活動類均繼承于 BaseActivity 類。
?*/
class AppManager {
? ? private static AppManager sManager = new AppManager();
? ? private Stack<BaseActivity> mActivities;
?
? ? private AppManager() {
? ? ? ? // 將作用域關鍵字設置為 private 以隱藏該類的構造器。
? ? ? ? // 該類的單例由 get() 方法引用。
? ? ? ? // 創(chuàng)建單例的同時創(chuàng)建活動堆棧。
? ? ? ? mActivities = new Stack<>();
? ? } // AppManager() (Class Constructor)
?
? ? /**
? ? ?* get(): 獲得 AppManager 類的單例。
? ? ?*
? ? ?* @return 該類的單例 sManager。
? ? ?*/
? ? static AppManager get() {
? ? ? ? return sManager;
? ? } // get()
?
? ? /**
? ? ?* addActivity(): 向堆棧中添加一個活動對象。
? ? ?*
? ? ?* @param activity 要添加的活動對象。
? ? ?*/
? ? void addActivity(BaseActivity activity) {
? ? ? ? mActivities.add(activity);
? ? } // addActivity()
?
? ? /**
? ? ?* removeActivity(): 從堆棧中移除一個活動對象。
? ? ?*
? ? ?* @param activity 要移除的活動對象。
? ? ?*/
? ? void removeActivity(BaseActivity activity) {
? ? ? ? mActivities.remove(activity);
? ? } // removeActivity()
?
? ? /**
? ? ?* finishAllExcept(): 除一個特定活動外,結束堆棧中其余所有活動。
? ? ?* 結束活動時會觸發(fā) BaseActivity 類的 onDestroy()方法,
? ? ?* 堆棧中的活動對象會同步移除。
? ? ?*
? ? ?* @param activityClass 要保留的活動的類名(xxxActivity.class)
? ? ?*/
? ? void finishAllExcept(Class activityClass) {
? ? ? ? int i, len;
? ? ? ? BaseActivity[] activities;
?
? ? ? ? // 結束活動時會調用活動的 onDestroy() 方法,堆棧的內容會實時改變
? ? ? ? // 為避免因此引起的引用錯誤,先將堆棧的內容復制到一個臨時數(shù)組里
? ? ? ? activities = mActivities.toArray(new BaseActivity[0]);
? ? ? ? len = activities.length;
? ? ? ? for (i = 0; i < len; ++i) {
? ? ? ? ? ? if (!activities[i].getClass().equals(activityClass)) {
? ? ? ? ? ? ? ? // 從數(shù)組里引用活動對象并結束,堆棧內容的改變不影響數(shù)組
? ? ? ? ? ? ? ? activities[i].finish();
? ? ? ? ? ? } // if (!activities[i].getClass().equals(activityClass))
? ? ? ? } // for (i = 0; i < len; ++i)
? ? } // finishAllExcept()
?
? ? /**
? ? ?* finishAllActivities(): 結束堆棧中的所有活動。
? ? ?* 結束活動時會觸發(fā) BaseActivity 類的 onDestroy()方法,
? ? ?* 堆棧中的活動對象會同步移除。
? ? ?*/
? ? void finishAllActivities() {
? ? ? ? int i, len;
? ? ? ? BaseActivity[] activities;
?
? ? ? ? // 結束活動時會調用活動的 onDestroy() 方法,堆棧的內容會實時改變
? ? ? ? // 為避免因此引起的引用錯誤,先將堆棧的內容復制到一個臨時數(shù)組里
? ? ? ? activities = mActivities.toArray(new BaseActivity[0]);
? ? ? ? len = activities.length;
? ? ? ? for (i = 0; i < len; ++i) {
? ? ? ? ? ? // 從數(shù)組里引用活動對象并結束,堆棧內容的改變不影響數(shù)組
? ? ? ? ? ? activities[i].finish();
? ? ? ? } // for (i = 0; i < len; ++i)
? ? } // finishAllActivities()
} // AppManager Class
?
// E.O.F上述代碼粘貼完后會報錯,別著急,那是因為我們還沒有建立和管理器相關聯(lián)的 BaseActivity 類?,F(xiàn)在我們在 base 包內再新建一個 BaseActivity.java,封裝活動間的跳轉方法。其中 jumpTo() 方法表示跳轉后活動堆棧中只保留跳轉后的那一個活動,壓在堆棧中的其他活動全部銷毀;而 open() 方法則保留活動堆棧。另外還有一個 showExitDialog() 的方法,用于詢問用戶是否退出程序,當用戶選擇“是”時,將堆棧中的所有活動一次性全部銷毀。
/**
?* BaseActivity: 該抽象類定義所有活動均擁有的共同屬性。
?* 本 APP 中所有活動對象均繼承此類。
?*/
public abstract class BaseActivity extends Activity {
? ? /**
? ? ?* onCreate(): 重寫父類的 onCreate() 方法,向應用管理器中添加本活動。
? ? ?*/
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? AppManager.get().addActivity(this);
? ? } // onCreate()
?
? ? /**
? ? ?* onDestroy(): 重寫父類的 onDestroy() 方法,從應用管理器中移除本活動。
? ? ?*/
? ? @Override
? ? protected void onDestroy() {
? ? ? ? super.onDestroy();
? ? ? ? AppManager.get().removeActivity(this);
? ? } // onDestroy()
?
? ? /**
? ? ?* jumpTo(): 實現(xiàn)不傳參的活動間跳轉。
? ? ?*
? ? ?* @param dst 要跳轉到的活動的類名(xxxActivity.class)。
? ? ?*/
? ? protected void jumpTo(Class dst) {
? ? ? ? Intent intent = new Intent(this, dst);
? ? ? ? startActivity(intent);
? ? ? ? AppManager.get().finishAllExcept(dst);
? ? } // jumpTo()
?
? ? /**
? ? ?* open(): 將當前活動壓入堆棧,打開一個新活動。
? ? ?*
? ? ?* @param dst 要打開的活動的類名(xxxActivity.class)。
? ? ?*/
? ? protected void open(Class dst) {
? ? ? ? Intent intent = new Intent(this, dst);
? ? ? ? startActivity(intent);
? ? } // open()
?
? ? /**
? ? ?* showExitDialog(): 顯示退出程序對話框,詢問用戶是否退出程序。
? ? ?*/
? ? protected void showExitDialog() {
? ? ? ? AlertDialog dialog;
? ? ? ? DialogInterface.OnClickListener onClick;
?
? ? ? ? onClick = new DialogInterface.OnClickListener() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void onClick(DialogInterface dialog, int which) {
? ? ? ? ? ? ? ? switch (which) {
? ? ? ? ? ? ? ? ? ? case DialogInterface.BUTTON_POSITIVE:
? ? ? ? ? ? ? ? ? ? ? ? // 確定按鈕
? ? ? ? ? ? ? ? ? ? ? ? dialog.dismiss();
? ? ? ? ? ? ? ? ? ? ? ? AppManager.get().finishAllActivities();
? ? ? ? ? ? ? ? ? ? ? ? break; // case DialogInterface.BUTTON_POSITIVE
?
? ? ? ? ? ? ? ? ? ? case DialogInterface.BUTTON_NEGATIVE:
? ? ? ? ? ? ? ? ? ? ? ? // 取消按鈕
? ? ? ? ? ? ? ? ? ? ? ? dialog.dismiss();
? ? ? ? ? ? ? ? ? ? ? ? break; // case DialogInterface.BUTTON_NEGATIVE
?
? ? ? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? ? ? break; // default
? ? ? ? ? ? ? ? } // switch (which)
? ? ? ? ? ? } // onClick()
? ? ? ? }; // onClick = new DialogInterface.OnClickListener()
?
? ? ? ? // 顯示對話框
? ? ? ? dialog = new AlertDialog.Builder(this)
? ? ? ? ? ? .setMessage(R.string.dlgExitMsg) // "確定要退出嗎?"
? ? ? ? ? ? .setPositiveButton(android.R.string.ok, onClick)
? ? ? ? ? ? .setNegativeButton(android.R.string.cancel, onClick)
? ? ? ? ? ? .create(); // dialog = new AlertDialog.Builder(this)...
? ? ? ? dialog.show();
? ? } // showExitDialog()
} // BaseActivity Abstract Class
?
// E.O.F上述代碼粘貼完后還是有一處報錯。這是因為這段代碼中用到了一處尚未定義的字符串資源。我們打開 res/values 目錄下的 strings.xml 文件,添加字符串資源:
<?xml version="1.0" encoding="utf-8"?> <resources> ? ? <!-- 這個字符串是你的工程名稱,可以自己取,不一定要是 My Application --> ? ? <string name="app_name">My Application</string> ? ? ? <!-- 這個是我們新添加的字符串 --> ? ? <string name="dlgExitMsg">確定要退出嗎?</string> ? ? ? <!-- 順便把之后要用到的字符串也一并準備了 --> ? ? <string name="btnNavHome">主頁</string> ? ? <string name="btnNavMessage">消息</string> ? ? <string name="btnNavSettings">設置</string> </resources>
至此,報錯全部消除。
現(xiàn)在我們建立底部導航欄的三個按鈕對應的三個 Activity 頁面。
準備導航欄的圖標資源,放入 res/drawable 文件夾內:

打開 res/values/colors.xml 文件,定義導航欄相關顏色(背景、選中顏色、非選中顏色)
<?xml version="1.0" encoding="utf-8"?> <resources> ? ? <!-- 定義導航欄的相關顏色 --> ? ? <color name="navBack">#e0e0e0</color> <!-- 導航欄背景色 Grey 300--> ? ? <color name="navNormal">#000000</color> <!-- 未激活項目的文字顏色 Black --> ? ? <color name="navActivated">#039be5</color> <!-- 已激活項目的文字顏色 Light Blue 600 --> </resources>
新建三個活動 HomeActivity、MessageActivity 和 SettingsActivity。
MainActivity 的布局文件 activity_main.xml 如下:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" ? ? xmlns:app="http://schemas.android.com/apk/res-auto" ? ? xmlns:tools="http://schemas.android.com/tools" ? ? android:layout_width="match_parent" ? ? android:layout_height="match_parent" ? ? android:background="@android:color/holo_blue_dark" ? ? tools:context=".HomeActivity"> ? ? ? <LinearLayout ? ? ? ? android:id="@+id/llHomePage" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="0dp" ? ? ? ? android:layout_marginStart="8dp" ? ? ? ? android:layout_marginLeft="8dp" ? ? ? ? android:layout_marginTop="8dp" ? ? ? ? android:layout_marginEnd="8dp" ? ? ? ? android:layout_marginRight="8dp" ? ? ? ? android:layout_marginBottom="8dp" ? ? ? ? android:orientation="vertical" ? ? ? ? app:layout_constraintBottom_toTopOf="@+id/llHomeNav" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toTopOf="parent"> ? ? ? ? <!-- ? ? ? ? 該 LinearLayout 為主體頁面布局的容器,你也可以根據(jù)需要換成其他形式的 Layout ? ? ? ? 以上代碼將頁面布局容器和底部的導航欄進行了約束 ? ? ? ? 即該容器的底端和導航欄的頂端彼此約束 ? ? ? ? 確保該容器的占用空間不會覆蓋導航欄 ? ? ? ? 頁面的主體布局請在該容器內部(即此處)創(chuàng)建 ? ? ? ? --> ? ? </LinearLayout> ? ? ? <!-- 以下為導航欄的布局 --> ? ? <LinearLayout ? ? ? ? android:id="@+id/llHomeNav" ? ? ? ? style="?android:attr/buttonBarStyle" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="wrap_content" ? ? ? ? android:background="@color/navBack" ? ? ? ? android:orientation="horizontal" ? ? ? ? app:layout_constraintBottom_toBottomOf="parent" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toBottomOf="@+id/llHomePage"> ? ? ? ? ? <!-- 【主頁】活動中,【主頁】按鈕設為已激活樣式,注意 drawableTop 和 textColor 屬性的值 --> ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavHome" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/home1" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavHome" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navActivated" /> ? ? ? ? ? <!-- 其他按鈕設為未激活樣式,注意 drawableTop 和 textColor 屬性的值 --> ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavMessage" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/message0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavMessage" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavSettings" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/settings0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavSettings" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? </LinearLayout> </android.support.constraint.ConstraintLayout>
切換到預覽頁面看一下,已經有了底部導航欄的雛形:

現(xiàn)在打開代碼文件 HomeActivity.java 編寫點擊導航欄按鈕時的活動跳轉代碼:
public class HomeActivity extends BaseActivity { // 請注意此處繼承的是 BaseActivity 而不是 Activity
? ? /**
? ? ?* onCreate(): 活動創(chuàng)建時觸發(fā)。
? ? ?*/
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_home);
? ? } // onCreate()
?
? ? /**
? ? ?* onNavButtonsTapped(): 點擊導航欄上的標簽時觸發(fā)。
? ? ?*
? ? ?* @param v 點擊的按鈕對象,用 v.getId() 獲取其資源 ID。
? ? ?*/
? ? public void onNavButtonsTapped(View v) {
? ? ? ? switch (v.getId()) {
? ? ? ? ? ? case R.id.btnNavMessage:
? ? ? ? ? ? ? ? open(MessageActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavMessage
?
? ? ? ? ? ? case R.id.btnNavSettings:
? ? ? ? ? ? ? ? open(SettingsActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavSettings
? ? ? ? } // switch (v.getId())
? ? } // onNavButtonsTapped()
?
? ? /**
? ? ?* onKeyDown(): 按下回退鍵時觸發(fā)。
? ? ?* 彈出對話框詢問是否退出程序。
? ? ?*/
? ? @Override
? ? public boolean onKeyDown(int keyCode, KeyEvent event) {
? ? ? ? if (keyCode == KeyEvent.KEYCODE_BACK) {
? ? ? ? ? ? showExitDialog();
? ? ? ? ? ? return true;
? ? ? ? } // if (keyCode == KeyEvent.KEYCODE_BACK)
? ? ? ? else {
? ? ? ? ? ? return super.onKeyDown(keyCode, event);
? ? ? ? } // else
? ? } // onKeyDown()
} // HomeActivity Class
?
// E.O.F另外兩個活動直接照葫蘆畫瓢就 OK 了。不過這里千萬要注意后兩個活動不能只復制 MainActivity 的布局就完事了,一定要更改導航欄各個按鈕的樣式!另外就是活動中的 onNavButtonsTapped() 方法的內容也不一樣!
activity_message.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" ? ? xmlns:app="http://schemas.android.com/apk/res-auto" ? ? xmlns:tools="http://schemas.android.com/tools" ? ? android:layout_width="match_parent" ? ? android:layout_height="match_parent" ? ? android:background="@android:color/holo_green_dark" ? ? tools:context=".MessageActivity"> <!-- 本活動的背景色為 holo green dark --> ? ? <!-- 注意上方的 Context --> ? ? ? <LinearLayout ? ? ? ? android:id="@+id/llMessagePage" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="0dp" ? ? ? ? android:layout_marginStart="8dp" ? ? ? ? android:layout_marginLeft="8dp" ? ? ? ? android:layout_marginTop="8dp" ? ? ? ? android:layout_marginEnd="8dp" ? ? ? ? android:layout_marginRight="8dp" ? ? ? ? android:layout_marginBottom="8dp" ? ? ? ? android:orientation="vertical" ? ? ? ? app:layout_constraintBottom_toTopOf="@+id/llMessageNav" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toTopOf="parent"> ? ? ? ? <!-- 在此定義頁面主體布局 --> ? ? </LinearLayout> ? ? ? <LinearLayout ? ? ? ? android:id="@+id/llMessageNav" ? ? ? ? style="?android:attr/buttonBarStyle" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="wrap_content" ? ? ? ? android:background="@color/navBack" ? ? ? ? android:orientation="horizontal" ? ? ? ? app:layout_constraintBottom_toBottomOf="parent" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toBottomOf="@+id/llMessagePage"> ? ? ? ? ? <!-- 請務必注意以下各按鈕的 drawableTop 和 textColor 屬性 --> ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavHome" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/home0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavHome" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavMessage" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/message1" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavMessage" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navActivated" /> ? ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavSettings" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/settings0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavSettings" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? </LinearLayout> </android.support.constraint.ConstraintLayout>
MessageActivity.java
public class MessageActivity extends BaseActivity {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_message);
? ? } // onCreate()
?
? ? // 請注意本方法內容的變化
? ? public void onNavButtonsTapped(View v) {
? ? ? ? switch (v.getId()) {
? ? ? ? ? ? case R.id.btnNavHome:
? ? ? ? ? ? ? ? open(HomeActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavHome
?
? ? ? ? ? ? case R.id.btnNavSettings:
? ? ? ? ? ? ? ? open(SettingsActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavSettings
? ? ? ? } // switch (v.getId())
? ? } // onNavButtonsTapped()
?
? ? @Override
? ? public boolean onKeyDown(int keyCode, KeyEvent event) {
? ? ? ? if (keyCode == KeyEvent.KEYCODE_BACK) {
? ? ? ? ? ? showExitDialog();
? ? ? ? ? ? return true;
? ? ? ? } // if (keyCode == KeyEvent.KEYCODE_BACK)
? ? ? ? else {
? ? ? ? ? ? return super.onKeyDown(keyCode, event);
? ? ? ? } // else
? ? } // onKeyDown()
} // MessageActivity Class
?
// E.O.F最后是 SettingsActivity。
activity_settings.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" ? ? xmlns:app="http://schemas.android.com/apk/res-auto" ? ? xmlns:tools="http://schemas.android.com/tools" ? ? android:layout_width="match_parent" ? ? android:layout_height="match_parent" ? ? android:background="@android:color/holo_orange_dark" ? ? tools:context=".SettingsActivity"> <!-- 本活動的背景色為 holo orange dark --> ? ? <!-- 注意上方的 Context --> ? ? ? <LinearLayout ? ? ? ? android:id="@+id/llHomePage" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="0dp" ? ? ? ? android:layout_marginStart="8dp" ? ? ? ? android:layout_marginLeft="8dp" ? ? ? ? android:layout_marginTop="8dp" ? ? ? ? android:layout_marginEnd="8dp" ? ? ? ? android:layout_marginRight="8dp" ? ? ? ? android:layout_marginBottom="8dp" ? ? ? ? android:orientation="vertical" ? ? ? ? app:layout_constraintBottom_toTopOf="@+id/llHomeNav" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toTopOf="parent"> ? ? ? ? <!-- 在此定義頁面主體布局 --> ? ? </LinearLayout> ? ? ? <!-- 以下為導航欄的布局 --> ? ? <LinearLayout ? ? ? ? android:id="@+id/llHomeNav" ? ? ? ? style="?android:attr/buttonBarStyle" ? ? ? ? android:layout_width="0dp" ? ? ? ? android:layout_height="wrap_content" ? ? ? ? android:background="@color/navBack" ? ? ? ? android:orientation="horizontal" ? ? ? ? app:layout_constraintBottom_toBottomOf="parent" ? ? ? ? app:layout_constraintEnd_toEndOf="parent" ? ? ? ? app:layout_constraintStart_toStartOf="parent" ? ? ? ? app:layout_constraintTop_toBottomOf="@+id/llHomePage"> ? ? ? ? ? <!-- 請務必注意以下各按鈕的 drawableTop 和 textColor 屬性 --> ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavHome" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/home0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavHome" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavMessage" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/message0" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavMessage" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navNormal" /> ? ? ? ? ? <Button ? ? ? ? ? ? android:id="@+id/btnNavSettings" ? ? ? ? ? ? style="?android:attr/buttonBarButtonStyle" ? ? ? ? ? ? android:layout_width="0dp" ? ? ? ? ? ? android:layout_height="wrap_content" ? ? ? ? ? ? android:layout_weight="1" ? ? ? ? ? ? android:drawableTop="@drawable/settings1" ? ? ? ? ? ? android:onClick="onNavButtonsTapped" ? ? ? ? ? ? android:text="@string/btnNavSettings" ? ? ? ? ? ? android:textAppearance="?android:attr/textAppearanceSmall" ? ? ? ? ? ? android:textColor="@color/navActivated" /> ? ? </LinearLayout> </android.support.constraint.ConstraintLayout>
SettingsActivity.java:
package com.example.myapplication;
?
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
?
import com.example.myapplication.base.BaseActivity;
?
public class SettingsActivity extends BaseActivity {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_settings);
? ? } // onCreate()
?
? ? // 請注意本方法內容的變化
? ? public void onNavButtonsTapped(View v) {
? ? ? ? switch (v.getId()) {
? ? ? ? ? ? case R.id.btnNavHome:
? ? ? ? ? ? ? ? open(HomeActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavHome
?
? ? ? ? ? ? case R.id.btnNavMessage:
? ? ? ? ? ? ? ? open(MessageActivity.class);
? ? ? ? ? ? ? ? break; // case R.id.btnNavMessage
? ? ? ? } // switch (v.getId())
? ? } // onNavButtonsTapped()
?
? ? @Override
? ? public boolean onKeyDown(int keyCode, KeyEvent event) {
? ? ? ? if (keyCode == KeyEvent.KEYCODE_BACK) {
? ? ? ? ? ? showExitDialog();
? ? ? ? ? ? return true;
? ? ? ? } // if (keyCode == KeyEvent.KEYCODE_BACK)
? ? ? ? else {
? ? ? ? ? ? return super.onKeyDown(keyCode, event);
? ? ? ? } // else
? ? } // onKeyDown()
} // SettingsActivity Class
?
// E.O.F做到這一步,先中場休息,打開模擬器調試一下。

我們之后還有兩個問題需要解決:
①活動間的跳轉是有動畫的,而我們并不需要這畫蛇添足的動畫;
②這一點也是更重要的。每次一點擊導航欄上的按鈕,就會打開一個新活動。當我們從主頁活動跳至設置活動,然后再由設置活動跳回主頁活動時,系統(tǒng)的堆棧里其實是有兩個主頁活動的實例的,如果反復跳轉,系統(tǒng)也會一直繼續(xù)創(chuàng)建活動實例,原先的活動實例名存實亡,造成實質上的內存泄漏,直到最后內存不夠用而崩潰。我們需要的是這樣的效果:跳回曾經已經創(chuàng)建過的活動時,不要新建實例,而是直接重新引用原先活動的實例。這樣,不論在導航欄上跳轉多少次,內存中最多只會有 3 個活動,永遠不會有內存泄漏的問題。
我們先來解決第一個問題。這個問題其實很好辦,在 res/values 目錄下有個 styles.xml 的文件,用于定義活動的主題樣式。我們在其中增加幾行代碼用于關閉活動間的跳轉動畫:
<resources> ? ? ? <!-- Base application theme. --> ? ? <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> ? ? ? ? <!-- Customize your theme here. --> ? ? ? ? <!-- 關閉動畫 --> ? ? ? ? <item name="android:windowIsTranslucent">true</item> ? ? ? ? <item name="android:windowAnimationStyle">@style/NoAnimation</item> ? ? </style> ? ? ? <!-- 定義無動畫樣式 --> ? ? <style name="NoAnimation"> ? ? ? ? <item name="android:activityCloseEnterAnimation">@null</item> ? ? ? ? <item name="android:activityCloseExitAnimation">@null</item> ? ? ? ? <item name="android:activityOpenEnterAnimation">@null</item> ? ? ? ? <item name="android:activityOpenExitAnimation">@null</item> ? ? ? ? <item name="android:taskCloseEnterAnimation">@null</item> ? ? ? ? <item name="android:taskCloseExitAnimation">@null</item> ? ? ? ? <item name="android:taskOpenEnterAnimation">@null</item> ? ? ? ? <item name="android:taskOpenExitAnimation">@null</item> ? ? ? ? <item name="android:taskToBackEnterAnimation">@null</item> ? ? ? ? <item name="android:taskToBackExitAnimation">@null</item> ? ? ? ? <item name="android:taskToFrontEnterAnimation">@null</item> ? ? ? ? <item name="android:taskToFrontExitAnimation">@null</item> ? ? </style> </resources>
接下來是第二個問題,保證每個活動只有唯一的實例,避免跳轉過程中活動實例反復創(chuàng)建造成的內存泄漏。
Android 中有一種活動啟動方式叫 singleInstance,它表示整個 Application 周期里對應的活動實例不能超過一個,當該活動已創(chuàng)建但之后又從其他的 intent 跳轉而來時,不新建實例,而是引用已有的實例。另外,singleInstance 啟動模式的活動各自擁有各自的活動堆棧,互不影響。我們打開 AndroidManifest,添加如下代碼:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ? ? package="com.example.myapplication"> ? ? ? <application ? ? ? ? android:allowBackup="true" ? ? ? ? android:icon="@mipmap/ic_launcher" ? ? ? ? android:label="@string/app_name" ? ? ? ? android:roundIcon="@mipmap/ic_launcher_round" ? ? ? ? android:supportsRtl="true" ? ? ? ? android:theme="@style/AppTheme"> ? ? ? ? <activity ? ? ? ? ? ? android:name=".HomeActivity" ? ? ? ? ? ? android:launchMode="singleInstance"> <!-- 設置活動的啟動方式為 singleInstance --> ? ? ? ? ? ? <intent-filter> ? ? ? ? ? ? ? ? <action android:name="android.intent.action.MAIN" /> ? ? ? ? ? ? ? ? ? <category android:name="android.intent.category.LAUNCHER" /> ? ? ? ? ? ? </intent-filter> ? ? ? ? </activity> ? ? ? ? <activity ? ? ? ? ? ? android:name=".MessageActivity" ? ? ? ? ? ? android:launchMode="singleInstance" /> <!-- 如法炮制 --> ? ? ? ? <activity ? ? ? ? ? ? android:name=".SettingsActivity" ? ? ? ? ? ? android:launchMode="singleInstance" /> <!-- 如法炮制 --> ? ? </application> ? </manifest>
再次打開模擬器進行調試:

效果 PERFECT。
至此,我們就實現(xiàn)了無 Fragment 的底部導航欄。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android ImageButton自定義按鈕的按下效果的代碼實現(xiàn)方法分享
這篇文章主要介紹了Android ImageButton自定義按鈕的按下效果的代碼實現(xiàn)方法,需要的朋友可以參考下2014-02-02
Android 圖片處理避免出現(xiàn)oom的方法詳解
本篇文章主要介紹了Android 圖片處理避免出現(xiàn)oom的方法詳解,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
Android使用Room操作數(shù)據(jù)庫流程詳解
谷歌推薦使用Room操作數(shù)據(jù)庫,Room在 SQLite 上提供了一個抽象層,在充分利用 SQLite強大功能的同時,能夠流暢地訪問數(shù)據(jù)庫2022-11-11
Android中控制和禁止ScrollView自動滑動到底部的方法
這篇文章主要給大家介紹了關于Android中控制和禁止ScrollView自動滑動到底部的相關資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者們具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-10-10
Android自定義View 實現(xiàn)水波紋動畫引導效果
在android程序開發(fā)中,我們經常簡單通過自定義view實現(xiàn)水波紋動畫引導功能,下面通過本文給大家分享實現(xiàn)代碼,需要的朋友參考下2017-01-01

