編輯:關於Android編程
Fragment是Android API中的一個類,它代表Activity中的一部分界面;您可以在一個Activity界面中使用多個Fragment,或者在多個Activity中重用某一個Fragment。
本文將介紹Fragment的定義、創建、添加、移除、生命周期 等,如需訪問官方原文,您可以點擊這個鏈接:《Fragments》。
可以把Fragment想象成Activity中的一個模塊,它有自己的生命周期、可以接收輸入事件、可以在Activity運行時將Fragment動態添加和移除等。
Fragment必須嵌入在Activity中才能生存,其生命周期也直接受宿主Activity的生命周期的影響。比如,若宿主Activity處於pause狀態,它所管轄的Fragment也將進入pause狀態。而當Activity處於resume狀態的時候,您可以獨立地控制每一個Fragment,如添加或刪除等。當執行一個Fragment事務時,也可以將該Fragment加入到一個由宿主Activity管轄的後退棧中,並由Activity記錄加入到後退棧的Fragment信息,按下後退鍵可以將Fragment從後退棧中一次彈出。
將Fragment添加至Activity的視圖布局中有兩種方式:一種是使用fragment標簽加入,Fragment的父視圖應是一個ViewGroup;另一種使用代碼動態加入,並將一個ViewGroup作為Fragment的容器。在某些情況下,fragment並不作為Activity視圖展示的一部分,它可能只是用來作為非顯示性的功能。
Fragment是Android 3.0 (API level 11)新加入的API,主要的設置目的是為了使UI在不同的屏幕上表現得更加靈活。由於平板比手機屏幕大的多,因此平板上可以呈現更多的內容,而Fragment可以實現同一視圖布局在不同大小的屏幕上顯示不同的效果,將Fragment加入到Activity的Layout中,可以在運行時動態替換Fragment並將Fragment保存至Activity管轄的後退棧中。
比如說,一個新聞應用程序運行在平板上時,一個Activity視圖界面可以裝載兩個Fragment,其中左邊的Fragment用於顯示新聞的標題,而右邊的Fragment用於顯示相應的新聞內容;若將該應用運行在手機上,一個Activity視圖界面無法裝載兩個Fragment,故將兩個Fragment分別裝載到兩個Activity中,如下所示:
為了創建Fragment,需要繼承一個Fragment類,並實現Fragment的生命周期回調方法,如onCreate(), onStart(), onPause(), onStop() 等。事實上,若需要在一個應用中加入Fragment,只需要將原來的Activity替換為Fragment,並將Activity的生命周期回調方法簡單地改為Fragment的生命周期回調方法即可。
一般來說,在Fragment中應至少重寫這些生命周期方法:
onCreate():當創建Fragment實例時,系統回調的方法。在該方法中,需要對一些必要的組件進行初始化,以保證這個組件的實例在Fragment處於pause或stop狀態時仍然存在。
onCreateView():當第一次在Fragment上繪制UI時,系統回調的方法。該方法返回一個View對象,該對象表示Fragment的根視圖;若Fragment不需要展示視圖,則該方法可以返回null。
onPause():當用戶離開Fragment時回調的方法(並不意味著該Fragment被銷毀)。在該方法中,可以對Fragment的數據信息做一些持久化的保存工作,因為用戶可能不再返回這個Fragment。
大多數情況下,需要重寫上述三個方法,有時還需要重寫其他生命周期方法,Fragment的生命周期如下所示:
圖1<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPHA+zqrBy7e9sePG8Lz7o6y8zLPQz8LD5tXi0KnM2MritcRGcmFnbWVudL/J0tS88ruvxuSz9cq8u6+5/bPMo7o8L3A+DQo8cD48YSBocmVmPQ=="https://developer.android.com/reference/android/app/DialogFragment.html">DialogFragment:可展示一個懸浮的對話框。使用該類創建的對話框可以很好地替換由Activity類中的方法創建的對話框,因為您可以像管理其他Fragment一樣管理DialogFragment——它們都被壓入由宿主Activity管理的Fragment棧中,這可以很方便的找回已被壓入棧中的Fragment。
ListFragment:可以展示一個內置的AdapterView,該AdapterView由一個Adapter管理著,如SimpleCursorAdapter。ListFragment類似於ListActivity,它提供了大量的用於管理List View的方法,比如回調方法onListItemClick(),它用於處理點擊項事件。
PreferenceFragment:可以展示層級嵌套的Preference對象列表。PreferenceFragment類似於PreferenceActivity,該類一般用於為應用程序編寫設置頁面。
必須重寫onCreateView()方法,為Fragment綁定布局,該方法返回的View就是Fragment的根視圖。
!請注意:若繼承的Fragment是ListFragment,onCreateView()
方法已默認返回了ListView對象,故無需再重寫該方法。
下面是一個將example_fragment.xml
布局文件綁定至ExampleFragment
的示例:
public static class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
方法回傳的第二個參數ViewGroup來自宿主Activity容器布局,Fragment的布局將其作為根視圖插入至該視圖中(is the parent ViewGroup (from the activity’s layout) in which your fragment layout will be inserted)。第三個參數Bundle用於回傳之前占據該位置的Fragment實例所保存的Bundle信息,當該Fragment的新實例處於resume狀態時,該參數被回傳(provides data about the previous instance of the fragment, if the fragment is being resumed )。
inflate()
方法需要三個參數:
參數1(int):需要綁定的Layout的資源ID;
參數2(ViewGroup):綁定的Layout布局的父視圖;
參數3(boolean):是否需要將參數1的Layout資源依附於(should be attached to)參數2的ViewGroup上,上例中為false,表示不依附。(系統已經默認將Layout插入至ViewGroup中,若為true,將添加一層冗余的視圖(redundant view group in the final layout))。
一般地,Fragment綁定的UI作為宿主Activity的一部分,嵌套在整個Activity層級視圖中。將Fragment加入Activity,有如下兩種方式:
方式一:使用fragment標簽加入(Declare the fragment inside the activity’s layout file):
其中標簽
中的屬性android:name
指定Fragment的全限定類名(specifies the Fragment class to instantiate in the layout)。
當系統加載Activity的layout視圖時,同時加載Fragment綁定的視圖,並回調相應Fragment的onCreateView()
方法,系統將
標簽替換為onCreateView()
方法返回的View。
!請注意:必須為fragment設定唯一的身份標識,以便當宿主Activity為restart狀態時可以恢復(restore)fragment。
有三種為fragment設置唯一標識的方式:
通過android:id
屬性為fragment指定唯一ID;
通過android:tag
屬性為fragment指定唯一字符串標識;
若上述兩種凡是均未指定,則該fragment的標識為其父容器控件的ID(the system uses the ID of the container view)。
當Activity處於running狀態時,可以將fragment添加至Activity布局layout中,您只需要指定fragment的父容器就行了。
為了在Activity中對fragment做添加、刪除、替換等操作(add, remove, or replace a fragment),需調用FragmentTransaction:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
使用add()
方法添加fragment,並指定其添加位置,最後調用commit()
方法提交事務:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
有時,為了讓fragment執行一些後台行為(background behavior),可以不為fragment綁定UI。
為了給Activity添加這種不帶UI的fragment,需調用add(Fragment, String)方法,其中第二個參數String是為fragment指定一個唯一的tag標簽,而非指定View的ID(supplying a unique string “tag” for the fragment, rather than a view ID)。由於未綁定UI,故無需重寫onCreateView()
方法。
用String標簽為未綁定UI的fragment指定唯一標識並不嚴謹(Supplying a string tag for the fragment isn’t strictly for non-UI fragments),您也可以給綁定了UI的fragment指定String標簽;但是若某個fragment未綁定UI,那麼只能用String標簽來標識該fragment,若需要在Activity中獲取該fragment,需調用findFragmentByTag()
方法。
在開發者下載的SDK中,有一個不帶UI的fragment的示例程序FragmentRetainInstance.java
,它的路徑為:
為了在Activity中管理Fragment,需調用getFragmentManager()
方法獲取FragmentManager
實例。您可以使用FragmentManager
完成如下操作:
調用findFragmentById()
方法獲取由Activity管轄的綁定了UI的Fragment實例(for fragments that provide a UI in the activity layout);調用findFragmentByTag()
方法獲取由Activity管轄的未綁定UI的Fragment實例(for fragments that do or don’t provide a UI);
調用popBackStack()
方法將Fragment從後退棧中彈出;
調用addOnBackStackChangedListener()
方法注冊監聽器,用於監聽後退棧的變化。
使用Fragment的最大好處就是可實現動態添加、刪除、替換 等 操作,實現與用戶的交互。每一組向Activity提交的變化稱為事務(Each set of changes that you commit to the activity is called a transaction),您可以使用FragmentTransaction
這個API操作事務。您也可以將事務保存在由Activity管轄的後退棧中,以方便用戶點擊後退鍵來查看Fragment的變化。
使用示例如下:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每個事務都是您希望同時執行的一組變化(Each transaction is a set of changes that you want to perform at the same time.),這些變化包括add(), remove(), 和 replace()
等操作,最後,為了使事務在Activity中生效,需調用commit()
方法。
在調用commit()
方法之前,可以調用addToBackStack()
方法,將該事物添加到由宿主Activity管轄的Fragment後退棧中。通過點擊後退鍵,用戶可以查看該後退棧中的內容。
舉例如下:
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
在上述示例中,將newFragment
對象所綁定的UI視圖替換至以R.id.fragment_container
為ID的layout容器中,並調用addToBackStack()
方法將該事物添加至後退棧中。這樣,這一事務就被保存到了後退棧中,用戶可以找回該事物;另外,點擊back按鈕也可以顯示替換之前的Fragment。
若您在一組事務中進行了不止一項操作(如同時調用了add()
、remove()
等方法),並調用addToBackStack()
方法將該事務加入後退棧中,那麼在調用commit()
之前,這些操作將作為一個事務整體存在於後退棧中,用戶點擊back鍵將會整體回退。
使用FragmentTransaction
操作事務時,操作順序是沒有規定的,但以下兩點必須注意:
commit()
必須在最後調用;
若您希望在一個布局容器中添加多個Fragment,那麼加入的順序決定了這些Fragment綁定的UI視圖在View樹中的層級順序(the order in which you add them determines the order they appear in the view hierarchy)。
若在事務中進行了remove操作,而且在提交事務之前未調用addToBackStack()
方法,那麼該Fragment會被destroy,用戶點擊back鍵將無法找回;相反,若調用了addToBackStack()
,那麼Fragment將處於stopped 狀態,用戶點擊back鍵將可以找回。
對於每個Fragment事務,您可以在調用commit()
方法之前調用setTransition()
方法,為事務的變化添加動畫。
調用commit()
方法並不會立即執行事務,因為執行事務是在UI線程(主線程)中進行的,只有當主線程空閒時,才會執行事務操作(it schedules it to run on the activity’s UI thread (the “main” thread) as soon as the thread is able to do so)。如有必要,可以在UI線程中調用executePendingTransactions()
方法,以便在commit()
方法調用後立即執行提交的事務。但一般沒必要這麼做,除非事務的操作依賴於其他線程(Doing so is usually not necessary unless the transaction is a dependency for jobs in other threads)。
調用commit()
提交事務的時機應是“Activity保存狀態之前”( prior to the activity saving its state),即用戶離開Activity之前。若試圖在這個時機之後調用commit()
,系統將拋出異常。
盡管一個Fragment實例獨立於一個Activity,並且一個Fragment可以嵌入到多個Activity中(可以重用),但Activity包含的Fragment將直接有這個Activity管轄。
具體來說,Fragment可以通過getActivity()
方法獲取其宿主Activity的對象引用,通過該引用,可以調用Activity中的findViewById()
方法獲得布局中的視圖控件:
View listView = getActivity().findViewById(R.id.list);
類似地,也可以在Activity中獲取Fragment的實例:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
有些情況下,您需要Fragment響應Activity的事件,好的做法是在Fragment中添加回調接口,並在其宿主Activity中實現。當Activity通過接口接收了回調,它可以與其他Fragment共享信息(When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary)。
舉例來說,一個新聞應用的Activity包含兩個Fragment,其中Fragment A用於顯示新聞標題,而Fragment B 用於顯示新聞內容。Fragment A必須告訴Activity它的列表項何時被點擊,這樣Activity可以控制Fragment B顯示新聞內容,所以必須在Fragment A中定義一個事件監聽接口OnArticleSelectedListener
:
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
接著,需要在Activity中實現OnArticleSelectedListener
接口,並重寫onArticleSelected()
方法,並通知Fragment B來自於Fragment A的點擊事件。為了保證宿主Activity實現該接口,需在Fragment A中的回調方法onAttach()
中做如下工作(當Fragment添加至Activity時,系統回調onAttach()
方法):
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}
若宿主Activity未實現OnArticleSelectedListener
接口,Fragment將拋出ClassCastException
異常。正常情況下,Fragment A的成員變量mListener持有實現了OnArticleSelectedListener
接口的對象引用,所以,Fragment A可以通過宿主Activity傳遞點擊事件。具體來說,當用戶點擊了某個列表項時,Fragment A中的onListItemClick()
方法被回調,在該方法中調用OnArticleSelectedListener
接口中的onArticleSelected()
方法(程序執行的實際上是重寫的onArticleSelected()
方法)。
在Fragment中回調onCreateOptionsMenu()
方法可以創建菜單項,為了保證這個方法能夠被系統回調,您必須在onCreate()
生命周期方法中調用setHasOptionsMenu()
方法。
當menu項被點擊時,Fragment也像Activity一樣,通過onOptionsItemSelected()
回調方法響應menu項的點擊事件。
與Activity類似,Fragment也同樣有三種狀態:
Resumed:在一個處於running狀態的Activity中,Fragment處於可見狀態(The fragment is visible in the running activity);
Paused:另一個Activity處於前台並獲得了焦點,該Fragment的宿主Activity並未被全部遮擋;
Stopped:Fragment不可見。可能由於宿主Activity已不可見或Fragment已被Activity移除(但加入到了Fragment後退棧中),一個處於stopped狀態的Fragment仍是alive 的,但當宿主Activity被kill時,該Fragment也將被kill。
圖二
與Activity類似,您同樣可以使用Bundle對象保存fragment的狀態信息,以防宿主Activity所在進程被kill、而迫使Activity重新創建、接著fragment才能重新創建、這時Fragment的狀態信息將丟失。
重寫Fragment的onSaveInstanceState()
回調方法,並在回傳的Bundle參數中保存狀態信息;接著在onCreate(), onCreateView(), onActivityCreated()
方法中獲得保存的狀態信息。
Activity與Fragment生命周期的一個最大區別就是它們的實例是如何存儲在各自的後退棧中的( how one is stored in its respective back stack):
在默認情況下,Activity實例會在stop狀態時被壓入由系統管理的Activity後退棧中(An activity is placed into a back stack of activities that’s managed by the system when it’s stopped, by default),所以用戶可以通過點擊back按鈕恢復已入棧的Activity實例。有關Activity後退棧的更多內容,您可以參考這個官方文檔:《Tasks and Back Stack》。
addToBackStack()
方法將fragment壓入由宿主Activity管理的fragment後退棧。
除此之外,fragment與Activity的生命周期非常相似,您只需了解Activity的生命周期是如何影響fragment的生命周期就行了。
!請注意:如需在Fragment中使用Context對象,您可以調用getActivity()
方法,但這個方法只能是在fragment已經依附於Activity後才能調用( to call getActivity() only when the fragment is attached to an activity)。當fragment未依附於某個Activity、或fragment已經處於其生命周期的末尾而不再依附於某個Activity時,調用getActivity()
方法會直接返回null。
宿主Activity的生命周期直接影響其管轄的fragment的生命周期,Activity的每一個生命周期方法被回調後,其管轄的fragment的相應生命周期方法會跟著回調。如當Activity回調onPause()時,fragment也會回調onPause()。
fragment的其他生命周期方法如下:
onAttach():當fragment實例依附於Activity時被回調,Activity對象的引用回傳到該方法中(the Activity is passed in here);
onCreateView():為fragment綁定UI視圖時,該方法被回調;
onActivityCreated():當宿主Activity的onCreate()
方法返回後,該方法被回調;
onDestroyView():當與fragment綁定的UI視圖被移除時,該方法被回調;
onDetach():當fragment不再依附於Activity時,該方法被回調;
除了上述方法外,其他的fragment生命周期方法均由其宿主Activity的生命周期直接影響。有些Activity的生命周期方法直接影響了多個fragment的生命周期方法,比如說,當Activity的onCreate()
被回調時,將導致fragment的onAttach()、onCreate()、onCreateView()、
onActivityCreate()被連續回調( you can see how each successive state of the activity determines which callback methods a fragment may receive)。如上圖二所示。
一旦Activity處於resume狀態時,您可以自由地添加或移除fragment,也就是說,只有當Activity的狀態為resume時,fragment才能夠自由地控制自己的生命周期。
當Activity不在resume狀態時,fragment的生命周期將由宿主Activity控制。
一、概述相信Android的開發者對於設配問題都比較苦惱,Google官方雖然給出了一系列的建議,但是想要單純使用這些建議將設備很輕松的做好,還是相當困難的
前言由於Android自帶的TextView控件沒有提供傾斜的(我暫時沒有找到),我們可以自定義控件來實現,下面首先來看我們實現的效果圖。TextView文字傾斜其實實現
前言React Native已經出現很久了,有很多應用也在進行嘗試,前面我們也講述了怎麼創建React Native工程以及怎麼搭建原生語言與js的開發環境。但是在實際應
Android 5.0(21)之後,android.hardware.Camera被廢棄(下面稱為Camera1),還有一個android.graphics.Camera