前言
目前,大多数app都是采用TabLayout+ViewPager+Fragment的方式来为用户提供一个平滑的多界面切换方式。本次课程将对这这三种组件使用进行以下基本的梳理以及简单的拓展。
基本使用
首先引入相应的依赖
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
其实如果对RecyclerView熟悉的话就会发现其实使用起来是类似的,其中主要关心四个对象:Tablayout、ViewPager、PagerAdapter、Fragment。其中前两个跟普通的View控件一样,可以直接通过XML来进行布局以及在onCreate获取相应的实例
首先说一下Fragment,Fragment是需要我们去继承并重写的,它具有以下性质:
- Fragment是依赖于Activity的,不能独立存在的。
- 一个Activity里可以有多个Fragment。
- 一个Fragment可以被多个Activity重用。
- Fragment有自己的生命周期,并能接收输入事件。
- 我们能在Activity运行时动态地添加或删除Fragment。
在这里我们由于是在讲基本使用,所以就给出Fragment的最基本的使用法方法。
首先,创建继承Fragment的类。
Fragment有很多可以复写的方法,其中最常用的就是onCreateView(),该方法返回Fragment的UI布局,我们需要去重写它,完成它和相应的布局文件的绑定。也比较类似于Activity的onCreat( ) 。
直接上代码:
class ExampleFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view: View = inflater.inflate(R.layout.example_fragment, container, false)
...
return view
}
}
以上就是最简单的Fragment。
同时Fragment还有其他方法在生命周期的各个时期被调用,在此不在赘述,有兴趣可以看一下。
如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据。
我们可以在Fragment的onAttach()
中(之后所调用方法的也可以)通过getArguments()
获得传进来的参数,并在之后使用这些参数。如果要获取Activity对象,不建议调用getActivity()
,而是在onAttach()
中将Context对象强转为Activity对象。
其他操作基本和Activity类似,大家可以类比推导一下。
OK,到这里基本上Fragment 的使用也就讲完了。
接下来说一下TabLayout+ViewPager+Fragment方法的使用流程:
- 创建存储多个Fragment实例的列表
- 创建PagerAdapter实例并关联到Viewpager中
- 将ViewPager关联到Tablayout中
- 根据需求改写Tablayout属性*
最后一步不是必须的,为了更加清楚地描述这个调用流程,贴上一个示意图:
同时,往往会通过扩展FragmentPagerAdapter的方式来使它变得更加好用一点~(我觉得原生的写出来就很不优雅)。我们往往会增加两个ArrayList和一个新的add方法来使其可以轻轻送送完成对Fragment的添加。
具体拓展如下:
class FragmentAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
var fragmentsOfMylist: MutableList = ArrayList()
var fragmentsTitles: MutableList = ArrayList()
fun add(fragment: Fragment, title: String) {
fragmentsOfMylist.add(fragment)
fragmentsTitles.add(title)
}
override fun getItem(position: Int): Fragment = fragmentsOfMylist[position]
override fun getCount(): Int = fragmentsOfMylist.size
override fun getPageTitle(position: Int): CharSequence = fragmentsTitles[position]
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
// super.destroyItem(container, position, `object`)
}
}
两个ArrayList一个储存Title,一个储存具体的fragment。再通过override相应的方法,以此达到绑定的效果。
之后我们就可以在mainActivity( )完成轻松调用。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.activity_main)
val mybookPager: ViewPager = findViewById(R.id.main_pager)
val mybookTabLayout: TabLayout = findViewById(R.id.main_tab)
val myhomePagerAdapter = FragmentAdapter(supportFragmentManager)//创建自己的FragmentPagerAdapter
myhomePagerAdapter.apply {
add(ExampleFragment(), "已借阅")
add(ExampleFragment(), "借阅统计")
add(ExampleFragment(), "阅读")
}//载入相应的fragment以及title
mybookPager.adapter = myhomePagerAdapter//设定viewPager的Adapter
mybookTabLayout.apply {
setupWithViewPager(mybookPager)//将tablayout与viewPager绑定
tabGravity = TabLayout.GRAVITY_FILL
setSelectedTabIndicatorColor(Color.parseColor("#A73870"))//自定义tablayout
}
}
}
清爽,简洁,高效
那如果我们不去做拓展,应该怎么调用呢?
不妨给大家看一下。
以下是部分代码
List fragments = new ArrayList<>();
List titles = new ArrayList<>();
···
fragments.add(MyFragment.newInstance("11111", "11111"));
fragments.add(MyFragment.newInstance("22222", "22222"));
fragments.add(MyFragment.newInstance("33333", "33333"));
fragments.add(MyFragment.newInstance("44444", "44444"));
fragments.add(MyFragment.newInstance("55555", "55555"));
titles.add("fragment1");
titles.add("fragment2");
titles.add("fragment3");
titles.add("fragment4");
titles.add("fragment5");
viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
});
tabLayout.setupWithViewPager(viewPager);
}
···
更恐怖的是,你需要在每一个用到TabLayout+Fragment+ViewPageractivity的里面都写一遍。
小拓展
PagerAdapter
PagerAdapter是一个抽象类,它有两个实现子类供我们使用,分别是FragmentStatePagerAdapter和FragmentPagerAdapter。创建这两个类的实例需要传入一个FragmentManager对象,像代码那样处理就行了,从类名就可以看出来它俩的最大差别就在“State-状态”上,什么意思呢?指的是所包含存储的Fragment对象的状态是否保存。
而这两个的最大区别在哪呢?我不得不先说一下ViewPager还有一个比较重要的函数是:
viewPager.setOffscreenPageLimit(int limit);
这个方法默认值为1,Google在开发ViewPager时,考虑到如果滑动的时候才创建Fragment实例时会带来一定程度的卡顿,因此为ViewPager设置了缓存机制,而上述函数则是设置缓存Fragment的数量,示意图如下:
也就是说,limit的值代表着还要缓存当前Fragment左右各limit个Fragment,一共会创建2*limit+1个Fragment。超出这个limit范围的Fragment就会被销毁,而上述两种PagerAdapter的差别就是销毁的流程不同!
这里就不放Log图给大家看,直接告诉大家,FragmentPagerAdapter在销毁Fragment时不会调用onDestroy()方法,而带了State的Adapter则会调用Fragment的onDestroy()方法,换言之,前者仅仅是销毁了Fragment的View视图而没有销毁Fragment这个对象,但是后者则彻彻底底地消灭了Fragment对象。
结论:
FragmentPagerAdapter适用于Fragment比较少的情况,它会把每一个Fragment保存在内存中,不用每次切换的时候,去保存现场,切换回来在重新创建,所以用户体验比较好。而对于Fragment比较多的情况,需要切换的时候销毁以前的Fragment以释放内存,就可以使用FragmentStatePagerAdapter。