在上一篇中我们详细介绍了Fragment的作用,使用方法,更是主要介绍了一个叫做FragmentTransaction类的主要方法。这一片中我们将把Fragment剩下的内容全部说完:
- Fragment退回栈介绍。
- Fragment与Activity通信。
目测篇幅不会很长。
Fragment回退栈
还记得我们再说Activity时候提到的一个任务栈Task Stack吗,它的效果和Tack Stack效果差不多,在使用时候我们只需要调用一个FragmentTransaction的方法就好:
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.frame, twoFragment)
.hide(oneFragment)
.addToBackStack(null)
.commit();
其他代码相信都很熟悉了,我们要说的是addToBackStack这个方法,点进去看一下这个方法:
/**
* Add this transaction to the back stack. This means that the transaction
* will be remembered after it is committed, and will reverse its operation
* when later popped off the stack.
*
* @param name An optional name for this back stack state, or null.
*/
public abstract FragmentTransaction addToBackStack(@Nullable String name);
翻译备注:将此事务添加到回退栈中,这意味这个事务递交之后会被记录,而当弹出栈时会扭转其操作。
其实后半句话我也不太懂是什么意思-。+,但是大体的意思我们知道了,他是将我们的FragmentTransaction存储起来了。然后点击Back键时,会将事务弹出栈。那么我们显示的Fragment肯定就消失了。
可能在我们以前的理解中以为是将Fragment存储到回退栈中。的确,效果跟存储Fragment一样,但是通过这些注释我们知道,他的本质是存储FragmentTransaction。
接下来我们做几个个简单的小实验:
1.在MainActivity中先显示OneFragment,然后再显示TwoFragment,都把他们放入退回栈中:
findViewById(R.id.fra1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.frame, oneFragment)
.addToBackStack(null)
.commit();
}
});
findViewById(R.id.fra2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.frame, twoFragment)
.addToBackStack(null)
.commit();
}
});
还是上一次的代码:这是MainActivity中的分别显示两个碎片。两个碎片的布局就不展示了。
我们看一下效果:
效果很明显:依次OneFragment和TwoFragment,点击Back会依次退出Fragment,看一下打印的日志也是对应的:
可能大家注意到了,我在OneFragment中最下面放置了一个输入框。其实这样还有一个问题:
大家注意下我上面实现显示的代码,是使用add添加的。现在有这样一个案例:我们在MainActivity中显示OneFragment,然后在OneFragment中显示TwoFragment(其实道理跟上面的一样,只不过是在OneFragment中显示TwoFragment了)。
我们有没有想过:由于整个Fragment是在帧布局作为容器的,那么根据帧布局的特性,他会将当前视图覆盖,但是还是保留当前视图可获取焦点的状态,如果这么说,那么在点开TwoFragment之后,还是可以给OneFragment的输入框输入数据:
我在OneFragment界面输入了123,当显示TwoFragment之后,又点击了那个位置,输入了456。点击Back之后果然能看到456。我想在做设计的时候如果这样,那真是太糟糕了。
有以下两种解决办法:
1.将Fragment添加方式设置为replace(或者先remove后add)。
这样我们就不会在TwoFragment显示的时候点击到之前OneFragment的输入框了,因为此时OneFragment的视图层次已经被销毁了(但是Fragment实例仍然保存,详细请看上一篇中关于FragmentTransaction的介绍:Fragment详解(1))
findViewById(R.id.fra2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction
// .add(R.id.frame, twoFragment)
.replace(R.id.frame,twoFragment)
.addToBackStack(null)
.commit();
}
});
看一下现在的效果:
但是这样也有一个弊端:如果说我们在OneFragment中输入框输入到一半的时候就跳转,但是我们还想保留OneFragment中的内容,这该怎么办呢?
2.hide隐藏OneFragment,在通过add显示TwoFragment。
我们知道hide是让Fragment隐藏,这样一来我们显示TwoFragment的时候,就不会点开OneFragment中的EditText了,而且OneFragment也在回退栈中,所以在我们点击Back关闭TwoFragment时,OneFragment又会显示出来。
代码如下:
findViewById(R.id.fra1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction
.add(R.id.frame, oneFragment)
.addToBackStack(null)
.commit();
}
});
findViewById(R.id.fra2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction
.hide(oneFragment)
.add(R.id.frame, twoFragment)
.addToBackStack(null)
.commit();
}
});
效果 :
以上便是关于回退栈的常用知识。接下来我们了解一下Fragment和Activity之间的通信:
Fragment与Activity通信:
我们知道Fragment是绑定在某个Activity上的,在有些时候我们需要Fragment给Activity传递数据,或在Fragment中对Activity进行操作,而在Activity中也需要调用对应Fragment的方法,以及数据的传递。
其通信方式主要分为以下两种:
- Activity调用Fragment相关属性。
- Fragment中调用Activity相关属性。
1.Activity调用Fragment相关属性:
该情况下有以下几种方式:
- 在Activity中有Fragment的引用,通过引用调用Fragment的public方法。
- Activity中通过FragmentManager.findFragmentById或findFragmentByTag方法获取Fragment引用,调用public方法。
2.Fragment中调用Activity相关属性:
我们可以可以通过getActivity方法获取Activity实例,但是我们一般情况都不会直接这么用,而是通过接口回调的方式实现:
public class OneFragment extends Fragment {
private static final String TAG = "OneFragment";
Button back;
private CallbackListener listener;
public interface CallbackListener {
void fromOneFragment(String str);
}
......
}
我们在OneFragment中创建了一个内部接口,并在OneFragment中声明了一个接口引用。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listener = (CallbackListener) getActivity();
Log.e(TAG, "onActivityCreated: ");
}
在onActivityCreated方法中给接口属性赋值。我们发现获取的Activity对象强转成了CallBackListener,所以我们需要在MainActivity中实现该接口:
public class MainActivity extends AppCompatActivity implements OneFragment.CallbackListener{
...
@Override
public void fromOneFragment(String str) {
Log.e(TAG, "fromOneFragment: " + str );
}
}
我们没有干什么,只是将传入Activity的数据打印一条日志。
在OneFragment中我们添加了一个按钮,用于从OneFragment向Activity传入数据:
back = (Button) view.findViewById(R.id.back_to_main);
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null)
listener.fromOneFragment("我是从OneFragment来的!!");
}
});
现在我们看一下效果:
这条日志确实是从MainActivity中打印出来的,而且就是我们从OneFragment传入的数据。
关于Fragment和Activity通信的最佳实践
关于这一点,本人是看了鸿阳大神的博客,在里面有提到这个说法,我觉得很有道理,所以这里融合自己的观点介绍一下:
我们可能通过Activity启动一个Fragment,也可能是从一个Fragment中启动另一个Fragment,但是我们都知道,这其中的Fragment都是与这个Activity相关联的,所以如果有Fragment启动Fragment的情况,我们不应该让一个Fragment直接去启动另一个Fragment,而是通过回调给MainActivity,让他去启动另一个Fragment。
<Button
android:id="@+id/add_twofragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启动TwoFragment"
android:textAllCaps="false"/>
首先这是OneFragment之前回调的按钮,我们进行界面上简单的修改。
接着我们看OneFragment中回调的方法:
@Override
public void fromOneFragment(String str) {
Log.e(TAG, "fromOneFragment: " + str);
getSupportFragmentManager().beginTransaction()
.hide(oneFragment)
.add(R.id.frame, twoFragment)
.addToBackStack(null).commit();
}
这样就实现了在OneFragment中启动TwoFragment的规范设计。效果如下:
以上便是关于Fragment常用到的内容了,如果有扩充的话,本人会在后面继续补充。喜欢的朋友可以关注一波,还是希望各位大大可以指出不明确的地方,谢谢大家支持!!