[]ViewPager相关的基础知识
{}ViewPager
ViewPager是support-v4提供的一个类,主要用于实现滑屏的效果。
ViewPager和Fragment是一对完美的组合,通过FragmentPagerAdapter或者是FragmentStatePagerAdapter,ViewPager能够轻松地管理多个Fragment。
ViewPager.getChildCount():返回ViewPager所管理的没有被销毁视图的Fragment数量,并不是所有的Fragment数量。
ViewPager.getAdapter().getCount():返回ViewPager管理的全部Fragment数量。
setOffscreenPageLimit(int limit):limit参数设置为ViewPager的页数时,ViewPager会一次性初始化全部的Fragment,之后切换都不会调用Fragment的生命周期方法。
{}FragmentPagerAdapter
每页都是一个Fragment,并且所有的Fragment实例一直保存在FragmentManager中。所以它适用于少量固定的Fragment,比如一组用于分页显示的标签。除了当Fragment不可见时,它的视图层(view hierarchy)有可能被销毁外,每页的Fragment都会被保存在内存中。
{}FragmentStatePagerAdapter
每页都是一个Fragment,当Fragment不被需要时(比如不可见),整个Fragment都会被销毁,除了Fragment的状态被保存外(保存下来的bundle用于恢复Fragment实例)。所以它适用于很多页的情况。
{}FragmentPagerAdapter和FragmentStatePagerAdapter需要实现下列两个方法
- Fragment getItem(int position)
当这个Fragment不存在时,getItem()被调用,返回指定位置的Fragment。
注意:getItem()是创建一个新的Fragment,不是返回一个已经存在的Fragment。对于FragmentPagerAdapter,当每页的Fragment被创建后,getItem()就不会被调到了。对于FragmentStatePagerAdapter,由于Fragment会被销毁,所以它仍会被调到。
- int getCount()
返回Fragment的数量。
其它方法
1. void destroyItem(ViewGroup container, int position, Object object)
销毁指定位置的Fragment。
FragmentPagerAdapter是detach()
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
.......
mCurTransaction.detach((Fragment)object);
}
FragmentStatePagerAdapter是remove()
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
......
mCurTransaction.remove(fragment);
}
{}FragmentPagerAdapter和FragmentStatePagerAdapter对比总结
FragmentPagerAdapter:对于不再需要的Fragment,会调用FragmentTransaction的detach() 。也就是说,只是销毁了Fragment的视图,但Fragment实例保留在FragmentManager中。因此, 创建的Fragment永远不会被销毁。
FragmentStatePagerAdapter:会销毁不需要的Fragment。会调用FragmentTransaction的remove()将Fragment彻底移除。但是在销毁Fragment时,它会将其onSaveInstanceState(Bundle) 方法中的Bundle信息保存下来。用户切换回原来的页面后,保存的实例状态可用于恢复生成新的fragment。
{}PagerAdapter
PagerAdapter是FragmentPagerAdapter和FragmentStatePagerAdapter的父类,其提供了更加一般的功能接口。
最少实现下列四个方法:
int getCount()
返回ViewPager的屏幕总数。boolean isViewFromObject(View view, Object object)
Object instantiateItem(ViewGroup container, int position)
初始化位置为position的屏幕的界面。void destroyItem(ViewGroup container, int position, Object object)
当位置为position的屏幕不再使用时,销毁它。典型的行为是将此屏幕的View对象从container中remove掉。
还有一些不常见的方法
- void finishUpdate(ViewGroup container)
ViewPager的更新即将完成时调用。
{}OnPageChangeListener
需要实现下列三个方法
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
页面在滑动的过程中调用
position参数: 当前页面的位置
positionOffset参数:当前页面偏移的百分比(0.0-1.0)
positionOffsetPixels参数:当前页面偏移的像素位置void onPageSelected(int position)
页面在滑动后调用
position参数: 滑动后当前页面的位置void onPageScrollStateChanged(int state)
[]ViewPager实现循环播放
循环播放是:从第一屏向右滑动可以到最后一屏,从最后一屏向左滑动可以到第一屏。
实现方式1:设置adapter的getCount()返回Integer.MAX_VALUE,在instantiateItem()通过取余来将position映射为实际所需的索引。主要的弊端是:太大的机会以极大的概率导致程序ANR。并且滑动到第一屏时无法向右滑动(其实可以解决)。
实现方式2:要想让最后一屏左滑到第一屏、第一屏右滑到最后一屏有一个平稳的过渡过程,那么在第一屏和最后一屏的两边必须存在着对应要跳转的页面,然后跳转完成后我们再“偷偷”(用户不可见,在内容不变的情况下替换了页面的位置)替换为要显示的真实页面(两者页面显示内容一样,只是位置不一样)。
假定现在想显示4张图片,分别为view0到view3。根据上面的思想:
(1)在adapter中额外多加两个view,即如图中的红色的view3和view0。这样position1的view0很容易过渡到position0的view3,同理最后一屏到第一屏的过渡也很自然。
(2)当滑到position0时,右滑之后并没有对应的view可以显示,同理position5。这就需要把position0的view3偷偷换成position4的view3,这样position0的右滑也就是position4的右滑,即滑到view2。
主要代码实现如下:
private final static int DEFAULT_BANNER_SIZE = 4;
private final static int FAKE_BANNER_SIZE = DEFAULT_BANNER_SIZE + 2;
class BannerAdapter extends PagerAdapter {
private Context context;
public BannerAdapter(Context context) {
this.context = context;
}
@Override
public int getCount() {
return FAKE_BANNER_SIZE;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
position = (position - 1 + DEFAULT_BANNER_SIZE) % DEFAULT_BANNER_SIZE;
ImageView imageView = new ImageView(context);
ViewGroup.LayoutParams viewPagerImageViewParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
imageView.setLayoutParams(viewPagerImageViewParams);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setImageResource(bannerArray[position]);
final int index = position;
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "click banner position :" + index, Toast.LENGTH_SHORT).show();
}
});
container.addView(imageView);
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public void finishUpdate(ViewGroup container) {
int mCurItem = bannerViewPager.getCurrentItem();
Log.i(TAG, "finish update before, mCurItem=" + mCurItem);
if (mCurItem == 0) {
mCurItem = DEFAULT_BANNER_SIZE;
bannerViewPager.setCurrentItem(mCurItem, false);
} else if (mCurItem == FAKE_BANNER_SIZE - 1) {
mCurItem = 1;
bannerViewPager.setCurrentItem(mCurItem, false);
}
Log.i(TAG, "finish update after, mCurItem=" + mCurItem);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
完整的源码(包括自动循环播放和小圆点):
https://github.com/lfyhhb116/banner_circulation
{}调用notifyDataSetChanged()更新ViewPager中的Fragment
重写FragmentPagerAdapter或者FragmentStatePagerAdapter的getItemPosition()
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
这种做法会使当前页的Fragment和相邻两页的Fragment都会销毁(FragmentPagerAdapter销毁的是布局,FragmentStatePagerAdapter销毁的是Fragment对象)并重建。
或者
@Override
public int getItemPosition(Object object) {
if(object instanceof MyFragment){
MyFragment fragment= (MyFragment) object;
fragment.update();
}else if(......){
......
}
return super.getItemPosition(object);
}
public class MyFragment extends Fragment {
public void update() {
// do your stuff
}
}
{}从FragmentStatePagerAdapter中获取Fragment对象
public class MyPagerAdapter extends FragmentStatePagerAdapter {
List<Fragment> fragments = new ArrayList<>();
......
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment)super.instantiateItem(container, position);
fragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
fragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getFragment(int position) {
return fragments.get(position);
}
}
或者重写setPrimaryItem()
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if(getCurrentFragment() != object){
mCurrentFragment = (Fragment)object;
}
super.setPrimaryItem(container, position, object);
}
参考以下文章:
循环广告位组件的实现
http://blog.csdn.net/singwhatiwanna/article/details/46541225
为什么调用 FragmentPagerAdapter.notifyDataSetChanged() 并不能更新其 Fragment?
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html