Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!


前言

上一篇文章中我们使用底部导航+Fragment的方式实现了Android主流App中大都存在的设计。并命名其为“Fragment最佳实践”,作为想到单独使用Fragment的用户来说,这个说法并不夸大,它解决了许多用户在使用Fragment时产生的这样那样可见或不可见的问题。不过Fragment还有其他的使用方式,就是我们本章要介绍的。(本来是介绍ListView的,等着ListView的读者不好意思了,我会很快更新的。)

注:为什么临时插入这一章,因为有读者在上一篇文章中评论了,我觉得大有道理,感谢

这里我就不打码了,,哈哈哈哈

TabLayout

TabLayout的静态使用

TabLayout是Android 5.0之后Google提供的一系列Material Design设计规范中的一个控件。我们在布局文件中可以这样使用

<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    app:tabIndicatorHeight="0dp"
    app:tabSelectedTextColor="@color/colorPrimary"
    >

    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 1"/>
    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 2"/>
    <android.support.design.widget.TabItem
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tab 3"/>
</android.support.design.widget.TabLayout>

TabLayout间接继承于ViewGroup,其内可包含0到n个TabItem,这个TabItem就是我们经常使用的标签,其是个自定义View

,这样我们就定义了一个包含3个标签页的TabLayout。其运行结果如下图:

TabLayout的动态使用

在布局文件中我们可以很方便定义顶部/底部 导航的布局。我们来看一下在代码中的使用

public class TabActivity extends AppCompatActivity {
    @BindView(R.id.tab_layout)
    TabLayout mTabLayout;
    @BindView(R.id.view_pager)
    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab);
        ButterKnife.bind(this);

        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3"));

        //为TabLayout添加Tab选择事件监听
        mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {//当标签被选择时回调

            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {//当标签从选择变为非选择时回调

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {//当标签被重新选择时回调

            }
        });

    }
}

关于运行结果我就不上图了,跟上面的运行结果是一样的。

TabLayout的更多属性

关于TabLayout的更多属性以及使用的说明请查看其官方文档。在这里我们只关心TabLayout+ViewPager的化学反应,这个组合也是我们平常在开发中使用最多的。在此之前我们先介绍ViewPager

ViewPager

先看看官方对ViewPager的说明

/*
Layout manager that allows the user to flip left and right
through pages of data.  You supply an implementation of a
{@link PagerAdapter} to generate the pages that the view shows.

ViewPager is most often used in conjunction with {@link android.app.Fragment}
There are standard adapters implemented for using fragments with the ViewPager,
which cover the most common use cases.  These are
{@link android.support.v4.app.FragmentPagerAdapter} and
{@link android.support.v4.app.FragmentStatePagerAdapter};*/

public class ViewPager extends ViewGroup {
}

上面英文的大致意思是ViewPager是一个布局管理类,这个类呢允许用户左右翻转页面。你必须实现一个PagerAdapter来生成这些显示的页面。ViewPager经常和Fragment一起使用。而且呢Google非常贴心的提供了两个类FragmentPagerAdapter和FragmentStatePagerAdapter来应付那些一般场景。

其实从ViewPager的说明中,我们基本上就能知道ViewPager是什么以及如何使用了。

PagerAdapter

ViewPager继承于ViewGroup,官方指导中就说了,你要自己实现PagerAdapter来生成显示的页面,那么我们来看看这个PagerAdapter

/**
 * Base class providing the adapter to populate pages inside of
 * a {@link ViewPager}.  You will most likely want to use a more
 * specific implementation of this, such as
 * {@link android.support.v4.app.FragmentPagerAdapter} or
 * {@link android.support.v4.app.FragmentStatePagerAdapter}.
 *
 * <p>When you implement a PagerAdapter, you must override the following methods
 * at minimum:</p>
 * <ul>
 * <li>{@link #instantiateItem(ViewGroup, int)}</li>
 * <li>{@link #destroyItem(ViewGroup, int, Object)}</li>
 * <li>{@link #getCount()}</li>
 * <li>{@link #isViewFromObject(View, Object)}</li>
 * </ul>
 * /
public abstract class PagerAdapter {
}

其实我们在看一个不太了解的类的时候,通过源码上的关于这个类的说明就可以知道很多信息了。关于PagerAdapter的说明就是如此。

先说了一下PagerAdapter的作用,是一个基类提供适配器给ViewPager中的页面,如果你想使用特定的实现类,那么你可以看两个类FragmentPagerAdapter和FragmentStatePagerAdapter,这两个类继承了PagerAdapter,并实现了其抽象方法。

后面一段的意思是你如果想自定义你自己的PagerAdapter,那么你最少要实现这4个方法

  1. instantiateItem(ViewGroup, int)
  2. destroyItem(ViewGroup, int, Object)
  3. getCount()
  4. isViewFromObject(View, Object)

下面我们以代码的形式,说明这4个方法的含义以及如何使用

private class MyViewPagerAdapter extends PagerAdapter {

    /**
      * 获取View的总数
      *
      * @return View总数
      */
    @Override
    public int getCount() {
        return 0;
    }

    /**
     * 为给定的位置创建相应的View。创建View之后,需要在该方法中自行添加到container中。
     *
     * @param container ViewPager本身
     * @param position  给定的位置
     * @return 提交给ViewPager进行保存的实例对象
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }

    /**
     * 给定的位置移除相应的View。
     *
     * @param container ViewPager本身
     * @param position  给定的位置
     * @param object    在instantiateItem中提交给ViewPager进行保存的实例对象
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }

    /**
     * 确认View与实例对象是否相互对应。ViewPager内部用于获取View对应的ItemInfo。
     *
     * @param view   ViewPager显示的View内容
     * @param object 在instantiateItem中提交给ViewPager进行保存的实例对象
     * @return 是否相互对应
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return false;
    }

}

这4个方法是必须的,,另外还有一些不是必须,但是可能会用到的

/**
 * 当ViewPager的内容有所变化时,进行调用。
 *
 * @param container ViewPager本身
 */
@Override
public void startUpdate(ViewGroup container) {
    super.startUpdate(container);
}

/**
 * ViewPager调用该方法来通知PageAdapter当前ViewPager显示的主要项,提供给用户对主要项进行操作的方法。
 *
 * @param container ViewPager本身
 * @param position  给定的位置
 * @param object    在instantiateItem中提交给ViewPager进行保存的实例对象
 */
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    super.setPrimaryItem(container, position, object);
}

/**
 * 较多的用于Design库中的TabLayout与ViewPager进行绑定时,提供显示的标题。
 *
 * @param position 给定的位置
 * @return 显示的标题
 */
@Override
public CharSequence getPageTitle(int position) {
    return super.getPageTitle(position);
}

FragmentPagerAdapter

上面呢只是列举说明了一下PagerAdapter,看起来有些枯燥,都是些说明,那么我们来看一下实践,ViewPager通畅跟Fragment一起使用,即其所管理的页面通畅是Fragment,所以Google提供了两个适配器FragmentPagerAdapter和FragmentStatePagerAdapter,我们这节分析FragmentPagerAdapter。

 /**
 *真是不看不知道,一看吓一跳。FragmentPagerAdapter也是个抽象类,
 *
 */
public abstract class FragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentPagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public FragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    /**
     *抽象方法,看来这个函数要子类自己实现了
     *
     * @param position ViewPager中Item的位置
     * @return 位置相关联的Fragment
     */
    public abstract Fragment getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this
                    + " requires a view id");
        }
    }

    /**
     * 为给定的位置创建相应的fragment。创建fragment之后,需要在该方法中自行添加到container中。
     *
     * @param container ViewPager本身
     * @param position  给定的位置
     * @return 提交给ViewPager进行保存的实例对象,这里是Fragment
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    /**
     * 移除给定的位置相应的fragment。
     *
     * @param container ViewPager本身
     * @param position  给定的位置
     * @param object    在instantiateItem中提交给ViewPager进行保存的实例对象
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment)object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

    /**
     * @param position ViewPager中Item的位置
     * @return 唯一的ItemID
     */
    public long getItemId(int position) {
        return position;
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }
}

代码比较少,总共也就100多行,逻辑也比较清晰明了,我们来着重分析instantiateItem和destroyItem

    /**
     * 为给定的位置创建相应的fragment。创建fragment之后,需要在该方法中自行添加到container中。
     *
     * @param container ViewPager本身
     * @param position  给定的位置
     * @return 提交给ViewPager进行保存的实例对象,这里是Fragment
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        //开启事务
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        //得到指定位置Item的ID
        final long itemId = getItemId(position);

       //根据id和ViewPager的ID生成item的name
        String name = makeFragmentName(container.getId(), itemId);

        //以name为Tag查找对应的Fragment
        Fragment fragment = mFragmentManager.findFragmentByTag(name);

        if (fragment != null) {//如果找到了
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            //调用事务的attach
            mCurTransaction.attach(fragment);
        } else {//没找到
            //通过我们重写的getItem方法得到相应fragment
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

            //调用事务的add方法,并设置Tag
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        //如果frament不等于当前主要的Item

        if (fragment != mCurrentPrimaryItem) {
            //设置其Menu不可见
            fragment.setMenuVisibility(false);
            //设置其不可见
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

instantiateItem方法主要功能是为ViewPager生成Item。

那么destroyItem方法的主要功能是销毁ViwePager内的Item

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        //调用事务的detach方法
        mCurTransaction.detach((Fragment)object);
    }

FragmentStatePagerAdapter

关于FragmentStatePagerAdapter,读者可自行分析,代码也不长。需要注意的地方是,两者对于destroyItem的不同实现

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    while (mSavedState.size() <= position) {
        mSavedState.add(null);
    }
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);

    //调用事务的remove方法
    mCurTransaction.remove(fragment);
}

小结

ViewPager是个ViewGroup,与其他布局LinearLayout或者其他任意的ViewGroup并无本质的不同,它被Google建议与Fragment结伴使用,也是说ViewPager所包裹的是Fragment布局。ViewPager需要适配器PagerAdapter操作Fragment,这一点就像ListView需要适配器操作其内部的Item一样。

适配器PagerAdapter是个抽象类,并且依照官方说明,我们必须至少实现其4个重要方法。4个方法可能太多,所以Google提供了FragmentPagerAdapter以及FragmentStatePagerAdapter,这两个也是抽象类,不过我们的自定义Adapter只需要实现其中的getItem(int position)方法即可。

关于FragmentPagerAdapter以及FragmentStatePagerAdapter的不同,我这里再总结一下。FragmentPagerAdapter销毁item的时候最终调用FragmentTransaction的detach()方法,使用detach()会将view从viewtree中删除,和FragmentStatePagerAdapter中使用的remove()不同,此时fragment的状态依然保持着,在使用attach()时会再次调用onCreateView()来重绘视图,注意使用detach()后fragment.isAdded()方法将返回false。

实例

更改后的TabActivity对应的布局文件

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

    <!--ViewPager-->
    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        >

    </android.support.v4.view.ViewPager>
    <!--分割线-->
    <ImageView
        android:id="@+id/image_1"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#919292"
        android:layout_above="@+id/tab_layout"/>

    <!--TabLayout-->
    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:tabIndicatorHeight="0dp"
        app:tabSelectedTextColor="@color/colorPrimary"
        >

    </android.support.design.widget.TabLayout>

</RelativeLayout>

更改后的TabActivity

public class TabActivity extends AppCompatActivity {
    @BindView(R.id.tab_layout)
    TabLayout mTabLayout;
    @BindView(R.id.view_pager)
    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab);
        ButterKnife.bind(this);

        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 1"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 2"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Tab 3"));

        //自定义的Adapter继承自FragmentPagerAdapter
        final PagerAdapter adapter = new PagerAdapter
            (getSupportFragmentManager(), mTabLayout.getTabCount());

        //ViewPager设置Adapter
        mViewPager.setAdapter(adapter);

        //为ViewPager添加页面改变监听
        mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));

        //为TabLayout添加Tab选择监听

        mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                mViewPager.setCurrentItem(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });

    }

}

而我们自定义的MyPagerAdapter也非常简单

public class MyPagerAdapter extends FragmentPagerAdapter {
    //fragment的数量
    int nNumOfTabs;
    public MyPagerAdapter(FragmentManager fm, int nNumOfTabs)
    {
        super(fm);
        this.nNumOfTabs=nNumOfTabs;
    }

    /**
     * 重写getItem方法
     *
     * @param position 指定的位置
     * @return 特定的Fragment
     */
    @Override
    public Fragment getItem(int position) {
        switch(position)
        {
            case 0:
                GoodsFragment tab1=new GoodsFragment();
                return tab1;
            case 1:
                CategoryFragment tab2=new CategoryFragment();
                return tab2;
            case 2:
                TaskFragment tab3=new TaskFragment();
                return tab3;
        }
        return null;
    }

    /**
     * 重写getCount方法
     *
     * @return fragment的数量
     */
    @Override
    public int getCount() {
        return nNumOfTabs;
    }
}

ViewPager预加载与网络请求

ViewPager的预加载机制

ViewPager可通过setOffscreenPageLimit(int limit)函数设置ViewPager预加载的View数目

public void setOffscreenPageLimit(int limit) {
    //DEFAULT_OFFSCREEN_PAGES=1

    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                + DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}

可以看到该函数的源码,当我们传入的limit<1时,limit还是被设置为1,当limit与成员变量mOffscreenPageLimit的值不同时(成员变量mOffscreenPageLimit的默认值为1),更新成员变量mOffscreenPageLimit的值,然后调用populate()函数。

而这个populate()函数就是给我们的ViewPager准备缓存页面并显示当前页面用的。

假如说我采用下面的方法调用setOffscreenPageLimit(2),此时ViewPager的简单示意图

注:从上面的代码也可以看出ViewPager最少会预加载一个页面。在本例中,也是我们在显示TAB1的时候,ViewPager已经加载了TAB2,具体方式是通过instantiateItem方法,该方法内部调用了我们重写的getItem方法,TAB2所表示的Fragment的onCreateView等相关生命周期方法会被回调。

ViewPager的网络请求

ViewPager的预加载机制其实在某些时候是个很让人不爽的问题,比如我们在Fragment做网络请求数据的时候,我们网络请求的代码通常会放在onCreateView中,我们只是打开第1个Fragment,但是由于ViewPager会加载第2个Fragment,可能也执行了第2个Fragment的网络请求代码。

而避免上述问题的主要依靠

public void setUserVisibleHint(boolean isVisibleToUser)

setUserVisibleHint(boolean isVisibleToUser)是Fragment中的一个回调函数。当前Fragment可见时,setUserVisibleHint()回调,其中isVisibleToUser=true。当前Fragment由可见到不可见或实例化时,setUserVisibleHint()回调,其中isVisibleToUser=false。

setUserVisibleHint(boolean isVisibleToUser)调用时机

  1. 在Fragment实例化,即在ViewPager中,由于ViewPager默认会预加载左右两个页面。此时预加载页面回调的生命周期流程:setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart() --> onResume()

    此时,setUserVisibleHint() 中的参数为false,因为不可见。

  2. 在Fragment可见时,即ViewPager中滑动到当前页面时,因为已经预加载过了,之前生命周期已经走到onResume() ,所以现在只会回调:setUserVisibleHint()。

    此时,setUserVisibleHint() 中的参数为true,因为可见。

  3. 在Fragment由可见变为不可见,即ViewPager由当前页面滑动到另一个页面,因为还要保持当前页面的预加载过程,所以只会回调:setUserVisibleHint()。

    此时,setUserVisibleHint() 中的参数为false,因为不可见。

  4. 由TabLayout直接跳转到一个未预加载的页面,此时生命周期的回调过程:setUserVisibleHint() -->setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> onActivityCreate() --> onStart()

    --> onResume()

    此时回调了两次setUserVisibleHint() ,一次代表初始化时,传入参数是false,一次代表可见时,传入参数是true。这种情况比较特殊。

总结:无论何时,setUserVisibleHint()都是先于其他生命周期的调用,并且初始化时调用,可见时调用,由可见转换成不可见时调用,一共三次时机。

ViewPager的网络请求的优化实现

我们在使用ViewPager+Fragment显示数据的时候,我们通常会把网络请求的操作放在onCreateView->onResume之间的生命周期内。这可能带来的问题我们上面已经探讨了。那么怎么解决这个问题呢?

本篇总结

我们在本篇博客中比较详细的探讨了TabLayout+ViewPager+Fragment的使用,我们在许多主流App中都能看到这种顶部、底部导航的效果,并且在此基础上我们探讨了TabLayout+ViewPager+Fragment网络数据加载问题。

我们希望Fragment可见时加载网络数据,不可见时不进行或者取消网络请求。

public abstract class BaseFragment extends Fragment {
    protected View rootView;

    private Unbinder mUnbinder;
    //当前Fragment是否处于可见状态标志,防止因ViewPager的缓存机制而导致回调函数的触发
    private boolean isFragmentVisible;
    //是否是第一次开启网络加载
    public boolean isFirst;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (rootView == null)
            rootView = inflater.inflate(getLayoutResource(), container, false);
        mUnbinder = ButterKnife.bind(this, rootView);
        initView();
        //可见,但是并没有加载过
        if (isFragmentVisible && !isFirst) {
            onFragmentVisibleChange(true);
        }
        return rootView;
    }

    //获取布局文件
    protected abstract int getLayoutResource();

    //初始化view
    protected abstract void initView();

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            isFragmentVisible = true;
        }
        if (rootView == null) {
            return;
        }
        //可见,并且没有加载过
        if (!isFirst&&isFragmentVisible) {
            onFragmentVisibleChange(true);
            return;
        }
        //由可见——>不可见 已经加载过
        if (isFragmentVisible) {
            onFragmentVisibleChange(false);
            isFragmentVisible = false;
        }
    }

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

    /**
     * 当前fragment可见状态发生变化时会回调该方法
     *
     * 如果当前fragment是第一次加载,等待onCreateView后才会回调该方法,其它情况回调时机跟 {@link #setUserVisibleHint(boolean)}一致
     * 在该回调方法中你可以做一些加载数据操作,甚至是控件的操作.
     *
     * @param isVisible true  不可见 -> 可见
     *                  false 可见  -> 不可见
     */
    protected void onFragmentVisibleChange(boolean isVisible) {

    }

}

我们设计抽象基类BaseFragment,所有的公共行为我们都可以在这个基类中定义,那么我们的Fragment是否可见就是其中的一种行为,所以我们上面重写了Fragment的setUserVisibleHint方法。

public class GoodsFragment extends BaseFragment {

    @Override
    protected void onFragmentVisibleChange(boolean isVisible) {
        if(isVisible){
            //可见,并且是第一次加载
            lazyLoad();
        }else{
            //取消加载
        }
    }

    private void lazyLoad() {
        if (!isFirst) {
            isFirst = true;
        }
    }

    @Override
    protected int getLayoutResource() {
        return R.layout.fragment_goods;
    }

    @Override
    protected void initView() {

    }
}

我们设计GoodsFragment继承BaseFragment并重写其onFragmentVisibleChange以控制自身的网络请求。


本篇总结

本篇为读者介绍了另外一种导航页切换的实现,我们使用TabLayout+ViewPager+Fragment的方式,其中读者需要重点理解以下几点

  1. ViewPager是个ViewGroup,它所关机的布局就是通常是我们的Fragment布局。
  2. ViewPager的预加载机制、可能带来的问题及如何解决。
  3. 理解PagerAdapter,以及如何实现它
  4. 理解Google提供了两个特定场景的PagerAdapter实现类FragmentPagerAdapter以及FragmentStatePagerAdapter,以及他们的主要区别

下篇预告

下篇打算往Fragment中加点东西,ListView



此致,敬礼

原文地址:https://www.cnblogs.com/wangle12138/p/8419496.html

时间: 2024-10-08 14:36:17

Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment的相关文章

Android开发之漫漫长途 XIV——RecyclerView

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!! 前言 上文我们很详细的分析了ListView的使用.优化.及ListView的RecycleBin机制,读者如果对ListView不太清楚,那么请参看我的上篇博文.不过呢,Google Material

Android开发之漫漫长途 Ⅷ——Android Binder(也许是最容易理解的)

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师. 前言 我们在上一篇中比较详尽的介绍了Android的消息机

Android开发之漫漫长途 X——Android序列化

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师. 前言 上一篇中我们比较详尽的分析了ServiceManag

Android开发自学笔记—1.1(番外)AndroidStudio常用功能介绍

一.界面区介绍 1.项目组织结构区,用于浏览项目文件,默认Project以Android组织方式展示. 2.设计区,默认在打开布局文件时为设计模式,可直接拖动控件到界面上实现所见即所得,下方的Design和Text就是代码和设计模式的切换按钮,切换至TEXT时,左侧为代码编辑区,右侧为所见即所得的预览. 下图为切换至TEXT的界面 而我们常用的代码编辑时的界面就很简单,左边项目树,右侧代码编辑区,下图为代码编辑界面: 3.组件树,用于展示整个页面布局的层级关系. 4.属性区,显示选中控件的可编辑

Android开发自学笔记&mdash;1.1(番外)AndroidStudio常用功能介绍

一.界面区介绍 1.项目组织结构区,用于浏览项目文件,默认Project以Android组织方式展示. 2.设计区,默认在打开布局文件时为设计模式,可直接拖动控件到界面上实现所见即所得,下方的Design和Text就是代码和设计模式的切换按钮,切换至TEXT时,左侧为代码编辑区,右侧为所见即所得的预览. 下图为切换至TEXT的界面 而我们常用的代码编辑时的界面就很简单,左边项目树,右侧代码编辑区,下图为代码编辑界面: 3.组件树,用于展示整个页面布局的层级关系. 4.属性区,显示选中控件的可编辑

Monkey源码分析番外篇之Android注入事件的三种方法比较

原文:http://www.pocketmagic.net/2012/04/injecting-events-programatically-on-android/#.VEoIoIuUcaV 往下分析monkey事件注入源码之前先了解下在android系统下事件注入的方式,翻译一篇国外文章如下. Method 1: Using internal APIs 方法1:使用内部APIs This approach has its risks, like it is always with intern

【喵&quot;的Android之路】【番外篇】有关于null的一些知识点

[喵"的Android之路][番外篇]有关于null的一些知识点 1.首先,到底什么是null? null是Java中的一个关键字,用于表示一个空对象引用,但其本身并不是任何类型也不是属于任何对象.因此,下面的做法是错误的: int a = null; 但: Object obj = null; 是可以的,因为null表示Object类型的一个空对象引用,符合其用意. [注1]引用类型使用null声明对象变量后,无法使用该变量访问对象的成员.例如上述obj对象如果使用obj.toString()

【喵&quot;的Android之路】【番外篇】关于==和equals

[喵"的Android之路][番外篇]关于==和equals 在实际的编程当中,经常会使用==和equals来判断变量是否相同.但是这两种比较方式也常常让人搞得云里雾里摸不着头脑.下面是我个人做的总结,希望能起到拨云见日的作用. [讲前普及] 请阅读 [喵"的Android之路][基础篇(一)][Java面向对象基础]数据类型与运算符 了解Java基本数据类型和引用数据类型 1."=="运算符 通俗点讲,==运算符比较的是两个变量在栈中的内容是否相同. 以a ==

Android好奇宝宝_番外篇_看脸的世界_05

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门 先上效果图(2G内存的机子运行模拟器,所以有点卡): 这个效果之前不知道在哪里看到过,我也忘了. 下面进入正题: 测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门. 对比上一篇的扩展: (1)SeekBar能通过触摸改变刻度 (2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示) (1)通