ViewPager不为人知的秘密

ViewPager不为人知的秘密

ViewPager翻页控制

关于控制ViewPager的翻页,在网上已经有很多解决方法了,我们一个个来看看。

setScanScroll()

我们先来看一下具体实现:

public class CustomViewPager extends ViewPager {  

    private boolean isCanScroll = true;  

    public CustomViewPager(Context context) {
        super(context);
    }  

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }  

    public void setScanScroll(boolean isCanScroll){
        this.isCanScroll = isCanScroll;
    }  

    @Override
    public void scrollTo(int x, int y){
        if (isCanScroll){
            super.scrollTo(x, y);
        }
    }
} 

通过控制isCanScroll变量,设置给scrollTo()方法,控制是否能滑动,看上去非常完美,实际上是最不靠谱的方法,因为你setScanScroll()调用之后状态就无法再修改这个状态了,甚至是setCurrentItem方法都不能调用了。

修改Touch事件

同样,我们先来看看代码:

public class NoScrollViewPager extends ViewPager {
    public NoScrollViewPager(Context context) {
        super(context);
    }

    public NoScrollViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent arg0) {
        return false;
    }
}

这代码也很简单,就是控制ViewPager的Touch事件,这个基本是万能的,毕竟是从根源上入手的。你可以在onTouchEvent和onInterceptTouchEvent中做逻辑的判断。

重写ViewPager

前面两种方法固然可以在一定程度上完成我们的要求,但是显得略2.所以,我们来看这种方式。

首先我们要了解下ViewPager切页的原理,经过一段时间的查找,我们找到了这个类:

    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
        int targetPage;
        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
            targetPage = velocity > 0 ? currentPage : currentPage + 1;
        } else {
            final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
            targetPage = (int) (currentPage + pageOffset + truncator);
        }

        if (mItems.size() > 0) {
            final ItemInfo firstItem = mItems.get(0);
            final ItemInfo lastItem = mItems.get(mItems.size() - 1);

            // Only let the user target pages we have items for
            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
        }
        return targetPage;
    }

不用问我是怎么找到的,这是程序员的嗅觉。

这个方法会在切页的时候重定向Page,那么我们只要在这个方法内重新定向到我们想要的Page就好了。

这是ViewPager的控制切页逻辑。

下面我们继续看,其实在ViewPager中,就给我们提供了一个重写的方法——canScroll,看名字就知道了,这个方法是来控制是否能够滑动的,我们来试下,我们先extends ViewPager,然后重写这个方法:

    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        boolean result = super.canScroll(v, checkV, dx, x, y);
        if (dx < 0 && (/*其它控制逻辑**/)) {
            return true;
        }
        return result;
    }

通过控制这个方法返回值,就可以真真实实的控制ViewPager的滑动了,你可以试一下,当然,肯定是可以的。

那是不是这样就可以了呢?当然不是的,不然我怎么能继续装逼呢?

虽然在大部分时间,这个回调已经可以实现ViewPager的翻页控制了,但是,如果你翻页速度很快,你就会发现,其实这个回调方法的执行,是跟不上你的速度的。如果你翻页很快,是可以跳过去的,如果你打log,你会发现,canScroll虽然会一直回调,但是回调并不是实时的,所以会出现bug。这也是为什么我开始要解释ViewPager翻页原理的原因,真不是我要装逼,而是为你留下的伏笔。

所以,最终的解决方案就是canScroll + determineTargetPage

首先,我们要重写ViewPager,不用害怕,ViewPager没有任何依赖,你可以把整个ViewPager的源代码全部copy过来,而不需要修改一行代码,除了包名。

然后,我们找到determineTargetPage这个方法,将targetPage修改下:

    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
        int targetPage;
        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
            targetPage = velocity > 0 ? currentPage : currentPage + 1;
        } else {
            final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
            targetPage = (int) (currentPage + pageOffset + truncator);
        }

        if (mItems.size() > 0) {
            final ItemInfo firstItem = mItems.get(0);
            final ItemInfo lastItem = mItems.get(mItems.size() - 1);

            // Only let the user target pages we have items for
            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
        }

        targetPage = reDetermineTargetPage(targetPage);

        return targetPage;
    }

targetPage = reDetermineTargetPage(targetPage)这个就是我们加的代码,通过reDetermineTargetPage方法,我们来修改ViewPager的targetPage,是不是很无耻的感觉,正常正常。

所以,我们要增加一个父类方法给我们后面继承的ViewPager重写:

    public int reDetermineTargetPage(int targetPage) {
        return targetPage;
    }

最后,我们在继承的ViewPager中,重写这两个方法:

public class MyViewPager extends ViewPager {

    @Override
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        boolean rt = super.canScroll(v, checkV, dx, x, y);
        if (dx < 0 && (/*其他逻辑控制**/)) {
            return true;
        }
        return rt;
    }

    @Override
    public int reDetermineTargetPage(int targetPage) {
        int rtn = targetPage;
        int currentPage = getCurrentItem();
        if (targetPage > currentPage && (/* 其他逻辑控制**/)) {
            rtn = currentPage;
        }
        return rtn;
    }
}

这样我们就非常完美的实现了ViewPager的翻页控制,在慢慢翻页的时候,canScroll就可以帮我们控制了,当快速翻页的时候reDetermineTargetPage给我们做了双保险,即使你翻页过去了,你也会被targetPage给带回来。

ViewPager强制刷新UI

ViewPager不能动态刷新UI的原因主要是因为PagerAdapter中调用notifyDataSetChanged是会失效的。

通用解决方法

当ViewPager绘制完Item之后,ViewPager会把child标记为POSITION_UNCHANGED,这样就不会在notifyDataSetChanged后更新这个View了。所以,要解决这个问题,我们只需要在:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

当我们调用PagerAdapter的notifyDataSetChanged方法之后,系统会去Adapter的getItemPosition方法中遍历所有的child,我们在上面的方法中改写了返回值,全部返回为POSITION_NONE,表示child都没有绘制过,这样ViewPager就会去重绘了。

更加优化一点的代码如下:

    @Override
    public void notifyDataSetChanged() {
        mChildCount = getCount();
        super.notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        // 重写getItemPosition,保证每次获取时都强制重绘UI
        if (mChildCount > 0) {
            mChildCount--;
            return POSITION_NONE;
        }
        return super.getItemPosition(object);
    }

我们增加一个mChildCount来记录子类的数量,在一定程度上减少重绘的次数。

因为重绘的时候,ViewPager会的Destory Item,增加了系统开销。

更加优化的方法

当我们只需要对ViewPager中的某些元素进行更新时,我们可以在instantiateItem方法调用时,用View.setTag方法加入标志,在需要更新View时,通过findViewWithTag的方法找到对应的View进行更新。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-27 07:52:57

ViewPager不为人知的秘密的相关文章

linux下so动态库一些不为人知的秘密

linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名.二者都使用广泛.本文主要讲动态库方面知识.    基本上每一个linux 程序都至少会有一个动态库,查看某个程序使用了那些动态库,使用ldd命令查看 # ldd /bin/ls linux-vdso.so.1 => (0x00007fff597ff000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00000036c2e00000) librt.so.1 => /

五分钟 知晓Eclipse不为人知的秘密(越熟悉 越陌生)

大家好,这篇博客的目的是总结一下Eclipse这个软件中一些不为常用的功能.与大家分享.谢谢~ 1.利用one hour看了一下Eclipse的使用,用two hour写了这篇blog. 2.在现实项目中,活学活用,才会真正对你有利,否则你浪费时间看了本博客,对你毫无帮助. 本博客结构:目录 + 按目录分述 目录: 1.给Eclipse添加书签. 2.通过Attach File查看源码和系统函数.也可以通过open Type:Open Type Hierarchy:Open resources查

ListView控件的不为人知的秘密

使用ListView控件展示数据 1.图像列表控件(ImageList控件) 图像列表控件(ImageList控件)是含有图像对象的集合,可以通过索引或关键字引用该集合的每个对象,ImageList控件不能独立使用,只能为Windows窗体的其他控件提供图像 图像列表的主要属性 Images 存储在图像列表中的所有图像 ImageSize 图像列表中图像的大小 TransparentColor 被视为透明的颜色 ColorDepth 获取图像列表的深度 ImageList控件所包含的图像可以被L

探求网页同步提交、ajax和comet不为人知的秘密(上篇)

标题里的技术都是web开发里最常见的技术,但是我想这些常用的技术有很多细节是很多朋友不太清楚的,理解这些细节是我们深入掌握这些技术的一把钥匙,今天我就讲讲我使用这些技术时体会到的这些细节. 同步提交是指通过对页面的form表单执行submit操作,将用户在页面上录入的数据提交到服务器,服务器处理完数据后将结果信息返回到页面上. 当我们使用同步提交的时候有一个不可或缺的元素,就是form标签,form标签代表了一个边界,在form范围内的input,select等元素,在form执行submit事

各大页面不为人知的“秘密”——管中窥豹看看各大电商如何做架构,如何做缓存的

淘宝  首页缓存1小时,用Tengine Web服务 亚马逊   居然...不缓存?? 京东  首页缓存2分钟,产品页缓存5分钟.JDWS 自己的服务中间件?还是只是...改个名... 首页 产品详情页 苏宁 首页缓存1个小时  真是和京东好基友,自定义"SNWS"? 产品页看样子还没去IBM... channel页面 http://weibo.com/p/1001603751231104387867 http://weibo.com/p/1001603751231129577980

探求网页同步提交、ajax和comet不为人知的秘密(中篇)

深入研究某项技术,了解使用这些技术的细节,其实最终目的都是为了完成一个选择问题:当我们要使用这些技术解决某个具体的问题时候我们到底该如何去选择.如果碰到有两种技术可以让我们达到同样的目的,我们就会不自然的去比较它们之间的差异,通过对这些差异的梳理,我们就能得出在使用它们时候我们到底该如何取舍. 上篇里我讲到XMLHttpRequest可以更加精确的控制http请求的报文头,如是乎我就去寻找在同步提交里我是否能像XMLHttpRequest那样的去控制http的头部信息呢,最终我发现同步提交下我们

linux下so动态库一些不为人知的秘密(转)

linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名.二者都使用广泛.本文主要讲动态库方面知识.基本上每一个linux 程序都至少会有一个动态库,查看某个程序使用了那些动态库,使用ldd命令查看 # ldd /bin/ls 使用 ldd -u /bin/ls 查看不需要链接的so 大家知不知道linux从程序(program或对象)变成进程(process或进程),要经过哪些步骤呢,这里如果详细的说,估计要另开一篇文章.简单的说分三步:    1.fork进程,在内核创建

揭秘北京赛车7码8码滚雪球走势技巧不为人知的秘密玩法

如果你觉得五码*师妹*对于你来说用规律技巧玩法的话太难的话.我建议你使用七码八码来滚雪球.七码八码基本百分之七十八十了.这么多号码你还找不出基本规律玩法.我就觉得你太失败了.玩七码八码最多有5组号码可以给你选着跟.最低都有1组操神规律在等着你.就看你用不用心找而已.  玩滚雪球最好就是先学会分配本金,如果你连基本分配自己的本钱都不懂,我觉得你只是在盲目投注. 其实我发现有很多的玩家对于滚雪球的意义很模糊,不知道原理是什么.那下面我给大家来说一说滚雪球的原理.滚雪球的原理就跟我们冬天在雪地上堆雪人

华丽的外壳隐藏不为人知的秘密

这是一场华丽突变的年代.这是一场没有硝烟的战争.这是一场人类的革命.人的这一生始终是生物链的顶端,可在这生物链顶端的人类,始终有一个一揽独权的霸主. 中国上下五千年文明历史,从原始社会的元谋人至夏王朝建立,三皇五帝,尧.舜.禹.这段时间里人们谋生计.求繁衍.到奴隶社会建立夏开始,经历商.西周,东周的春秋时期,这时生物链顶端的人物出现了,直到封建社会瓦解,封建社会从东周的战国时期开始,直到中国建国之后,这段时间人们为了权和利,一直在争夺着.人们时而清闲自在.时而水深火热.时而蓬勃生机,时而黯淡失色