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

对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。

扫描二维码关注公众号,回复: 9188310 查看本文章

解决办法:

上面的例子“使用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;
    }
}
发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/73823032