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库之后总结出来的。

本文来自作者同步博客

时间: 2024-10-24 20:08:56

ViewPager使用记录3——循环展示的相关文章

iOS开发UI篇—无限轮播(循环展示)

一.简单说明 之前的程序还存在一个问题,那就是不能循环展示,因为plist文件中只有五个数组,因此第一个和最后一个之后就没有了,下面介绍处理这种循环展示问题的小技巧. 方法一:使用一个for循环,循环200次,创建200*=1000个模型,且默认程序启动后处在第100组的位置,向前有500个模型,向后也有500个模型,产生一种循环展示的假象. 代码如下: 1 // 2 // YYViewController.m 3 // 07-无限滚动(循环利用) 4 // 5 // Created by ap

Elastislide - 响应式的图片循环展示效果

Elastislide 是一款非常优秀的响应式 jQuery 图片循环展示(旋转木马)插件,集成了 Touchwipe 插件以支持触屏设备.提供了四种效果:水平图片传送带.垂直图片传送带.固定在屏幕底部以及缩略图形式预览. 效果演示      插件下载 您可能感兴趣的相关文章 网站开发中很有用的 jQuery 效果[附源码] 分享35个让人惊讶的 CSS3 动画效果演示 十分惊艳的8个 HTML5 & JavaScript 特效 Web 开发中很实用的10个效果[源码下载] 12款经典的白富美型

JSON工具学习记录--FastJSON循环引用问题

JSON工具学习记录–FastJSON循环引用问题 标签(空格分隔): fastjson 最近基于他人项目做二次开发,遇到了循环引用的问题,简单来说A引用了B,B引用了C,C引用了A,那么转换json就会无休止的转换下去. 更复杂的情况,A中引用了B,B中引用了一个A的集合,比如广告引用了广告类型,广告类型里面又有该类型下的所属广告. 1.构造情景 这种又叫做双向引用,个人感觉这种设计本身就不是很合理,当然还要看具体使用场景了. 广告类: /** * @author Niu Li * @date

Xamarin 实现 Android 无限循环展示, FlipView

先发个图, 让你们知道我说的是什么: 在这之前,  写过一个用于 Xamarin.Form (以下简称 XF ) 的 FlipView,  Android 下是用 HorizontalScrollView 模拟的. 上上周, 无意间看到了 ViewPager 如何实现无限循环的方法 , 于是捉摸在 Xamarin 下把它给实现一下. 在 XF 下, 你可以看到从网络加载的 大图 显示的很流畅 , 因为 XF 下, UriImageSource 这个图片源有这么两个属性: CacheValidit

无限轮播(循环展示)

无限轮播(循环展示) 一.简单说明 之前的程序还存在一个问题,那就是不能循环展示,因为plist文件中只有五个数组,因此第一个和最后一个之后就没有了,下面介绍处理这种循环展示问题的小技巧. 方法一:使用一个for循环,循环200次,创建200*=1000个模型,且默认程序启动后处在第100组的位置,向前有500个模型,向后也有500个模型,产生一种循环展示的假象. 代码如下: 8 9 #import "YYViewController.h" 10 #import "MJExt

android 的viewpager如何实现左右循环

在用到viewpager时,发现这个viewpager在左右滑动的时候,无法进行循环,在viewpager处于第一个或最后的一页面时,无法再次进行滑动,只能逆向滑动.但看其它很多优秀的应用,在viewpager处理时,是可以进行无限循环滑动,于是便研究了一下,发现其实这个效果是很容易做到的. 首先,在用到viewpager时,对于PagerAdapter 我们肯定都很熟悉,这个类主要是处理viewpager中的view的加载和销毁,一般我们都需要复写四个方法,分别是: getCount(); i

使用Axure制作无限循环展示图片效果

一.实现的效果 如图: 1.此次需要实现的效果是,进入界面后,在图片展示区域的图片根据事先设定好的时间,自动切换不同的图片: 2.循环不间断: 3.页面不出现闪烁的现象. 二.做前工作 图片:4张 软件:Axure 三.制作流程 1.在开打的空页面上添加一个动态面板(X:20 ,Y:20 :W:600,H:382)如图: 2.给动态面板取名为“动态广告”,再在该动态面板上添加4个字状态,分别取名为,图01.图02.图03.图04.如图: 3.为每个状态分别添加不同的图片.如图: 4.为了实现这个

calendar循环展示过去12个月

public static void main(String[] args) { // 展示过去12个月 Calendar calendar = Calendar.getInstance(); Date now = null; try { now = DateUtils.parseDate( DateFormatUtils.format(new Date(), "yyyy-MM"), "yyyy-MM"); } catch (ParseException e) {

解决图片循环展示间距

// 图片展示代码<view class="img-wrap" wx:for="{{imgList}}" wx:key=""> <image src="{{item}}" /> </view> .img-wrap { width: 100%; } .img-wrap image { width: 100%; display: block; } 将图片设置为block 高度展示图片自身高度,宽