zoukankan      html  css  js  c++  java
  • ViewPager使用记录3——循环展示

    ViewPager是v4支持库中的一个控件,相信几乎所有接触Android开发的人都对它不陌生。之所以还要在这里翻旧账,是因为我在最近的项目中有多个需求用到了它,觉得自己对它的认识不够深刻。我计划从最简单的使用场景出发,记录我到目前为止所对ViewPager的使用情况以及有关它的一些知识点。

    这个系列的代码将存放在Github仓库中,每篇文章对应一个分支或几个分支。

    这是第三篇文章,将讨论集中有关如何使用ViewPager展示无限循环视图的方法。

    方法1:极大化PagerAdapter.getCount的返回值

    这是最简单的实现方法。关键在于重写PagerAdapter.getCount方法,将其返回值设置为Integer.MAX_VALUE,然后通通过取模position%count的方式获取得对应的数据进行视图渲染。

    ...
    @Override
    public int getCount() {  
        return Integer.MAX_VALUE;
    }
    
    @Override
    public Object instantiateItem(ViewGroup container, int position) {  
        int index = position % 3;
        String text = texts.get(index);
        TextView textView = new TextView(container.getContext());
        textView.setText(text);
        container.addView(textView);
        return textView;
    }
    ...

    这种方法毕竟不是真实的无限循环,只是虚拟了一个极大的页数,让用户翻页的时候很触及到“世界的尽头”。所以在初始化的时候需要完成一个关键初始化:

    viewPager.setCurrentItem(Integer.MAX_VALUE / 2, false);  

    把初始化页面定位到世界的中央。

    相关代码在分支:03-fake-infinite-cycle可以获取。

    方法2:在数据源首尾添加重复节点

    这是实现ViewPager无限循环的另一种方案:通过在数据源的首尾处添加重复的数据(在源数据前插入最后一个数据,其后插入原来的第一个数据),这两个重复数据的作用是在滚动过程中作为中间视图,当滚动停止时立刻切换到最终的视图,进入下一个滚动循环。

    相关代码见分支:03-infinite-cycle-with-additional-views

    首先在往PagerAdapter插入数据的时候对数据进行一下处理:

    public void setTexts(List<String> texts) {  
        this.texts.clear();
        if (texts == null) {
            notifyDataSetChanged();
            return;
        }
    
        // 只有一个数据时不循环
        if (texts.size() == 1) {
            this.texts.addAll(texts);
    
        // 多个数据,插入重复数据
        } else if (texts.size() > 1) {
            this.texts.add(texts.get(texts.size() - 1));
            this.texts.addAll(texts);
            this.texts.add(texts.get(0));
        }
    
        notifyDataSetChanged();
    }

    其次让ViewPager实现ViewPager.OnPageChangeListener接口,监听滚动状态。代码如下:

    @Override
    public void onPageSelected(int position) {  
        int realCount = getCount() - 2;
        // 多于1,才会循环跳转
        if ( getCount() > 1) {
            // 首位之前,跳转到末尾(N)
            if ( position < 1) {
                position = realCount;
                viewPager.setCurrentItem(position,false);
            }
            // 末位之后,跳转到首位(1)
            else if ( position > realCount) {
                position = 1;
                viewPager.setCurrentItem(position,false);
            }
        }
    }

    最后组装一下ViewPagerPagerAdapter即可:

    viewPager.setAdapter(adapter);  
    viewPager.addOnPageChangeListener(adapter);  
    if (adapter.getCount() > 1) {  
        viewPager.setCurrentItem(1, false);
    }

    注意最后的if语句,它让ViewPager默认显示第一页。否则页面将展示最后一个源数据的内容且无法向右滑动。

    实际上这种方法也是有缺陷的。当用户滑动ViewPager到源数据的最后一个节点(下标:getCount()-2)并且先要继续滑动显示下一个节点时,这期间ViewPager首先随用户手指一动正常展示我们插入的重复内容(下标:getCount()-1),当滚动停止且触发了onPageSelected回调,ViewPager立即切换到源数据的第一页(下标:1)进入下一个循环。这会导致几个不协调的现象:

    1. 切换到下一个循环的时候会破坏ViewPager的滚动动画(如:滚动惯性动画)。
    2. 切换前展示的缓存视图在切换时被销毁,切换后的视图需要重新生成。如果这里有需要延迟加载的内容也会导致展示不协调。
    方法3:改进方法2

    针对上述方法2提出的两个缺点,在此将着重解决缺点1出现的动画不连贯的现象,作为第三种方案进行介绍。至于缺点2可以通过缓存视图的方式解决,就不在此赘述。

    方法3的代码见分支:03-infinite-cycle-better-practise

    该方案已经满足我目前的需求。它的关键点如下:

    首先,如方法2一样在数据源头尾插入重复节点,用于过渡。这里我重新写了setTexts方法,让只有一个数据的场景也可以循环:

    public void setTexts(List<String> texts) {  
        this.count = 0;
        this.texts.clear();
        if (texts != null && texts.size() > 0) {
            this.count = texts.size();
            for (int i = 0; i <= count + 1; i++) {
                if (i == 0) {
                    this.texts.add(texts.get(count - 1));
                } else if (i == count + 1) {
                    this.texts.add(texts.get(0));
                } else {
                    this.texts.add(texts.get(i - 1));
                }
            }
        }
        notifyDataSetChanged();
    }

    接下来解决方法2的动画不连贯的问题。注意到在方法2中在OnPageChangeListeneronPageSelected方法中处理了循环的跳转逻辑。然后onPageSelectedViewPager处理ACTION_UP事件时回调的。也就是说,当用户的手指时快速拖动后离开ViewPager时,ViewPager回调了该方法,然后还会继续后续的衰减动画。在这个时间点使用setCurrentItem跳转到指定视图必然会造成动画停顿的问题。

    把切换循环改在ViewPager的滚动状态发生变化时进行。怎么做呢?见代码:

    // count为源数据的条目
    // currentItem为PagerAdapter当前选中项
    @Override
    public void onPageSelected(int position) {  
        currentItem = position;
    }
    @Override
    public void onPageScrollStateChanged(int state) {  
        switch (state) {
            case ViewPager.SCROLL_STATE_IDLE://No operation
                if (currentItem == 0) {
                    viewPager.setCurrentItem(count, false);
                } else if (currentItem == count + 1) {
                    viewPager.setCurrentItem(1, false);
                }
                break;
            case ViewPager.SCROLL_STATE_DRAGGING: //start Sliding
                if (currentItem == 0) {
                    viewPager.setCurrentItem(count, false);
                } else if (currentItem == count + 1) {
                    viewPager.setCurrentItem(1, false);
                }
                break;
            case ViewPager.SCROLL_STATE_SETTLING://end Sliding
                break;
        }
    }

    代码中在状态变为停止“SCROLL_STATE_IDLE”或状态变为开始滚动“SCROLL_STATE_DRAGGING”时处理了循环切换的逻辑。

    这里描述一下整个流程。如果用户处于第一页且继续向右滑动手指,或者处于最后一页且继续向左滑动手指时,在状态由空闲变为开始滚动“SCROLL_STATE_DRAGGING”进行切换。第一种情况,如果最终成功切换到目标页面,那么在状态变为空闲时由于currentItem已经发生变化,所以不会重复切换。第二种情况,如果没有成功切换到目标页面,ViewPager需要在状态变为“SCROLL_STATE_IDLE”时再次切换回原来的视图。

    注意在初始化ViewPager时调用一下setCurrentItem(1),让它正确显示第一个视图。

    小结

    ViewPager循环展示数据的方法目前就介绍到这里。我认为方法1和方法3根据不同场景考虑是否使用。出于某种情结,我更倾向于使用方法3,毕竟方法三是查看了github中的banner库之后总结出来的。

    本文来自作者同步博客

  • 相关阅读:
    DPDK安装方法 17.12.13
    numa.h:No such file or directory 解决方法
    17秋 软件工程 第六次作业 Beta冲刺 Scrum3
    17秋 软件工程 第六次作业 Beta冲刺 总结博客
    17秋 软件工程 第六次作业 Beta冲刺 Scrum2
    Paper Reviews and Presentations
    17秋 软件工程 第六次作业 Beta冲刺 Scrum1
    17秋 软件工程 第六次作业 Beta冲刺
    error: could not create '/System/Library/Frameworks/Python.framework/Versions/2.7/share': Operation not permitted
    17秋 软件工程 个人作业 软件产品案例分析
  • 原文地址:https://www.cnblogs.com/developerdaily/p/8025592.html
Copyright © 2011-2022 走看看