Android开发笔记(十七)——Fragment详解
Fragment基本介绍和使用
- Fragment有自己的生命周期。
- Fragment依赖于Activity。
- Fragment通过
getActivity()
可以获取所在的Activity;Activity通过FragmentManager的findFragmentById()
或者findFragmentByTag()
获取Fragment。 - Fragment和Activity是多对多的关系。
新建一个package名为 fragment
,在这个包内创建一个Activity ContainerActivity
作为Fragment演示界面,对应会创建好布局 activity_container.xml
,之后在该包内创建两个Fragment分别为 AFragment
和 BFragment
,创建相对应的布局 fragment_a
和 fragment_b
。
在布局 fragment_a
的布局如下: fragment_b
类似。
AFragment.java
中的代码如下: BFragment.java
类似。
public class AFragment extends Fragment {
private TextView mTvTitle;
//设置布局文件
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//相当于Activity中setContentView的作用
View view = inflater.inflate(R.layout.fragment_a, container, false);
return view;
}
//设置findViewById
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTvTitle=view.findViewById(R.id.tv_title);
}
}
布局 activity_container.xml
如下:其中 FrameLayout
是帧布局,这个布局直接在屏幕上开辟出一块空白的区域,当我们往里面添加控件的时候,会默认把他们放到这块区域的左上角。
从 MainActivity
进入 ContainerActivity
,想要把AFragment放到 ContainerActivity
中, ContainerActivity.java
中的代码如下:
当前效果展示:
想要点击按钮,FrameLayout
中显示的内容从AFragment变成BFragment, 在 ContainerActivity.java
中的代码如下:替换的实现使用replace()函数
效果如下:
Fragment中getActivity()为null的问题
Fragment中获取当前所在的Activity可以使用 getActivity()
方法,切换到后台或者横竖屏去转换的时候,可能会出现 getActivity()
为null的问题。
向Fragment传递参数
之前添加Fragment的时候实例化是直接new一个Fragment,如果在实例化Fragment的时候希望传参,可以这样做:在 AFragment.java
中添加 newInstance
方法:在这个方法中实例化一个AFragment,并且给这个fragment setArguments,通过getArguments得到具体的值:
之后在 ContainerActivity.java
中改变一下实例化的方法:调用AFragment的一个静态的方法 newInstance
Fragment回退栈
更改之前的布局,activity_container.xml
的整个界面是一个FrameLayout
布局 fragment_a
更改如下:
ContainerActivity
中代码修改如下:在MainActivity上点击Button进入整个屏幕都是AFragment。
AFragment中的Button "更换为BFragment" 实现的是点击按钮,当前界面显示为BFragment的内容,button “更改TextView的文字内容” 实现的是点击按钮,AFragment中TextView的内容更改。
此时效果如下:
当点击按钮 "更换为BFragment" ,当前界面显示为BFragment的内容,返回时会直接返回到MainActivity中,如果想要返回上一届界面也就是AFragment时,可以如下操作:在commit之前把Fragment添加到回退栈当中去。
此时效果如下:
日志如下:
尽管AFragment没有被重新实例化,但是 onCreateView
方法还是会重新运行,也就是说实例还是同一个实例,但是视图会被重新创建,从显示效果可以看到,在AFragment中,先更换TextView的文字内容,再更换为BFragment,这时返回的时候可以发现,显示的文字依旧是之前的文字,这就验证了Activity还是之前的实例,但是 onCreateView
方法还是会重新调用,所以他的视图还又重新刷新了一遍,TextView回到了最开始设置的文字。
那么如果希望从BFragment返回的时候,AFragment中的TextView是之前更换过的状态,可以如下操作:先将AFragment隐藏掉,再add一个新的Fragment,之后再添加一个BFragment,接着添加到回退栈当中,最后commit,而不是直接replace(会导致前一个Fragment的视图没有被保存下来)
此时效果如下:
可以看到此时视图并没有被重新创建,而是保持了之前的视图状态,
Fragment和Activity的通信
希望在AFragment中点击Button,把ContainerActivity中的TextView设置文字(从Fragment中发出消息,通知Activity去执行的环节)
在ContainerActivity中写一个公共的方法:实现当调用这个方法的时候可以给TextView设置文字
public void setData(String text){
mTvTitle.setText(text);
}
在AFragment中添加点击事件如下:把getActivity()转换成ContainerActivity,然后调用ContainerActivity中的setData方法
mBtnMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((ContainerActivity)getActivity()).setData("你好!");
}
});
该方法可行但是并不推荐。更推荐下面的方法:
通过回调接口来实现Fragment和Activity的通信
在Activity中去实现一个在Fragment中声明好的接口,通过回调接口来实现数据的传递。
首先在Fragment中写一个接口:
public interface IOnMessageClick{
void onClick(String text);
}
让Activity去实现这个接口:
@Override
public void onClick(String text) {
mTvTitle.setText(text);
}
在AFragment中声明接口:
private IOnMessageClick listener;
当Fragment依附到Activity中时候,Fragment会调用一个方法 onAttach()
:把context强制转换成接口,如果转换失败,则Activity没有实现这个接口IOnMessageClick,就抛出异常。
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
listener = (IOnMessageClick) context;
}catch (ClassCastException e){
throw new ClassCastException("Activity 必须实现IOnMessageClick接口");
}
}
添加点击事件如下:
mBtnMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// ((ContainerActivity)getActivity()).setData("你好!");
listener.onClick("你好呀!");
}
});
这时候可以正常通信。