ViewPager的使用

[]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需要实现下列两个方法

  1. Fragment getItem(int position)

当这个Fragment不存在时,getItem()被调用,返回指定位置的Fragment。

注意:getItem()是创建一个新的Fragment,不是返回一个已经存在的Fragment。对于FragmentPagerAdapter,当每页的Fragment被创建后,getItem()就不会被调到了。对于FragmentStatePagerAdapter,由于Fragment会被销毁,所以它仍会被调到。

  1. 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的父类,其提供了更加一般的功能接口。

最少实现下列四个方法:

  1. int getCount()
    返回ViewPager的屏幕总数。

  2. boolean isViewFromObject(View view, Object object)

  3. Object instantiateItem(ViewGroup container, int position)
    初始化位置为position的屏幕的界面。

  4. void destroyItem(ViewGroup container, int position, Object object)
    当位置为position的屏幕不再使用时,销毁它。典型的行为是将此屏幕的View对象从container中remove掉。

还有一些不常见的方法

  1. void finishUpdate(ViewGroup container)
    ViewPager的更新即将完成时调用。

{}OnPageChangeListener

需要实现下列三个方法

  1. void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
    页面在滑动的过程中调用
    position参数: 当前页面的位置
    positionOffset参数:当前页面偏移的百分比(0.0-1.0)
    positionOffsetPixels参数:当前页面偏移的像素位置

  2. void onPageSelected(int position)
    页面在滑动后调用
    position参数: 滑动后当前页面的位置

  3. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值