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); } }就可以发现传值成功了。
在上面的例子上进行修改,在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.
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间的传值。
首先分析下思路。其实最简单的做法就是在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做的很简单,只是为了讲解用,但再复杂的传值也是基于这些方式。就讲到这吧。
代码下载