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

打開(kāi) res/values/colors.xml 文件,定義導(dǎo)航欄相關(guān)顏色(背景、選中顏色、非選中顏色)
<?xml version="1.0" encoding="utf-8"?> <resources> ? ? <!-- 定義導(dǎo)航欄的相關(guān)顏色 --> ? ? <color name="navBack">#e0e0e0</color> <!-- 導(dǎo)航欄背景色 Grey 300--> ? ? <color name="navNormal">#000000</color> <!-- 未激活項(xiàng)目的文字顏色 Black --> ? ? <color name="navActivated">#039be5</color> <!-- 已激活項(xiàng)目的文字顏色 Light Blue 600 --> </resources>
新建三個(gè)活動(dòng) 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 為主體頁(yè)面布局的容器,你也可以根據(jù)需要換成其他形式的 Layout ? ? ? ? 以上代碼將頁(yè)面布局容器和底部的導(dǎo)航欄進(jìn)行了約束 ? ? ? ? 即該容器的底端和導(dǎo)航欄的頂端彼此約束 ? ? ? ? 確保該容器的占用空間不會(huì)覆蓋導(dǎo)航欄 ? ? ? ? 頁(yè)面的主體布局請(qǐng)?jiān)谠撊萜鲀?nèi)部(即此處)創(chuàng)建 ? ? ? ? --> ? ? </LinearLayout> ? ? ? <!-- 以下為導(dǎo)航欄的布局 --> ? ? <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"> ? ? ? ? ? <!-- 【主頁(yè)】活動(dòng)中,【主頁(yè)】按鈕設(shè)為已激活樣式,注意 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" /> ? ? ? ? ? <!-- 其他按鈕設(shè)為未激活樣式,注意 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>
切換到預(yù)覽頁(yè)面看一下,已經(jīng)有了底部導(dǎo)航欄的雛形:

現(xiàn)在打開(kāi)代碼文件 HomeActivity.java 編寫(xiě)點(diǎn)擊導(dǎo)航欄按鈕時(shí)的活動(dòng)跳轉(zhuǎn)代碼:
public class HomeActivity extends BaseActivity { // 請(qǐng)注意此處繼承的是 BaseActivity 而不是 Activity
? ? /**
? ? ?* onCreate(): 活動(dòng)創(chuàng)建時(shí)觸發(fā)。
? ? ?*/
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_home);
? ? } // onCreate()
?
? ? /**
? ? ?* onNavButtonsTapped(): 點(diǎn)擊導(dǎo)航欄上的標(biāo)簽時(shí)觸發(fā)。
? ? ?*
? ? ?* @param v 點(diǎn)擊的按鈕對(duì)象,用 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(): 按下回退鍵時(shí)觸發(fā)。
? ? ?* 彈出對(duì)話框詢問(wèn)是否退出程序。
? ? ?*/
? ? @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另外兩個(gè)活動(dòng)直接照葫蘆畫(huà)瓢就 OK 了。不過(guò)這里千萬(wàn)要注意后兩個(gè)活動(dòng)不能只復(fù)制 MainActivity 的布局就完事了,一定要更改導(dǎo)航欄各個(gè)按鈕的樣式!另外就是活動(dòng)中的 onNavButtonsTapped() 方法的內(nèi)容也不一樣!
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"> <!-- 本活動(dòng)的背景色為 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"> ? ? ? ? <!-- 在此定義頁(yè)面主體布局 --> ? ? </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"> ? ? ? ? ? <!-- 請(qǐng)務(wù)必注意以下各按鈕的 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()
?
? ? // 請(qǐng)注意本方法內(nèi)容的變化
? ? 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"> <!-- 本活動(dòng)的背景色為 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"> ? ? ? ? <!-- 在此定義頁(yè)面主體布局 --> ? ? </LinearLayout> ? ? ? <!-- 以下為導(dǎo)航欄的布局 --> ? ? <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"> ? ? ? ? ? <!-- 請(qǐng)務(wù)必注意以下各按鈕的 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()
?
? ? // 請(qǐng)注意本方法內(nèi)容的變化
? ? 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做到這一步,先中場(chǎng)休息,打開(kāi)模擬器調(diào)試一下。

我們之后還有兩個(gè)問(wèn)題需要解決:
①活動(dòng)間的跳轉(zhuǎn)是有動(dòng)畫(huà)的,而我們并不需要這畫(huà)蛇添足的動(dòng)畫(huà);
②這一點(diǎn)也是更重要的。每次一點(diǎn)擊導(dǎo)航欄上的按鈕,就會(huì)打開(kāi)一個(gè)新活動(dòng)。當(dāng)我們從主頁(yè)活動(dòng)跳至設(shè)置活動(dòng),然后再由設(shè)置活動(dòng)跳回主頁(yè)活動(dòng)時(shí),系統(tǒng)的堆棧里其實(shí)是有兩個(gè)主頁(yè)活動(dòng)的實(shí)例的,如果反復(fù)跳轉(zhuǎn),系統(tǒng)也會(huì)一直繼續(xù)創(chuàng)建活動(dòng)實(shí)例,原先的活動(dòng)實(shí)例名存實(shí)亡,造成實(shí)質(zhì)上的內(nèi)存泄漏,直到最后內(nèi)存不夠用而崩潰。我們需要的是這樣的效果:跳回曾經(jīng)已經(jīng)創(chuàng)建過(guò)的活動(dòng)時(shí),不要新建實(shí)例,而是直接重新引用原先活動(dòng)的實(shí)例。這樣,不論在導(dǎo)航欄上跳轉(zhuǎn)多少次,內(nèi)存中最多只會(huì)有 3 個(gè)活動(dòng),永遠(yuǎn)不會(huì)有內(nèi)存泄漏的問(wèn)題。
我們先來(lái)解決第一個(gè)問(wèn)題。這個(gè)問(wèn)題其實(shí)很好辦,在 res/values 目錄下有個(gè) styles.xml 的文件,用于定義活動(dòng)的主題樣式。我們?cè)谄渲性黾訋仔写a用于關(guān)閉活動(dòng)間的跳轉(zhuǎn)動(dòng)畫(huà):
<resources> ? ? ? <!-- Base application theme. --> ? ? <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> ? ? ? ? <!-- Customize your theme here. --> ? ? ? ? <!-- 關(guān)閉動(dòng)畫(huà) --> ? ? ? ? <item name="android:windowIsTranslucent">true</item> ? ? ? ? <item name="android:windowAnimationStyle">@style/NoAnimation</item> ? ? </style> ? ? ? <!-- 定義無(wú)動(dòng)畫(huà)樣式 --> ? ? <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>
接下來(lái)是第二個(gè)問(wèn)題,保證每個(gè)活動(dòng)只有唯一的實(shí)例,避免跳轉(zhuǎn)過(guò)程中活動(dòng)實(shí)例反復(fù)創(chuàng)建造成的內(nèi)存泄漏。
Android 中有一種活動(dòng)啟動(dòng)方式叫 singleInstance,它表示整個(gè) Application 周期里對(duì)應(yīng)的活動(dòng)實(shí)例不能超過(guò)一個(gè),當(dāng)該活動(dòng)已創(chuàng)建但之后又從其他的 intent 跳轉(zhuǎn)而來(lái)時(shí),不新建實(shí)例,而是引用已有的實(shí)例。另外,singleInstance 啟動(dòng)模式的活動(dòng)各自擁有各自的活動(dòng)堆棧,互不影響。我們打開(kāi) 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"> <!-- 設(shè)置活動(dòng)的啟動(dòng)方式為 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>
再次打開(kāi)模擬器進(jìn)行調(diào)試:

效果 PERFECT。
至此,我們就實(shí)現(xiàn)了無(wú) Fragment 的底部導(dǎo)航欄。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)歡迎界面停留3秒效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)歡迎界面停留3秒效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
Android ImageButton自定義按鈕的按下效果的代碼實(shí)現(xiàn)方法分享
這篇文章主要介紹了Android ImageButton自定義按鈕的按下效果的代碼實(shí)現(xiàn)方法,需要的朋友可以參考下2014-02-02
Android 圖片處理避免出現(xiàn)oom的方法詳解
本篇文章主要介紹了Android 圖片處理避免出現(xiàn)oom的方法詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
Android進(jìn)階事件分發(fā)機(jī)制解決事件沖突
這篇文章主要為大家介紹了Android進(jìn)階事件分發(fā)機(jī)制解決事件沖突過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Android使用Room操作數(shù)據(jù)庫(kù)流程詳解
谷歌推薦使用Room操作數(shù)據(jù)庫(kù),Room在 SQLite 上提供了一個(gè)抽象層,在充分利用 SQLite強(qiáng)大功能的同時(shí),能夠流暢地訪問(wèn)數(shù)據(jù)庫(kù)2022-11-11
Android寫(xiě)一個(gè)實(shí)時(shí)輸入框功能
這篇文章主要介紹了Android寫(xiě)一個(gè)實(shí)時(shí)輸入框功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android中控制和禁止ScrollView自動(dòng)滑動(dòng)到底部的方法
這篇文章主要給大家介紹了關(guān)于Android中控制和禁止ScrollView自動(dòng)滑動(dòng)到底部的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
Android自定義View 實(shí)現(xiàn)水波紋動(dòng)畫(huà)引導(dǎo)效果
在android程序開(kāi)發(fā)中,我們經(jīng)常簡(jiǎn)單通過(guò)自定義view實(shí)現(xiàn)水波紋動(dòng)畫(huà)引導(dǎo)功能,下面通過(guò)本文給大家分享實(shí)現(xiàn)代碼,需要的朋友參考下2017-01-01

