Fragment 番外篇——TabLayout+ViewPager+Fragment

作者:忘了12138
地址:http://www.cnblogs.com/wangle12138/p/8419496.html
声明:本文是 忘了12138 原创投稿,转发等请联系原作者授权。

该文章是一个系列文章,是本人在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();
   }
}

本篇总结

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

下篇预告

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

(0)

相关推荐