重要控件总结:ViewPager

       越来越发现在学习中随随便便的盲目搜文章学习,各种解释详细的文章博客一堆,看的真是应接不暇,顿时感觉学啥都是入了无底洞。学不完,于是决定看一手资料紧接着就打开了官方文档和这个控件的源码。结果发现官方文档和这个类的注释介绍(以后找到官方文档直接浏览器翻译就看类的简介啦,不用担心粘贴类的简介带注释符号影响翻译啦)
       bb了一大堆,进入正文哈。ViewPager这个控件在App中也是常见的。微信底部tab对应的四个界面、电商app首页Banner都可以找到ViewPager的足迹。今天就来总结下这个重要的控件。

效果预览

知识图

在这里插入图片描述

一、基本使用

1、简介

官方文档对其简介:一个继承了ViewGroup的容器,用户向ViewPager提供若干个带数据的界面。ViewPager允许这些界面进行翻转。
理解:
1、翻转:随着用户手指在屏幕滑动,而切换用户提供的界面。
2、数据界面:用户提供View或者fragment,通过适配器设置给ViewPager

2、常用api

1、setAdapter(PagerAdapter adapter) 设置适配器,学过RecyclerView、ListView都知道。
2、setOffscreenPageLimit(int limit) 设置缓存的页面个数,默认是 1。
3、setCurrentItem(int item) 跳转到指定页面
4、addOnPageChangeListener(OnPageChangeListener listener)页面滑动监听
5、setPageTransformer(boolean reverseDrawingOrder,PageTransformer transformer, int pageLayerType) 设置页面滑动的动画效果。
6、setPageMargin(int marginPixels) 设置不同页面之间的间隔
7、setPageMarginDrawable(…) 设置不同页面间隔之间的装饰图也就是 divide ,要想显示设置的图片,需要同时设置 setPageMargin()

3、常见的Adapter介绍及PagerAdapter探讨
  • PagerAdapter
  • FragmentPagerAdapter
  • FragmentStatePagerAdapter

PagerAdapter:ViewPager的Adapter基类,此类为抽象类。
这里主要讲解PagerAdapter。至于FragmentPagerAdapter、FragmentStatePagerAdapter下文中讲解。

(1)PagerAdapter使用栗子

PagerAdapterDemoActivity代码

public class PagerAdapterDemoActivity extends AppCompatActivity {
    
    
    // 准备一组图片
    private int[] imgs = new int[]{
    
    
            R.drawable.img_yao, R.drawable.img_zhuge,
            R.drawable.img_libai, R.drawable.img_mingren,
            R.drawable.img_haizei, R.drawable.img_douluo};
    private List<View> mList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager_adapter_demo);
        offerData();
        initViewPager();
    }
    private void offerData() {
    
    
        // adapter 提供数据
        mList = new ArrayList<>(); 
        // 为每个view视图内的图片填充数据
        for (int img : imgs) {
    
    
            @SuppressLint("InflateParams") View mView = LayoutInflater.from(this).inflate(R.layout.page_pageradapter, null, false);
            AppCompatImageView imageView = mView.findViewById(R.id.img);
            imageView.setImageResource(img);
            mList.add(mView);
        }
    }
    private void initViewPager() {
    
    
        ViewPager viewPager = findViewById(R.id.vp_pager);
        // 设置adapter
        viewPager.setAdapter(new MyPagerAdapter(mList));
    }
}

MyPagerAdapter 代码:

/**
 * Created by sunnyDay on 2019/12/14 10:39
 */
public class MyPagerAdapter extends PagerAdapter {
    
    
    private List<View> mList;

    public MyPagerAdapter(List<View> mList) {
    
    
        this.mList = mList;
    }

    @Override
    public int getCount() {
    
    
        return mList == null ? 0 : mList.size();
    }

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

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
    
    
        container.addView(mList.get(position));
        return mList.get(position);
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
        container.removeView((View) object);
    }
}

实现思路非常简单,activity中通过for 创建了6个带数据的View,然后通过MyPagerAdapter的构造吧数据传递给ViewPager的adapter。ViewPager 为ViewGroup的子类,所以也是个容器,adapter获得数据后Viewpager就会知道,便从adapter中获得子view。填充展示。

(2)补充

1、ViewPager本身没有直接提供子视图(View或者fragment)回收机制,而是采用回调来展示页面更新过程
2、PagerAdapter可以实现视图的回收、管理,例如FragmentPagerAdapter。Fragment事务来管理每个视图。
3、ViewPager将每个页面与键对象关联,而不是直接与Views关联。此键用于跟踪和唯一标识给定页面,而与给定页面在适配器中的位置无关。
4、当ViewPager执行setAdapter方法时,这个方法内部会调用pagerAdapter的startUpdate(this)方法通知更新页面,这时pagerAdapter便会调destroyItem先销毁界面,销毁destroyItem执行完毕后便发出finishUpdate(this)(页面销毁)通知。这时instantiateItem将与返回的键对象相关联的视图添加到传递给这些方法的父级ViewGroup中,并且destroyItem 删除这些键对象 相关联的视图。
5、一个非常简单的PagerAdapter可以选择将页面Views本身用作关键对象,从instantiateItem(ViewGroup, int) 创建后返回它们并将它们添加到父ViewGroup。匹配的 destroyItem(ViewGroup, int, Object)实现会将View从父ViewGroup中删除,并且isViewFromObject(View, Object) 可以实现为return view == object。
ps:补充来自官方文档资料理解这些东西需要稍微看下Viewpager、PagerAdapter部分方法源码。

(3)四个要实现的方法

1int getCount() 

返回值:ViewPager页面的个数,一般我们给定view或者fragment的集合。集合的大小就是页面个数

 2public boolean isViewFromObject(@NonNull View view, @NonNull Object object)

view:view 视图
object:键对象
返回值:标识页面视图是否与键对象相关联
true:当前view界面与给定的键对应
false:当前的view界面与给定的键不对应
一般我们直接view==object,通过布尔返回值自动判断

3public Object instantiateItem(@NonNull ViewGroup container, int position)

container:父容器,其实就是Viewpager 对象(ViewPager的setAdapter源码可以看出)
position:当前页面索引
返回值:页面对象所关联的键
ps:此方法内部需要开发者手动add下view到容器。这时view就被添加到Viewpager了

4public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) 

container:viewpager对象
position:当前页面索引
object:页面对象所关联的键

4、解惑

(1)有关PagerAdapter清除View的写法

使用PagerAdapter时为啥这两种写法都行?

 @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    
         container.removeView((View) object); // 方式1
        // container.removeView(mList.get(position)); // 方式2
    }

其实二者都是删除指定的界面因为:上述介绍过
在这里插入图片描述

(2)有关LayoutInflater的使用疑问
上文栗子代码中有这样一段:

View mView = LayoutInflater.from(this).inflate(R.layout.page_pageradapter, null, false);
//1、View mView = LayoutInflater.from(this).inflate(R.layout.page_pageradapter, viewPager, false);
//2、View mView = LayoutInflater.from(this).inflate(R.layout.page_pageradapter, viewPager, true);

都使用过ListView,RecyclerView,我们或许会疑问这里inflate的后两个参数怎么填呢?大家可能会写成注释2里面的写法结果发现啥bug也没有?记得之前注释2的写法在recyclerView中就直接炸了,什么此view已经有了个parent,你要removeView才能添加这个view之类的错误就出来了?这里怎么回事小老弟?接下来我们就分析下:
ps:对LayoutInflater原理不熟的可以移步这里

注释2写法分析:

注释2代表将view视图添加到ViewPager,而在adapter中视图初始化我们 container.addView(mList.get(position));又添加了一遍为啥不报:The specified child already has a parent. You must call removeView() on the child’s parent first.

分析setAdapter源码:
在这里插入图片描述

可见我们上文我们通经过inflate吧View添加到viewPager时,就算添加成功,在设置adapter时,也会先进行View界面清空,在添加相应的View界面。所以同一个View就不会添加到两个不同的容器而报错了!!!!

(3)作死系列就是想崩溃
在这里插入图片描述

这种经常发生在我们初学viewpager阶段,本来是准备提供不同的view对象结果这样一写,添加了一堆相同的view到集合中。最终在adapter中 container.addView(mList.get(position));时崩溃就出来了,哈哈如愿以偿,快乐了吧在这里插入图片描述

5、ViewPager添加View

我们或许会疑问,ViewPager作为容器可以添加哪些View?可以在xml中给ViewPager添加子View吗?代码直接可以addView吗?

查看ViewPager的addView源码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可见直接代码添加View时这个View必须是DecorView类型的类

(1)ViewPager添加子view的两种方式

  • 通过PagerAdapter
  • 自定义View实现DecorView接口

添加普通的View到ViewPager中普通的View是不会生效的。直接添加View时这个View必须是DecorView类型的类,所以xml写在ViewPager控件的普通View,通过代码手动添加view到ViewGroup都不生效。

二、进阶玩法

本章节主要探讨下ViewPager 结合TableLayout+fragment的使用,以及常见的轮播图功能实现。

1、结合Tablayout+fragment

(1)adapter的介绍

结合fragment时就要用到FragmentPagerAdapter或者FragmentStatePagerAdapter了。

  • FragmentPagerAdapter

上述说了PagerAdapter为抽象类,这里的FragmentPagerAdapter就是PagerAdapter实现类。它将每一个页面表示为一个 Fragment,并且每一个Fragment都将会保存到fragment manager当中。
这种pager十分适用于有一些静态fragment,例如一组tabs,的时候使用。每个页面对应的Fragment当用户可以访问的时候会一直存在内存中,但是,当这个页面不可见的时候,view hierarchy将会被销毁。这样子会导致应用程序占有太多资源。当页面数量比较大的时候,建议使用 FragmentStatePagerAdapter。

    public Fragment getItem(int position)
    public int getCount() 
  • FragmentStatePagerAdapter

也是PagerAdapter的直接子类。
当使用FragmentStatePagerAdapter 时,实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。

(2)简单栗子(有坑)
FragmentStatePageAdapterActivity代码:

public class FragmentStatePageAdapterActivity extends AppCompatActivity {
    
    
    private com.google.android.material.tabs.TabLayout tabLayout;
    private ViewPager viewPager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_state_page_adapter);
        getSupportActionBar().hide();
        initView();
        initData();

    }

    private void initData() {
    
    
        populateTabsData();
        populateViewPagerData();
    }

    private void populateViewPagerData() {
    
    
        List<Fragment> mList = new ArrayList<>();
        mList.add(new AnimeFragment());
        mList.add(new TvFragment());
        mList.add(new LoLFragment());
        mList.add(new KingGloryFragment());
        mList.add(new PeaceFragment());
        viewPager.setAdapter(new MyFragmentStateAdapter(getSupportFragmentManager(), FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mList));
        tabLayout.setupWithViewPager(viewPager, false);//自动绑定viewpager,实现联动效果
    }

    private void populateTabsData() {
    
    
        String[] arr = new String[]{
    
    
                "动漫", "电视剧", "LOL", "王者荣耀", "和平精英",
        };
        for (String s : arr) {
    
    
           //注意这里的对象使用(不要直接new TableLayout,使用的对象要为上文findviewbyid那个)否则回报空指针
            tabLayout.addTab(tabLayout.newTab().setText(s));
        }

    }

    private void initView() {
    
    
        tabLayout = findViewById(R.id.my_tab_layout);
        viewPager = findViewById(R.id.vp_pager);
    }
}

FragmentStatePagerAdapter代码:

/**
 * Created by sunnyDay on 2019/12/16 19:46
 */
public class MyFragmentStateAdapter extends FragmentStatePagerAdapter {
    
    
    private List<Fragment> mList;

    public MyFragmentStateAdapter(@NonNull FragmentManager fm, int behavior, List<Fragment> mlist) {
    
    
        super(fm, behavior);
        this.mList = mlist;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
    
    
        return mList.get(position);
    }

    @Override
    public int getCount() {
    
    
        return mList == null ? 0 : mList.size();
    }

    /**
     * view pager 和 tab layout 联动时这个方法必须重写 否则 tab 标题会被全部置空
     * */
 //    @Nullable
//    @Override
//    public CharSequence getPageTitle(int position) {
    
    
//        String[] arr = new String[]{
    
    
//                "动漫", "电视剧", "LOL", "王者荣耀", "和平精英",
//        };
//        return arr[position];
//    }
}

实现流程梳理:
1、xml中放置了两个控件tablayout和viewpager很简单(具体参考下文给出的全部源码,这里不再给出)
2、给tableLayout控件的tabs赋值。这里注意上文代码中的注释。
3、搞个adapter吧fragment的集合传递过来即可
4、FragmentStatePagerAdapter的参数

FragmentManager fm:fragmentManger对象
int behavior

behavior的值:

  • BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT:指示仅当前片段处于该Lifecycle.State.RESUMED 状态。
  • BEHAVIOR_SET_USER_VISIBLE_HINT:已经不建议使用,建议使用BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

5、构造函数FragmentStatePagerAdapter。如果BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT传入,则仅当前Fragment处于Lifecycle.State.RESUMED状态,而所有其他Fragment的上限为Lifecycle.State.STARTED。如果BEHAVIOR_SET_USER_VISIBLE_HINT通过,则所有片段都处于Lifecycle.State.RESUMED状态,并且将有的回调Fragment.setUserVisibleHint(boolean)。

(3)Viewpager与tablayout联动的坑

如上: tabLayout.setupWithViewPager(viewPager, false);设置了这句代码后我们惊奇的发现tablayout的文字全部都是空了。这时我们分析下源码如下。

  private void setupWithViewPager( @Nullable final ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) {
    
    
...
...
... 忽略n行

if (viewPager != null) {
    
    
      this.viewPager = viewPager;
      if (pageChangeListener == null) {
    
    
        pageChangeListener = new TabLayoutOnPageChangeListener(this);
      }
      pageChangeListener.reset();
      viewPager.addOnPageChangeListener(pageChangeListener);
      currentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
      addOnTabSelectedListener(currentVpSelectedListener);
      final PagerAdapter adapter = viewPager.getAdapter();
      if (adapter != null) {
    
    
        // 重要方法
        setPagerAdapter(adapter, autoRefresh);
      }
      if (adapterChangeListener == null) {
    
    
        adapterChangeListener = new AdapterChangeListener();
      }
      adapterChangeListener.setAutoRefresh(autoRefresh);
      viewPager.addOnAdapterChangeListener(adapterChangeListener);
      setScrollPosition(viewPager.getCurrentItem(), 0f, true);
    } else {
    
    
      this.viewPager = null;
      // 重要方法
      setPagerAdapter(null, false);
    }  

这个方法内部有个重要方法setPagerAdapter,这时我们进入setPagerAdapter方法看看。

  void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
    
    
     ... 省略n行
    populateFromPagerAdapter(); // 重要方法
  }

省略的代码都是有关观察者注册adapter的,这里最后有个populateFromPagerAdapter方法。

在这里插入图片描述

这里就明白为啥联动时不重写adapter的getPageTitle时,tabs消失的坑。

2、常见轮播图的实现
未完 待续!!!

源码:
传送门
参考:
官方文档
ViewPager 超详解:玩出十八般花样
ViewPager源码分析

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/103532857