Fragment,碎片,是Android 3.0之后加入的一個非常重要的概念。每個Fragment都有相應的Activity對它進行托管。一個Activity中可以有多個Fragment,這很自然的給大屏幕的適配提供了很便捷的方案。現在大家在開發中都必不可上的用上Fragment。本文總結了Fragment在不同情況下的傳值方法,包括不同Activity下的Fragment的傳值,相同Acitvity托管下不同Fragment的傳值。同一界面不同Fragment傳值并實時變化的情況。了解了這些,基本上Fragment的通信就不會再有問題了。接下來分部分介紹
一,不同Acitivity托管下的Frament如何傳值
相信大家對不同Activity間的傳值都很熟悉,其實Fragment的傳值就與Acitvity一樣簡單。傳值情況如下:
下面通過一個demo來講解,界面很簡單,firstFragment中有一個EditText,點擊按鈕實現將值傳到secondFragment中顯示在TextView上。界面的布局就不再貼出。
定義layout_main.xml如下:
~~~
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragmentContain"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</FrameLayout>
~~~
很簡單,用來放fragment。接下來定義供繼承的frameActivity類
~~~
public abstract class SingleFragment extends FragmentActivity {
protected abstract Fragment createFragment();
protected int getLayoutResId(){
return R.layout.activity_main;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContain);
if(fragment == null){
fragment = createFragment();
fm.beginTransaction().add(R.id.fragmentContain,fragment).commit();
}
}
}
~~~
相信大家都知道這是用與被Activity繼承的類,把重復的代碼定義在基類中,減少冗余代碼,這是適配器設計模式。
接下來就完成FirstActivity,不能再簡單了。
~~~
public class FirstActivity extends SingleFragment {
@Override
protected Fragment createFragment() {
return new FirstFragment();
}
}
~~~
FristFragment通過調用Fragment.startActivity(intent)將值傳到SecondActivity中。具體代碼如下:
~~~
public class FirstFragment extends Fragment {
Button btn;
EditText edit;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.firstfragment,container,false);
initView(v);
return v;
}
public void initView(View v){
btn = (Button)v.findViewById(R.id.btn);
edit = (EditText)v.findViewById(R.id.edit1);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(),SecondActivity.class);
intent.putExtra(SecondFragment.EXTRA_STRING, FirstFragment.this.edit.getText().toString());
startActivity(intent);
getActivity().finish();
}
});
}
}
~~~
這樣就把值傳到了SecondActivity,其實跟Acitvity一樣。接下來就是如何在SecondFragment中獲得SecondActivity的值。有兩種方法,第一種如下:
~~~
String str = getActivity().getIntent().getStringExtra(EXTRA_STRING);
txt.setText(str);
~~~
直接通過getIntent獲取值,但這樣做就破壞的Fragment的獨立性。因為此時SecondFragment總需要被SecondActivity托管,而不能用于其他Activity中,否則就可能因獲取不到intent而報錯。
正常的設托管模式是Activity知道Fragment的具體情況,但Fragment不能也不應該知道Activity中的具體情況。所以一般采用以下第二種方法。
每個fragment都有一個Bundle對象。第二種方法就是把傳過來的值存到bundle中。bundle可以添加argument(key-value對象),在給fragment添加bundle時要注意,Fragment.setArguments(bundle)需要在fragment創建后,添加到activity前完成。然后通過acitivity在建立fragment時傳入值來實現fragment的獨立性。修改SecondFragment代碼如下:
~~~
public class SecondFragment extends Fragment {
public static final String EXTRA_STRING="DATA";
TextView txt;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.secondfragment,container,false);
txt = (TextView)v.findViewById(R.id.txt2);
String str = getArguments().getString(EXTRA_STRING);
txt.setText(str);
return v;
}
public static Fragment newInstance(String s){
Bundle args = new Bundle();
args.putString(EXTRA_STRING,s);
SecondFragment fragment = new SecondFragment();
fragment.setArguments(args);
return fragment;
}
}
~~~
然后在SecondActivity中將值傳入
~~~
public class SecondActivity extends SingleFragment {
@Override
protected Fragment createFragment() {
String str = getIntent().getStringExtra(SecondFragment.EXTRA_STRING);
return SecondFragment.newInstance(str);
}
}
~~~
就可以發現傳值成功了。
二,相同activity托管的兩個Fragment傳值問題。
在上面的例子上進行修改,在SecondFragment加入一個按鈕打開一個dialogFragment,傳值進去,dialog銷毀后返回值。
在SecondFragment中打開dialogFragment,代碼修改如下:
~~~
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fm = getActivity().getSupportFragmentManager();
MyDialog myDialog = (MyDialog) MyDialog.newInstance(SecondFragment.this.txt.getText().toString());
myDialog.setTargetFragment(SecondFragment.this,0);
myDialog.show(fm,"Data");
}
});
~~~
最主要的是在把SecondFragment設為是myDialog的目標Fragment.使兩者建立聯系,這樣目標Fragment就交給了FragmentManage管理,方便之后獲取目標Fragment.
接下來是完成dialogFragment,代碼如下:
~~~
public class MyDialog extends DialogFragment {
public static final String EXTRADATA = "DIALOG";
EditText dialogEdit;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View v = getActivity().getLayoutInflater().inflate(R.layout.dialogfragment,null);
dialogEdit= (EditText)v.findViewById(R.id.edit);
dialogEdit.setText(getArguments().getString(EXTRADATA));
return new AlertDialog.Builder(getActivity()).setTitle("").setView(v)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
}).create();
}
public static Fragment newInstance(String s){
Bundle args = new Bundle();
args.putString(EXTRADATA,s);
MyDialog fragment = new MyDialog();
fragment.setArguments(args);
return fragment;
}
public void sendResult(int s){
if(getTargetFragment() == null){
return;
}else{
Intent i = new Intent();
i.putExtra(EXTRADATA,dialogEdit.getText().toString());
getTargetFragment().onActivityResult(getTargetRequestCode(),s,i);
}
}
}
~~~
newInstance不用講,跟上面的原理一樣。主要是sendResult,在sendResult中調用父Fragment的回調方法將修改后的值返回到父Fragment中。
最后在SecondFragment中添加回調方法處理返回值。
~~~
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode != Activity.RESULT_OK){
return;
}else{
String str = data.getStringExtra(MyDialog.EXTRADATA);
txt.setText(str);
}
}
~~~
就完成了同個Activity托管的不同Fragment間的傳值。
三,同一界面修改一Fragment另一個Fragment實時改變
現在有這樣一個需求,一個界面同時有兩個Fragment,右邊的Fragment里的EditText變化會引起左邊的實時變化。修改上面的例子,在FirstFragment中添加一個按鈕打開一個新的界面,實現上面的功能。
首先分析下思路。其實最簡單的做法就是在EditText的Fragment中監聽edit的變化,然后直接創建FragmentManger,獲得另一個Fragment并動態的改變其中的內容。這是最直接最簡單的方法,但是還是那句話,Fragment的獨立性很重要,如果這樣做就要求EditText的Fragment知道TextView所在的Fragment的相關細節。最好的方法就是用回調。
新建一個thirdfragment.xml文件。
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/frame1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/frame2"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
~~~
新建一個ForthFragment,它的布局文件就只需要一個EditText。
~~~
public class ForthFragment extends Fragment{
EditText edit;
private Callbacks mCallbacks;
public interface Callbacks{
void onChangeText(String s);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fourfragment,container,false);
initView(v);
return v;
}
public void initView(View v){
edit = (EditText)v.findViewById(R.id.edit1);
edit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String str = edit.getText().toString();
mCallbacks.onChangeText(str);
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallbacks = (Callbacks)activity;
}
}
~~~
在fragment中定義了回調接口,回調接口定義了fragment委托給托管activity處理的工作。任何托管這個fragment都要實現這個接口。在onAttach方法中,將activity強制轉換成callbacks并賦值給Callbacks變量。這樣在onTextChange中調用接口的onChangeText()相當于在Activity中調用。接下來看看托管兩個Fragment的Activity。
~~~
public class ThirdActivity extends FragmentActivity implements ForthFragment.Callbacks {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.thirdfragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment1 = fm.findFragmentById(R.id.frame1);
Fragment fragment2 = fm.findFragmentById(R.id.frame2);
if(fragment1 == null){
fragment1 = new ForthFragment();
fm.beginTransaction().add(R.id.frame1,fragment1).commit();
}
if(fragment2 == null){
fragment2 = new FiveFragment();
fm.beginTransaction().add(R.id.frame2,fragment2).commit();
}
}
@Override
public void onChangeText(String s) {
FragmentManager fm = getSupportFragmentManager();
FiveFragment listFragment= (FiveFragment)fm.findFragmentById(R.id.frame2);
listFragment.update(s);
}
}
~~~
代碼很簡單,分兩次加載不同的Fragment。并實現回調接口。在接口中獲得另一個fragment調用其update()方法。最后就只剩下在FiveFragment中實現update()了。
~~~
public class FiveFragment extends Fragment {
TextView txt;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fivefragment,container,false);
txt = (TextView)v.findViewById(R.id.txt2);
return v;
}
public void update(String str){
txt.setText("數據是:"+str);
}
}
}
}
@Override
public void onChangeText(String s) {
FragmentManager fm = getSupportFragmentManager();
FiveFragment listFragment= (FiveFragment)fm.findFragmentById(R.id.frame2);
listFragment.update(s);
}
}
~~~
這樣就實現了界面的實時變化。
到這里fragment的幾種傳值方式就講完了,demo做的很簡單,只是為了講解用,但再復雜的傳值也是基于這些方式。就講到這吧。
[代碼下載](http://download.csdn.net/detail/u014486880/8952375)
- 前言
- Android底部tab與標題欄相結合
- Android免費獲取短信驗證碼
- android中Handler的源碼分析
- 詳解Fragment的傳值問題
- 詳談gson解析
- android新控件之toolbar,floatingActionButton,SnackBar,CollapsingToolbarLayout
- android自定義控件
- 淺談android的線程池
- Android的消息處理機制,AsyncTask源碼解析
- IPC——android進程間通信
- CoCos2d_android入門所需知道的一切
- Cocos2d_android你所需要知道的一切(下)
- Activity你需要知道的一切
- Activity啟動過程源碼分析
- Data Binding Guide——google官方文檔翻譯(上)
- Data Binding Guide——google官方文檔翻譯(下)
- android TextView實現跑馬燈效果
- android中生成excel
- Volley源碼解析
- LayoutInflater源碼解析
- android發送郵件
- android測試工具MonkeyRunner--google官網翻譯
- android View繪制源碼分析
- Android中Window添加View的底層原理
- 仿美團商品選購下拉菜單實現