Android之 Fragment页面碎片详解

一 简介

1.1 Fragment是Android3.0新增的概念,中文意思是“碎片”,它与Activity非常相似,是用来描述一些行为或者一部分用户界面

1.2 可以在一个单独的Activity中建立多个Fragment面板,也可以在多个Activity中复用Fragment

1.3 Fragment拥有自己的生命周期和接收、处理用户的事件,可以动态的添加、替换和移除某个Fragment

1.4 Fragment必须总是被嵌入到一个Activity中,它的生命周期受宿主Activity生命周期影响,它的状态会随宿主的状态变化而变化

1.5 要创建一个Fragment 必须创建一个Fragment的子类,或者继承自另一个已经存在的Fragment的子类,并重写onCreateView()方法加载UI

二 Fragment生病周期

图示:

Fragment生命周期

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
  • onCreate():Fragment被创建时调用。
  • onCreateView():创建Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

三  Fragment 静态使用

3.1 使用步骤实战:

第一步:创建fragment

public class Fragment1 extends Fragment {
    private TextView tvText;

 
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }
}

 第二步:创建Activity

public class TestActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
    }
}

第三步:在activity的布局文件xml里面设置fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <fragment
        android:id="@+id/fragment1"
        android:name="com.dinghe.servicetest.fragment.Fragment1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
</LinearLayout>

3.2 注意:

  • 通过name属性将Fragment引入Activity布局,被引入的Fragment要写全路径
  • fragment必须指定id,否则报错

四 Fragment的动态使用

4.1 使用步骤

第一步:还是创建fragment

public class Fragment1 extends Fragment {
    private TextView tvText;

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }
}

第二步: 创建Activity的布局文件xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

第三步:创建Activity,并在上面的fl_content里面添加fragment

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        Fragment1 fragment1 = new Fragment1();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fl_content, fragment1);
        transaction.commit();
    }
}

4.2 使用流程总结:

  • 获取到FragmentManager,在Activity中可以直接通过getSupportFragmentManager得到
  • 开启一个事务,通过调用getSupportFragmentManager().beginTransaction()方法开启
  • 向容器内加入Fragment,一般使用replace方法实现,需要传入容器的id和Fragment的实例
  • FragmentTransaction提交事务,调用commit方法提交

五 Fragment的add使用

5.1 除了上面的 transaction.replace() 添加Fragment方法,还有 transaction.add() 也可以添加,添加之后就可以用 transaction.show() 和 transaction.hide() 来控制Fragment显示和隐藏

5.2 那replace和add方法有什么区别呢?

replace是覆盖的意思,使用该方式会替换原有的Fragment,这就意味着原来的Fragment会结束其生命周期,缓存会被清理,会释放内存。

add是添加的意思,可以添加很多个Fragment,通过show和hide来控制显示和隐藏,所以该方式会缓存Fragment的全部内容,这也会导致内存会累加,占用大量内存。

总结起来,一个Fragment两者基本没区别,多个Fragment:

  • replace可以节省内存,但多个fragment频繁创建和删除会造成性能的开销大。
  • 而add可以缓存Fragment,性能流畅,但也伴随着内存的增加,可能发生OOM风险。

5.3 使用:

第一步:创建两个Fragment

public class Fragment1 extends Fragment {
    private TextView tvText;



    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }
}
public class Fragment2 extends Fragment {
    private TextView tvText;



    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }
}

第二步: 一样创建Activity的布局文件xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <FrameLayout
        android:id="@+id/fl_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:text="页面1"/>
        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:text="页面2"/>
    </LinearLayout>
</LinearLayout>

第三步:Activity添加Fragment,并控制显示隐藏

public class TestActivity extends AppCompatActivity implements View.OnClickListener {
    private FrameLayout flContent;
    private Button button1;
    private Button button2;



    private Fragment1 fragment1;
    private Fragment2 fragment2;

    private  FragmentManager fragmentManager;
    private FragmentTransaction transaction;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        flContent = (FrameLayout) findViewById(R.id.fl_content);
        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);

        button1.setOnClickListener(this);
        button2.setOnClickListener(this);

    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button1:
                //先隐藏全部Fragment
                hideFragment();
                //添加并显示Fragment1
                fragmentManager = getSupportFragmentManager();
                transaction = fragmentManager.beginTransaction();
                if(fragment1==null){
                    fragment1 = new Fragment1();
                    transaction.add(R.id.fl_content, fragment1);
                }
                transaction.show(fragment1);
                transaction.commit();
                break;
            case R.id.button2:
                //先隐藏全部Fragment
                hideFragment();
                //添加并显示Fragment2
                fragmentManager = getSupportFragmentManager();
                transaction = fragmentManager.beginTransaction();
                if(fragment2==null){
                    fragment2 = new Fragment2();
                    transaction.add(R.id.fl_content, fragment2);
                }
                transaction.show(fragment2);
                transaction.commit();
                break;
        }
    }

    /**
     * 隐藏全部Fragment
     */
    private void hideFragment(){
        if(fragment1!=null){
            transaction.hide(fragment1);
        }

        if(fragment2!=null){
            transaction.hide(fragment2);
        }
        transaction.commit();
    }
}

5.4 使用注意:

  • 判断Fragment是否被添加过,没添加要先add,再show,避免重复添加
  • show之前要先隐藏其它Fragment,否则可能出现页面重叠
  •  每次提交commit()之后,再add,show,hide都需要新开始一个事务beginTransaction(),否则会报异常

六 Fragment与viewpager的使用

6.1 上面是通过按钮控制Fragment的显示和隐藏,还有一种配合ViewPager的使用,可以做左右滑动切换布局的效果,不用再手动控制添加,显示和隐藏,完全通过ViewPager的滑动状态来自动完成。

6.2 我们经常使用ListView,RecycleView等滑动控件,都有自己的Adapter适配器。ViewPager也是滑动控件,也有自己的适配器PagerAdapter,可以加载任何控件,比如VIew,ImageView,TextView等。如下PagerAdapte简单示例:

public class MyViewPagerActivity extends Activity {
    private ViewPager viewPager;


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

        viewPager = (ViewPager) findViewById(R.id.view_pager);

        List<View> viewList = new ArrayList<>();
        viewList.add(new View(this));
        viewList.add(new View(this));
        ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(viewList);
        //设置ViewPager适配器
        viewPager.setAdapter(viewPagerAdapter);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                //监听滑动距离
            }

            @Override
            public void onPageSelected(int position) {
                //监听滑动页码
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                //监听滑动状态,滑动中还是滑动结束
            }
        });
    }


    /**
     * ViewPager适配器
     */
    private class ViewPagerAdapter extends PagerAdapter {
        private List<View> viewList;

        public ViewPagerAdapter(List<View> viewList) {
            this.viewList = viewList;
        }

        // 获取要滑动的控件的数量,在这里我们以滑动的广告栏为例,那么这里就应该是展示的广告图片的ImageView数量
        @Override
        public int getCount() {
            return viewList.size();
        }

        // 来判断显示的是否是同一张图片,这里我们将两个参数相比较返回即可
        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0 == arg1;
        }

        // PagerAdapter只缓存三张要显示的图片,如果滑动的图片超出了缓存的范围,就会调用这个方法,将图片销毁
        @Override
        public void destroyItem(ViewGroup view, int position, Object object) {
            view.removeView(viewList.get(position));
        }

        // 当要显示的图片可以进行缓存的时候,会调用这个方法进行显示图片的初始化,我们将要显示的ImageView加入到ViewGroup中,然后作为返回值返回即可
        @Override
        public Object instantiateItem(ViewGroup view, int position) {
            view.addView(viewList.get(position));
            return viewList.get(position);
        }
    }

}

6.3 上面示例是ViewPager加载View控件,那ViewPager加载Fragment也是有适配器的,即FragmentPagerAdapter,它继承于上面说的PagerAdapter,专门用于加载Fragment。介绍如下:

  • FragmentPagerAdapter派生自PagerAdapter,它是专门用来呈现 Fragment页面的。
  • 该类中每一个生成的Fragment都将保存在内存中,所以这个适配器更适合那些数量相对较少,静态的页面。对于存在多个fragment的情况,一般推荐使用FragmentStatePagerAdapter。
  • FragmentPagerAdapter重载了几个必须实现的函数:getItem()、 getCount()。

6.4  我们常见的Activity种类有,Activity,FragmentActivity和AppCompatActivity,这对于Fragment的支持也不同,如下区别:

  • Activity是最基础的页面类,对应getFragmentManager方法来控制Activity和 Fragment之间的交互。
  • FragmentActivity间接继承自Activity,并提供了对v4包中support Fragment的支持,在FragmentActivity中必须使用getSupportFragmentManager方法来处理 support Fragment的交互。
  • AppCompatActivity继承自FragmentActivity,为Material Design风格控件提供 了便利。

6.5 示例:

第一步:还是创建两个Fragment

public class Fragment1 extends Fragment {
    private TextView tvText;



    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }
}
public class Fragment2 extends Fragment {
    private TextView tvText;



    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        tvText = (TextView) view.findViewById(R.id.tv_text);
        return view;
    }
}

 第二步:创建ViewPager布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

第三步:Activity里面加载 FragmentPagerAdapter 适配器

public class MyFragmentViewPagerActivity extends AppCompatActivity {
    private ViewPager viewPager;


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

        viewPager = (ViewPager) findViewById(R.id.view_pager);

        List<Fragment> viewList = new ArrayList<>();
        viewList.add(new Fragment1());
        viewList.add(new Fragment2());
        MyFragmentPagerAdapter viewPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),viewList);
        //设置ViewPager适配器
        viewPager.setAdapter(viewPagerAdapter);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                //监听滑动距离
            }

            @Override
            public void onPageSelected(int position) {
                //监听滑动页码
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                //监听滑动状态,滑动中还是滑动结束
            }
        });
    }

    /**
     * Fragment适配器
     */
    public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
        //1.创建Fragment数组
        private List<Fragment> mFragments;
        
        //2.接收从Activity页面传递过来的Fragment数组
        public MyFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments){
            super(fm);
            mFragments = fragments;
        }
        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }
        @Override
        public int getCount() {
            return mFragments.size();
        }
    }

}

6.6 可以看到 PagerAdapter 和 FragmentPagerAdapter 的用法几乎一样,只是把集合View换成了Fragment

七 Fragment事务Transaction详解

7.1 add()

添加一个Fragment到Activity中。

7.2 remove()

从Activity中移除一个Fragment。如果Fragment没有被加入回退栈中,则该Fragment会进行销毁。

7.3 replace()

用另一个Fragment替换当前的Fragment,本质上时先remove再add。

7.4 hide()

隐藏当前的Fragment,设置为不可见,不会销毁,与之对应的是show。本质是对View进行调用了View.GONE。

7.5 show()

显示之前隐藏的Fragment,与之对应的是hide。本质是对View进行调用了View.VISIBLE。

7.6 detach()

将视图进行移除,但是并不销毁Fragment,Fragment依旧由FragmentManager进行管理。

7.7 addToBackStack()

将Fragment加入返回栈。当移除或替换一个Fragment并向返回栈添加事务时,系统会停止(而非销毁)移除的Fragment。如果没有addToBackStack,则被替换的Fragment会直接进行销毁。

//示例,如果用户执行回退操作进行Fragment的恢复,被替换的Fragment将重新启动。
transaction.replace(R.id.fl_content, LearnFragment()).addToBackStack(null).commitAllowingStateLoss()

7.8 setMaxLifecycle()

设定Fragment生命周期最高上限。如果设置的生命周期上限低于当前的生命周期上限,则会进行回退设定的生命周期。

比如,当前生命周期为RESUME,如果设置MaxLifecycle为START,则生命周期则会回到RESUME。

说一个例子,在调用hide和show函数的时候,Frgament的生命周期并不会进行变化。但是我就是想要在show的时候让Fragment走到RESUME呢?此时可以进行如下操作。

//隐藏
transaction.setMaxLifecycle(fragment,Lifecycle.State.STARTED).hide(fragment)
//显示
transaction.setMaxLifecycle(fragment,Lifecycle.State.RESUMED).show(fragment)

7.9 commit/commitAllowingStateLoss()

提交一个事务。commit如果状态丢失则会抛异常,commitAllowingStateLoss则不会。 

八 Fragment提交事务种类区分

8.1 commit()

异步的操作。对应的同步方法为commitNow()。安排当前事务FragmentTransaction进行提交。但是提交后Fragment不会立即创建,而是由主线程异步来创建。也就是说使用commit()之后,你的Fragment不会被立即加入到Activity中。
本次提交,必须在Activity的onSaveInstanceState调用之前提交。否则会抛异常。

8.2 commitAllowingStateLoss()

和commit类似。但是如果本次是在Activity的onSaveInstanceState调用之后,那么本次提交记录在Activity恢复的时候,可能不被保存。

8.3 commitNow()

同步的操作。对应的异步方法为commit()。将事务立即提交。所有添加的Fragment会被立即初始化,并开始生命周期。所有被移除的Fragment将会被立即移除。调用这个方法,相当于调用commit,然后调用FragmentManager的executePendingTransactions()。

8.4 commitNowAllowingStateLoss()

和commitNow类似。但是如果在在Activity的onSaveInstanceState调用之后,那么本次提交记录在Activity恢复的时候,可能不被保存。

8.5 总结:

  • 每个FragmentTransaction只能提交commit一次。包括commit、commitNow、commitAllowingStateLoss、commitNowAllowingStateLoss。
  • 提交完一次之后,再commit就会抛出异常,也就是说不要每进行一个操作就commit一次,应该把多个操作都在FragmentTransact

九 Fragment通信和Fragment数据更新

9.1 在Fragment初始化的时候通过setArguments传值如下:

Activity新建Fragemnt的时候设置数据

public static QianggouChildFragment newInstance(int type, String categoryId) {
    QianggouChildFragment newFragment = new QianggouChildFragment();
    Bundle bundle = new Bundle();
    bundle.putInt("type", type);
    bundle.putString("categoryId", categoryId);
    newFragment.setArguments(bundle);
    return newFragment;
}

Fragment里面接收数据:

public class Fragment1 extends Fragment {

  @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        int type = getArguments().getInt("type");
        String categoryId = getArguments().getString("categoryId");
    }
}

9.2 获取Fragment实例更新数据

Fragment里面添加设置数据方法

public class Fragment1 extends Fragment {

    int type;
    String categoryId;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         type = getArguments().getInt("type");
         categoryId = getArguments().getString("categoryId");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        return view;
    }

    //设置数据
    public void setCategoryId(String categoryId) {
        this.categoryId = categoryId;
    }

    //获取数据
    public String getCategoryId() {
        return categoryId;
    }
}

Activity里面获取Fragment实例,设置数据和获取数据

//设置数据
public void setData(){
	if(fragment1!=null){
		fragment1.setCategoryId("111");
	}
}
//获取数据
public void getData(){
	if(fragment1!=null){
		fragment1.getCategoryId();
	}
}

 完整Activity

public class TestActivity extends AppCompatActivity implements View.OnClickListener {
    private FrameLayout flContent;
    private Button button1;
    private Button button2;



    private Fragment1 fragment1;
    private Fragment2 fragment2;

    private  FragmentManager fragmentManager;
    private FragmentTransaction transaction;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        flContent = (FrameLayout) findViewById(R.id.fl_content);
        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);

        button1.setOnClickListener(this);
        button2.setOnClickListener(this);

    }


    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button1:
                //先隐藏全部Fragment
                hideFragment();
                //添加并显示Fragment1
                fragmentManager = getSupportFragmentManager();
                transaction = fragmentManager.beginTransaction();
                if(fragment1==null){
                    fragment1 = new Fragment1();
                    transaction.add(R.id.fl_content, fragment1);
                }
                transaction.show(fragment1);
                transaction.commit();
                break;
            case R.id.button2:
                //先隐藏全部Fragment
                hideFragment();
                //添加并显示Fragment2
                fragmentManager = getSupportFragmentManager();
                transaction = fragmentManager.beginTransaction();
                if(fragment2==null){
                    fragment2 = new Fragment2();
                    transaction.add(R.id.fl_content, fragment2);
                }
                transaction.show(fragment2);
                transaction.commit();
                break;
        }
    }

    /**
     * 隐藏全部Fragment
     */
    private void hideFragment(){
        if(fragment1!=null){
            transaction.hide(fragment1);
        }

        if(fragment2!=null){
            transaction.hide(fragment2);
        }
        transaction.commit();
    }

    //设置数据
    public void setData(){
        if(fragment1!=null){
            fragment1.setCategoryId("111");
        }
    }

    //获取数据
    public void getData(){
        if(fragment1!=null){
            fragment1.getCategoryId();
        }
    }
}

 9.3 有种比较特殊,Fragment+ViewPager里面加载Fragment采用动态加载单例的方式,并不是Fragment的List集合,单例这种是获取不到Fragment实例的,如下实例:

public class MyPagerAdapter extends FragmentPagerAdapter {

	public MyPagerAdapter(FragmentManager fm) {
		super(fm);
	}

	@Override
	public Fragment getItem(int position) {
		return Fragment1.newInstance(type, categoryList.get(position).id);
	}

	@Override
	public int getCount() {
		return 2;
	}

}

通过id也获取不到,因为在ViewPager里Fragment是new出来动态加到ViewPager里的,上需的Id只是Fragment的布局文件的根Id,不是Fragment 的Id,故获取的Fragment为空。如下示例:

Fragment1 fragment1 =(Fragment1)getSupportFragmentManager().findFragmentById(R.id.fragment);

解决方法:通过tag获取

Fragemnt在创建的时候,系统会为Fragment分配tag,其格式如下:"android:switcher:" + R.id.viewpager + ":0"  其中0是Fragment在viewpager中的位置 

示例:

//获取当前fragment,android:switcher:2131364838:0
public Fragment getCurrentFragment(int position) {
	FragmentManager manager = getChildFragmentManager();
	Fragment fragment = manager.findFragmentByTag(makeFragmentName(mDataBinding.viewPager.getId(), position));
	return fragment;
}  

 使用:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
	@Override
	public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
	}

	@Override
	public void onPageSelected(int position) {


	}

	@Override
	public void onPageScrollStateChanged(int state) {
		//滑动结束,不是当前item,取消懒加载
		if (state == ViewPager.SCROLL_STATE_SETTLING) {
		    //遍历Fragment
			for (int i = 0; i < pagerAdapter.getCount(); i++) {
			    //获取Fragment实例
				Fragment1 fragment1 = (Fragment1) getCurrentFragment(i);
				if (fragment1 != null) {
				    //刷新数据
					mDataBinding.resetData();
				}
			}
		}
	}
});

 9.4 当然还有其它方式:比如Fragment集合获取索引实例,EventBus等消息机制来进行数据交互

十 Fragment懒加载

10.1 由于Fragment添加之后就会调用资源和请求数据,和ViewPager配合使用也是如此,可能刚初始化还未被用户可见就调用很多Fragment。为了解决这种问题,防止大量数据的加载,就有了懒加载的方案,所谓懒加载就是Fragment被用户可见的时候才去加载资源和请求数据。

10.2 利用setUserVisibleHint()配合FragmentPagerAdapter实现懒加载,Fragment提供了界面被用户可见的监听,即setUserVisibleHint(),用户可见会返回true,不可见会返回false。如下示例:

public abstract class BaseFragment extends Fragment {
 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return null;
    }


    /**
     * Fragment数据的懒加载.
     */
    protected boolean isVisible;//是否可见

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {
            //可见
            isVisible = true;
            lazyLoad();
        } else {
            //不可见
            isVisible = false;
        }
    }

    protected void lazyLoad() {
	  //懒加载数据,比如请求接口
    }
	
}

注意:setUserVisibleHint只有结合FragmentPagerAdapter才能使用,不然不会走 setUserVisibleHint方法

10.3 也可以用onHiddenChanged配合replace或者add,show,hide方法进行懒加载的,每次切换Fragment会走回调

public class FragmentTest extends Fragment {
    //判断是否已进行过加载,避免重复加载
    private boolean isLoad=false;
    //判断当前fragment是否可见
    private boolean isHidden=true;

    @Override
    public void onResume() {
        super.onResume();
        lazyLoad();
    }
	
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        //hidden==false 表示不可见
        isHidden=hidden;
        lazyLoad();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLoad=false; //注意当view销毁时需要把isLoad置为false
    }

    private void lazyLoad() {
        if (!isLoad&&!isHidden){
            //懒加载
            isLoad=true;
        }
    }
}

注意:onHiddenChanged在FragmentPagerAdapter不起用,只有add或者replace方式起用

十一 Fragment 返回监听

11.1 由于Fragment依赖与Activity,所以Fragment的返回键会被Activity拦截,那怎样来监听Fragment的返回事件呢,下面介绍三种方法

11.2 在宿主Activity的返回监听里面循环赋予Fragment监听回退功能,如下:

第一步:建一个基类,增加退回监听方法

public class BaseFragment extends Fragment {

    /*
     * fragment中的返回键
     *
     * 默认返回flase,交给Activity处理
     * 返回true:执行fragment中需要执行的逻辑
     * 返回false:执行activity中的 onBackPressed
     * */
    public boolean onBackPressed() {
        //返回true拦截
        return false;
    }


}

第二步:在Activity的onBackPressed 返回监听里面循环调用Fragment的方法

   @Override
    public void onBackPressed() {
        List<Fragment> fragments = getSupportFragmentManager().getFragments();

        for (Fragment fragment : fragments) {
            //如果是自己封装的Fragment的子类  判断是否需要处理返回事件
            if (fragment instanceof Fragment) {
                if (((Fragment) fragment).onBackPressed()) {
                    //在Fragment中处理返回事件
                    return;
                }
            }
        }
        super.onBackPressed();
    }

 11.3 在Fragment的onResume里面监听View的点击事件

@Override
public void onResume() {
    super.onResume();
    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
                // handle back button
                return true;
            }
            return false;
        }
    });
}

但这种方法存在一定的问题:当Fragment中的某些控件获取到焦点后,下述方法则无法再监听到 KeyEvent 

11.4  使用Google提供的 OnBackPressedDispatcher 实现监听

public abstract class BackFragment extends BaseFragment {

    /*
     * 执行放弃方法
     */
    abstract void abandon();

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        requireActivity().getOnBackPressedDispatcher().addCallback(this/*LifecycleOwner*/, mCallback);
    }

    private OnBackPressedCallback mCallback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            abandon();
        }
    };
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        mCallback.remove();
    }
}

这里OnBackPressedCallback的构造需要传一个参数enabled,标识fragment是否要处理这个返回键事件,我们可以动态设置这个值来决定fragment是否要处理用户的返回键事

十二 Fragment回退栈

12.1 我们知道Activity是有任务栈的,通过startActivity将Activity加入栈,点击返回按钮将Activity出栈。Fragment也是有栈的,称之为回退栈。

12.2 有了回退栈我们就可以像Activity一样管理Fragment的跳转和返回,之前见过有个应用是这样搞得,只有一个主Activity,其它全部是Fragment页面。

12.3 回退栈的使用

第一步:设置回退栈

public class BaseFragment extends Fragment {

    //跳转Fragment
    public void startToFragment(Context context, int container, Fragment newFragment) {
        FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.replace(container, newFragment);
        //添加回退栈
        transaction.addToBackStack(context.getClass().getName());
        transaction.commit();
    }

    //返回上一个页面
    public void back(){
        getActivity().getFragmentManager().popBackStack();
    }

    //返回到第一个Fragment,或者回退栈中某个Fragment之上的所有Fragment
    public void popAllFragment(){
        FragmentManager fragmentManager = getFragmentManager();
        fragmentManager.popBackStackImmediate(
                getActivity().getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}

第二步:添加三个Fragment

public class Fragment1 extends BaseFragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        return view;
    }
  
}
public class Fragment2 extends BaseFragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment2, container, false);

        return view;
    }
  
}
public class Fragment3 extends BaseFragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment3, container, false);

        return view;
    }
  
}

第三步:Activity加载Fragment1

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        Fragment1 fragment1 = new Fragment1();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fl_content, fragment1);
        transaction.commit();
    }
	
}

第四步:Fragment1 跳转 Fragment2

public class Fragment1 extends BaseFragment implements View.OnClickListener {
 
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        return view;
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.button1:
			    //跳转Fragment2
                startToFragment(getActivity(),R.id.fl_content,new Fragment2());
                break;
        }
    }
}

第五步:Fragment2 返回 Fragment1

public class Fragment2 extends BaseFragment implements View.OnClickListener {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        return view;
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.iv_back:
                //返回上个Fragment
                popBackFragment();
                break;
        }
    }
}

第六步:Fragment3 返回到 Fragment1

public class Fragment3 extends BaseFragment implements View.OnClickListener {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        return view;
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.iv_back:
                //返回到首页
                popBackFragment();
                break;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_29848853/article/details/130645278