Mono for Android 實(shí)現(xiàn)高效的導(dǎo)航(Effective Navigation)
Android 4.0 系統(tǒng)定義了一系列的高效導(dǎo)航方式 (Effective Navigation), 主要包括標(biāo)簽、下拉列表、以及向上和返回等, 本文介紹如何用 Mono for Android 實(shí)現(xiàn)這些的導(dǎo)航方式。
準(zhǔn)備 Android 4.0 ICS 項(xiàng)目
新建 Android ICS 項(xiàng)目
打開 MonoDevelop , 新建一個(gè) Mono for Android 項(xiàng)目, 并在項(xiàng)目的屬性頁(yè)將 Target Framework 設(shè)置為 Android 4.0.3 (Ice Cream Sandwich) , 如下圖所示:

添加 Mono.Android.Support.v4 引用項(xiàng)
在解決方案窗口, 選中項(xiàng)目的引用節(jié)點(diǎn), 右擊選擇編輯引用, 添加對(duì) Mono.Android.Support.v4.dll 的引用, 如圖所示:

在項(xiàng)目中新建一個(gè)目錄 SupportLib , 并添加對(duì) android-support-v4.jar 文件(位于 android-sdk/extras/android/support/v4 目錄, 如果沒(méi)有, 需要用 SDK Manager 安裝)的引用, 并將 jar 文件的編譯動(dòng)作 (BuildAction) 設(shè)置為 AndroidJavaLibrary , 如下圖所示:

本文提到的導(dǎo)航都是根據(jù) Android 4.0 設(shè)計(jì)規(guī)范中推薦的 ActionBar 實(shí)現(xiàn)的, 因此整個(gè)應(yīng)用程序啟用帶 ActionBar 的主題, 如果使用 Java 的話, 需要手工編輯 AppManifest.xml 文件的設(shè)置, 而用 Mono for Android 的話, 基本上不需要手工編輯這個(gè)文件。
Mono for Android 的做法是, 新建一個(gè) App 類, 繼承自 Android.App.Application 類, 并添加 Android.App.ApplicationAttribute 標(biāo)記, 在編譯時(shí), Mono for Android 會(huì)根據(jù)這些標(biāo)記自動(dòng)生成一個(gè) AppManifest.xml 文件并打包到最終的 apk 文件中。
App 類的代碼如下:
[Application(Label = "@string/AppName", Icon = "@drawable/ic_launcher",
Theme = "@android:style/Theme.Holo.Light.DarkActionBar")]
public class App : Application {
public App(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) {
}
}
添加這個(gè)類之后, 項(xiàng)目中的每個(gè) Activity 將默認(rèn)都是用這個(gè)主題, 如果有 Activity 要使用其它的主題, 才需要添加自己的主題屬性。
標(biāo)簽導(dǎo)航
Android 的標(biāo)簽用 ActionBar 實(shí)現(xiàn), 用戶既可以點(diǎn)擊標(biāo)簽切換視圖, 也可以水平滑動(dòng)切換視圖, 如下圖所示:

用戶既可以點(diǎn)擊上面的 ‘SECTION 0'、 ‘SECTION 1'、 ‘SECTION 2' 標(biāo)簽切換視圖, 也可以在視圖上水平拖動(dòng)切換視圖, 同時(shí)標(biāo)簽選中項(xiàng)也要同步選中, 實(shí)現(xiàn)的代碼如下:
[Activity (Label = "@string/AppName", Icon = "@drawable/ic_launcher", MainLauncher = true)]
public class MainActivity : FragmentActivity {
/// <summary>
/// AppSectionsPagerAdapter 提供要顯示的視圖, 繼承自
/// Mono.Android.Support.V4.View.PagerAdapter, 所有加載過(guò)視圖都保存在內(nèi)存中,
/// 如果視圖占用內(nèi)存過(guò)多, 考慮替換成 FragmentStatePagerAdapter 。
/// </summary>
AppSectionsPagerAdapter _appSectionsPagerAdapter;
/// <summary>
/// 用 ViewPager 來(lái)顯示視圖三個(gè)主視圖, 每次只顯示一個(gè)。
/// </summary>
ViewPager _viewPager;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
this.SetContentView(Resource.Layout.MainActivity);
// 創(chuàng)建 Adapter
this._appSectionsPagerAdapter = new AppSectionsPagerAdapter(this.SupportFragmentManager);
// 設(shè)置 ActionBar
var actionBar = this.ActionBar;
// 首頁(yè)不需要向上的 Home 按鈕
actionBar.SetHomeButtonEnabled(false);
// 設(shè)置標(biāo)簽導(dǎo)航模式
actionBar.NavigationMode = ActionBarNavigationMode.Tabs;
// 設(shè)置 ViewPager 的 Adapter , 這樣用戶就可以水平滑動(dòng)切換視圖了
this._viewPager = this.FindViewById<ViewPager>(Resource.Id.Pager);
this._viewPager.Adapter = this._appSectionsPagerAdapter;
// 當(dāng)水平滑動(dòng)切換視圖時(shí), 設(shè)置選中的標(biāo)簽
this._viewPager.PageSelected += delegate(object sender, ViewPager.PageSelectedEventArgs e) {
actionBar.SetSelectedNavigationItem(e.P0);
};
// 依次添加三個(gè)標(biāo)簽, 并添加標(biāo)簽的選中事件處理函數(shù), 設(shè)置當(dāng)前的視圖。
for (var i = 0; i < this._appSectionsPagerAdapter.Count; i++) {
var tab = actionBar.NewTab().SetText(this._appSectionsPagerAdapter.GetPageTitle(i));
tab.TabSelected += delegate(object sender, Android.App.ActionBar.TabEventArgs e) {
this._viewPager.CurrentItem = tab.Position;
};
actionBar.AddTab(tab);
}
}
}
左右導(dǎo)航
標(biāo)簽導(dǎo)航并不適合所有的場(chǎng)景, 有時(shí)僅僅需要顯示視圖的標(biāo)題即可, 但是同樣可以水平滑動(dòng)切換視圖, 如下圖所示:

這種導(dǎo)航方式相當(dāng)于標(biāo)簽式導(dǎo)航的簡(jiǎn)化版, 用戶只可以左右滑動(dòng)切換視圖, 實(shí)現(xiàn)的代碼如下:
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
this.SetContentView(Resource.Layout.CollectionDemoActivity);
// 創(chuàng)建 Adapter
this._demoCollectionPagerAdapter = new DemoCollectionPagerAdapter(this.SupportFragmentManager);
// 設(shè)置 ViewPager 的 Adapter
this._viewPager = this.FindViewById<ViewPager>(Resource.Id.Pager);
this._viewPager.Adapter = this.mDemoCollectionPagerAdapter;
}
因?yàn)橐@示標(biāo)題, 所以這個(gè) Activity 的 Layout 添加了一個(gè) PagerTitleStrip , Layout 源代碼如下:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/Pager"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--
PaterTitleStrip 即可顯示選中頁(yè)面的標(biāo)題, 也顯示臨近選中的幾個(gè)視圖的標(biāo)題
-->
<android.support.v4.view.PagerTitleStrip android:id="@+id/PagerTitleStrip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
下拉列表
下拉列表導(dǎo)航是在 ActionBar 中顯示一個(gè)下拉列表 (Spinner), 就像一個(gè)菜單, 只顯示選中的菜單項(xiàng)對(duì)應(yīng)的視圖, 如下圖所示:

將 ActionBar 設(shè)置為下拉列表導(dǎo)航時(shí), 一般不顯示 Activity 自身的標(biāo)題, 因此需要將 Activity 的 Label 標(biāo)記為空字符串, 并且 Activity 需要實(shí)現(xiàn)接口 ActionBar.IOnNavigationListener , ListNavigationActivity 的部分實(shí)現(xiàn)代碼如下:
[Activity (Label = "")]
public class ListNavigationActivity
: FragmentActivity, ActionBar.IOnNavigationListener {
ListNavSectionsPagerAdapter _navSectionsPagerAdapter;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
/* 其他代碼省略 … */
// 設(shè)置 ActionBar
var actionBar = this.ActionBar;
// 將 Home 設(shè)置為向上
actionBar.SetDisplayHomeAsUpEnabled(true);
// 設(shè)置 ActionBar 的導(dǎo)航模式為下拉列表
actionBar.NavigationMode = ActionBarNavigationMode.List;
var titles = new string[this._navSectionsPagerAdapter.Count];
for (var i = 0; i < titles.Length; i++) {
titles[i] = this._navSectionsPagerAdapter.GetPageTitle(i);
}
// 設(shè)置列表導(dǎo)航的回調(diào)參數(shù)
actionBar.SetListNavigationCallbacks(
new ArrayAdapter(
actionBar.ThemedContext,
Resource.Layout.ListNavigationActivityActionbarListItem,
Android.Resource.Id.Text1,
titles
),
this
);
// 設(shè)置 ViewPager
this._viewPager = this.FindViewById<ViewPager>(Resource.Id.Pager);
this._viewPager.Adapter = this._navSectionsPagerAdapter;
// 當(dāng) ViewPager 的選中頁(yè)切換時(shí), 同步 actionBar 的選中項(xiàng)。
this._viewPager.PageSelected += delegate(object sender, ViewPager.PageSelectedEventArgs e) {
actionBar.SetSelectedNavigationItem(e.P0);
};
}
// ActionBar.IOnNavigationListener
public bool OnNavigationItemSelected(int itemPosition, long itemId) {
this._viewPager.CurrentItem = itemPosition;
return true;
}
}
向上導(dǎo)航
所謂的向上導(dǎo)航, 就是在 Activity 的圖標(biāo)上顯示一個(gè)向左的箭頭, 點(diǎn)擊圖標(biāo)返回應(yīng)用程序的上一級(jí) Activity , 注意是上一級(jí) Activity , 不是上一個(gè) Activity , 關(guān)于向上與返回的區(qū)別, 可以看看 Android SDK 中的 Providing Ancestral and Temporal Navigation 一文, 將向上和返回講解的非常清楚, 在這里只討論 Mono for Android 的實(shí)現(xiàn)方式。
要顯示向上導(dǎo)航的按鈕, 需要在 OnCreate 方法中對(duì) ActionBar 做如下設(shè)置:
// 設(shè)置 ActionBar
var actionBar = this.ActionBar;
// 將 Home 按鈕顯示為向上, 提示用戶點(diǎn)擊這個(gè)按鈕可以返回應(yīng)用程序的上一級(jí)。
actionBar.SetDisplayHomeAsUpEnabled(true);同時(shí)還需要重寫 OnOptionsItemSelected 方法, 當(dāng)用戶點(diǎn)擊 Home 按鈕時(shí), 做相應(yīng)的處理, 實(shí)現(xiàn)向上導(dǎo)航的代碼如下:
public override bool OnOptionsItemSelected(Android.Views.IMenuItem item) {
// 作為示例, 只處理用戶點(diǎn)擊 Home 按鈕的情況。
if (item.ItemId == Android.Resource.Id.Home) {
// 當(dāng) Home 按鈕被點(diǎn)擊時(shí)會(huì)調(diào)用到這里
// 創(chuàng)建啟動(dòng)上級(jí) Activity 的 Intent
var upIntent = new Intent(this, typeof(MainActivity));
// 使用 Suport Package 中的 NavUtils 來(lái)正確處理向上導(dǎo)航
if (NavUtils.ShouldUpRecreateTask(this, upIntent)) {
// 上級(jí) Activity 沒(méi)有起動(dòng)過(guò), 需要?jiǎng)?chuàng)建一個(gè)新的導(dǎo)航棧道
TaskStackBuilder.Create(this)
// If there are ancestor activities, they should be added here.
.AddNextIntent(upIntent)
.StartActivities();
this.Finish();
}
else {
// 上級(jí) Activity 已經(jīng)創(chuàng)建過(guò)了, 直接導(dǎo)航就行。
NavUtils.NavigateUpTo(this, upIntent);
}
return true;
}
return base.OnOptionsItemSelected(item);
}
總結(jié)
Android 系統(tǒng)的導(dǎo)航與 iOS 相比復(fù)雜很多, 實(shí)現(xiàn)起來(lái)也相對(duì)麻煩一些, 好在有 Google 的 Support Package 已經(jīng)多大部分操作提供了比較好的封裝, 還是比較容易掌握的。 文中的完整的源代碼已經(jīng)提交的 Github 上, 地址是 https://github.com/beginor/MonoDroid/tree/master/EffectiveNavigation 。
相關(guān)文章
Android LayoutInflater加載布局詳解及實(shí)例代碼
這篇文章主要介紹了Android LayoutInflater加載布局詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android開發(fā)之Socket通信傳輸簡(jiǎn)單示例
這篇文章主要介紹了Android開發(fā)之Socket通信傳輸實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Android socket傳輸?shù)脑?、?shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-08-08
Android集成百度地圖開發(fā)流程和注意事項(xiàng)
現(xiàn)在很多項(xiàng)目都需要集成百度地圖,所以就把自己做過(guò)一個(gè)項(xiàng)目的經(jīng)驗(yàn)寫出來(lái)和大家分享,方便自己和大家使用的時(shí)候參考借鑒,下面就來(lái)一起看看Android集成百度地圖開發(fā)流程和注意事項(xiàng)吧。2016-09-09
Java Base64位編碼與String字符串的相互轉(zhuǎn)換,Base64與Bitmap的相互轉(zhuǎn)換實(shí)例代碼
這篇文章主要介紹了Java Base64位編碼與String字符串的相互轉(zhuǎn)換,Base64與Bitmap的相互轉(zhuǎn)換實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
Android實(shí)現(xiàn)自動(dòng)點(diǎn)擊無(wú)障礙服務(wù)功能的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)自動(dòng)點(diǎn)擊無(wú)障礙服務(wù)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Handler實(shí)現(xiàn)線程之間的通信下載文件動(dòng)態(tài)更新進(jìn)度條
每一個(gè)線程對(duì)應(yīng)一個(gè)消息隊(duì)列MessageQueue,實(shí)現(xiàn)線程之間的通信,可通過(guò)Handler對(duì)象將數(shù)據(jù)裝進(jìn)Message中,再將消息加入消息隊(duì)列,而后線程會(huì)依次處理消息隊(duì)列中的消息。這篇文章主要介紹了Handler實(shí)現(xiàn)線程之間的通信下載文件動(dòng)態(tài)更新進(jìn)度條,需要的朋友可以參考下2017-08-08

