Android實(shí)現(xiàn)底部導(dǎo)航欄效果
目前網(wǎng)上主流的文章都是用底部的 RadioGroup + 頁面部分的 Fragment 實(shí)現(xiàn)導(dǎo)航欄切換頁面效果的。
然而底部的 RadioGroup 是如此麻煩,每個(gè)按鈕的圖片和文字部分都要做一個(gè) selector 用于表示選中和非選中兩種狀態(tài)時(shí)的樣式。
另外 Fragment 也有很多坑,先不管大家是否已熟練掌握,反正我是看著看著就學(xué)不下去了,所以我另辟蹊徑用 Activity 的方式實(shí)現(xiàn)了偽 Fragment 的效果。
這里我們就來做一個(gè)三個(gè)按鈕的底部導(dǎo)航欄。
因?yàn)槲覀冞@里是用三個(gè) Activity 實(shí)現(xiàn)三個(gè)頁面,而并非一個(gè) Activity 中的三個(gè) Fragment,所以在此之前,我們需要建立一個(gè)管理活動堆棧的類,以便在程序退出時(shí)能直接結(jié)束堆棧中的所有活動,不至于要依次退出三個(gè) Activity。
在 java 代碼目錄里新建一個(gè) base 包,在包內(nèi)新建文件 AppManager.java:
/** ?* AppManager: 用于對活動進(jìn)行管理。 ?* 該模塊僅限 base 包內(nèi)使用。 ?* 該模塊為單一實(shí)例,您需要調(diào)用 AppManager.get() 獲取實(shí)例后再調(diào)用方法。 ?* <p> ?* 為確保應(yīng)用管理器正常工作,請新建一個(gè)繼承 Activity 的抽象類 BaseActivity, ?* 然后重寫 BaseActivity 類的 onCreate() 和 onDestroy() 方法。 ?* 請給 BaseActivity 類的 onCreate() 方法添加如下代碼: ?* AppManager.get().addActivity(this); ?* 請給 BaseActivity 類的 onDestroy() 方法添加如下代碼: ?* AppManager.get().removeActivity(this); ?* 最后,確保本 APP 內(nèi)的所有活動類均繼承于 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)建活動堆棧。 ? ? ? ? mActivities = new Stack<>(); ? ? } // AppManager() (Class Constructor) ? ? ? /** ? ? ?* get(): 獲得 AppManager 類的單例。 ? ? ?* ? ? ?* @return 該類的單例 sManager。 ? ? ?*/ ? ? static AppManager get() { ? ? ? ? return sManager; ? ? } // get() ? ? ? /** ? ? ?* addActivity(): 向堆棧中添加一個(gè)活動對象。 ? ? ?* ? ? ?* @param activity 要添加的活動對象。 ? ? ?*/ ? ? void addActivity(BaseActivity activity) { ? ? ? ? mActivities.add(activity); ? ? } // addActivity() ? ? ? /** ? ? ?* removeActivity(): 從堆棧中移除一個(gè)活動對象。 ? ? ?* ? ? ?* @param activity 要移除的活動對象。 ? ? ?*/ ? ? void removeActivity(BaseActivity activity) { ? ? ? ? mActivities.remove(activity); ? ? } // removeActivity() ? ? ? /** ? ? ?* finishAllExcept(): 除一個(gè)特定活動外,結(jié)束堆棧中其余所有活動。 ? ? ?* 結(jié)束活動時(shí)會觸發(fā) BaseActivity 類的 onDestroy()方法, ? ? ?* 堆棧中的活動對象會同步移除。 ? ? ?* ? ? ?* @param activityClass 要保留的活動的類名(xxxActivity.class) ? ? ?*/ ? ? void finishAllExcept(Class activityClass) { ? ? ? ? int i, len; ? ? ? ? BaseActivity[] activities; ? ? ? ? ? // 結(jié)束活動時(shí)會調(diào)用活動的 onDestroy() 方法,堆棧的內(nèi)容會實(shí)時(shí)改變 ? ? ? ? // 為避免因此引起的引用錯誤,先將堆棧的內(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ù)組里引用活動對象并結(jié)束,堆棧內(nèi)容的改變不影響數(shù)組 ? ? ? ? ? ? ? ? activities[i].finish(); ? ? ? ? ? ? } // if (!activities[i].getClass().equals(activityClass)) ? ? ? ? } // for (i = 0; i < len; ++i) ? ? } // finishAllExcept() ? ? ? /** ? ? ?* finishAllActivities(): 結(jié)束堆棧中的所有活動。 ? ? ?* 結(jié)束活動時(shí)會觸發(fā) BaseActivity 類的 onDestroy()方法, ? ? ?* 堆棧中的活動對象會同步移除。 ? ? ?*/ ? ? void finishAllActivities() { ? ? ? ? int i, len; ? ? ? ? BaseActivity[] activities; ? ? ? ? ? // 結(jié)束活動時(shí)會調(diào)用活動的 onDestroy() 方法,堆棧的內(nèi)容會實(shí)時(shí)改變 ? ? ? ? // 為避免因此引起的引用錯誤,先將堆棧的內(nèi)容復(fù)制到一個(gè)臨時(shí)數(shù)組里 ? ? ? ? activities = mActivities.toArray(new BaseActivity[0]); ? ? ? ? len = activities.length; ? ? ? ? for (i = 0; i < len; ++i) { ? ? ? ? ? ? // 從數(shù)組里引用活動對象并結(jié)束,堆棧內(nèi)容的改變不影響數(shù)組 ? ? ? ? ? ? activities[i].finish(); ? ? ? ? } // for (i = 0; i < len; ++i) ? ? } // finishAllActivities() } // AppManager Class ? // E.O.F
上述代碼粘貼完后會報(bào)錯,別著急,那是因?yàn)槲覀冞€沒有建立和管理器相關(guān)聯(lián)的 BaseActivity 類。現(xiàn)在我們在 base 包內(nèi)再新建一個(gè) BaseActivity.java,封裝活動間的跳轉(zhuǎn)方法。其中 jumpTo() 方法表示跳轉(zhuǎn)后活動堆棧中只保留跳轉(zhuǎn)后的那一個(gè)活動,壓在堆棧中的其他活動全部銷毀;而 open() 方法則保留活動堆棧。另外還有一個(gè) showExitDialog() 的方法,用于詢問用戶是否退出程序,當(dāng)用戶選擇“是”時(shí),將堆棧中的所有活動一次性全部銷毀。
/** ?* BaseActivity: 該抽象類定義所有活動均擁有的共同屬性。 ?* 本 APP 中所有活動對象均繼承此類。 ?*/ public abstract class BaseActivity extends Activity { ? ? /** ? ? ?* onCreate(): 重寫父類的 onCreate() 方法,向應(yīng)用管理器中添加本活動。 ? ? ?*/ ? ? @Override ? ? protected void onCreate(Bundle savedInstanceState) { ? ? ? ? super.onCreate(savedInstanceState); ? ? ? ? AppManager.get().addActivity(this); ? ? } // onCreate() ? ? ? /** ? ? ?* onDestroy(): 重寫父類的 onDestroy() 方法,從應(yīng)用管理器中移除本活動。 ? ? ?*/ ? ? @Override ? ? protected void onDestroy() { ? ? ? ? super.onDestroy(); ? ? ? ? AppManager.get().removeActivity(this); ? ? } // onDestroy() ? ? ? /** ? ? ?* jumpTo(): 實(shí)現(xiàn)不傳參的活動間跳轉(zhuǎn)。 ? ? ?* ? ? ?* @param dst 要跳轉(zhuǎn)到的活動的類名(xxxActivity.class)。 ? ? ?*/ ? ? protected void jumpTo(Class dst) { ? ? ? ? Intent intent = new Intent(this, dst); ? ? ? ? startActivity(intent); ? ? ? ? AppManager.get().finishAllExcept(dst); ? ? } // jumpTo() ? ? ? /** ? ? ?* open(): 將當(dāng)前活動壓入堆棧,打開一個(gè)新活動。 ? ? ?* ? ? ?* @param dst 要打開的活動的類名(xxxActivity.class)。 ? ? ?*/ ? ? protected void open(Class dst) { ? ? ? ? Intent intent = new Intent(this, dst); ? ? ? ? startActivity(intent); ? ? } // open() ? ? ? /** ? ? ?* showExitDialog(): 顯示退出程序?qū)υ捒?,詢問用戶是否退出程序? ? ? ?*/ ? ? 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
上述代碼粘貼完后還是有一處報(bào)錯。這是因?yàn)檫@段代碼中用到了一處尚未定義的字符串資源。我們打開 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">主頁</string> ? ? <string name="btnNavMessage">消息</string> ? ? <string name="btnNavSettings">設(shè)置</string> </resources>
至此,報(bào)錯全部消除。
現(xiàn)在我們建立底部導(dǎo)航欄的三個(gè)按鈕對應(yīng)的三個(gè) Activity 頁面。
準(zhǔn)備導(dǎo)航欄的圖標(biāo)資源,放入 res/drawable 文件夾內(nè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è)活動 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 ? ? ? ? 以上代碼將頁面布局容器和底部的導(dǎo)航欄進(jìn)行了約束 ? ? ? ? 即該容器的底端和導(dǎo)航欄的頂端彼此約束 ? ? ? ? 確保該容器的占用空間不會覆蓋導(dǎo)航欄 ? ? ? ? 頁面的主體布局請?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"> ? ? ? ? ? <!-- 【主頁】活動中,【主頁】按鈕設(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ù)覽頁面看一下,已經(jīng)有了底部導(dǎo)航欄的雛形:
現(xiàn)在打開代碼文件 HomeActivity.java 編寫點(diǎn)擊導(dǎo)航欄按鈕時(shí)的活動跳轉(zhuǎn)代碼:
public class HomeActivity extends BaseActivity { // 請注意此處繼承的是 BaseActivity 而不是 Activity ? ? /** ? ? ?* onCreate(): 活動創(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)擊的按鈕對象,用 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ā)。 ? ? ?* 彈出對話框詢問是否退出程序。 ? ? ?*/ ? ? @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è)活動直接照葫蘆畫瓢就 OK 了。不過這里千萬要注意后兩個(gè)活動不能只復(fù)制 MainActivity 的布局就完事了,一定要更改導(dǎo)航欄各個(gè)按鈕的樣式!另外就是活動中的 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"> <!-- 本活動的背景色為 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"> ? ? ? ? ? <!-- 請務(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() ? ? ? // 請注意本方法內(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"> <!-- 本活動的背景色為 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> ? ? ? <!-- 以下為導(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"> ? ? ? ? ? <!-- 請務(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() ? ? ? // 請注意本方法內(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
做到這一步,先中場休息,打開模擬器調(diào)試一下。
我們之后還有兩個(gè)問題需要解決:
①活動間的跳轉(zhuǎn)是有動畫的,而我們并不需要這畫蛇添足的動畫;
②這一點(diǎn)也是更重要的。每次一點(diǎn)擊導(dǎo)航欄上的按鈕,就會打開一個(gè)新活動。當(dāng)我們從主頁活動跳至設(shè)置活動,然后再由設(shè)置活動跳回主頁活動時(shí),系統(tǒng)的堆棧里其實(shí)是有兩個(gè)主頁活動的實(shí)例的,如果反復(fù)跳轉(zhuǎn),系統(tǒng)也會一直繼續(xù)創(chuàng)建活動實(shí)例,原先的活動實(shí)例名存實(shí)亡,造成實(shí)質(zhì)上的內(nèi)存泄漏,直到最后內(nèi)存不夠用而崩潰。我們需要的是這樣的效果:跳回曾經(jīng)已經(jīng)創(chuàng)建過的活動時(shí),不要新建實(shí)例,而是直接重新引用原先活動的實(shí)例。這樣,不論在導(dǎo)航欄上跳轉(zhuǎn)多少次,內(nèi)存中最多只會有 3 個(gè)活動,永遠(yuǎn)不會有內(nèi)存泄漏的問題。
我們先來解決第一個(gè)問題。這個(gè)問題其實(shí)很好辦,在 res/values 目錄下有個(gè) styles.xml 的文件,用于定義活動的主題樣式。我們在其中增加幾行代碼用于關(guān)閉活動間的跳轉(zhuǎn)動畫:
<resources> ? ? ? <!-- Base application theme. --> ? ? <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> ? ? ? ? <!-- Customize your theme here. --> ? ? ? ? <!-- 關(guān)閉動畫 --> ? ? ? ? <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>
接下來是第二個(gè)問題,保證每個(gè)活動只有唯一的實(shí)例,避免跳轉(zhuǎn)過程中活動實(shí)例反復(fù)創(chuàng)建造成的內(nèi)存泄漏。
Android 中有一種活動啟動方式叫 singleInstance,它表示整個(gè) Application 周期里對應(yīng)的活動實(shí)例不能超過一個(gè),當(dāng)該活動已創(chuàng)建但之后又從其他的 intent 跳轉(zhuǎn)而來時(shí),不新建實(shí)例,而是引用已有的實(shí)例。另外,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"> <!-- 設(shè)置活動的啟動方式為 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>
再次打開模擬器進(jìn)行調(diào)試:
效果 PERFECT。
至此,我們就實(shí)現(xiàn)了無 Fragment 的底部導(dǎo)航欄。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)歡迎界面停留3秒效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)歡迎界面停留3秒效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Android ImageButton自定義按鈕的按下效果的代碼實(shí)現(xiàn)方法分享
這篇文章主要介紹了Android ImageButton自定義按鈕的按下效果的代碼實(shí)現(xiàn)方法,需要的朋友可以參考下2014-02-02Android 圖片處理避免出現(xiàn)oom的方法詳解
本篇文章主要介紹了Android 圖片處理避免出現(xiàn)oom的方法詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android進(jìn)階事件分發(fā)機(jī)制解決事件沖突
這篇文章主要為大家介紹了Android進(jìn)階事件分發(fā)機(jī)制解決事件沖突過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Android使用Room操作數(shù)據(jù)庫流程詳解
谷歌推薦使用Room操作數(shù)據(jù)庫,Room在 SQLite 上提供了一個(gè)抽象層,在充分利用 SQLite強(qiáng)大功能的同時(shí),能夠流暢地訪問數(shù)據(jù)庫2022-11-11Android寫一個(gè)實(shí)時(shí)輸入框功能
這篇文章主要介紹了Android寫一個(gè)實(shí)時(shí)輸入框功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android中控制和禁止ScrollView自動滑動到底部的方法
這篇文章主要給大家介紹了關(guān)于Android中控制和禁止ScrollView自動滑動到底部的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10Android自定義View 實(shí)現(xiàn)水波紋動畫引導(dǎo)效果
在android程序開發(fā)中,我們經(jīng)常簡單通過自定義view實(shí)現(xiàn)水波紋動畫引導(dǎo)功能,下面通過本文給大家分享實(shí)現(xiàn)代碼,需要的朋友參考下2017-01-01