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)目的屬性頁將 Target Framework 設(shè)置為 Android 4.0.3 (Ice Cream Sandwich) , 如下圖所示:

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

在項(xiàng)目中新建一個(gè)目錄 SupportLib , 并添加對 android-support-v4.jar 文件(位于 android-sdk/extras/android/support/v4 目錄, 如果沒有, 需要用 SDK Manager 安裝)的引用, 并將 jar 文件的編譯動作 (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 會根據(jù)這些標(biāo)記自動生成一個(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)簽切換視圖, 也可以水平滑動切換視圖, 如下圖所示:

用戶既可以點(diǎn)擊上面的 ‘SECTION 0'、 ‘SECTION 1'、 ‘SECTION 2' 標(biāo)簽切換視圖, 也可以在視圖上水平拖動切換視圖, 同時(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, 所有加載過視圖都保存在內(nèi)存中,
/// 如果視圖占用內(nèi)存過多, 考慮替換成 FragmentStatePagerAdapter 。
/// </summary>
AppSectionsPagerAdapter _appSectionsPagerAdapter;
/// <summary>
/// 用 ViewPager 來顯示視圖三個(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;
// 首頁不需要向上的 Home 按鈕
actionBar.SetHomeButtonEnabled(false);
// 設(shè)置標(biāo)簽導(dǎo)航模式
actionBar.NavigationMode = ActionBarNavigationMode.Tabs;
// 設(shè)置 ViewPager 的 Adapter , 這樣用戶就可以水平滑動切換視圖了
this._viewPager = this.FindViewById<ViewPager>(Resource.Id.Pager);
this._viewPager.Adapter = this._appSectionsPagerAdapter;
// 當(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)航并不適合所有的場景, 有時(shí)僅僅需要顯示視圖的標(biāo)題即可, 但是同樣可以水平滑動切換視圖, 如下圖所示:

這種導(dǎo)航方式相當(dāng)于標(biāo)簽式導(dǎo)航的簡化版, 用戶只可以左右滑動切換視圖, 實(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 即可顯示選中頁面的標(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)對應(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 的選中頁切換時(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)用程序的上一級 Activity , 注意是上一級 Activity , 不是上一個(gè) Activity , 關(guān)于向上與返回的區(qū)別, 可以看看 Android SDK 中的 Providing Ancestral and Temporal Navigation 一文, 將向上和返回講解的非常清楚, 在這里只討論 Mono for Android 的實(shí)現(xiàn)方式。
要顯示向上導(dǎo)航的按鈕, 需要在 OnCreate 方法中對 ActionBar 做如下設(shè)置:
// 設(shè)置 ActionBar
var actionBar = this.ActionBar;
// 將 Home 按鈕顯示為向上, 提示用戶點(diǎn)擊這個(gè)按鈕可以返回應(yīng)用程序的上一級。
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í)會調(diào)用到這里
// 創(chuàng)建啟動上級 Activity 的 Intent
var upIntent = new Intent(this, typeof(MainActivity));
// 使用 Suport Package 中的 NavUtils 來正確處理向上導(dǎo)航
if (NavUtils.ShouldUpRecreateTask(this, upIntent)) {
// 上級 Activity 沒有起動過, 需要創(chuàng)建一個(gè)新的導(dǎo)航棧道
TaskStackBuilder.Create(this)
// If there are ancestor activities, they should be added here.
.AddNextIntent(upIntent)
.StartActivities();
this.Finish();
}
else {
// 上級 Activity 已經(jīng)創(chuàng)建過了, 直接導(dǎo)航就行。
NavUtils.NavigateUpTo(this, upIntent);
}
return true;
}
return base.OnOptionsItemSelected(item);
}
總結(jié)
Android 系統(tǒng)的導(dǎo)航與 iOS 相比復(fù)雜很多, 實(shí)現(xiàn)起來也相對麻煩一些, 好在有 Google 的 Support Package 已經(jīng)多大部分操作提供了比較好的封裝, 還是比較容易掌握的。 文中的完整的源代碼已經(jīng)提交的 Github 上, 地址是 https://github.com/beginor/MonoDroid/tree/master/EffectiveNavigation 。
相關(guān)文章
Android自定義View實(shí)現(xiàn)體重表盤詳解流程
對于安卓程序員來說,自定義view簡直不要太重要,畢竟有很多功能,譬如圓形頭像這些,用單純的原生非常難以實(shí)現(xiàn),而用自定義view,簡直分分鐘2021-11-11
Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器一)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實(shí)現(xiàn)BaseAdapter通用適配器第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Android實(shí)現(xiàn)上傳文件到服務(wù)器實(shí)例詳解
本篇文章詳細(xì)介紹了Android實(shí)現(xiàn)上傳文件到服務(wù)器實(shí)例詳解,實(shí)現(xiàn)了文件每隔5秒進(jìn)行上傳,有需要的可以了解一下。2016-11-11

