Fragment+ViewPager实现项目首页

  • Post author:
  • Post category:其他



本文讲述如何使用Fragment+ViewPager实现项目首页。


一.代码实现


宿主Activity代码

public class FragmentMainActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener, ViewPager.OnPageChangeListener{

    private ViewPager viewPager;
    private CustomFragmentPagerAdapter pagerAdapter;

    private RadioGroup activityMainRadiogroup;
    private RadioButton activityMainIndexradiobutton;
    private RadioButton activityMainMissionradiobutton;
    private RadioButton activityMainArchivesradiobutton;

    public static final int PAGE_ONE = 0;
    public static final int PAGE_TWO = 1;
    public static final int PAGE_THREE = 2;

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

    /**
     * 初始化各种View
     */

    public void initView() {
        viewPager = findViewById(R.id.activity_mainfragment_viewpager);
        pagerAdapter = new CustomFragmentPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pagerAdapter);
        viewPager.setCurrentItem(0);//ViewPager 显示第一个
        viewPager.addOnPageChangeListener(this);//ViewPager滑动监听
        viewPager.setOffscreenPageLimit(2);//ViewPager左右缓存各2个Fragment的View

        activityMainRadiogroup = findViewById(R.id.activity_mainfragment_radiogroup);
        activityMainIndexradiobutton = findViewById(R.id.activity_mainfragment_indexradiobutton);
        activityMainMissionradiobutton = findViewById(R.id.activity_mainfragment_missionradiobutton);
        activityMainArchivesradiobutton = findViewById(R.id.activity_mainfragment_archivesradiobutton);
        activityMainRadiogroup.setOnCheckedChangeListener(this);
        activityMainIndexradiobutton.setChecked(true);//默认首页选中
    }

    /**
     * RadioGroup Item监听
     */

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.activity_mainfragment_indexradiobutton:
                viewPager.setCurrentItem(PAGE_ONE);
                break;
            case R.id.activity_mainfragment_missionradiobutton:
                viewPager.setCurrentItem(PAGE_TWO);
                break;
            case R.id.activity_mainfragment_archivesradiobutton:
                viewPager.setCurrentItem(PAGE_THREE);
                break;
        }
    }

    /**
     * ViewPager 监听
     */

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    /**
     * ViewPager 监听
     */

    @Override
    public void onPageSelected(int position) {

    }

    /**
     * ViewPager 监听
     * state的状态有三个,0:表示什么都没做  1:正在滑动,2:滑动完毕
     */

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == 2) {
            switch (viewPager.getCurrentItem()) {
                case PAGE_ONE:
                    activityMainIndexradiobutton.setChecked(true);
                    break;
                case PAGE_TWO:
                    activityMainMissionradiobutton.setChecked(true);
                    break;
                case PAGE_THREE:
                    activityMainArchivesradiobutton.setChecked(true);
                    break;
            }
        }
    }
}


宿主Activity布局

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

    <android.support.v4.view.ViewPager
        android:id="@+id/activity_mainfragment_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/activity_mainfragment_bottomview">

    </android.support.v4.view.ViewPager>

    <View
        android:id="@+id/activity_mainfragment_bottomview"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_above="@+id/activity_mainfragment_radiogroup"
        android:background="#CCCCCC" />

    <RadioGroup
        android:id="@+id/activity_mainfragment_radiogroup"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:layout_alignParentBottom="true"
        android:background="#FFFFFF"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/activity_mainfragment_indexradiobutton"
            style="@style/index_tab_style"
            android:drawableTop="@drawable/index_tab_indeximageview"
            android:text="@string/index_tab_indextextviewname" />

        <RadioButton
            android:id="@+id/activity_mainfragment_missionradiobutton"
            style="@style/index_tab_style"
            android:drawableTop="@drawable/index_tab_missionimageview"
            android:text="@string/index_tab_missiontextviewname" />

        <RadioButton
            android:id="@+id/activity_mainfragment_archivesradiobutton"
            style="@style/index_tab_style"
            android:drawableTop="@drawable/index_tab_archivesimageview"
            android:text="@string/index_tab_archivestextviewname" />

    </RadioGroup>

</RelativeLayout>


其中一个Fragment代码

public class OneFragment extends Fragment {

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d("TAG", "第一个Fragment页面onAttach方法");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("TAG", "第一个Fragment页面onCreate方法");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d("TAG", "第一个Fragment页面onCreateView方法");
        return inflater.inflate(R.layout.fragment_one, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d("TAG", "第一个Fragment页面onActivityCreated方法");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d("TAG", "第一个Fragment页面onStart方法");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d("TAG", "第一个Fragment页面onResume方法");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d("TAG", "第一个Fragment页面onPause方法");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d("TAG", "第一个Fragment页面onStop方法");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d("TAG", "第一个Fragment页面onDestroyView方法");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("TAG", "第一个Fragment页面onDestroy方法");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d("TAG", "第一个Fragment页面onDetach方法");
    }
}


Fragment布局

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

    <TextView
        android:id="@+id/fragment_one_textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/app_color"
        android:gravity="center"
        android:text="第一个Fragment页面"
        android:textColor="#ffffff"
        android:textSize="20dp" />


</RelativeLayout>


FragmentPagerAdapter

public class CustomFragmentPagerAdapter extends FragmentPagerAdapter {

    private final int PAGER_COUNT = 3;
    private OneFragment oneFragment = null;
    private TwoFragment twoFragment = null;
    private ThreeFragment threeFragment = null;

    public CustomFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
        oneFragment = new OneFragment();
        twoFragment = new TwoFragment();
        threeFragment = new ThreeFragment();
    }

    @Override
    public Fragment getItem(int position) {
        Fragment fragment = null;
        switch (position) {
            case FragmentMainActivity.PAGE_ONE:
                if (null == fragment) {
                    fragment = oneFragment;
                }
                break;
            case FragmentMainActivity.PAGE_TWO:
                if (null == fragment) {
                    fragment = twoFragment;
                }
                break;
            case FragmentMainActivity.PAGE_THREE:
                if (null == fragment) {
                    fragment = threeFragment;
                }
                break;
        }
        return fragment;
    }

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }

}


项目中底部由RadioGroup和RadioButton实现,也可使用ImageView+TextView实现(这样更能严格按照UI标注)


二.详解


1.ViewPager简介


一个页面切换的组件,我们可以往里面填充多个View,然后我们可以通过触摸屏幕左右滑动切换不同的View,和前面学习的ListView一样,我们需要一个Adapter(适配器),将要显示的View和 我们的ViewPager进行绑定,而ViewPager有他自己特定的Adapter——PagerAdapter。


另外,Google 官方是建议我们使用Fragment来填充ViewPager的,这样可以更加方便的生成每个Page以及管理 每个Page的生命周期。当然它给我们提供了两个不同的Adapter,他们分别是: FragmentPageAdapter和FragmentStatePagerAdapter。 上面项目用到是前者:FragmentPageAdapter


2.

ViewPager的缓存机制


ViewPager限定

预加载的页面个数

。 任何一个页面的左边可以预加载  limit  个页面,右边也可以加载  limit  个页面

setOffscreenPageLimit(int limit)


ViewPager里面还定义了一个 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;默认值 是1。即不手动设置的情况下,默认预加载一个页面。


3.

结合Fragment讲解ViewPager的缓存


假设当前有三个Fragment的tab。显示一个,预先加载下一个。这样你在移动前就已经加载了下一个界面,移动时就可以看到已经加载的界面了。


<1> ViewPager缓存预加载所有的Fragment。

viewPager.setOffscreenPageLimit(2);//ViewPager左右缓存各2个Fragment的View


三个Fragment的各种状态。


(1) 刚刚进入APP。

D/TAG: 第一个Fragment页面onAttach方法

D/TAG: 第一个Fragment页面onCreate方法



D/TAG: 第二个Fragment页面onAttach方法

D/TAG: 第二个Fragment页面onCreate方法



D/TAG: 第三个Fragment页面onAttach方法

D/TAG: 第三个Fragment页面onCreate方法



D/TAG: 第一个Fragment页面onCreateView方法

D/TAG: 第一个Fragment页面onActivityCreated方法



D/TAG: 第一个Fragment页面onStart方法

D/TAG: 第一个Fragment页面onResume方法



D/TAG: 第二个Fragment页面onCreateView方法

D/TAG: 第二个Fragment页面onActivityCreated方法



D/TAG: 第三个Fragment页面onCreateView方法

D/TAG: 第三个Fragment页面onActivityCreated方法



D/TAG: 第二个Fragment页面onStart方法

D/TAG: 第二个Fragment页面onResume方法



D/TAG: 第三个Fragment页面onStart方法

D/TAG: 第三个Fragment页面onResume方法


可见,除了第一个默认显示的Fragment,其他两个Fragment都执行到了

onResume方法

,也就是说

预加载了这两个Fragment



(2) 此时,点击第二个Fragment或点击第三个Fragment(即随意切换三个Fragment,三个Fragment的生命周期方法都不执行)


(3) 退出APP时。

D/TAG: 第一个Fragment页面onPause方法

D/TAG: 第二个Fragment页面onPause方法

D/TAG: 第三个Fragment页面onPause方法



D/TAG: 第一个Fragment页面onStop方法

D/TAG: 第二个Fragment页面onStop方法

D/TAG: 第三个Fragment页面onStop方法



D/TAG: 第一个Fragment页面onDestroyView方法

D/TAG: 第一个Fragment页面onDestroy方法

D/TAG: 第一个Fragment页面onDetach方法



D/TAG: 第二个Fragment页面onDestroyView方法

D/TAG: 第二个Fragment页面onDestroy方法

D/TAG: 第二个Fragment页面onDetach方法



D/TAG: 第三个Fragment页面onDestroyView方法

D/TAG: 第三个Fragment页面onDestroy方法

D/TAG: 第三个Fragment页面onDetach方法


即 三个Fragment相继销毁。


<2> ViewPager缓存预加载1个Fragment。

viewPager.setOffscreenPageLimit(1);//ViewPager左右缓存各1个Fragment的View


三个Fragment的各种状态。


(1) 刚刚进入APP。

D/TAG: 第一个Fragment页面onAttach方法

D/TAG: 第一个Fragment页面onCreate方法



D/TAG: 第二个Fragment页面onAttach方法

D/TAG: 第二个Fragment页面onCreate方法



D/TAG: 第一个Fragment页面onCreateView方法

D/TAG: 第一个Fragment页面onActivityCreated方法



D/TAG: 第一个Fragment页面onStart方法

D/TAG: 第一个Fragment页面onResume方法



D/TAG: 第二个Fragment页面onCreateView方法

D/TAG: 第二个Fragment页面onActivityCreated方法



D/TAG: 第二个Fragment页面onStart方法

D/TAG: 第二个Fragment页面onResume方法


即,除了默认的第一个Fragment,刚刚进入APP的时候

缓存预加载了第二个Fragment

。第三个Fragment没有生命周期方法被执行,也就是说没有被预加载。


(2) 此时,点击第二个Fragment。

D/TAG: 第三个Fragment页面onAttach方法

D/TAG: 第三个Fragment页面onCreate方法

D/TAG: 第三个Fragment页面onCreateView方法

D/TAG: 第三个Fragment页面onActivityCreated方法

D/TAG: 第三个Fragment页面onStart方法

D/TAG: 第三个Fragment页面onResume方法


即,预加载了第三个Fragment页面,执行了它的生命周期方法。


(3) 此时,点击第三个Fragment。

D/TAG: 第一个Fragment页面onPause方法

D/TAG: 第一个Fragment页面onStop方法

D/TAG: 第一个Fragment页面onDestroyView方法


即,将第一个Fragment销毁。因为只缓存一个Fragment。


(4) 此时,点击第一个Fragment。

D/TAG: 第一个Fragment页面onCreateView方法

D/TAG: 第一个Fragment页面onActivityCreated方法

D/TAG: 第一个Fragment页面onStart方法

D/TAG: 第一个Fragment页面onResume方法



D/TAG: 第三个Fragment页面onPause方法

D/TAG: 第三个Fragment页面onStop方法

D/TAG: 第三个Fragment页面onDestroyView方法


即,重新创建第一个Fragment,然后销毁第三个Fragment。


3.总结


有上述可知,

如果不缓存预加载全部的Fragment,在切换各个Fragment时,就要销毁某个Faragment,然后重建另外一个Fragment。这样反复操作显然不友好




如果缓存预加载全部的Fragment,每个Fragment生命周期是有保证的不会反复执行,但是预加载的不可见的Fragment的生命周期方法都执行了,如果涉及到网络请求。这样既浪费了流量,也不友好。


4.解决办法



预加载全部的Fragment,但是对于预加载不可见的Fragment,要通过方法不要执行这些页面的耗时方法(比如 网络请求)





Fragment的懒加载


setUserVisibleHint(boolean isVisibleToUser)



isVisibleToUser

:表示

当前Fragment是否对用户可见

。由此方法来实现Fragment的懒加载。


由Log日志可知该方法在onAttach方法之前执行。


代码

package com.dchealth.patient.XinSui.test.app;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.dchealth.patient.XinSui.R;

public class ThreeFragment extends Fragment {

    private boolean isRequestData = true;//是否请求数据
    private boolean isViewCreate = false;//View是否被创建
    private boolean isVisibleToUser = false;//Fragment是否用户可见

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d("TAG", "第三个Fragment页面onCreateView方法");
        View view = inflater.inflate(R.layout.fragment_three, container, false);
        isViewCreate = true;
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        requestData();
        Log.d("TAG", "第三个Fragment页面setUserVisibleHint方法isVisibleToUser----:" + isVisibleToUser);
    }

    /**
     * 请求数据
     */

    private void requestData() {
        if (isVisibleToUser && isRequestData && isViewCreate) {
            Toast.makeText(getActivity(), "第三个Fragment开始加载数据...", Toast.LENGTH_SHORT).show();
            isRequestData = false;
        }
    }

}


5.FragmentPagerAdapter与FragmentStatePagerAdapter的区别


<1> FragmentPagerAdapter




意思是说:实现这个Adapter意味着呈现的每一个fragment 页面都是持久化保存到fragment manager 中,它最适合于一些静态页面(比如不需要请求网络数据)。在这里,用户访问过的每一个fragment实例都会保存到内存,即使fragment中的view会destroy。


<2> FragmentStatePagerAdapter




这段注释是说,它会更适合有大量page的情况。因为它只缓存保存状态的Fragment,当Fragment变得不可见时(指Fragment不在Viewpage缓存内),不止是Fragment的View会destroy,连同Fragment实例也会destroy。也就是当Fragment被移除缓存后,它就会执行:onPause–>onStop–>onDestroyView–>onDestroy–>onDetach;


6.PagerAdapter

package com.dchealth.patient.XinSui.test.app;

import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;

public class MyPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return false;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }
}


<1> getCount( ):获得viewpager中有多少个view。


<2> destroyItem( ):移除一个给定位置的页面。适配器有责任从容器中删除这个视图。这是为了确保 在finishUpdate(viewGroup)返回时视图能够被移除。


<3> instantiateItem( ):①将给定位置的view添加到ViewGroup(容器)中,创建并显示出来 ②返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了, 当然你也可以自定义自己的key,但是key和每个view要一一对应的关系。


<4> isViewFromObject( ):判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是 代表的同一个视图(即它俩是否是对应的,对应的表示同一个View),通常我们直接写 return view == object;就可以了,至于为什么要这样讲起来比较复杂,后面有机会进行了解吧 貌似是ViewPager中有个存储view状态信息的ArrayList,根据View取出对应信息的吧。



版权声明:本文为weixin_37730482原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。