關於這主題, Dianne Hackborn已經在這講的很詳細了, 這邊挑了一些要點
Duo panel的設計在iPad上已經是蠻常見的了, 這設計的一個特點是, 在Landscape模式時為了充分利用空間, 把Panel切割成兩部分, 但使用者轉換成Portrait時, 則會轉換成Single panel
不意外的, 在Android上實現Duo panel的方式是可以利用Fragment的
在Hackborn的範例中, 總共有兩個Fragment (TitlesFragment, DetailsFragment) 和兩個Activity (FragmentLayout - 這邊姑且稱之為main activity, DetailsActivity), 這部份的codes可以在ApiDemos裡面找到
在main activity的layout設計上面, 為了達到Portrait是single panel而Landscape是duo panel的設計, 其實是要portrait跟ladscape分開各一種layout, 在landscape是要包含左右兩邊的Fragment, 但portrait就只能包含左邊的
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:orientation="horizontal" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<fragment class="com.example.android.apis.app.TitlesFragment" | |
android:id="@+id/titles" android:layout_weight="1" | |
android:layout_width="0px" | |
android:layout_height="match_parent" /> | |
<FrameLayout android:id="@+id/details" android:layout_weight="1" | |
android:layout_width="0px" | |
android:layout_height="match_parent" /> | |
</LinearLayout> |
因此, 在portrait mode時main activity由於不會有右邊的panel的ID, 所以必須自行偵測右邊是否存在(或是偵測目前是不是在portrait), 然後決定按下list item的行為, 如果是portrait, 就不能使用DetailsFragment去取代右邊panel, 取而代之的必須呼叫DetailsActivity來顯示, 這作法其實頗為tricky, 而且在這作法下, 懶惰的programmer再也不能overrride onConfigurationChange來偷懶了, 對於習慣不好的programmer應該是蠻容易在Orientation change這邊產生side effects
/** | |
* Helper function to show the details of a selected item, either by | |
* displaying a fragment in-place in the current UI, or starting a | |
* whole new activity in which it is displayed. | |
*/ | |
void showDetails(int index) { | |
mCurCheckPosition = index; | |
if (mDualPane) { | |
// We can display everything in-place with fragments. | |
// Have the list highlight this item and show the data. | |
getListView().setItemChecked(index, true); | |
// Check what fragment is shown, replace if needed. | |
DetailsFragment details = (DetailsFragment) | |
getFragmentManager().findFragmentById(R.id.details); | |
if (details == null || details.getShownIndex() != index) { | |
// Make new fragment to show this selection. | |
details = DetailsFragment.newInstance(index); | |
// Execute a transaction, replacing any existing | |
// fragment with this one inside the frame. | |
FragmentTransaction ft | |
= getFragmentManager().beginTransaction(); | |
ft.replace(R.id.details, details); | |
ft.setTransition( | |
FragmentTransaction.TRANSIT_FRAGMENT_FADE); | |
ft.commit(); | |
} | |
} else { | |
// Otherwise we need to launch a new activity to display | |
// the dialog fragment with selected text. | |
Intent intent = new Intent(); | |
intent.setClass(getActivity(), DetailsActivity.class); | |
intent.putExtra("index", index); | |
startActivity(intent); | |
} | |
} |
上面就是利用不同的Orientation方向判斷到底是用DetailsFragment取代右邊Panel還是呼叫DeatilsActivity, 而DetailsActivity其實就只是一個DetailsFragment一個包裝, 也就是當在landscape時, 本來就只有main activity左右兩個Fragment, 在portrait時, 這兩個Fragment被拆開成兩個Activities, 但如果這時候從portrait的DetailActivity轉回landscape要怎回到原本main activity的duo panel型態呢? 作法很簡單(也有點tricky), 在lands cape mode時, DetailsActivity就把自己給finish掉了, 不過這邊要能夠確定history stack的前一個的確是main activity, 不然也會很怪的(一般應該都會是才對)
在Hackborn的範例中, 每次都得重新建立一個新的DetailsFragment, 這樣的缺點是, 每次花在DetailsFragment初始化還有inflat layout的時間會有點浪費, 如果是在複雜的layout跟Fragment, 這樣容易造成許多不必要的垃圾, 當然能夠reuse最好, 如果把她用的replace換成這個replace, 自己給個tag, 那之後就可以用FragmentManager.findFragmentByTag將這個Fragment instance給取出重複利用, 但, Fragment是不可以被重複加入的, 你可以拿instance出來重複使用但如果試圖把這個instance重複給FragmentTranscaction, 那是會發生錯誤的
附記: 如果沒去設定target sdk version是11的話, 那這個應用程式會被當做是小螢幕的而不是一個tablet application, 此時你看到的會是在中間一個縮小版的(學iPad也不要學這麼像嘛... = =")