2013年3月20日 星期三

[Android] Android Developer Note Best Practices 4

Supporting Tablets and Handsets (見Android Developer Supporting Tablets and Handsets)

序言
Android平台在不同螢幕大小上跑,
且系統優雅地(又來了= =)重調你app的UI大小,來適應每個螢幕。
典型狀況來說,你需要的就是把UI設計成有彈性,
然後把一些元件對不同大小作最佳化(藉由提供不同的資源)。

(剩下的話就是前面說過的類似東西)

3.0以上的版本引入了一套新的framework APIs,
允許你可以更有效率來設計activities在大的螢幕上: Fragment APIs。
Fragments允許你將不同的動作的組件在使用者介面上分開來,
那麼當在平板上時,你就可以用多重窗格的layout了。
3.0也引進了ActionBar,可以提供專用的(dedicated)UI在螢幕上方,
用以讓app能被辨認且提供使用者動作和導航。

這篇文章告訴你如何使用Fragment和Action Bar來最佳化使用體驗。

Basic Guidelines
三項指引來讓你在平板和手持裝置上都能提供最佳化的使用經驗。
1. 將activity的設計根基於fragments上使你可以重複使用不同的組合,
透過多窗格(multi-pane)的layout在平板,以及單窗格的layout在手持裝置上。

Fragment代表一個行為或在activity裡面一部分的使用者介面。
你可把fragment想成是一個activity中模組化的區塊,
有其自己的生命周期,且在activity在跑的時候可以加入或移除。

2. 使用action bar,但像前面指引的那樣去確保符合各種螢幕大小的相容。
ActionBar是一個activities的UI組件,用來取代傳統的Title Bar。
預設會在左邊,伴隨著activity title,且從右邊的選擇目錄上存取物件。
同樣可以加上navigation功能,詳情請洽Action Bar的developer guide。

3. 實作有彈性(flexible)的layouts,就像前面討論過的那樣。
由於平板大小也不盡相同,你還是需要設計出有彈性的介面,
來適應不同螢幕大小的需求。

Creating Single-pane and Multi-pane Layouts
最有效區分手機和平板的使用者體驗的方式就是用不同的fragments的組合,
像是你可以設計多重窗格給平板,而使用單窗格layout給手機。
比方說手機讀文章可能是選單選了以後進入文章,
平板則可能是左邊列出列表,選了以後,右邊的窗格來開啟文章,
這時候你仍然可以選左邊的其他文章,不需要先倒退一次。
用fragments設計的技巧:
1. 多窗格單activity: 不考慮裝置的大小,只使用一個activity,
但決定在執行期時是否在layout裡去結合fragment(multi-pane design),
或掃掉fragments(single-pane design),或者其他......
2. 多窗格多activities: 在平板上使用單一activity來裝多個窗格;
而在手持裝置使用分開的activities來管理各個fragments。
如此的話在手機上就需要切換窗格來開別的activity來管。

後者在手機上就會比較麻煩,因為要不停的切換activity。

範例像上圖那樣子。
在平板上,Activity A就是主activity,Activity B則是作為Fragment B的載體。
依照大小,系統套用不同的main.xml的layout檔案:


res/layout/main.xml for handsets:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- "Fragment A" -->
    <fragment class="com.example.android.TitlesFragment"
              android:id="@+id/list_frag"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
</FrameLayout>
res/layout-large/main.xml for tablets:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/frags">
    <!-- "Fragment A" -->
  <fragment class="com.example.android.TitlesFragment"
            android:id="@+id/list_frag"
            android:layout_width="@dimen/titles_size"
            android:layout_height="match_parent"/>
    <!-- "Fragment B" -->
  <fragment class="com.example.android.DetailsFragment"
            android:id="@+id/details_frag"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
</LinearLayout>

反應方式:
1. 如果Fragment B在layout裡面,則Activity A會提醒Fragment B更新自己的狀態。
2. 如果Fragment B不在layout裡面,則Activity A會開啟Activity B(來主管Frament B)。

實作app的時候,開發fragments讓其高度劃分(highly compartmentalized)是非常重要的。
特別是你該遵循下面的兩點:
1. 不要直接從一個fragment去操作另一個fragment。
2. 讓所有code專門就作該fragment本身內部的事情,
而不要將code放在主activity的code裡面。

為了避免直接去從一個fragment去叫另一個fragment,在每個fragment class裡面,
定義call back的介面用來將events遞交給主activity(也就是在主activity裡實作callback)。
當activity收到callback時,activity就會適當的就現在的fragment組態作出回應。
舉例來說,當Activity A在處理選擇項目的狀況時:

public class MainActivity extends Activity implements TitlesFragment.OnItemSelectedListener {
    ...

    /** This is a callback that the list fragment (Fragment A)
        calls when a list item is selected */
    public void onItemSelected(int position) {
        DisplayFragment displayFrag = (DisplayFragment) getFragmentManager()
                                    .findFragmentById(R.id.display_frag);
        if (displayFrag == null) {
            // DisplayFragment (Fragment B) is not in the layout (handset layout),
            // so start DisplayActivity (Activity B)
            // and pass it the info about the selected item
            Intent intent = new Intent(this, DisplayActivity.class);
            intent.putExtra("position", position);
            startActivity(intent);
        } else {
            // DisplayFragment (Fragment B) is in the layout (tablet layout),
            // so tell the fragment to update
            displayFrag.updateContent(position);
        }
    }
}

DisplayActivity也就是Activity B開始時,就會從Intent讀進資料,
並送到DisplayFragment也就是Fragment B。

如果Fragment B需要去送結果回來給Fragment A的話,(比如一開始用startActivityForResult())
那麼callback機制也是相類似的對應,不過要setResult()後自我結束掉;
接著Activity A就會拿到結果並送交給Fragment A。

Using the Action Bar
我們可以利用ActionBar APIs來方便地加上選單的功能。
1. 當設定目錄項目為action item時,避免一直使用"always"的值。
在menu資源裡使用"ifRoom"android:showAsAction 屬性,
如果你希望目錄項目在action bar上顯示的話。
但當一個action view並沒有提供一個預設的action給overflow menu的話,
你可能需要"always"。在幾乎所有別的case裡,當你要項目看起來像是一個action項目的話,
就用"ifRoom"android:showAsAction 屬性吧!
將太多了action項目塞到action bar可能會弄出一個雜亂的UI,
而且action項目可能會彼此和其他元件重疊比如像是title或者navigation項目。
2. 當以一個文字title加上action items給action bar時,記得也附上一個icon。
適當的狀況下可以聲明showAsAction="ifRoom|withText"
這樣的話如果沒有足夠空間給title但有足夠空間給icon的話,icon就會被使用。
3. 永遠都要記得提供一個title給你的action items,
因為使用者長按item可以看到tool-tip的提示。
4. 可能的話避免用客製化的navigation modes。用內建的方式較好。
這樣可以讓系統作自動調整適當的位置來顯示內容。


Using split action bar
Action bar在4.0之後的版本可以用split的功能,
將功能鍵和一些選項可以分在上下。
開啟的方式是加 uiOptions="splitActionBarWhenNarrow"到你的 <activity>
<application> manifest元素裡。

除此以外也可以呼叫 setDisplayShowHomeEnabled(false)
來關掉action bar上面的應用程式icon。
雖然 uiOptions屬性只在4.0版本以上支援,
但之前的版本看到它的時候會忽略掉,所以不用擔心相容問題。
Using "up" navigation
如同前面討論到的,我們可以利用action bar來做導覽鍵的類似功能,
就像也可以做出類似回到home activity的功能一樣。
比起back的導覽,這樣的作法會有更明確的知道一些按法到底會回到哪裡,
且方便於回到原狀,因為使用者可能中途去點通知之類等因而離開。

Other Design Tips
1. 使用ListView時應注意可用空間來決定每條項目要顯示多少資訊。
2. 用不同的資源檔案給不同的值來讓layout不同的螢幕大小變簡易。




沒有留言:

張貼留言