Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android最佳實踐之高效的應用導航

Android最佳實踐之高效的應用導航

編輯:關於Android編程

設計(一)- 規劃Screens和他們之間的關系

原文地址:http://developer.android.com/training/design-navigation/screen-planning.html

設計和開發Android應用程序的第一個步驟是確定用戶能夠查看和處理應用。一旦你知道用戶與之交互的應用程序之間交互什麼數據,下一步就是設計交互,允許用戶導航到app的不同部分,進入和退出應用程序中的界面。

這篇文章開始向你展示如何規劃高水平的應用程序層次結構,然後選擇合適的導航形式允許用戶有效、直觀地遍歷你的內容。每一堂課涵蓋了Android應用程序導航的交互設計過程的不同階段,大致是按照時間排序的。學完這門課程後,你應該能夠把這裡概述的方法和導航模式,將其應用到你自己的應用程序中,為用戶提供一個流暢的導航體驗。

規劃Screens和他們之間的關系

大多數應用程序都有一個固有的信息模型,可以表示為一個樹或圖的對象類型。很明顯,你可以將用戶在你的應用交互中代表的不同信息類型畫成一個圖。軟件工程師和數據架構師經常使用實體關系圖(ERD)來描述應用程序的信息模型。
讓我們考慮一個示例應用程序,允許用戶浏覽一組分類新聞和照片。這樣的一個可能的模型應用在ERD的形式如下所示

創建一個Screen列表

定義信息模型之後,你可以開始定義必要的上下文,以便用戶能夠有效地發現、觀察並按應用程序中的數據操作。實際操作中,實現的方法之一是確定哪些屏幕允許用戶導航到數據並與之交互的。這些屏幕的展現我們實際上通常取決於目標設備;特別是在設計過程的早期考慮這個非常重要,它能確保應用程序能夠適應設備環境。
在我們的示例應用程序中,我們想讓用戶查看、保存、分享分類的故事和照片。下面是這些用例的屏幕的詳盡清單:
- 桌面或快捷方式用來訪問Story和Photo
- 分類列表Category
- 給定類別的新聞列表
- 未分類的的照片列表,
- 照片詳細信息視圖(我們可以保存和分享)
- 所有保存項目的列表
- 保存照片的列表
- 保存Story的列表
- 圖表和Screen的關系

現在我們可以定義Screen之間的直接關系,箭頭從一個屏幕A到另一個屏幕B意味著屏幕B應該可以被一些用戶直接通過屏幕A訪問到。一旦我們定義兩組屏幕和它們之間的關系,我們可以一致的表達這些屏幕為地圖,顯示你所有的屏幕和它們之間的關系:

如果我們以後想允許用戶提交新聞故事或上傳照片,我們可以添加另外的屏幕圖。

極簡的設計

我們可以從這個詳盡的屏幕地圖上設計一個功能齊全的應用程序。一個簡單的用戶界面可以包含列表,列表可以點擊按鈕導航到子屏幕
按鈕導航到不同部分(如故事、照片,保存項目)
垂直列表代表收藏(如照片列表,故事列表等等)。
詳細信息視圖。(全屏照片視圖等等)
不過,你可以使用屏幕分組技術和更先進的導航元素更直觀、屏幕友好的展示內容。下節課,我們探索屏幕分組技術,如為平板設備提供多窗格布局。之後,我們將深入介紹在Android上的各種導航模式

設計(二)- 規劃多個觸摸屏尺寸

原文地址:http://developer.android.com/training/design-navigation/multiple-sizes.html
上面講的詳細的屏幕地圖並不能關聯到一個特定設備尺寸,盡管它通常能手機或同尺寸設備上顯式良好。但是Android應用程序需要適應許多不同類型的設備,從3英寸的手機到10英寸的平板電腦再到42英寸電視。在這節課中我們將在組成多屏的詳盡屏幕地圖中探討原因和策略。
注意:為電視機設計應用程序還需要注意其他因素如交互方法(沒有觸摸屏),遠距離閱讀文本的易讀性等等。盡管這個討論超出了這篇文章的范圍,你可以在Google TV中的 design patterns.找到更多的信息

多窗格屏幕布局分組屏幕

3到4英寸的屏幕通常只適合於顯示一個垂直面板的內容,包括列表或者詳細信息,等等。因此這些設備的屏幕一般和水平層次結構信息(類別列表category→對象列表object list→對象細節object detail)是一對一的關系。
另一方面,平板電腦和電視的大屏幕,通常有更多可用的屏幕空間和能夠顯示多個窗格的內容。在橫屏時,窗格通常從左到右順序顯示詳細。用戶從年復一年的大屏幕桌面應用程序和桌面web站點使用中習慣了多個窗格。很多桌面應用程序和網站提供左側導航面板或者使用主/明細雙欄布局。
除了滿足這些用戶的期望之外,通常需要在平板電腦上提供多個窗格的信息,以避免留下太多的空白或無意中引入尴尬的交互,

多窗格在橫屏布局中有更好的視覺平衡,同時提供更多實用和易讀性

注意:在決定為哪個屏幕尺寸的單窗格和多窗格布局之間繪制後,可以為不同屏幕大小設備提供包含一個或多個窗格的不同布局(如large/xlarge)或不同的最低屏幕寬度(例如sw600dp)。

注意:當一個子屏幕在Activity的子類中被分成單獨的窗格,我們就可以用Fragment的子類來實現。這樣可以在不同的尺寸和顯示內容的區域最大化的進行代碼重用。

設計成多個平板方向

雖然我們還沒有開始安排我們屏幕上的用戶界面元素,但這是一個很好的時間來考慮多窗格屏幕將如何適應不同設備方向。多窗格在橫向布局中工作很好,因為大量的可用的水平空間。然而,在豎向屏幕中,你的水平空間很有限,所以你可能需要設計一個單獨的布局方向。
- Stretch
最直接的策略是每個面板的寬度延伸到最好的展現畫像中的每個窗格的內容方向。窗格可以固定寬度或采取一定比例可用的屏幕寬度
- Expand/collapse
上述延伸策略的一個變化就是在屏幕豎向時折疊。這個在master/detail的窗格中表現的很好,左邊的窗格(master)可以很容易的折疊
- Show/Hide
在這個場景中,左邊的面板是在豎屏模式下完全隱藏
- Stack
最後一個策略是垂直方向堆疊在水平方向放置的窗格。

在屏幕地圖中進行屏幕分組

現在我們能夠在大屏幕設備上通過提供多窗格布局單獨進行屏幕分組,在這些設備上,將這種技術應用到我們上一課中的屏幕地圖中以便更好地了解應用程序的層次結構

平板電腦最新的demo新聞應用程序屏幕地圖
在接下來的課程中我們將討論子導航和橫向導航。

設計(三) - 設計子代導航和水平導航

原文地址:http://developer.android.com/training/design-navigation/descendant-lateral.html
這節課中我們將討論子代導航(允許用戶點擊按鈕進入子屏幕)、水平導航(允許同級訪問)
app-navigation-descendant-lateral-desc
圖1:豎向(子頁面)和水平導航

有2種同級的導航視圖:列表類(collection-related)和區域類(section-related)的。看下圖:
Collection-related children and section-related childrenvcC0yrXP1qGjVUnJ6LzGxKPKvSy549Llyc/LtcDgy8bT2sjtvP7J6LzGxKPKvSzL/MrHs6O8+7XEuLTTw727u6XJ6LzGzsrM4rXEveK+9re9sLiho8/Cw+a497K/t9bO0sPHvavMvczW0rvQqbOjvPu1xMuuxr21vLq9xKPKvTwvcD4NCjxoMiBpZD0="按鈕和簡單的控件buttons-and-simple-targets">按鈕和簡單的控件(Buttons and Simple Targets)

section-related形式的導航,一般是包含了按鈕,列表Views,或Text鏈接的UI組件。點擊其中一個就可以進入相應的子界面,替換掉整個當前界面。按鈕和其他一些簡單的控件很少出現在List視圖上。
app-navigation-descendant-lateral-buttons
圖3:基於按鈕導航界面和有關Screen Map的摘要示例

一般,基於按鈕導航到不同層級的視圖的模式稱為儀表盤(dashboard)模式。dashboard模式是由一些大的,圖標話的按鈕充斥整個或大部分父視圖的模式。網格一般有2-3列,根據不同app來定。這種模式是一種非常好的展現模式。大的UI組件很容易被點擊,用戶體驗會比較好。然而,這種模式不適合大屏幕,因為他需要額外的操作才能進入到不同的子視圖內容。

ListView、GridView、水平滾動視圖(Carousels)以及卡片視圖(Stacks)

對於列表類的視圖,一般采用ListView|GridView
app-navigation-descendant-lateral-lists
圖4:各種視圖及相關對應的界面摘要

這種模式有幾個問題。基於列表的導航,用戶通常要進行大量的滑動點擊,進入下一個列表,這種情況下性能不高,也不好處理,特別是對於“手快”的用戶,用戶體驗不好。
使用垂直的列表視圖,也會比較尴尬,因為通常在大屏上,寬度是全寬,高度的固定的,這樣會留出大量的空白區域出來。一種緩和的方案是加一些額外的文字信息到列表上來填充水平的區域;另一種方案是在列表旁邊添加另一個水平窗格視圖。

Tabs

Tab是實現水平導航的一個流行的做法。它允許一個界面同時存在多屏內容。最適合在4英寸或更小的屏幕上使用。
app-navigation-descendant-lateral-tabs
圖5:基於Tab導航的示例

使用標簽時的一些最佳實踐:Tab應該保留在屏幕上,當選擇一個選項卡時只有指定的內容區域應該改變,Tab指示器應保持隨時可用。Tab切換不應保留歷史記錄,比如當用戶從Tab A切換到Tab B,按下返回鍵,就不應該返回到Tab A。Tab通常水平放置,盡管有時比如在Action Bar(pattern docs at Android Design)上是豎向排列的。最後,最重要的是,標簽應該運行在屏幕的頂端,和不應該對齊屏幕的底部。

相對於基於按鈕的導航,Tab導航有一些很明顯的優勢。

因為是初始化好的Tab,所以選中後可直接訪問裡面的內容 用戶可以輕松的訪問相鄰的屏幕內容,而不用先回到父界面

注意:切換Tab時,不要有阻塞的操作。比如Loading數據時顯示模態對話框。

水平分頁導航 (Swipe Views)

另一個流行的水平導航模式是水平分頁導航,也叫Swipe Views。這種模式最適用於子代導航的兄弟屏幕上一個列表(世界、業務、技術和健康等)。像Tab一樣,這種模式還允許分組屏幕,父界面提供子界面的內容嵌入到自己的布局。
app-navigation-descendant-lateral-paging
圖6:水平分頁導航

在一個水平分頁導航中,一次只能顯示一個子頁面。可以點擊或拖拽鄰近的屏幕進行切換,這樣方便用戶進行操作也避免設計更多的入口。示例包括點指示器(tick marks), 滾動標簽(scrolling labels), 和Tabs。
app-navigation-descendant-lateral-paging-companion
圖7:分頁導航的UI元素的例子。

最好避免在水平導航中的子頁面中有水平平移的交互,這樣UI上會有沖突(比如子界面裡嵌入一個地圖控件)。

在下一課,我們將討論允許用戶浏覽信息層次結構以及返回之前訪問過的界面的機制

設計(四) - 向上導航和時間導航

原文地址:http://developer.android.com/training/design-navigation/ancestral-temporal.html
既然用戶可以沿著視圖的層級向下導航到一級一級,那麼我們也需要提供一個方法向上導航返回他的父界面一直到最上面。另外我們還要確保時間導航遵守Android的規則。

支持時間導航-回退

時間導航,或者說歷史界面之間的導航,是深深扎根於Android系統之中。所有的Android用戶期望通過Back按鈕回到前面的界面。按Back鍵我們總是可以回到系統桌面的,再按就沒有效果了。
app-navigation-ancestral-navigate-back
圖1:Back按鈕的行為

系統已經自動處理了Back鍵的行為,我們不用擔心。Back按鈕的默認行為是返回上一個界面。
但有時候我們需要重寫Back的方法,改變他的默認行為。比如,界面中包含有一個WebView,我們希望Back按鈕的行為是返回上一個網頁,這時就需要人為去控制它,否則他會直接關閉當前界面。

提供向上導航 - 向上(up)和Home桌面(home)

能夠直接返回桌面,是一種能讓用戶感覺到舒服和安全的設計。無論用戶在哪個app的哪個界面,他都可以通過Home鍵直接返回到最上層的桌面。
Android3.0提出了Up的說法,來替代上面說的Home鍵的功能。通過點擊上一級(Up),用戶能夠返回視圖層級的上一級(就像上面描述的Back鍵的功能一樣),但這並不是很普遍的情況,因此,開發者應該確保Up能使界面返回到上一個簡單的可呈現的父界面上。
app-navigation-ancestral-navigate-up
圖2:從聯系人App進入郵件App的Up導航示例
在一些情況下,使用Up導航比Back返回到父界面更適合。例如,在基於Android3.0的平板上的Gmail的app,橫屏時一般左邊是郵件List,右邊是郵件詳細,是一個典型的父 /子的設計,也是上一課描述的。當豎屏看時,我們只看到郵件的詳細界面,Up按鈕用來臨時顯示他的父界面,也就是從屏幕左邊劃出來的列表Panel。再點一下Up按鈕,當左邊部分顯示後,會退出單個的郵件會話,郵件列表會全屏顯示。

最後最佳實踐,不管是使用Home導航還是Up導航,請確保會清掉棧裡的View。在Home模式下,最終保留的界面就是Home界面。對於Up導航,當前界面應該從Back棧裡清除,除此之外,導航橫穿當前視圖層級進入另一個。你可以使用FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_NEW_TASK一起來實現這個。
接下來,我們將應用到目前為止所有課中討論的概念為我們的創建一個交互設計線框圖新聞App示例。

設計(五) - 借米生蛋,示例App框架

原文地址:http://developer.android.com/training/design-navigation/wireframing.html

我們有了對屏幕導航模式和分組技術扎實的理解,是時候將它們應用到我們的設計中了。讓我們來看另一個詳盡的示例App界面示意圖。
app-navigation-screen-planning-exhaustive-map
圖1:新聞App詳細的界面示意圖
下一步我們就是選擇和應用前面的課程中討論的導航設計模式了。最大化的響應速度最小化的點擊次數能訪問到數據,同時保持界面直觀,且符合Android的最佳實踐。我們還需要對不同的目標設備做出不同的選擇。簡單起見,讓我們只關注平板電腦和手機。

選擇模式

首先,我們的第二層界面(StoryList,PhotoList和緩存List)應該用Tab組織起來。注意我們不一定要使用水平的Tab,對於窄長的手機有時下拉的UI組件可以充當替代角色。我們還要在手機上將Saved Photo List 和Saved Story List 組織在一起或者在平板上使用多個垂直Panel。
最後,讓我們看看如何展示新聞故事(News Story)。第一項工作就是簡化不同Story分類導航,在頂部設置一組標簽實現水平分頁並可以切換。在平板的水平方向,我們可以設置左邊時列表,右邊是Story的詳細View。
下面這個圖就顯示了在手機和平板上應用了導航模式後的新的界面示意圖
app-navigation-wireframing-map-example-phone
圖2:最後在手機上的界面示意圖

app-navigation-wireframing-map-example-tablet
圖3:最後在平板上的界面示意圖

此時,我們要好好想想界面示意圖如何改進一下,以防在實際工作中他們不能Work Well。下圖的例子,就是將平板界面示意圖改了下,在不同分類下顯示Story的List,然後Story的詳細View單獨抽出來給StoryList和Saved StoryList共用。
app-navigation-wireframing-map-example-tablet-alt
圖4:變化後的平板橫屏界面示意圖

梗概和線框圖

線框圖是設計過程中將你的設計進行實際布局的一個步驟。開始發揮想象如何安排允許用戶導航的UI元素。記住,此時並不需要像素級精度(創建高保真的原型)。
開始最簡單快捷的方法是使用紙和鉛筆手工勾勒出你的界面。一旦你開始畫草圖,你可能會發現在選擇原來哪個示意圖上存在一些問題。在某些情況下,理論上模式可能也只適用於一個特定的設計問題,但實際上他們相互矛盾。如果發生這種情況,探索其他導航模式,或改進選擇的模式,以得到一個更好的草圖。
決定好線框圖後,就要用 Adobe? Illustrator, Adobe? Fireworks, OmniGraffle或其他一些矢量圖形工具勾畫出量化的框架。

數字線框圖

在紙上布局草圖後,選擇一個適合你的數字線框圖工具。創建數字線框圖,將作為應用程序的視覺設計的起點。下面是我們的新聞App線框圖例子,與我們的一開始的示意圖是一一對應的。
app-navigation-wireframing-wires-phone
圖5:手機豎向的數字線框圖

app-navigation-wireframing-wires-tablet
圖6:平板橫向的數字線框圖

下一步

現在你已經為你的App設計了有效和直觀的內在的導航。接下來你就可以開始花時間每個單獨的界面改進用戶界面。比如,你可以選擇使用更豐富的小部件代替簡單的文本標簽、圖片和按鈕時顯示的內容。你也可以開始從你的品牌的可視化語言定義應用程序元素的視覺樣式。

實現(一) - 使用Tabs實現滑動(Swipe)

原文地址:http://developer.android.com/training/implementing-navigation/index.html
看完這個系列課程,你將對於如何使用Tab、Swipe Views和Drawer實現導航模式有一個深刻的認識,也應該理解如何提供正確的Up和Back導航。

依賴和前提:
- Android2.2或更高
- 理解Fragment和Android布局
- Android Support Library
- 設計高效的導航(上節課的內容)
Swipe views提供切換界面的一種水平導航模式,類似於Tabs導航一樣。這篇文章將告訴你如何使用Tabs創建Swipe Views,或者使用標題欄取代Tabs。

注意:需要設計高效應用導航的課程和Swipe Views的知識。

實現滑動視圖(Swipe Views)

你可以使用Support Library中的ViewPager創建滑動視圖,ViewPager是一種子視圖都是獨立布局的布局。
再布局中創建ViewPager,方法如下:


向ViewPager中添加子Views,你需要為這個布局綁定一個適配器PagerAdapter。你可以使用下面2種適配器:

FragmentPagerAdapter
當子視圖少且固定的時候最好用這個 FragmentStatePagerAdapter
當子布局比較多且不定時用這個最好,最小化內存占用。
使用FragmentStatePagerAdapter適配一個Fragment列表的用法如下:
public class CollectionDemoActivity extends FragmentActivity {
    // When requested, this adapter returns a DemoObjectFragment,
    // representing an object in the collection.
    DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;
    ViewPager mViewPager;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collection_demo);

        // ViewPager and its adapters use support library
        // fragments, so use getSupportFragmentManager.
        mDemoCollectionPagerAdapter =
                new DemoCollectionPagerAdapter(
                        getSupportFragmentManager());
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mDemoCollectionPagerAdapter);
    }
}

// Since this is an object collection, use a FragmentStatePagerAdapter,
// and NOT a FragmentPagerAdapter.
public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
    public DemoCollectionPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        Fragment fragment = new DemoObjectFragment();
        Bundle args = new Bundle();
        // Our object is just an integer :-P
        args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public int getCount() {
        return 100;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "OBJECT " + (position + 1);
    }
}

// Instances of this class are fragments representing a single
// object in our collection.
public static class DemoObjectFragment extends Fragment {
    public static final String ARG_OBJECT = "object";

    @Override
    public View onCreateView(LayoutInflater inflater,
            ViewGroup container, Bundle savedInstanceState) {
        // The last two arguments ensure LayoutParams are inflated
        // properly.
        View rootView = inflater.inflate(
                R.layout.fragment_collection_object, container, false);
        Bundle args = getArguments();
        ((TextView) rootView.findViewById(android.R.id.text1)).setText(
                Integer.toString(args.getInt(ARG_OBJECT)));
        return rootView;
    }
}

上面用代碼展示了如何創建滑動視圖(Swipe Views),下面將創建標簽(Tabs)來幫助切換頁面。

在Action Bar中添加Tabs

使用Action Bar添加Tab,必須先允許NAVIGATION_MODE_TABS,然後創建若干ActionBar.Tab,並實現ActionBar.TabListener接口。示例代碼如下:

@Override
public void onCreate(Bundle savedInstanceState) {
    final ActionBar actionBar = getActionBar();
    ...

    // Specify that tabs should be displayed in the action bar.
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

    // Create a tab listener that is called when the user changes tabs.
    ActionBar.TabListener tabListener = new ActionBar.TabListener() {
        public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
            // show the given tab
        }

        public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
            // hide the given tab
        }

        public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
            // probably ignore this event
        }
    };

    // Add 3 tabs, specifying the tab's text and TabListener
    for (int i = 0; i < 3; i++) {
        actionBar.addTab(
                actionBar.newTab()
                        .setText("Tab " + (i + 1))
                        .setTabListener(tabListener));
    }
}

再ActionBar.TabListener中處理Tab切換的動作。如果使用Fragment實現ViewPager,接下來將展示切換時如何更新Tab的內容。

在SwipeView中切換Tab

調用ViewPager中的setCurrentItem()方法來選中當前的Tab中的內容。

@Override
public void onCreate(Bundle savedInstanceState) {
    ...

    // Create a tab listener that is called when the user changes tabs.
    ActionBar.TabListener tabListener = new ActionBar.TabListener() {
        public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
            // When the tab is selected, switch to the
            // corresponding page in the ViewPager.
            mViewPager.setCurrentItem(tab.getPosition());
        }
        ...
    };
}

同樣得,實現ViewPager.OnPageChangeListener接口,當界面切換時改變Tab的內容。

用標題欄代替選項卡(Tabs)

如果你不想用ActionBar的Tab,而使用scrollable tabs創建較短的可視化界面,你可以在SwipeView中使用PagerTitleStrip。



    

PagerTitleStrip是一個沒有交互的指示器,它是特意被設計在XML布局中作為ViewPager 的子元素的。設置他的屬性 android:layout_gravity為top或bottom來固定它在頂部或底部。

實現(二)- 創建一個導航抽屜(NavigationDrawer)

參考地址:http://developer.android.com/training/implementing-navigation/nav-drawer.html

導航抽屜是在窗體左邊的顯示主界面選項的一種導航。它大部分時候都是隱藏的,當用戶用手指滑動主界面或點擊ActionBar上的App圖標時,才會顯示出來。
這節課描述如何使用Support Library中的DrawerLayout來實現一個導航抽屜。

導航抽屜的設計原則參見:http://developer.android.com/design/patterns/navigation-drawer.html

創建一個Drawer Layout

使用導航抽屜,你要使用DrawerLayout作為你布局的RootView。在DrawerLayout的子View中,創建一個View作為DrawerLayout的content,另一個View作為Drawer。
比如,下面的布局使用了DrawerLayout,他有2個子View,一個是包含了主視圖(一般在運行時是一個Fragment)FrameLayout,另一個是ListView作為導航抽屜。


    
    <framelayout android:id="@+id/content_frame" android:layout_height="match_parent" android:layout_width="match_parent">
    
    
</framelayout>

這個布局展示了一些重要的布局特點:

上面的主界面(FrameLayout)必須是DrawerLayout的第一個子View。因為XML中z軸順序必須讓抽屜置於內容(Content)的上面。 主界面(FrameLayout)必須和它的父視圖保持一樣的寬高,這樣Drawer隱藏時,他可以顯示整個的UI 抽屜視圖(ListView)必須指定它的android:layout_gravity屬性,為支持right-to-left (RTL) 語言,需要指定‘start’而不是‘left’將抽屜設置到左邊(同樣,‘end’取代‘right’設置為右邊)。 抽屜視圖(ListView)需要用dp單位指定它的寬高,並保證不會超過320dp,這樣就可以一直顯示主界面了。

初始化Drawer List

在Activity中,我們首先要初始化Drawer中的內容。一般Drawer中是ListView,我們需要為它指定適配器和數據。下面我們使用String-Array作為例子:

public class MainActivity extends Activity {
    private String[] mPlanetTitles;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPlanetTitles = getResources().getStringArray(R.array.planets_array);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set the adapter for the list view
        mDrawerList.setAdapter(new ArrayAdapter(this,
                R.layout.drawer_list_item, mPlanetTitles));
        // Set the list's click listener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        ...
    }
}

需要調用setOnItemClickListener()為ListView指定監聽,來響應點擊事件。

處理導航點擊事件

點擊了抽屜中ListView的某一項,我們需要為它設置事件。下面的例子,我們讓ListView中每一項點擊後都插入一個新的Fragment到主界面中(Framelayout的id設置為R.id.content_frame)。

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
        selectItem(position);
    }
}

/** Swaps fragments in the main content view */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Highlight the selected item, update the title, and close the drawer
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

監聽打開和關閉抽屜的事件

調用DrawerLayout的setDrawerListener()方法為抽屜注冊監聽,並實現DrawerLayout.DrawerListener接口(實現了onDrawerOpened() 和 onDrawerClosed()的回調)。
當你的Activity有ActionBar的時候,就不能實現DrawerLayout.DrawerListener接口了,你需要繼承ActionBarDrawerToggle類,因為ActionBarDrawerToggle類實現了DrawerLayout.DrawerListener,並重寫其中的回調方法。但你也要處理ActionBar和導航抽屜的交互問題(下一部分將深入探討哦)。
下面的是ActionBarDrawerToggle的一個實例重寫了DrawerLayout.DrawerListener回調方法的一個例子:

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

下一部分將討論ActionBarDrawerToggle的構造器參數。

使用App圖標打開關閉抽屜

打開關閉抽屜導航可以使用手勢滑動的方式實現,也可以在ActionBar中通過點擊App圖標做到。要實現這種情況,需要使用ActionBarDrawerToggle類。ActionBarDrawerToggle的構造函數有下面這些參數:
1. 托管抽屜的Activity
2. DrawerLayout實例
3. 作為抽屜指示器的Drawable資源。標准的抽屜icon參考Download the Action Bar Icon Pack.
4. 描述打開抽屜的字符串資源
5. 描述關閉抽屜的字符串資源

然後,不管你是否創建了一個ActionBarDrawerToggle的子類作為你的抽屜監聽,你都需要在Activity的生命周期中訪問ActionBarDrawerToggle。

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...

    public void onCreate(Bundle savedInstanceState) {
        ...

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }

    ...
}

 

滑動視圖(SwipeViews)的使用

原文參考:http://developer.android.com/design/patterns/swipe-views.html

高性能的導航是一個設計良好的應用程序的基石之一。應用程序通常是建立在分層視圖上,在一些場景中,水平導航可以使垂直布局扁平化,並使訪問相關數據變得更快更有趣。SwipeViews允許用戶使用一個簡單的手勢浏覽數據,並使浏覽更流暢的體驗。

在詳細視圖中滑動

App的數據經常被組織成“列表/詳細”的關系。用戶可以看到一個列表的相關數據,比如圖片、圖表或Email,然後選擇其中一個進入單獨的界面中查看詳細內容。
swipe_views
圖1:列表(左)/詳細(右)的視圖

在手機上,一般是這種模式,看列表數據需要從詳細界面返回然後再選擇一個點擊進入另一條數據。有時候,用戶希望可以通過滑動手勢進行連續浏覽數據。
swipe_views2
swipe_views3
圖2-3:通過手勢滑動查看下一條郵件

在Tab之間滑動

swipe_tabs
如果你的應用程序使用ActionBar Tabs,使用手勢滑動在不同的Views之間導航。

備注:
- 用戶使用手勢滑動滑動平移不同的View時,不用完全滑完一個View的寬度
- 如果過去你在詳細視圖上使用按鈕切換視圖,現在請使用滑動視圖
- 考慮在詳細View中加入列表的索引,讓用戶知道現在是哪一個了。
- 使用滑動快速的在詳細視圖或標簽頁之間導航

 

實現(三) - 提供Up導航

參考地址:http://developer.android.com/training/implementing-navigation/ancestral.html
這節課討論如何在ActionBar上添加一個Up按鈕,實現上一級的導航。
implementing-navigation
圖1:ActionBar上的Up按鈕

指定一個父Activity

為實現Up導航,第一步就是要聲明哪個Activity是每個當前Activity的父Activity。這樣做是為了讓系統更好的在manifest文件中確認邏輯上的父Activity。
從Android 4.1(API 16)開始,你可以在元素中設置android:parentActivityName屬性為Activity指定一個邏輯的父Activity。
如果你的App支持Android 4.0或更早的版本,需依賴Support Library,在Activity中添加元素,設置android.support.PARENT_ACTIVITY屬性,這個和android:parentActivityName對應。
例如:

<code class=" hljs r"><application ...="">
    ...
    <activity android:name="com.example.myfirstapp.MainActivity" ...="">
        ...
    </activity>
    <activity android:name="com.example.myfirstapp.DisplayMessageActivity" android:label="@string/title_activity_display_message" android:parentactivityname="com.example.myfirstapp.MainActivity">
        <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.example.myfirstapp.MainActivity">
    </meta-data></activity>
</application></code>

這樣聲明之後,可以使用NavUtils的API導航到父Activity。NavUtils下面會講到。

處理Up行為

調用setDisplayHomeAsUpEnabled(),使ActionBar上的icon有Up的導航功能:

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    getActionBar().setDisplayHomeAsUpEnabled(true);
}

上述代碼會在ActionBar上的App icon左邊添加一個左向的箭頭,點擊它會回調 onOptionsItemSelected(),其id是android.R.id.home。

導航到父Activity

你可以使用NavUtils的靜態方法navigateUpFromSameTask()來實現。這個方法調用後,會啟動(或Resume)一個父Activity,如果父Activity在同一個Back任務棧裡的話,它將會回到前台。父Activity回到前台是否會調用onNewIntent()取決於:

如果父Activity的啟動模式是,或者在Intent中設置了FLAG_ACTIVITY_CLEAR_TOP的Flag,那麼在父Activity回到前台時會調用onNewIntent() 如果父Activity的啟動模式是,而且沒有設置FLAG_ACTIVITY_CLEAR_TOP的Flag,那麼Up導航後會生成一個新的父Activity的實例。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
        NavUtils.navigateUpFromSameTask(this);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

navigateUpFromSameTask()這個方法只適用你的App是當前任務棧的所有者(即當前TaskStack從你的App啟動)。如果不是這種情況,而且你的App在別的App創建的Task中啟動了,則Up導航需要為你的App創建一個新的屬於你App的Back任務棧。

導航到一個新的Back棧

如果你的Activity在intent filters 中聲明可以被其他的App調用,那麼你需要實現onOptionsItemSelected()方法,這樣的話,當用戶在其他App的任務棧裡點擊了Up,則你的App會創建一個新的Back棧,然後Resume。
你可以使用NavUtils的shouldUpRecreateTask()方法判斷當前Activity是否存在與另一個App的任務棧裡。如果返回true,則使用TaskStackBuilder創建一個新的Task,否則可以使用上面描述的navigateUpFromSameTask()方法直接返回。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
        Intent upIntent = NavUtils.getParentActivityIntent(this);
        if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
            // This activity is NOT part of this app's task, so create a new task
            // when navigating up, with a synthesized back stack.
            TaskStackBuilder.create(this)
                    // Add all of this activity's parents to the back stack
                    .addNextIntentWithParentStack(upIntent)
                    // Navigate up to the closest parent
                    .startActivities();
        } else {
            // This activity is part of this app's task, so simply
            // navigate up to the logical parent activity.
            NavUtils.navigateUpTo(this, upIntent);
        }
        return true;
    }
    return super.onOptionsItemSelected(item);
}

為了讓addNextIntentWithParentStack()方法有效,需要在manifest中為Activity聲明父Activity,使用android:parentActivityName (或 )。

 

實現(四) - 提供Back導航

參考地址:http://developer.android.com/training/implementing-navigation/temporal.html
Back導航是讓用戶可以返回到之前訪問過的界面,所有的Android設備都為這種導航提供了Back按鈕,所以你的App的UI中不應該添加Back按鈕
大部分情況下,當用戶點擊Back按鈕系統都提供了默認的Back棧管理Activity,但一些情況下,允許你的App自定義Back按鈕的行為給用戶更好的體驗。
導航模式中需要你手動指定的行為包括:

當用戶從 notification、app widget或navigation drawer直接進入App中比較深的Activity時 用戶在多個Fragment中導航 在WebView中切換網頁
下面介紹如何最好的實現Back導航。

為深度訪問Activity生成一個Back Stack


比如,你收到一個notification,點擊進入到新聞詳情界面,那麼返回時,不應該退出應用,而是返回新聞列表。這時,你需要創建一個新的Back棧,因為之前新聞App沒有創建任何Stack。

在Manifest中指定父Activity


 

當啟動Activity時啟動一個Back棧


使用TaskStackBuilder將一個Activity定義到一個新的Stack中,使用startActivities()或使用getPendingIntent()創建合適的Intent來啟動這個Activity。
例如,當一個notification讓用戶點擊很深的Activity中,你可以使用下面這段代碼創建一個PendingIntent來啟動這個Activity並插入一個新的Stack到當前Task中。

// Intent for the activity to open when user selects the notification
Intent detailsIntent = new Intent(this, DetailsActivity.class);

// Use TaskStackBuilder to build the back stack and get the PendingIntent
PendingIntent pendingIntent =
        TaskStackBuilder.create(this)
                        // add all of DetailsActivity's parents to the stack,
                        // followed by DetailsActivity itself
                        .addNextIntentWithParentStack(upIntent)
                        .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent);
...

PendingIntent不僅指定要啟動的Activity,一個Back Stack也插入到當前Task中(DetailsActivity所有的父Activity都被 detailsIntent定義好了)。所以當DetailsActivity啟動,按Back按鈕,可以導航到DetailsActivity 的各個父Activity中。

為了讓addNextIntentWithParentStack()方法有效,需要在manifest中為Activity聲明父Activity,使用android:parentActivityName (或 元素)

為Fragment實現Back導航

當你在App中使用Fragment時,每一個FragmentTransaction對象代表可能變化的加入到Stack中的Context。
比如說,你在手機上要通過置換Fragment實現一個“列表/詳細”流,就應該確保在詳細界面按下Back按鈕會返回到列表界面。在提交transaction之前調用addToBackStack()

// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
                           .add(detailFragment, "detail")
                           // Add this transaction to the back stack
                           .addToBackStack()
                           .commit();

當FragmentTransaction對象在Back棧中,用戶按下Back按鈕時,FragmentManager會從Back棧中取出最上面的transaction並執行反向操作(比如如果transaction要添加進去就刪除Fragment)。

注意:在水平導航(比如切換tabs)或修改內容的外觀時,你不應該向Back棧中添加transaction。
如果你的應用程序需要更新其他UI元素(比如ActionBar)來反映當前Fragment的狀態的話,記住當commit transaction時要更新UI。除此之外,當Back棧變化後你也要更新UI。你可以在FragmentTransaction恢復後使用FragmentManager.OnBackStackChangedListener監聽:

getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });

為WebView實現Back導航

如果你的應用中有webview,你需要為讓Back按鈕點擊變成返回上一個網頁。

@Override
public void onBackPressed() {
    if (mWebView.canGoBack()) {
        mWebView.goBack();
        return;
    }

    // Otherwise defer to system default behavior.
    super.onBackPressed();
}

在這種機制中使用會產生大量History的高度動態網頁時要小心。因為他會讓用戶一直返回,很難跳出當前Activity。

實現(五) - 實現子代導航

原文參考:http://developer.android.com/training/implementing-navigation/descendant.html

子代導航通常使用startActivity()或者使用FragmentTransaction對象創建Fragment來實現。

在手機和平板上實現Master/Detail流

在手機上,使用startActivity()分別在兩個Activity上是實現;在平板上,通常是左右結構,左邊是Master,右邊時Detail,然後使用FragmentTransaction添加、刪除、置換Fragment實現。

導航到外部的Activities

我們經常需要打開外部的App,比如打開聯系人,發送郵件,打開地圖等等。我們通常不希望當用戶從桌面重新啟動我們的App時進入到別人的App之中,這樣會讓人感到很迷惑的。
為避免發生這樣的事情,我們在Intent中添加FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET的Flag。如:

Intent externalActivityIntent = new Intent(Intent.ACTION_PICK);
externalActivityIntent.setType("image/*");
externalActivityIntent.addFlags(
        Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivity(externalActivityIntent);
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved