在關Fragment間參數的傳遞,有兩種情況:
- 第一種情況:同一個container中不同fragment間的參數傳遞。這種情況一般發生在fragment跳轉時,上一個Fragment將參數傳遞給下一個Fragment。
- 第二種情況:是同一個Activity中,不個container間Fragment的參數傳遞。
第一種情況:Fragment跳轉時傳遞參數及結果回傳的方法
效果圖:
1、點擊“加載第二個Fragment按鈕”,加載出第二個Fragment,同時傳遞過去參數:“從Fragment1傳來的參數”這幾個String;
2、當用戶點擊第二個Fragment中的幾個圖片時,將點中的結果返回給第一個Fragment,將用戶的選擇在第一個Fragment顯示出來
一、基本架構搭建
首先,我們要把整個架構搭起來,然后再進行參數傳遞和回傳
(一)、基本XML構建:
根據上面的效果,大家很容易看到兩個Fragment的布局:
1、Fragment1的布局:(fragment1.xml)
很簡單,垂直布局,上面一個ImageView來盛裝返回過來的圖片結果,下面一個Button來用來點擊加載第二個Fragment;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <ImageView android:id="@+id/img_result" android:layout_width="100dp" android:layout_height="100dp" android:scaleType="center"/> <Button android:id="@+id/load_fragment2_btn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="加載第二個Fragment"/> </LinearLayout>
2、Fragment2的布局:(fragment2.xml)
這個也是垂直布局,上面的一個TextView用來盛裝從Fragment1傳過來的String參數,下面的幾個ImageView用來顯示幾個供用戶選擇的圖片
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is fragment 2" android:textColor="#000000" android:textSize="25sp" /> <ImageView android:id="@+id/img1" android:layout_width="100dip" android:layout_height="100dp" android:scaleType="center" android:src="@drawable/animal1"/> <ImageView android:id="@+id/img2" android:layout_width="100dip" android:layout_height="100dp" android:scaleType="center" android:src="@drawable/animal2"/> <ImageView android:id="@+id/img3" android:layout_width="100dip" android:layout_height="100dp" android:scaleType="center" android:src="@drawable/animal3"/> <ImageView android:id="@+id/img4" android:layout_width="100dip" android:layout_height="100dp" android:scaleType="center" android:src="@drawable/animal4"/> </LinearLayout>
(二)對應的Fragment類
1、在MainActivity初始化時,將Fragment1顯示出來:
MainActivity對應的XML文件:(main_activity.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
對應的代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Fragment1 fragment1 = new Fragment1(); getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit(); } }
2、Fragment1:在用戶點擊時,將fragment2添加到當前頁面顯示出來;
public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container, false); Button btn = (Button)view.findViewById(R.id.load_fragment2_btn); btn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(final View view) { Fragment2 fragment2 = new Fragment2(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.add(R.id.main_layout, fragment2); transaction.addToBackStack(null); transaction.commit(); } }); return view; } }
3、Fragment2:至於目前的它還是很簡單的,只要能顯示出來 就好了,所以他的代碼為:
public class Fragment2 extends Fragment implements View.OnClickListener { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment2, container, false); return view; } }
二、Fragment間參數傳遞
在Fragment2中,新建一個函數:newInstance(String text)來接收傳過來的參數:
新建一個Fragment2實例,然后將參數通過SetArguments設置到其中;
public static Fragment2 newInstance(String text) { Fragment2 fragment = new Fragment2(); Bundle args = new Bundle(); args.putString("param", text); fragment.setArguments(args); return fragment; }
然后在Fragment2的OnCreateView的時候再從arguments中獲取參數:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment2, container, false); if (getArguments() != null) { String mParam1 = getArguments().getString("param"); TextView tv = (TextView)view.findViewById(R.id.textview); tv.setText(mParam1); } return view; }
在Fragment1中,在調起Fragmen2t時,通過調用newInstance函數來獲取實例並傳遞參數:
public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container, false); Button btn = (Button)view.findViewById(R.id.load_fragment2_btn); btn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(final View view) { Fragment2 fragment2 = Fragment2.newInstance("從Fragment1傳來的參數"); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.add(R.id.main_layout, fragment2); transaction.addToBackStack(null); transaction.commit(); } }); return view; } }
(三)、從Fragment2向Fragment1回傳參數
這里只有利用回調,有關回調傳遞參數的問題,我在前一篇文章中:《詳解Dialog(三)——自定義對話框視圖及參數傳遞》第三部分:參數傳遞;詳細講過,大家可以先看源碼,如果源碼不懂,可以參考下這篇文章,這里就不再贅述。
第二種情況:不同container間Fragment的參數傳遞
二、同一個Activity,不同container間的參數傳遞
這里到了這篇文章的重點內容了哦,這可並不是說上一部分不重要哈,其實上一部分要比這部分重要!同一個container中不同Fragment間的參數傳遞一般的工程都會用到的,所以大家一定要看。而我這里不講,是因為以前有講過,這里就沒必要再重復一遍了,好了,廢話說了好多……開整吧。
先看看效果圖:
1、在這個Actiivty中有兩個Fragment;
2、Fragment1中有一個listView,當我們點擊ListView的Item的時候,把Item上的內容更新到Fragment2上
這里有多種實現方法,最可取的是方法三。我們由簡到易慢慢講。
我們想使兩個fragment實例要能通信,那如果我們都能通過findViewById()找到所有的控件,直接操控的話,豈不就實現了。而通過findViewById()能找到所有控件實例的地方就是在Activity中了,所以這就有了方法一。
方法一:直接在Activity中操作
在Activity中找到對應的控件實例,然后直接操控即可。
先看看MainActivity的布局:activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:baselineAligned="false" > <fragment android:id="@+id/fragment1" android:name="com.harvic.com.harvicblog5_1.Fragment1" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/fragment2" android:name="com.harvic.com.harvicblog5_1.Fragment2" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>
在這個布局中,橫向放兩個fragment,由於這里的fragment是靜態添加的,所以每個fragment都是有id值的,所以這時候如果我們要獲取某個fragment的實例,就可以通過FragmentManager::findFragmentById()來找到了。
然后是這兩個fragment的布局。
fragment1.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ff00ff" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is fragment 1" android:textColor="#000000" android:textSize="25sp" /> <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"></ListView> </LinearLayout>
可以看到fragment1的布局中,除了一個標識當前Fragment的TextView,就是一個listview;
fragment2.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff00" android:orientation="vertical" > <TextView android:id="@+id/fragment2_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is fragment 2" android:textColor="#000000" android:textSize="25sp" /> </LinearLayout>
可以看到在fragment2中非常干凈,只有一個TextView來顯示當前用戶在fragment1中的點擊結果。
下面看看在MainActivity中是如何實現的吧
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ArrayAdapter arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,mStrings); ListView listView = (ListView)findViewById(R.id.list); listView.setAdapter(arrayAdapter); mFragment2_tv = (TextView)findViewById(R.id.fragment2_tv); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mFragment2_tv.setText(mStrings[position]); } }); }
其中:
private String[] mStrings = {"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler"};
難度不大,通過(ListView)findViewById(R.id.list);找到fragment1中的listview,通過(TextView)findViewById(R.id.fragment2_tv);找到fragment2中的textView,然后直接對他們進行操作。即下面的代碼:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mFragment2_tv.setText(mStrings[position]); } });
當用戶點擊listView的一個item時,將值setText到fragment2的textView中。
可見,直接在activity中操作各個fragment的控件就可以實現消息互傳。但,這樣真的好嗎?如果每個fragment中的控件都在Activity中操作,那還要fragment干嘛!最最起碼,應該每個fragment負責自己的控件操作才對嘛!
所以,我們對這種方法進行改進,將點擊Item的賦值操作放到fragment1中去。所以,這就有方法二;
方法二:直接在fragment中操作
在這里我們會把所有方法寫在Fragment1中,這里涉及到兩方面的內容:
第一:在Fragment中如何獲得自己控件的引用,比較這里Fragment1里的listview控件。
第二:在Fragment中如何獲得其它Fragment頁面中控件的引用,比如這里Fragment2里的TextView控件。
首先,獲取自己控件引用的方法:
方法一:在onCreateView()中獲取。
就比如這里獲取自己的listView控件的引用,代碼如下:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment1, container, false); listView = (ListView)rootView.findViewById(R.id.list);//獲取自己視圖里的控件引用,方法一 return rootView; }
由於在onCreateView()中,還沒有創建視圖,所以在這里如果使用getView()方法將返回空。所以如果要獲取其實圖中指定控件的引用,只用用inflater.inflate()返回的rootView;在這個rootView()中用findViewById來查找。
方法二:在onActivityCreated()函數中獲取。
從《Fragment詳解之一——概述》的流程圖中可以看到,onActivityCreated()回調會在Activity的OnCreate()執行完成后再執行,也就是說,onActivityCreated()會在Activity的OnCreate()工作完成以后才會執行。所以當執行到onActivityCreated()的時候Activity已經創建完成,它其中的各個fragment也視圖等等的也都已經創建完成。所在可以在這里獲取跟Activity相關的各種資源。第二個問題中的獲取其它Fragment頁面中控件的引用也是在onActivityCreated()中來做的。先看看在onActivityCreated()中如何獲得自己視圖中控件的引用吧
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二 }
然后,獲得其它Fragment頁面中控件的引用的方法
在上面已經說了,要獲取Activity中的資源,就必須等Acitivity創建完成以后,所以必須放在onActivityCreated()回調函數中。
其獲取方法為:
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!! }
上面講了一堆之后,下面就看看在Fragment1中如何實現的吧。
由上面的講述可知,無論是獲取自己視圖中控件的引用還是獲取其它fragment中控件的引用都可以放在onActivityCreated()函數中,所以我們就把它們全部放在onActivityCreated()中來實現了。
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//獲取其它fragment中的控件引用的唯一方法!!! listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二 ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings); listView.setAdapter(arrayAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String str = mStrings[position]; mFragment2_tv.setText(str); } }); }
難度不大,也是獲取到了每個控件以后,直接對他們進行操作。
我們這里直接在fragment1中操作了fragment2的控件,這樣就違背了模塊分離的思想,我們應該讓他們各自處理各自的代碼才好。所以,考慮到將他們分離,我們這里就出現了方法三。
方法三:在各自的fragment中操作
好,我們先想想要怎么解決這個問題,首先,我們把各自的事件放在各自的fragment中處理,即在fragment1中能得到當前用戶點擊的ITEM的String字符串,在fragment2中可以設置textview的值。那問題來了,各自獲得了各自的東東,那通過什么讓他們交互呢?
答案顯然是activity。
很顯然,在activity中可以直接通過FragmentManager::findFragmentById()來獲取fragment2的實例。進而調用fragment2中的任意方法,這就實現了與fragment2的通信。
那fragment1又怎么把結果回傳給Activity呢?大家如果在看到這里之前先看了《Fragment跳轉時傳遞參數及結果回傳的方法》就很容易想到,用回調!請記住。在不同頁面中回傳結果,回調是萬能的解決方案。
1、Fragment2設置textView函數:
先看個簡單的,fragment2中的處理代碼:
public class Fragment2 extends Fragment { private TextView mTv; ………… public void setText(String text) { mTv.setText(text); } }
2、Fragment1中的處理方式:
(1)、定義接口及變量
由於是用回調,所以要先定義一個接口及對應的變量:
private titleSelectInterface mSelectInterface; public interface titleSelectInterface{ public void onTitleSelect(String title); }
(2)、接口變量賦值
接口是給activity用的,所以要在activity中給這里的接口變量賦值,可以有很多方法,當然可以選擇寫一個setXXX()函數來賦值,但如果用戶忘了怎么辦?所以我們要強制用戶賦值。所以采用強轉的方式,在fragment與activity相關聯時,進行強轉賦值:
public void onAttach(Activity activity) { super.onAttach(activity); try { mSelectInterface = (titleSelectInterface) activity; } catch (Exception e) { throw new ClassCastException(activity.toString() + "must implement OnArticleSelectedListener"); } }
采用強轉的方式的問題在於,如果用戶的activity沒有implements titleSelectInterface,就會拋出錯誤,所以在調試過程中就會發現。
(3)、調用接口變量
下一步就是在fragment1中在用戶點擊listView的item的時候,將結果回傳給Activity了,代碼如下:
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); listView = (ListView) getView().findViewById(R.id.list);//獲取自己視圖里的控件引用,方法二 ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings); listView.setAdapter(arrayAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String str = mStrings[position]; mSelectInterface.onTitleSelect(str); } }); }
(4)、在Activity中實現titleSelectInterface接口
首先是MainActivity必須實現titleSelectInterface接口,然后結果會在onTitleSelect(String title)中返回,在結果返回后利用fragment2.setText()操作textView;代碼如下:
public class MainActivity extends FragmentActivity implements Fragment1.titleSelectInterface { …… @Override public void onTitleSelect(String title) { FragmentManager manager = getSupportFragmentManager(); Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2); fragment2.setText(title); } }
在上面代碼中可以看出,在結果返回后,通過findFragmentById()來獲得fragment2的實例,這里首次出現了findFragmentById()函數的用法,這個函數主要用來靜態添加的fragment中,通過fragment的ID值來獲取它的實例。在獲得fragment2的實例以后,通過調用我們寫好了setText()方法來將結果顯示在textView中。
Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2);