建立靈活的用戶界面-android Fragment. html
Android Support Library(支持庫)提供了包含一個API庫的JAR文件,當你的應用運行在Android早期版本時,Support Library(支持庫)容許你的應用使用最近版本的Android API。例如:Support Library提供了Fragment版本的API,這樣你就能夠在Android1.6(API level 4)或者更高的版本上使用Fragment API了。 java
這節課將爲你演示如何在你的應用中設置Support Library,而且使用Fragment構建一個動態的應用UI。 android
設置你的項目: windows
1使用SDK Manager下載Android Support包 app
2在你項目的頂層目錄下建立libs目錄 框架
3找到你想要引入庫的JAR文件,而後將它複製到libs目錄
例如:支持API Level 4的庫就位於<sdk>/extras/android/support/v4/android-support-v4.jar.
譯者注:這個<sdk>表明着你安裝android sdk的目錄,好比本人的徹底目錄爲:E:\android-sdk-windows\extras\android\support\v4\android-support-v4.jar ide
4修改你的manifest文件,設置最低級別爲API level 4,目標API level爲最新版本: 模塊化
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15" />
圖1: Android SDK Manager,其中Android Support package已選中。 函數
支持庫包含了一系列的API,這些API或許在最近的Android版本中增長了,或許在platform(平臺)中根本不存在,而僅僅當你開發特定的應用功能時提供了附加的支持。 佈局
你能夠在android.support.v4.*這個平臺支持庫中找到全部的API參考文檔。
警告:請確保你不是剛好在一個老系統版本中使用新的API,請確認你引入的Fragment類以及相關的API都來自android.support.v4.app包
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ...
當使用Support Library建立有關fragment的Activity時,你必須繼承FragmentActivity類,而不是傳統的Activity類,你將會在下一節課中學習到一些簡單的關於fragment以及Activity的代碼。
你能夠認爲fragment是activity的模塊化組件,它擁有本身的生命週期,接受它本身的輸入事件,你也能夠在運行activity的時候添加或者移除它(有點像「子activity」你能夠在不一樣的activity中重用) 這節課演示怎麼樣使用Support Library繼承Fragment類,如此你的app(應用)就能與運行android1.6老版本的系統設備兼容 。
注意:若是你由於一些其餘緣由決定你的app須要的最低API版本爲11或者更高,那你就不必使用Support Library,而能夠直接使用框架內置的Fragment類和相關API,要知道這節課主要關注於使用來自Support Library的API,使用特定包名下的API跟內置在平臺(API)的仍是略有不一樣的
要建立一個fragment須要繼承Fragment類,而後重寫關鍵的生命週期方法插入你本身的應用邏輯,操做的方式跟建立一個Activity相似。 不一樣的是當你建立一個Fragment時,你必須使用onCreateView()回調去定義你的佈局,事實上,這是惟一你須要讓fragment得到運行的回調函數。 例如:這裏有一個簡單的指定了本身佈局的fragment:
import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.ViewGroup; public class ArticleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.article_view, container, false); } }
就跟activity同樣,fragment也應該要實現其餘的生命週期方法來管理它的狀態,當fragment被activity添加或者刪除,當activity生命週期狀態轉換時(相似這種操做都會影響到fragment的狀態); 例如:當activity的onPuase()方法被調用的時候,全部activity中的fragment也都會接收到onPause()方法的調用。
更多關於fragment生命週期和回調函數的方法均可以在Fragment 開發指南中找到。
fragment是可重用的,模塊化的UI組件,每個Fragment實例必須與父類FragmentActivity相關聯,你能夠經過在你Activity佈局XML文件中定義每個fragment來得到關聯。 注意:FragmentActivity是在系統版本低於API level 11時由Support Library提供用來管理fragment的特殊activity,若是你支持的最低系統版本是API level 11或者更高,那你能夠直接使用常規的Activity。 如下是一個例子:當設備屏幕被認爲「大」的時候,一個佈局文件添加了兩個fragment到activity 譯者注:當屏幕比較大的時候(好比平板)是能夠同時顯示兩個fragment的,可是屏幕比較小(好比普通手機)同一時間只能顯示一個fragment,這是因爲它們的屏幕尺寸形成的,後續的課程也會提到這個 這個佈局文件被指定在「高」分辨率的目錄名下。(譯者注:請注意下面xml的目錄結構:是在res目錄下的layout-large目錄下,這樣的目錄下存放的文件一般都是用來支持高分辨率的佈局文件)
res/layout-large/news_articles.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
注:更多關於爲不一樣屏幕大小建立佈局,請參照Supporting Different Screen Sizes(支持不一樣的屏幕大小)
這裏列舉了在activity中怎樣應用佈局:
import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); } }
注意:當你經過在佈局XML文件中定義fragment以達到添加一個fragment到activity中時,你不能在運行時移除此fragment,若是你計劃在用戶交互期間使fragment交替替換,那你必須在activity第一次啓動時將fragment添加進去,咱們將會在下一節課中演示。
當你設計支持普遍屏幕大小的應用時,你能夠在不一樣的佈局配置中重用你的frament,在可用的屏幕空間基礎上優化用戶體驗。 例如,在手持設備上,對於一個單窗口的用戶界面來講同一時間可能只適合顯示一個fragment。 反之,你可能想在尺寸更大平板的兩端設置更多的fragment以顯示更多的信息給用戶
如上圖:兩個fragment,同一個activity,不一樣的配置,顯示在不一樣的屏幕尺寸上。在大的屏幕中,兩個fragment各佔屏幕一端,可是在手持設備中,在同一時間,僅僅只佔有一個fragment,兩個fragment必須相互替換爲用戶導航。爲建立一個動態的體驗,FragmentManager類提供了方法容許你在activity運行時對fragment進行添加,移除,和替換。
相比上節課提到的使用<fragment>標籤在佈局文件中爲activity定義一個fragment組件,更好的方式是在activity運行時添加,並且這樣作是必須的,若是你想在activity的生命週期中變換fragment的話。
執行相似添加或者刪除fragment的事務,你必須使用FragmentManager建立一個FragmentTransaction,它提供了添加,刪除以及其餘fragment事務的API。 若是你的activity容許移除或者替換fragment,你應該在activity的onCreate()方法中添加初始化的fragment。 在你處理fragment的時候,有一個很重要的規則(尤爲是你在運行時添加fragment) 那就是你的fragment放置位置的佈局中必須有一個視圖容器。 下面這個佈局是上節課在同一時間只顯示一個fragment佈局的替代品,爲了將一個fragment替換成另外一個,這個activity佈局包含了一個空的FrameLayout做爲fragment容器。 注意這個文件名跟上節課的佈局文件名字同樣,可是這個佈局並無指定在「高分辨率」目錄中(譯者注:請注意下面xml的路徑,res/layout這個layout文件夾並無像上節課提到的是一個layout-large文件夾),如此這個佈局是用在比large更小的設備屏幕上,由於這個屏幕不能在同一時間充滿兩個fragment。 res/layout/news_articles.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />
在你的activity中,使用Support LibraryAPI,調用getSupportFragmentManager()能夠獲得一個FragmentManager對象,以後調用beginTransaction去建立一個FragmentTransaction對象, 再調用add()方法便可添加一個fragment。 你能夠對activity使用同一個FragmentTransaction對象去執行多個fragment事務,當你肯定要作這些操做時,你必須調用commint()方法。 例如,如下代碼演示怎樣添加一個fragment到前面的layout:
import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.fragment_container) != null) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create an instance of ExampleFragment HeadlinesFragment firstFragment = new HeadlinesFragment(); // In case this activity was started with special instructions from an Intent, // pass the Intent's extras to the fragment as arguments firstFragment.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } }
因爲fragment是在運行時添加到FrameLayout,而不是直接使用<fragment>標籤訂義在activity的佈局中,activity能夠移除它或者使用另一個不一樣的fragment替換它。
替換一個fragment的過程跟添加差很少,可是須要的是replace()方法,而不是add()方法。 須要注意的是,當你執行fragment事務時,好比替換或者刪除一個fragment。容許用戶「後退」或者「撤銷」改變一般是比較合適的作法。爲了讓用戶能夠經過fragment事務「後退」,你必須在你提交fragment事務以前調用 addToBackStack()方法。
注意:當你移除或者替換fragment時將事務添加到堆棧中,被移除的fragment就被中止了(沒有消亡),若是用戶導航回來從新加載這個fragment,它將會從新啓動;若是你沒有把事務加入到堆棧中,當fragment被刪除或者替換時,這個fragment也就消亡了;
如下使用fragment替換另外一個的例子:
// Create fragment and give it an argument specifying the article it should show ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
addToBackStack()方法有一個可選的字符串參數,用來指定事務的惟一名稱。這個名稱不是必須的除非你打算使用FragmentManager.BackStackEntry API執行跟高級的fragment操做。
目錄 |
爲了重用Fragment UI組件,你應該將Fragment創建成徹底獨立,模塊化而且定義了本身佈局和行爲的組件。一旦你定義了這些可重用的Fragment, 你能夠經過activity,應用程序邏輯使它們關聯,交互以組成一個總體複合型UI。 一般狀況下,你但願一個Fragment能夠與另外一個交互。好比在用戶事件的基礎上去修改內容,全部Fragment到Fragment的交互都是經過相關聯的activity來作的,兩個fragment應該從不直接交互;
爲了容許Fragment與它的activity交互,你能夠在fragment類中定義一個接口而且在activity中實現它。fragment能夠在onAttach()方法中獲取接口的實現並調用接口的方法與activity交互。 如下是fragment到activity的交互例子:
public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // Container Activity must implement this interface public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // This makes sure that the container activity has implemented // the callback interface. If not, it throws an exception try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } ... }
如今fragment可使用OnHeadlineSelectedListener的實例mCallback調用onArticleSelected()方法(或者其餘接口內的方法)提供信息給activity了。 例如,當 用戶點擊list item(list子項)時就會調用下面在fragment的方法。fragment使用回調接口提供事件到父的activity。
@Override public void onListItemClick(ListView l, View v, int position, long id) { // Send the event to the host activity mCallback.onArticleSelected(position); }
爲了接收來自fragment的事件回調,主activity(你須要用來與fragment交互的activity)必須實現定義在fragment類中的接口。 例如:下面這個activity就實現了上一例子中的接口:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(Uri articleUri) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article } }
主activity可使用findFragmentById()方法獲取Fragment實例,而後直接調用fragment的公共方法提供信息給fragment。
例如,想象一下在上面顯示的那個activity中可能包含另一個fragment,而且用來顯示由上面那個回調方法返回的數據指定的項目 在這個案例中,這個activity能夠從回調函數中得到信息而且傳遞給其餘顯示項目的fragment
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { // If article frag is available, we're in two-pane layout... // Call a method in the ArticleFragment to update its content articleFrag.updateArticleView(position); } else { // Otherwise, we're in the one-pane layout and must swap frags... // Create fragment and give it an argument for the selected article ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit(); } } }