我们在上一篇文章《Android里Fragment的相关知识(一)》中介绍了Fragment的存在意义以及基本使用。本篇文章将进一步介绍Fragment的其它一些相关知识。
Fragment与Actvitiy之间的交互
一般情况下,因为要考虑Fragment的复用性,所以都要尽量地减少Fragment与Actvitity的耦合。至于两个Fragment之间直接交互就更不应该了,因为这样会使得代码交差得凌乱和严重影响可维护性,失去了使用的Fragment的意义。在Fragment中可以通过getActivity()得到当前Fragment所在的Actvitiy实例。而在Actvitiy中可以通过findViewById()像正常情况下一样得到的Fragment里所有控件的View对象,而想要获得整个Fragment对象,则要通过getFragmentManager.findFragmentByTag()或者findFragmentById()来获取,获得了Fragment对象后,等于获得了Fragment所在类的对象,所以可以通过此对象可直接访问类里的public方法。例如在Activity中可以这样:
MyFragment myFragment =(MyFragment)getFragmentManager().findFragmentById(R.id.my_fragment);
myFragment.testFunction();
经典示例
新建一个title的fragment:
public class TitleFragment extends Fragmentimplements View.OnClickListener {
public interface OnTitleSelectedListener {
void onTitleSelected(CharSequence text);
}
private TextView mTxtTitle1;
private TextView mTxtTitle2;
private OnTitleSelectedListener mListener;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
mTxtTitle1 = (TextView) view.findViewById(R.id.txt_title1);
mTxtTitle1.setOnClickListener(this);
mTxtTitle2 = (TextView) view.findViewById(R.id.txt_title2);
mTxtTitle2.setOnClickListener(this);
return view;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnTitleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implementOnTitleSelectedListener");
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.txt_title1) {
mListener.onTitleSelected(mTxtTitle1.getText());
} else if (v.getId() == R.id.txt_title2) {
mListener.onTitleSelected(mTxtTitle2.getText());
}
}
}
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@color/title"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_title1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="标题1"
android:textSize="20sp"
android:textColor="@color/title_text"
android:layout_weight="1"/>
<TextView
android:id="@+id/txt_title2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="标题2"
android:textSize="20sp"
android:textColor="@color/title_text"
android:layout_weight="1"/>
</LinearLayout>
注意OnTitleSelectedListener这个接口,它是为了让TitleFragment所在的Activity实现的事件回调,当在onClick中把TitleFragment想要传递的信息传递给Activity。TitleFragment的onAttach()回调方法(系统在向Activity 添加Fragment时调用的方法)会通过转换传递到onAttach()中的Activity来实例化 OnTitleSelectedListener的实例。
新建一个content的fragment:
public class ContentFragment extendsFragment {
private TextView mTxtContent;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container,false);
mTxtContent = (TextView) view.findViewById(R.id.txt_content);
return view;
}
public void setContent(CharSequence text) {
mTxtContent.setText(text);
}
}
<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content">
<TextView
android:id="@+id/txt_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="30dp"
android:text="我是ContentFragment" />
</RelativeLayout>
ContentFragment比较简单,布局就是在屏幕中间有一个TextView,而代码中定义了一个public的方法,用于所在Activity在接收到TitleFragment传递的信息后再次传递给ContentFragment,然后更新屏幕中间的TextView内容。
最后我们来看看Actvitity的实现:
public class MainActivity extends Activityimplements TitleFragment.OnTitleSelectedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onTitleSelected(CharSequence text) {
ContentFragment myFragment =(ContentFragment)getFragmentManager().findFragmentById(R.id.fragment_content);
myFragment.setContent(text);
}
}
<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment_title"
android:name="project.test.com.myapplication.TitleFragment"
android:layout_width="match_parent"
android:layout_height="40dp" />
<fragment
android:id="@+id/fragment_content"
android:name="project.test.com.myapplication.ContentFragment"
android:layout_below="@id/fragment_title"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
Activity继承了TitleFragment的OnTitleSelectedListener接口,实现了onTitleSelected方法,它像一个中介的角色,将TitleFragment中的信息最终传递到ContentFragment中去。
Fragment的返回栈
Fragment与Actvitiy之间存在一个主要的区别:当Actvitiy进入后台时,会被放到backstack中。这样当用户按back按钮时,Actvitiy可以恢复。但是,Fragment在进入后台时不会被自动放到back stack中。要实现这一目的,需要在Fragment调用commit()之前调用addToBackStack()方法。这样便可使Fragment任务添加到返回栈,该返回栈由Activity管理,允许用户通过按返回按钮返回上一Fragment状态,直到所有Fragment都从回退栈中弹出,则就会退出当前Actvitity。
示例
修改MainActivity:
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction =fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_main, new FragmentA(),"A");
fragmentTransaction.commit();
}
Log.d("FragmentTest", "MainActivity onCreate");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("FragmentTest", "MainActivity onDestroy");
}
}
<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fragment_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MainActivity在onCreate中动态加载了FragmentA
接着新建FragmentA:
public class FragmentA extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, container, false);
Button btnGoto = (Button) view.findViewById(R.id.btn_goto);
btnGoto.setOnClickListener(newView.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager= getFragmentManager();
FragmentTransactionfragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_main, new FragmentB(),"B");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
});
Log.d("FragmentTest","FragmentA onCreateView");
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d("FragmentTest", "FragmentA onDestroyView");
}
}
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是Fragment_A" />
<Button
android:id="@+id/btn_goto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转去B"/>
</LinearLayout>
FragmentA中按钮点击后跳转至FragmentB。
再接着新建FragmentB:
public class FragmentB extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_b, container, false);
Button btnGoto = (Button) view.findViewById(R.id.btn_goto);
btnGoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager= getFragmentManager();
FragmentTransactionfragmentTransaction = fragmentManager.beginTransaction();
//fragmentTransaction.replace(R.id.fragment_main, new FragmentC(),"C");
fragmentTransaction.add(R.id.fragment_main , new FragmentC(),"C");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
});
Log.d("FragmentTest", "FragmentB onCreateView");
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d("FragmentTest", "FragmentB onDestroyView");
}
}
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是Fragment_B" />
<Button
android:id="@+id/btn_goto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转去C"/>
</LinearLayout>
FragmentB中按钮点击后跳转至FragmentC。
最后加新建FragmentC:
public class FragmentC extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_c, container, false);
Log.d("FragmentTest", "FragmentC onCreateView");
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d("FragmentTest", "FragmentC onDestroyView");
}
}
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是Fragment_C" />
</LinearLayout>
当程序运行后,我们可以看到日志打印如下:
FragmentTest: MainActivity onCreate
FragmentTest: FragmentA onCreateView
FragmentTest: FragmentA onDestroyView
FragmentTest: FragmentB onCreateView
FragmentTest: FragmentC onCreateView
到了展示FragmentC后,我们点击返回按钮会返回到FragmentB,继续点击返回按钮会返回到FragmentA,再继续点击返回按钮就退出程序,日志打印如下:
FragmentTest: FragmentC onDestroyView
FragmentTest: FragmentB onDestroyView
FragmentTest: FragmentA onCreateView
FragmentTest: FragmentA onDestroyView
FragmentTest: MainActivity onDestroy
这里来仔细看看FragmentA和FragmentB在日志上的区别,FragmentA在跳转到FragmentB时,它会调到onDestroyView,然后到最后返回到FragmentA时,也会重新调到onCreateView。而FragmentB由始到终都是只调一次onCreateView和onDestroyView。原因就是FragmentA在跳转FragmentB时使用了replace()方法。我们在上节提到,replace()是会remove()再add(),而FragmentB在跳转FragmentC时使用了add()方法,等于FragmentC只是盖在FragmentB的上面,FragmentB一直都还存在。我们在实际开始中可以根据实际情况来决定使用replace()还是add()方法。
Fragment跳转中传递参数
我们知道,一个Activity跳转到另一个Activity时,可以通过Intent的putExtra来附加参数。而Fragment也是可以做到的,实现是Fragment的setArguments方法。如上示例FragmentA跳转FragmentB,我们来修改一下代码看看。
FragmentA:
FragmentManager fragmentManager =getFragmentManager();
FragmentTransaction fragmentTransaction =fragmentManager.beginTransaction();
// 增加的代码--------------------------------
Bundle bundle = new Bundle();
bundle.putString("fragmment_parameter","Hello Fragment");
FragmentB fragmentB = new FragmentB();
fragmentB.setArguments(bundle);
// 增加的代码--------------------------------
fragmentTransaction.replace(R.id.fragment_main,fragmentB, "B");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
FragmentB:
@Override
public View onCreateView(LayoutInflaterinflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_b, container, false);
// 增加的代码--------------------------------
Bundle bundle = getArguments();
if (bundle != null) {
String parameter = bundle.getString("fragmment_parameter");
Toast.makeText(getActivity(), parameter, Toast.LENGTH_SHORT).show();
}
// 增加的代码--------------------------------
……
}