详解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)