详解Fragment的传值问题

最后更新于:2022-04-01 14:25:51

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)
';