第十四章 Android常見的Fragment的使用

1. 引言

??現如今移動設備的發展非常的迅速,手機和平板都非常的普及了。這兩者的差距除了屏幕的大小以外,其他的差距都非常的小。我們知道一般的手機的屏幕是4-6英寸,平板一般是7-10英寸。屏幕大小的差距會讓同樣的界面的視覺效果有較大的差異。有些控件會被過分的拉伸,元素之間的排列空隙變大,圖片的效果失真。

2.碎片的概念

??Android自從3.0開始引入Fragment(碎片),它可以讓界面在平板上更好的顯示。Fragment是一種可以鑲嵌在Activity當中的UI片段。它可以讓程序更加合理充分的利用大屏幕的空間。因此在平板上應用非常的普遍。在你對Actiivty有過了解之后,你再來了解Fragment的話,簡單了很多,他們之間很多地方都非常的相像。你甚至可以說Fragment是Activity的翻版。因為我們知道,平板的屏幕比手機的屏幕要大。程序員在設計的時候就得兼顧手機和平板。假設我們在開發今日頭條的新聞App,一個界面展示一組的新聞標題,另外一個界面展示新聞的內容。現在手機開發,可以是兩個Activity,一個Activity展示標題,一個Activity展示內容。那么如果是平板呢?新聞的標題在手機界面上能展示出來,在平板是當然也可以,但是你能容忍還有一大片空白以及標題被拉伸么?這時候就得用上碎片了,使用兩個碎片分別放置標題和內容,再將這兩個碎片引入到同一個活動里。這樣屏幕控件就被充分利用了么。如下圖所示:

碎片的使用

3. 碎片的使用

3.1 碎片的簡單用法

在一個活動中添加兩個碎片,并讓這兩個碎片平分活動空間。left_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="left_Fragment"/>

</LinearLayout>

接著新建一個LeftFragment類繼承Fragment。注意,這里可能會有兩個不同的包下的Fragment可以選擇。一個是系統內置的android.app.Fragment,一個是support-v4庫下的android.support.v4.app.Fragment。這里強烈建議你用V4包下的Fragment,因為它可以在所有的Android系統版本中保持功能的一致性。而系統包下的Fragment在4.2之前的設備不能使用。
在使用的時候,不需要在build.gradle文件中添加support-v4庫的依賴,builder.gradle文件中添加了appcompat_v7庫的依賴,包括了support_v4庫。


public class LeftFragment  extends Fragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view=inflater.inflate(R.layout.left_fragment,container, false);
        return view;
    }
}

right_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="right_fragment"
        android:textSize="20sp"
        />

</LinearLayout>

right的寫法與left都一樣。然后修改activity_main.xml。之前講過設置權重,然后就是通過使用<fragment>標簽在布局中添加碎片。最后還有設置android:name 屬性,來指明需要添加的碎片全包名。項目結果如下圖所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.fragmentdemo.MainActivity">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>

    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>
</LinearLayout>

fragment

3.2 碎片的高級用法

1.上面只是講了碎片在布局文件中的用法,碎片的真正強大之處在于他可以在程序中動態的添加到Activity當中。新建代碼new_right_fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ff00">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="新的Fragment"
        android:textSize="20sp"
        />

</LinearLayout>

2.新建Fragment,NewRightFragment繼承 Fragment


public class NewRightFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.new_right_fragment,container,false);
        return  view;
    }
}

3.修改activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.demo.fragmentdemo.MainActivity">

    <fragment
        android:id="@+id/left_fragment" android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>

    <FrameLayout
        android:id="@+id/new_right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>
</LinearLayout>

之前的文章介紹了FrameLayout,所有的控件默認放在左上角。這里只有一個碎片,不需要任何定位,非常適合FrameLayout。
4.修改MainActivity,這里我們設計通過點擊左邊的碎片的按鈕,實現右邊的碎片進行替換。最后設置點擊Back鍵,返回之前的碎片。

public class MainActivity extends AppCompatActivity  implements OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RightFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                replaceFragment(new NewRightFragment());
                break;
            default:
                break;
        }
    }


    private   void replaceFragment(Fragment fragment){
        FragmentManager  fragmentManager=getSupportFragmentManager();
        FragmentTransaction  fragmentTransaction=fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.right_layout,fragment);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }
}

可以看到,首先我們給左側的碎片中的按鈕button注冊了點擊事件,然后調用 replaceFragment()這個方法來動態替換掉右邊的fragment。換成NewRightFragment。結合代碼可以看到:
1.創建帶添加的代碼實例
2.獲取FragmentManager,可以在Activity中直接調用getSupportFragmentManager()方法得到
3.開啟一個事務。通過調用beginTransaction()的方法開啟
4.向容器內添加或替換碎片。一般使用replace()方法實現,需要傳入容器的id,和待添加的碎片實例。
5.在提交事務之前,調用FragmentTransaction 的addToBackStack()的方法,它可以接受一個名字用于描述返回棧的狀態,一般傳入null就可以了。點擊Back鍵,你會發現程序沒有退出,會回退到上一層RightFragment,再點擊一次,RightFreagment界面會消失,再點一次,才會退出程序。

6.提交事務,調用commit()的方法
效果如下圖所示:

fragment的動態添加

4.碎片和活動之間的通信

我們知道碎片是鑲嵌在活動Activity中顯示的,但是他們之間的關系并不是特別的親密。從上面的代碼中,你可以看出Fragment和Actiivty都各自存在一個類中。哪么他們之間有沒有明顯的方式來直接通信呢?肯定是有的。

4.1 活動中獲取碎片的方法

為了方便碎片和活動之間進行通信,FragmentManager提供了一個類似于findviewbyId的方法來從布局文件中獲取碎片的實例,代碼如下:

RightFragment  right=(RightFragment)  getSupportFragmentManager().findFragmentById(R.id.right_fragment);

通過FragmentManager的findFragmentById可以在活動中得到相應的碎片的實例。然后調用碎片里面的方法。

4.2 碎片中獲取活動的方法

因為我們知道碎片是嵌入到活動中的,那么每個碎片想要獲取對象和的Activity的方法就非常簡單了,只需要調用getActivity()的方法來獲取和當前碎片相關的實例就可以了。

MainActivity activity =(MainActivity) getActivity();

這樣就獲取到了活動的實例,有了活動的實例就好辦了,你可以隨便調用這個活動的方法。另外當碎片需要使用Context對象的時候,也可以使用getActivity()的方法。因為獲取的活動本身就是一個Context對象。

4.3 碎片與碎片之間的進行通信

看到這里,我和你說碎片和碎片之間不能通信,你是不是一板磚直接拍過來的啊。好好說話不動手。咳咳,這個 碎片都是在活動中的,每個碎片都是獨立的fragment,那么如果fragment之間想要通信怎么辦?當然是找爸爸啊,啊不對,是找Activity的。一個活動首先得得到它相關聯的活動,然后通過它的活動再去過去另一個碎片的實例。這樣就可以實現碎片之間的通信功能了。

5.Fragment的生命周期

看到生命周期是不是很熟悉啊,都說了 Fragment是嵌入到Activity中的,Activity有生命周期,Fragment也肯定會有的啊。來,看圖說話:

生命周期的對比

聯想一下Activity的生命周期,它在生命周期內一會共有 運行狀態,暫停狀態,停止狀態和銷毀四種狀態。所以fragment也有這四種狀態,在一些小的地方會有所差別。

1.運行狀態
當一個碎片可見的時候,并且所關聯的Activity正處在運行狀態中,該碎片也處于運行狀態。
2.暫停狀態
當Activity進入暫停的狀態時,與它相關的可見的碎片就會進入暫停狀態
3.停止狀態
當一個活動進入停止狀態,與它相關聯的碎片就會進入到停止狀態,或者通過調用Fragment中的FragmentTransaction()的remove(),replace的方法將碎片從活動中移除,但是如果在事務提交之前調用addToBackStack()方法的話,碎片也會進入到停止狀態。總的來說,進入停止的碎片對用戶來說是不可見的,有可能會被回收掉。
4.銷毀狀態
碎片依附于活動,活動被銷毀了,碎片也要被銷毀。Fragment中的FragmentTransaction在提交事務之前調用remove(),replace()的方法將碎片從活動中移除。但是事務提交之前沒有調用addToBackStack()的方法的話,碎片也會進入銷毀狀態。
碎片的幾個附加的回調方法:
1.onAttach() 當碎片和活動簡歷關聯的時候,調用
2.onCreateView() 碎片加載布局的時候調用
3.onActivityCreated() Activity已經創建的時候調用
4.onDestoryView() 與碎片相關聯的activity被移除的時候調用
5.onDetch() 當碎片和活動解除關聯的時候調用

將RightFragment的生命周期方法加上


public class RightFragment extends Fragment {
    private static final String TAG = "RightFragment";

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG, "onAttach: ");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        View view=inflater.inflate(R.layout.right_fragment,container,false);
        return  view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG, "onActivityCreated: ");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: ");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }


    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: ");
    }


    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "onDestroyView: ");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG, "onDetach: ");
    }
}

然后繼續代碼,點擊fragment左邊的button,替換右邊的fragment,然后點擊Back鍵回退。

打印日志

1.通過日志發現,在運行項目代碼的時候,第一次啟動項目,會調用onAttach() , onCreate() , onCreateView(),onActivityCreated(),onStart(),onResume()方法,然后點擊左邊的fragment的按鈕,日志改變,調用了onPause() , onStop(),onDestoryView(),這時候RightFragment已經被停止了。因為調用了addToBackStack()方法,如果沒有調用會被銷毀。onDestory()和onDetach()方法會得到執行。
2.接著,點擊Back鍵,RightFragment重新回到了屏幕,這時候重新調用了onCreateView(),onActivityCreated(),onStart(),onResume()方法。因為借助addToBackStack(),RightFragment方法沒有被銷毀,所以不會執行onCreate()方法。
3.最后,再次點擊Back鍵依次執行 onPause() , onStop() , onDestoryView() , onDestory() , onDetach() 方法。這樣完整體驗了一遍Fragment的生命周期了。

6.Fragment動態加載布局的小技巧

??因為我們知道,同樣的布局,手機放一個,平板可以放兩個。假如程序能夠根據設備的分辨率或者屏幕大小來判定加載哪個布局,那樣我們發揮的控件就更多了,這里說點小技巧

1.通過限定符判斷 Android: layout_width="Match_parent"

很多平板都采用雙頁模式(左側顯示列表,右側顯示內容),因為屏幕大,能放得下,但是手機屏幕小,放不下,只能顯示一頁的內容,這時候咋辦?舉個栗子:
1.新建activity_main3.xml的文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>


</LinearLayout>

2.然后在res目錄下創建一個layout_larger包,再新建一個activity_main2.xml的文件。如果創建了包,但是文件無法創建的,是因為沒有對包做引用。可以參考我的這篇文章:Android Studio 創建一個layout_large文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>
    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>

</LinearLayout>

這時候我們可以看到layout/activity_main2 布局只包含了一個碎片,而layout_larger/activity_main2布局包括了兩個布局。其中large就是一個限定符,哪些屏幕被認為是large的設備會自動加載layout_large文件夾下的布局。小屏幕則會加載layout下的布局。效果如下圖所示:

手機顯示效果圖
平板顯示效果圖

2. 使用最小寬度限定符

我們使用了Large解決單雙頁的問題,但是我們不知道到底屏幕多大才符合large呢,這時候可以使用最小寬度限定符。同樣的方法創建layout_sw600dp文件夾,創建activity_main2.xml文件

<?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">

    <fragment
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.LeftFragment"/>
    <fragment
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_weight="3"
        android:layout_height="match_parent"
        android:name="com.demo.fragmentdemo.RightFragment"/>
</LinearLayout>

這就意味著,當程序運行在屏幕寬度大于600dp的時候,回家在layout_sw600dp/activity_main2這個文件,屏幕小于600的時候仍然會加載layout/activity_main2.xml文件。
好了,關于fragment的一些用法就差不多了。

最后奉上github地址:https://github.com/wangxin3119/fragment1

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容