1. 引言
??現(xiàn)如今移動設(shè)備的發(fā)展非常的迅速椒振,手機和平板都非常的普及了。這兩者的差距除了屏幕的大小以外梧乘,其他的差距都非常的小澎迎。我們知道一般的手機的屏幕是4-6英寸,平板一般是7-10英寸。屏幕大小的差距會讓同樣的界面的視覺效果有較大的差異夹供。有些控件會被過分的拉伸辑莫,元素之間的排列空隙變大,圖片的效果失真罩引。
2.碎片的概念
??Android自從3.0開始引入Fragment(碎片),它可以讓界面在平板上更好的顯示各吨。Fragment是一種可以鑲嵌在Activity當中的UI片段。它可以讓程序更加合理充分的利用大屏幕的空間袁铐。因此在平板上應(yīng)用非常的普遍揭蜒。在你對Actiivty有過了解之后,你再來了解Fragment的話剔桨,簡單了很多屉更,他們之間很多地方都非常的相像。你甚至可以說Fragment是Activity的翻版洒缀。因為我們知道瑰谜,平板的屏幕比手機的屏幕要大。程序員在設(shè)計的時候就得兼顧手機和平板树绩。假設(shè)我們在開發(fā)今日頭條的新聞App萨脑,一個界面展示一組的新聞標題,另外一個界面展示新聞的內(nèi)容〗确梗現(xiàn)在手機開發(fā)渤早,可以是兩個Activity,一個Activity展示標題瘫俊,一個Activity展示內(nèi)容鹊杖。那么如果是平板呢?新聞的標題在手機界面上能展示出來扛芽,在平板是當然也可以骂蓖,但是你能容忍還有一大片空白以及標題被拉伸么?這時候就得用上碎片了川尖,使用兩個碎片分別放置標題和內(nèi)容登下,再將這兩個碎片引入到同一個活動里。這樣屏幕控件就被充分利用了么空厌。如下圖所示:
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可以選擇揩瞪。一個是系統(tǒng)內(nèi)置的android.app.Fragment,一個是support-v4庫下的android.support.v4.app.Fragment赋朦。這里強烈建議你用V4包下的Fragment,因為它可以在所有的Android系統(tǒng)版本中保持功能的一致性。而系統(tǒng)包下的Fragment在4.2之前的設(shè)備不能使用宠哄。
在使用的時候壹将,不需要在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承粤。之前講過設(shè)置權(quán)重暴区,然后就是通過使用<fragment>標簽在布局中添加碎片。最后還有設(shè)置android:name 屬性辛臊,來指明需要添加的碎片全包名仙粱。項目結(jié)果如下圖所示:
<?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>
3.2 碎片的高級用法
1.上面只是講了碎片在布局文件中的用法,碎片的真正強大之處在于他可以在程序中動態(tài)的添加到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,這里我們設(shè)計通過點擊左邊的碎片的按鈕尚胞,實現(xiàn)右邊的碎片進行替換济炎。最后設(shè)置點擊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();
}
}
可以看到须尚,首先我們給左側(cè)的碎片中的按鈕button注冊了點擊事件,然后調(diào)用 replaceFragment()這個方法來動態(tài)替換掉右邊的fragment侍咱。換成NewRightFragment耐床。結(jié)合代碼可以看到:
1.創(chuàng)建帶添加的代碼實例
2.獲取FragmentManager,可以在Activity中直接調(diào)用getSupportFragmentManager()方法得到
3.開啟一個事務(wù)楔脯。通過調(diào)用beginTransaction()的方法開啟
4.向容器內(nèi)添加或替換碎片撩轰。一般使用replace()方法實現(xiàn),需要傳入容器的id,和待添加的碎片實例昧廷。
5.在提交事務(wù)之前堪嫂,調(diào)用FragmentTransaction 的addToBackStack()的方法,它可以接受一個名字用于描述返回棧的狀態(tài)木柬,一般傳入null就可以了皆串。點擊Back鍵,你會發(fā)現(xiàn)程序沒有退出眉枕,會回退到上一層RightFragment恶复,再點擊一次怜森,RightFreagment界面會消失,再點一次谤牡,才會退出程序副硅。
6.提交事務(wù),調(diào)用commit()的方法
效果如下圖所示:
4.碎片和活動之間的通信
我們知道碎片是鑲嵌在活動Activity中顯示的翅萤,但是他們之間的關(guān)系并不是特別的親密恐疲。從上面的代碼中,你可以看出Fragment和Actiivty都各自存在一個類中套么。哪么他們之間有沒有明顯的方式來直接通信呢培己?肯定是有的。
4.1 活動中獲取碎片的方法
為了方便碎片和活動之間進行通信违诗,F(xiàn)ragmentManager提供了一個類似于findviewbyId的方法來從布局文件中獲取碎片的實例漱凝,代碼如下:
RightFragment right=(RightFragment) getSupportFragmentManager().findFragmentById(R.id.right_fragment);
通過FragmentManager的findFragmentById可以在活動中得到相應(yīng)的碎片的實例。然后調(diào)用碎片里面的方法诸迟。
4.2 碎片中獲取活動的方法
因為我們知道碎片是嵌入到活動中的茸炒,那么每個碎片想要獲取對象和的Activity的方法就非常簡單了,只需要調(diào)用getActivity()的方法來獲取和當前碎片相關(guān)的實例就可以了阵苇。
MainActivity activity =(MainActivity) getActivity();
這樣就獲取到了活動的實例壁公,有了活動的實例就好辦了,你可以隨便調(diào)用這個活動的方法绅项。另外當碎片需要使用Context對象的時候紊册,也可以使用getActivity()的方法。因為獲取的活動本身就是一個Context對象快耿。
4.3 碎片與碎片之間的進行通信
看到這里囊陡,我和你說碎片和碎片之間不能通信,你是不是一板磚直接拍過來的啊掀亥。好好說話不動手撞反。咳咳搪花,這個 碎片都是在活動中的遏片,每個碎片都是獨立的fragment,那么如果fragment之間想要通信怎么辦?當然是找爸爸啊撮竿,啊不對吮便,是找Activity的。一個活動首先得得到它相關(guān)聯(lián)的活動幢踏,然后通過它的活動再去過去另一個碎片的實例髓需。這樣就可以實現(xiàn)碎片之間的通信功能了。
5.Fragment的生命周期
看到生命周期是不是很熟悉啊惑折,都說了 Fragment是嵌入到Activity中的授账,Activity有生命周期枯跑,F(xiàn)ragment也肯定會有的啊惨驶。來白热,看圖說話:
聯(lián)想一下Activity的生命周期,它在生命周期內(nèi)一會共有 運行狀態(tài)粗卜,暫停狀態(tài)屋确,停止狀態(tài)和銷毀四種狀態(tài)。所以fragment也有這四種狀態(tài)续扔,在一些小的地方會有所差別攻臀。
1.運行狀態(tài)
當一個碎片可見的時候,并且所關(guān)聯(lián)的Activity正處在運行狀態(tài)中纱昧,該碎片也處于運行狀態(tài)刨啸。
2.暫停狀態(tài)
當Activity進入暫停的狀態(tài)時,與它相關(guān)的可見的碎片就會進入暫停狀態(tài)
3.停止狀態(tài)
當一個活動進入停止狀態(tài)识脆,與它相關(guān)聯(lián)的碎片就會進入到停止狀態(tài)设联,或者通過調(diào)用Fragment中的FragmentTransaction()的remove(),replace的方法將碎片從活動中移除灼捂,但是如果在事務(wù)提交之前調(diào)用addToBackStack()方法的話离例,碎片也會進入到停止狀態(tài)∠こ恚總的來說宫蛆,進入停止的碎片對用戶來說是不可見的,有可能會被回收掉的猛。
4.銷毀狀態(tài)
碎片依附于活動耀盗,活動被銷毀了,碎片也要被銷毀卦尊。Fragment中的FragmentTransaction在提交事務(wù)之前調(diào)用remove(),replace()的方法將碎片從活動中移除叛拷。但是事務(wù)提交之前沒有調(diào)用addToBackStack()的方法的話,碎片也會進入銷毀狀態(tài)猫牡。
碎片的幾個附加的回調(diào)方法:
1.onAttach() 當碎片和活動簡歷關(guān)聯(lián)的時候胡诗,調(diào)用
2.onCreateView() 碎片加載布局的時候調(diào)用
3.onActivityCreated() Activity已經(jīng)創(chuàng)建的時候調(diào)用
4.onDestoryView() 與碎片相關(guān)聯(lián)的activity被移除的時候調(diào)用
5.onDetch() 當碎片和活動解除關(guān)聯(lián)的時候調(diào)用
將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: ");
}
}
然后繼續(xù)代碼,點擊fragment左邊的button淌友,替換右邊的fragment,然后點擊Back鍵回退煌恢。
1.通過日志發(fā)現(xiàn),在運行項目代碼的時候震庭,第一次啟動項目瑰抵,會調(diào)用onAttach() , onCreate() , onCreateView(),onActivityCreated(),onStart(),onResume()方法,然后點擊左邊的fragment的按鈕器联,日志改變二汛,調(diào)用了onPause() 婿崭, onStop(),onDestoryView(),這時候RightFragment已經(jīng)被停止了肴颊。因為調(diào)用了addToBackStack()方法氓栈,如果沒有調(diào)用會被銷毀。onDestory()和onDetach()方法會得到執(zhí)行婿着。
2.接著授瘦,點擊Back鍵,RightFragment重新回到了屏幕竟宋,這時候重新調(diào)用了onCreateView(),onActivityCreated(),onStart(),onResume()方法提完。因為借助addToBackStack(),RightFragment方法沒有被銷毀丘侠,所以不會執(zhí)行onCreate()方法徒欣。
3.最后,再次點擊Back鍵依次執(zhí)行 onPause() , onStop() , onDestoryView() , onDestory() , onDetach() 方法蜗字。這樣完整體驗了一遍Fragment的生命周期了打肝。
6.Fragment動態(tài)加載布局的小技巧
??因為我們知道,同樣的布局秽澳,手機放一個闯睹,平板可以放兩個。假如程序能夠根據(jù)設(shè)備的分辨率或者屏幕大小來判定加載哪個布局担神,那樣我們發(fā)揮的控件就更多了楼吃,這里說點小技巧
1.通過限定符判斷 Android: layout_width="Match_parent"
很多平板都采用雙頁模式(左側(cè)顯示列表,右側(cè)顯示內(nèi)容)妄讯,因為屏幕大孩锡,能放得下,但是手機屏幕小亥贸,放不下躬窜,只能顯示一頁的內(nèi)容,這時候咋辦炕置?舉個栗子:
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目錄下創(chuàng)建一個layout_larger包荣挨,再新建一個activity_main2.xml的文件。如果創(chuàng)建了包朴摊,但是文件無法創(chuàng)建的默垄,是因為沒有對包做引用∩醺伲可以參考我的這篇文章:Android Studio 創(chuàng)建一個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布局包括了兩個布局介杆。其中l(wèi)arge就是一個限定符鹃操,哪些屏幕被認為是large的設(shè)備會自動加載layout_large文件夾下的布局韭寸。小屏幕則會加載layout下的布局。效果如下圖所示:
2. 使用最小寬度限定符
我們使用了Large解決單雙頁的問題荆隘,但是我們不知道到底屏幕多大才符合large呢恩伺,這時候可以使用最小寬度限定符。同樣的方法創(chuàng)建layout_sw600dp文件夾,創(chuàng)建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文件耸三。
好了,關(guān)于fragment的一些用法就差不多了浇揩。
最后奉上github地址:https://github.com/wangxin3119/fragment1