Android Fragment 建國科技大學資管系饒瑞佶 2017/10 V1
Android 3.0 後才支援 Fragment 解決部分 App 適應螢幕大小的問題 它類似於 Activity, 可以像 Activity 可以擁有自己的版面設計, 也和 Activity 一樣有自己的生命週期 ( 具備 oncreate() oncreateview() 與 onpause() 方法 )
LifeCycle 圖片來源 : https://stackoverflow.com/questions/36339811/w hen-is-onattach-called-during-the-fragmentlifecycle
Fragment 特點 螢幕上同時間只能出現一個 Activity, 但卻可以出現多個 Fragment Fragment 有自己的 layout 與事件 ( 跟 Activity 一樣 ) 只是 Fragment 無法單獨存在, 它必須依附在 Activity 下 Fragment 可以動態被加入 Activity 或從 Activity 移除, 因此可以做到手機和平板有不同的顯示方式
Activity 加入 Fragment 方法 1: 直接在 layout 中使用 <fragment> 加入 方法 2: 透過程式, 使用 FragmentTransaction 類別, 指定 fragment 要加入到哪個 ViewGroup 內
方法 1 直接在 layout 中使用 <fragment> 加入
建立 fragment_1.xml
<?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:background="#ff00ff" > <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text=" 我是 fragment1" android:textsize="30sp"/> </LinearLayout>
建立 Fragment_1.java 只建立 oncreateview 就可以 繼承自 class Fragment Fragment 自己的 layout
@TargetApi(Build.VERSION_CODES.HONEYCOMB) public class Fragment_1 extends Fragment { @Override public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.fragment_1, container, false); } }
建立主畫面 usefragment.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/fragment1" android:name="tw.edu.ctu.imetest.myapplication.fragment_1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout> 載入對應的 Fragment class
方法 2 使用 FragmentTransaction 類別
作法 首先建立主畫面 fragmain.xml
fragmain.xml 未來放置 Fragment 的 ViewGroup 按鈕
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/back_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 前一頁 " android:textsize="30sp" /> <Button android:id="@+id/next_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 下一頁 " android:textsize="30sp" /> </LinearLayout> 按鈕 Button </LinearLayout>
建立 class DetailsFragment 繼承自 class Fragment
newinstance 方法回傳一個 fragment 建立 fragment 物件 在 fragment 物件上綁定參數
附加到 root layout Fragment 自己的 layout
public static DetailsFragment newinstance(context context, int index) { ctx = context; DetailsFragment f = new DetailsFragment(); Bundle args = new Bundle(); // 透過 Bundle 設定 index 參數 args.putint("index", index); f.setarguments(args); return f; } public int getshownindex() { // 取得 index 值 return getarguments().getint("index", 0); } @Override public View oncreateview(layoutinflater inflater, @Nullable ViewGroup container, Bundle savedinstancestate) { // Fragment 的 layout v=inflater.inflate(r.layout.fragment_sub,container,false); TextView text=(textview)v.findviewbyid(r.id.text_view); text.settext("page=" + getshownindex()); return v; }
fragment_sub.xml <?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="vertical" > <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="fragment" /> </LinearLayout>
繼承 FragmentActivity 建立主程式 GoFragment.java
private Button firstbtn, secondbtn; private int page = 1;
protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.fragmain); // 找到按鈕 firstbtn = (Button) findviewbyid(r.id.back_button); secondbtn = (Button) findviewbyid(r.id.next_button); // 先顯示第一頁 changefragment(detailsfragment.newinstance(this,page)); // 按鈕對應事件 firstbtn.setonclicklistener(new View.OnClickListener() { @Override public void onclick(view v) { if (page > 1) { // 退後一頁 changefragment(detailsfragment.newinstance(fragmain.this, --page)); } else { // 維持在第一頁 changefragment(detailsfragment.newinstance(fragmain.this, page)); } } }); secondbtn.setonclicklistener(new View.OnClickListener() { @Override public void onclick(view v) { changefragment(detailsfragment.newinstance(fragmain.this, ++page)); } }); }
在 frgmain.xml 中
// 設定 fragment 版面 private void changefragment(fragment f) { FragmentTransaction transaction = getsupportfragmentmanager().begintransaction(); // 要取代的位置 transaction.replace(r.id.fragment_container, f); // 觸發 DetailsFragment 內的 oncreateview transaction.commitallowingstateloss(); }
可以設計兩個不同版面來切換 延伸應用
fragment_sub1.xml <?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="vertical" > <ImageView android:id="@+id/imageview1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" /> </LinearLayout>
@Override public View oncreateview(layoutinflater inflater, @Nullable ViewGroup container, Bundle savedinstancestate) { // 對應的畫面 switch (getshownindex()) { case 1: v = inflater.inflate(r.layout.fragment_sub, container, false); TextView text = (TextView) v.findviewbyid(r.id.text_view); text.settext("page" + getshownindex()); break; case 2: v = inflater.inflate(r.layout.fragment_sub1, container, false); Button bt1 = (Button) v.findviewbyid(r.id.button1); bt1.setonclicklistener(new View.OnClickListener() { @Override public void onclick(view v) { Toast.makeText(ctx, "Fragment page2", Toast.LENGTH_LONG).show(); } }); break; default: v = inflater.inflate(r.layout.fragment_sub, container, false); TextView text1 = (TextView) v.findviewbyid(r.id.text_view); text1.settext("page" + getshownindex()); break; } return v; }
後記 類似功能用 ViewFlipper 也可以達成 用 Viewflipper 包多個要切換的頁面, 在同一支程式內做切換 但一個版面可以建立多個 Fragment 做多個局部切換 一個 Fragment 可以在多個 Activity 中被重複使用
依據旋轉或大小動態改變載入 Fragment
首先建立可以並排的 Fragment 直接在 xml 中載入使用
建立 fragment_1.xml
<?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:background="#ff00ff" > <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text=" 我是 fragment1" android:textsize="30sp"/> </LinearLayout>
建立 fragment_2.xml
<?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:background="#ffff00" > <ImageView android:id="@+id/imageview1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" /> </LinearLayout>
只建立 oncreateview 就可以 建立 Fragment_1.java
只建立 oncreateview 就可以 建立 class Fragment_2
@TargetApi(Build.VERSION_CODES.HONEYCOMB) public class Fragment_1 extends Fragment { @Override public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.fragment_1, container, false); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class Fragment_2 extends Fragment { @Override public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.fragment_2, container, false); } }
建立主畫面 parrelfragment.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/fragment1" android:name="tw.edu.ctu.imetest.myapplication.fragment_1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/fragment2" android:name="tw.edu.ctu.imetest.myapplication.fragment_2" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout> 各自載入對應的 class
result 可以看到兩個並排, 但是沒有動態偵測手機或平板加以改變
依據旋轉或大小動態改變載入 Fragment 建立兩個 Activity(TitleActivity 與 DetailActivity), 各自對應一個 Fragment 建立兩個 Fragment 可以沿用前面的 Fragment_1( 搭配 fragment_1.xml) 與 Fragment_2 ( 搭配 viewer.xml, 因為後續要做互動 ) 建立兩個主畫面 (fragmenta.xml) 給 TitleActivity 使用, 一個用於直向 (res/layout), 一個用於橫向 (res/layout-land), fragmentb.xml 給 DetailActivity 使用
進入點 TitleActivity 相對關係 +fragmenta.xml 橫向 : Fragment_1 fragment_1.xml 直接互動 +Fragment_2 viewer.xml Intent 直向 : Fragment_1 fragment_1.xml DetailActivity +fragmentb.xml + FragmentB viewer.xml
TitleActivity.java 要有兩個, 分別用於直向與橫向
res/layout/fragmenta.xml Fragment_1 直向時, 只使用一個 Fragment
<?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/titles" android:name="tw.edu.ctu.rcjao.fragment.androidfragmentsample.fragment_1" android:layout_width="match_parent" android:layout_height="match_parent" />
res/layout-land/fragmenta.xml 橫向時, 使用兩個 Fragment <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:name="ctu.im.rcjao.hw1963001.fragment_1" android:layout_width="0dp" android:layout_height="match_parent" android:id="@+id/titles" android:layout_weight="45"> </fragment> <fragment android:name="ctu.im.rcjao.hw1963001.fragment_2" android:layout_width="0dp" android:layout_height="match_parent" android:id="@+id/viewers" android:layout_weight="55"> </fragment> </LinearLayout>
目前到這旋轉螢幕, 畫面是可以變化, 但是沒有動作
DetailActivity.java 一個就可以
fragmentb.xml Fragment_2
<?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/titles" android:name="tw.edu.im.tv.myapplication.fragment_2" android:layout_width="match_parent" android:layout_height="match_parent" />
Fragment_1.java (I) Fragment_1 本身是 List, 不需要再建立 layout( 不需要 oncreateview 方法 )
Fragment_1.java (II) Fragment_2 Fragment_2 看目前 Fragment_2 是否存在, 如果不在表示直向, 用 Intent 跳過去如果存在, 直接更新 Fragment_2 上面的顏色
@TargetApi(Build.VERSION_CODES.HONEYCOMB) public class Fragment_1 extends ListFragment { private final String[] tutoriallist = {"RED", "GREEN", "BLUE",}; @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setlistadapter(new ArrayAdapter(getActivity(). getapplicationcontext(), android.r.layout.simple_list_item_activated_1, tutoriallist) { @Override public View getview(int position, View convertview, ViewGroup parent) { View view = super.getview(position, convertview, parent); TextView text = (TextView) view.findviewbyid(android.r.id.text1); text.settextcolor(color.black); return view; } } ); } } @Override public void onlistitemclick(listview l, View v, int position, long id) { // TODO Auto-generated method stub super.onlistitemclick(l, v, position, id); Fragment_2 viewer = (Fragment_2) getfragmentmanager().findfragmentbyid(r.id.viewers); if (viewer == null!viewer.isinlayout()) { Intent launchingintent = new Intent(getActivity(), DetailActivity.class); launchingintent.putextra("color_type", tutoriallist[position]); startactivity(launchingintent); } else { viewer.updatecolor(tutoriallist[position]); } }
Fragment_2.java Fragment_2 從 Fragment_1 傳來的 Fragment_2 自己的 LAYOUT
@TargetApi(Build.VERSION_CODES.HONEYCOMB) public class Fragment_2 extends Fragment { TextView mtext; @Override public View oncreateview(layoutinflater inflater, ViewGroup container,bundle savedinstancestate) { Intent launchingintent = getactivity().getintent(); String color = launchingintent.getstringextra("color_type"); View viewer = (View) inflater.inflate(r.layout.viewer, container, false); mtext = (TextView) viewer.findviewbyid(r.id.textsample); updatecolor(color); return viewer; } } public void updatecolor(string color) { if(color!= null) if(color.equals("red")) mtext.setbackgroundcolor(color.red); else if(color.equals("blue")) mtext.setbackgroundcolor(color.blue); else if(color.equals("green")) mtext.setbackgroundcolor(color.green); }
viewer.xml 設定成滿版
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="horizontal" > <TextView android:id="@+id/textsample" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#cc9988" android:gravity="center" android:text="this is the sample text" android:textcolor="#000000" /> </LinearLayout>
result