Fragment-踩坑

背景

Fragment已经成为Android开发界面设计中不可或缺的一部分,同时也发挥着愈来愈重要的角色,虽然Fragment已经能出色的项目开发,可是在使用过程当中也暴露了愈来愈多的问题,虽然google也一直在及时的修复,可是仍是有不少坑,因此决定记录Fragment使用过程当中的使用问题,避免小伙伴们重复踩坑。java

在了解踩坑以前,咱们须要先了解Fragment的使用要点和使用方法android


Fragment介绍

做为 view 界面的一部分,Fragment 的存在必须依附于 FragmentActivit使用,而且与 FragmentActivit 同样,拥有本身的独立的生命周期,同时处理用户的交互动做。同一个 FragmentActivit 能够有一个或多个 Fragment 做为界面内容,一样Fragment也能够拥有多个子Fragment,而且能够动态添加、删除 Fragment,让UI的重复利用率和易修改性得以提高,一样能够用来解决部分屏幕适配问题。git

另外一方面,support v4 包中也提供了 Fragment,兼容 Android 3.0 以前的系统,使用兼容包须要注意两点:github

  • 宿主Activity 必须继承自 FragmentActivity网络

  • 使用getSupportFragmentManager() 方法获取 FragmentManager 对象;异步


生命周期

Fragment一样是具有了独立的生命周期,可是和Activity的生命周期还有不同的地方,如图:原图地址ide

Fragment初始化

Fragment默认有两种初始化的方法,一种new另外一种是嵌入xml动画

  • newthis

    FirstFragment firstFragment=new FirstFragment();复制代码
  • xmlgoogle

    <fragment
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          class="com.wzgiceman.fragmentpit.Fragment.FirstFragment"/>复制代码

上面两种方法均可以初始获得一个Fragment对象,可是前者比后者的有点在于,前者更加的灵活,因此推荐使用第一种方式。


ActivityFragment传参

默认建立Fragment系统已经给咱们初始了传参的代码

/** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment FirstFragment. */
    // TODO: Rename and change types and number of parameters
    public static FirstFragment newInstance(String param1, String param2) {
        FirstFragment fragment = new FirstFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }复制代码

这无疑是最好的选择


回调

Fragment 类提供有startActivityForResult()方法用于 Activity 间的页面跳转和数据回传,其实内部也是调用 Activity 的对应方法。可是在页面返回时须要注意 Fragment 没有提供 setResult() 方法,能够经过宿主 Activity 实现。


FragmentManagerFragmentTransaction使用

FragmentManager

Activity中使用Fragment可使用getSupportFragmentManager获取一个FragmentManager对象,可是在Fragment中显示子Fragment须要调用FragmentgetChildFragmentManager()

源码以下:

public final FragmentManager getChildFragmentManager() {
        throw new RuntimeException("Stub!");
    }复制代码

FragmentTransaction

Fragment 的动态添加、删除等操做都须要借助于 FragmentTransaction 类来完成,好比上面提到的 commit() 操做,下面是几种经常使用的方法:

  • add() 系列:添加 Fragment 到 Activity 界面中;

  • remove():移除 Activity 中的指定 Fragment;

  • replace() 系列:经过内部调用 remove() 和 add() 完成 Fragment 的修改;

  • hide() 和 show():隐藏和显示 Activity 中的 Fragment;

  • addToBackStack():添加当前事务到回退栈中,即当按下返回键时,界面回归到当前事物状态;

  • commit():提交事务,全部经过上述方法对 Fragment 的改动都必须经过调用 commit() 方法完成提交

replace()hide()区别

replace()hide()均可以动态的在Activity中显示多个Fragment,而且能够来回灵活的切换,可是它们有很大的区别,replace() 方法不会保留 Fragment 的状态,也就是说诸如 EditText 内容输入等用户操做在 remove() 时会消失;可是hide()却不会,能完整的保留用户的处理信息。

addToBackStack()退栈
当用户按下返回键时,若是回退栈中保存有以前的事务,会先执行事务回退,而后再执行Activityfinish()方法 。

简单使用

经过FragmentManagerFragmentTransaction结合使用,咱们能够将第一种初始化的Fragment动态的显示到界面中,这里使用replace()演示:

FragmentManager fm = getSupportFragmentManager();
 FragmentTransaction ft = fm.beginTransaction();
 ft.replace(R.id.fl_fragment, FirstFragment.newInstance("A","B"));
 ft.commit();复制代码

踩坑

在了解了Fragment的基础使用后,能够开始使用过程当中的踩坑了

getActivity() 引用问题

Fragment中经常须要使用到content对象,好比网络加载如今一个progress等等,这时候可能你遇到过getActivity()返回null,或者平时运行无缺的代码,在“内存重启”以后,调用getActivity()的地方却返回null,报了空指针异常。

大多数状况下的缘由:你在调用了getActivity()时,当前的Fragment已经onDetach()了宿主Activity
好比:你在popFragment以后,该Fragment的异步任务仍然在执行,而且在执行完成后调用了getActivity()方法,这样就会空指针;

解决办法

  • getContext()替代getActivity()

  • 定义全局变量,在FragmentonAttach(Activity activity)准备废弃或者onAttach(Context context)方法中初始化

    Context context;
      @Override
      public void onAttach(Context context) {
          super.onAttach(context);
          this.context=context;
      }复制代码

    显然第一种方法更加灵活方便了。


高耦合

当子Fragment须要调用宿主Acitivity的方法时,好比子Fragment须要发送一个广播,可是Fragment没有改方法,因此须要借助宿主Activity去发送,这时候经常须要强制转换content对象,而后调用宿主Acitivity发方发送广播,这种直接使用的方式违背了高聚低耦的设计原则;

解决办法

经过接口抽象的方法,经过接口去调用宿主Activity的方法。

  • 定义接口
/** * 发送广播 * Created by WZG on 2016/12/31. */

public interface SendBListener {
    void send();
}复制代码
  • 实现接口
public class FirstFragment extends Fragment {
    SendBListener listener;

    public void setListener(SendBListener listener) {
        this.listener = listener;
    }

    @OnClick(value = R.id.tv)
    void onTvClick(View view) {
        listener.send();
    }
   }复制代码
  • 调用
public class MainActivity extends AppCompatActivity implements SendBListener{
    @BindView(R.id.fl_fragment)
    FrameLayout mFlFragment;

    @Override
    public void send() {
        sendBroadcast(new Intent("xxxxxx"));
    }

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

        FirstFragment firstFragment=new FirstFragment();
        firstFragment.setListener(this);
      }
 }复制代码

重叠

因为采用建立对象的方式去初始化Fragment对象,当宿主Activity在界面销毁或者界面从新执行onCreate()方法时,就有可能再一次的执行Fragment的建立初始,而以前已经存在的 Fragment 实例也会销毁再次建立,这不就与 Activity 中 onCreate() 方法里面第二次建立的 Fragment 同时显示从而发生 UI 重叠的问题。

若是宿主界面Acitivity能够横竖屏切换,致使的生命周期从新刷新也同理可致使界面的重叠问题。

解决办法

  • 推荐:利用savedInstanceState判断
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        FirstFragment firstFragment;
        if (savedInstanceState==null) {
            firstFragment=new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        }else {
            firstFragment = (FirstFragment) fm.findFragmentByTag("FirstFragment");
        }

    }复制代码
  • Activity 提供的 onAttachFragment() 方法中处理
@Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
        if (fragment instanceof  FirstFragment){
            firstFragment = (FirstFragment) fragment;
        }
   }复制代码
  • 建立Fragment时判断
Fragment fragment = getSupportFragmentManager().findFragmentByTag("FirstFragment");
        if (fragment==null) {
            firstFragment =new FirstFragment();
            ft.add(R.id.fl_fragment, firstFragment, "FirstFragment");
        }else {
            firstFragment = (FirstFragment) fragment;
        }复制代码

Fragment转场动画

若是你想给下一个Fragment设置进栈动画和出栈动画,setCustomAnimations(enter, exit)只能设置进栈动画,第二个参数并非设置出栈动画;
请使用setCustomAnimations(enter, exit, popEnter, popExit),这个方法的第1个参数对应进栈动画,第4个参数对应出栈动画,因此是setCustomAnimations(进, exit, popEnter, 出))


Fragment状态监听

不少时候,咱们须要在多Fragment中刷新界面,固然因为Fragment有本身独立的生命周期可是也依赖宿主Activity存在,因此在刷新界面的时候须要注意如:

当宿主Activity A进入B中,又冲B返回到A,这时候宿主A执行onResume()方法,固然这时候的Fragment也会执行onResume()

当宿主Activity A中的Fragment所有初始完成显示过,在切换Fragment的时候不会再一次触发onResume()方法,可是却能够触发Fragment的onHiddenChanged(boolean hidden)方法

因此当咱们须要实时刷新Fragment界面的时候,须要同时结合onResume()onHiddenChanged(boolean hidden)方法去刷新当前显示Fragment而避免刷新hide()Fragment

使用

Override public void onResume() {
        super.onResume();
        //当前是不是现实状态
        if (isVisible()){
            //刷新界面
            updateUI();
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //方法重复发起刷新界面
        if (isVisible() && isResumed()){
            updateUI();
        }
    }复制代码

交流

QQ交流群,谈谈梦想,聊聊人生!

相关文章
相关标签/搜索