您的位置:首页 > 其它

Fragment使用过程中一些需要注意的点

2017-06-27 20:09 239 查看
对Fragment相关知识的一点复习和整理。

1 使用add-hide-show来显示和切换fragment

add(int containerViewId, Fragment fragment)
:将fragment添加到容器中,并不会remove掉容器中已有的fragment。

fragment默认是可见的,下面两个方法可以改变fragment的可见状态:

hide(Fragment fragment)
:隐藏fragment

show(Fragment fragment)
:显示fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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"
android:orientation="vertical"
tools:context="cn.szx.myapplication.MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:id="@+id/btn1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="show Fragment1" />

<Button
android:id="@+id/btn2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="show Fragment2" />
</LinearLayout>

<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Fragment fragment1, fragment2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn1).setOnClickListener(this);
findViewById(R.id.btn2).setOnClickListener(this);

fragment1 = new Fragment1();
fragment2 = new Fragment2();
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment1)
.add(R.id.fragment_container, fragment2)
.hide(fragment2)
.commit();
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
getFragmentManager().beginTransaction()
.hide(fragment2)
.show(fragment1)
.commit();
break;
case R.id.btn2:
getFragmentManager().beginTransaction()
.hide(fragment1)
.show(fragment2)
.commit();
break;
}
}
}


当fragment被add或replace到容器中时,会创建视图(onCreate -> onCreateView -> onStart -> onResume被调用),当fragment被从容器中remove掉或replace掉时,会销毁视图(onPause -> onStop -> onDestroyView -> onDestroy被调用)。

而当fragment被hide或show时,只是其可见性的改变,不会有视图的创建和销毁,因此上面8个生命周期方法中任何一个都不会被调用。

2 使用replace来显示和切换fragment

replace(int containerViewId, Fragment fragment)
:将fragment添加到容器中,在添加之前,会先remove掉容器中已有的全部fragment。

当Fragment对象被从容器中移除时(被remove掉或被另一个Fragment对象 replace掉),无论该Fragment对象是否被引用,它的视图都会被销毁(onPause -> onStop -> onDestroyView -> onDestroy都会被调用),例如在下面的代码中,当点击btn2时,容器中原有的Fragment1对象的视图就会被销毁。

尽管Fragment1对象的视图被销毁了,但是因为它还被成员变量fragment1引用,因此Fragment1对象本身并不会被销毁(表现为Fragment1对象的onPause -> onStop -> onDestroyView -> onDestroy被调用之后,成员变量fragment1并不为null)。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Fragment fragment1, fragment2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn1).setOnClickListener(this);
findViewById(R.id.btn2).setOnClickListener(this);

fragment1 = new Fragment1();
fragment2 = new Fragment2();
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment1).commit();
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment1).commit();
break;
case R.id.btn2:
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment2).commit();
break;
}
}
}


3 activity销毁重建后fragment重叠的问题

情景:

在activity的生命周期方法(如onCreate)中创建fragmentA并add到容器中,如果acitivity因旋转屏幕、内存不足等原因被销毁了,那么activity被重建后,你会发现容器中出现了重叠的多个fragmentA。

原因:

当activity可能被销毁时,系统会在onSaveInstanceState中保存fragmentA的各种状态。当activity重建时,系统会在onCreate中检测参数savedInstanceState,如果发现其中保存有fragmentA的信息,则会通过反射调用FragmentA类的空参数构造方法来重新创建fragmentA,并将savedInstanceState中保存的fragmentA的信息恢复到重新创建的fragmentA,然后再将此fragmentA添加到容器中。除此之外,我们在onCreate方法中创建fragmentA并add到容器中的代码也会被执行——这样一来,容器中就有了两个fragmentA,因此便出现了重叠现象。

注意:如果使用replace来显示fragment则不会有上述问题,因为replace在添加之前会先移除容器中的全部fragment。

解决办法:

上面的例子“使用add-hide-show来显示和切换fragment”中,就会存在fragment重叠的问题,下面以此为例来说明解决办法:

1.在activity的onCreate方法中检测参数savedInstanceState是否为null,如果非null则说明是销毁后重建,此时就不再将fragmentA add到容器中。

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn1).setOnClickListener(this);
findViewById(R.id.btn2).setOnClickListener(this);

fragment1 = new Fragment1();
fragment2 = new Fragment2();
if (savedInstanceState == null) {//非销毁后重建
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment1)
.add(R.id.fragment_container, fragment2)
.hide(fragment2)
.commit();
}
}


上面的Fragment来自
android.app
,这里其实还有一个系统库的bug,就是在保存恢复fragment的状态时,并不会保存和恢复fragment的显示隐藏状态,也就是说,假设在activity销毁之前,两个fragment一个显示一个隐藏,那么当activity重建之后,两个fragment都会变成显示状态,因此仍然会出现重叠现象。

建议使用最新版本的
android.support.v4.app
包中的Fragment
,这个Fragment已经解决了这个bug(fragment的显示和隐藏状态也会被保存和恢复)。

2.不保存fragment的状态:在Activity中重写onSaveInstanceState方法,将
super.onSaveInstanceState(outState);
注释掉,让其不再保存fragment的状态。

@Override
public void onSaveInstanceState(Bundle outState) {
//super.onSaveInstanceState(outState);
}


4 使用setArguments方法向Fragment传递数据

问题:

我们习惯通过构造方法来创建对象并向其传递数据,然而如果我们通过Fragment的构造方法来向Fragment传递数据的话,可能会出现问题。

我们知道,当activity被销毁、重建时,其中的fragment也会被销毁并重建。然而,重建fragment是通过反射调用其无参数的构造方法来实现的,也就是说此fragment带有传递数据功能的构造方法并没有被调用,因而数据并没有被传递给重建的fragment。

如下例所示:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (savedInstanceState == null) {//非重建
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, new Fragment1("ZhangSan"))
.commit();
}
}
}


public class Fragment1 extends Fragment {
String name = "nothing";

Fragment1() {

}
Fragment1(String name) {
this.name = name;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment1, null);
((TextView) view.findViewById(R.id.tv)).setText(name);
return view;
}
}


旋转屏幕之前,tv中显示的是”ZhangSan”,而旋转屏幕之后,tv中显示的是”nothing”。

解决办法:

在activity中使用setArguments来向fragment传递数据,在fragment中使用getArguments来获取数据。

当activity销毁和重建时,系统会保存和恢复传递给fragment的arguments,因此在重建的fragment中仍然可以通过getArguments来获取数据。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (savedInstanceState == null) {//非重建
Fragment1 fragment1 = new Fragment1();
Bundle bundle = new Bundle();
bundle.putCharSequence("name", "Zhangsan");
fragment1.setArguments(bundle);

getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment1)
.commit();
}
}
}


public class Fragment1 extends Fragment {
String name = "nothing";

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
name = (String) getArguments().getCharSequence("name");

View view = inflater.inflate(R.layout.fragment1, null);
((TextView) view.findViewById(R.id.tv)).setText(this.name);
return view;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: