Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ViewPager源码分析

ViewPager源码分析

原创
作者头像
allanlin
修改于 2020-08-21 13:34:58
修改于 2020-08-21 13:34:58
1.8K0
举报
文章被收录于专栏:Android开发之路Android开发之路

| 导语 ViewPager是一个很常用的Android组件,其提供的接口和功能基本已经可以满足项目的大部分需要,但如果需要定制一些不一样的行为,比如实现一个类似iOS多任务那样的卡片列表控件,熟悉和修改ViewPager源码来实现就会简单得多。 所以,有了本篇。

本篇有2000字,阅读起来大概要10分钟。

分析一个自定义ViewGroup的源码,一般可以从以下3个方面入手:

1. 自定义ViewGroup对自己以及子View的宽高限制规则,即onMeasure方法。

2. 自定义ViewGroup对子View的布局摆放规则,即onLayout方法。

3. 自定义ViewGroup的触摸事件处理,即dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent 3个方法。

对于ViewPager来说,除了上述3个方面,还可以再加上一点:

4. 子View是怎么add到ViewGroup的,我们知道ViewPager的每一个item是通过Adapter获取的,那ViewPager是在哪里调用addView把item加上去的?

以下分析基于androidx.viewpager.widget.ViewPager的源码(androidx是啥?可以理解为替代以前support.v4/v7这些包的统一集合,最新版本的AndroidStudio3.4新建工程已经默认替换了,support包找不到了,可以在gradle.properties里设置关掉androidx,微笑)。

1. onMeasure

onMeasure的作用是View对自己的宽高进行计算和赋值,如果是ViewGroup,还需要去调用每一个子View的onMeasure让子View也进行宽高计算,onMeasure的具体用法这里不细讲,推荐一篇教材:https://hencoder.com/ui-2-2/ ,讲得很通俗易懂。

下面直接看ViewPager的onMeasure做了什么,主要分成3块。

1.1 给自己宽高赋值

这部分主要做了两件事,第一是调用setMeasuredDimension给自己的宽高赋值,大小是getDefaultSize获得的,除非是写死固定宽高,否则父View提供给ViewPager的空间多大就多大;第二是计算出child的可用宽高,用第一步计算出的宽高减去padding,就是child的可用宽高,这里的childWidth指的是一个item view的width,而不是ViewPager所有child加起来的width。

1.2 对Decor View处理

这部分通过注释也知道,是对Decor view进行测量,Decor View是啥?可以是viewpager顶部的tab,也可以是底部的下标,是独立于item view的部分,这里不讲这部分,Android已经实现了PagerTabStrip和PagerTitleStrip两个Decor View,可以参考他们的实现,一般我们需要tab的话也可以自己实现一个,再和ViewPager进行组合,有时更灵活。

1.3 对item view处理

最后这部分,首先调用了populate,这是ViewPager很重要的一个方法,第4部分讲addView的时候会讲,可以理解为把当前需要显示的item view填充到屏幕上;然后就是对每一个child进行measure,需要注意的是,前面测量Decor View(如果有的话)的时候是会把可用宽高减去Decor View的宽高,剩下的才是item view的可用宽高。

2. onLayout

       onLayout的作用是ViewGroup对子view的摆放位置进行计算,也即算出子view的left,top,right,bottom四个属性值,具体用法可以参考这篇教程:https://hencoder.com/ui-2-3/

下面我们看看ViewPager的onLayout做了什么,主要分为2部分。

2.1 对Decor View处理

第一部分和onMeausre类似,是对Decor View的onLayout处理,这里不讲,值得注意的是,Decor View的摆放位置可以是上下左右四个方向,具体可以看看源码。

2.2 对item view处理

第二部分就是对每个item view进行layout处理,这里重点看childLeft这个值,childLef=paddingLeft+loff,loff=childWidth*ii.offset,offset这里简单理解就是屏幕上显示的所有item的index,比如第一个item的offset就是0,那么第一个item的left就是paddingLeft,第2个item的offset是1,其left就是paddingLeft+childWidth,所以ViewPager的item都是一个个横向排列着,和LinearLayout类似。

不过这里只是简化了说,offset的功能还不止表示index,因为ViewPager的item之间是可以设置pageMargin(可以是负值)的,可以利用这个pageMargin来做卡片重叠的效果,所以offset的值其实还和pageMargin有关,具体计算的代码在calculatePageOffsets这个方法里,这里不讲。

3. 触摸事件处理

     View的触摸事件分发顺序是dispatchTouchEvent –> onInterceptTouchEvent -> onTouchEvent,关于这3个事件的区别,这里也不细讲。

我们直接看ViewPager对这3个事件的处理是怎样的。

3.1 dispatchTouchEvent

       ViewPager没有重写,哦耶!一般也不需要重写这个函数。

3.2 onInterceptTouchEvent

       onInterceptTouchEvent的作用是判断是否要拦截事件,返回true则后续事件会传给onTouchEvent处理,这里重点看down和move事件。

先看down事件,第一部分是初始化触摸坐标和相关变量,比较简单;第二部分是当ViewPager处于SCROLL_STATE_SETTLING(快要滑到最终位置)时,先停止其滚动,mIsBeingDragged=true,想想平时对一个滚动中的ViewPager按下去,ViewPager是先暂停下来,然后可以继续滑动。这里的mIsBeingDragged变量很重要,onInterceptTouchEvent的返回值就是mIsBeingDragged,返回true说明ViewPager正在被拖动,需要到onTouchEvent处理。

再来看看move事件,主要工作是判断手指左右滑动的距离,超过一定阙值后就把mIsBeingDragged设为true,说明ViewPager要消费这个事件,最终拖动逻辑在onTouchEvent处理。

3.3 onTouchEvent

       onInterceptTouchEvent返回true后,后续的事件就会到onTouchEvent这边来,这里重点看move和up事件。

       move事件主要做两件事,第一件事是当mIsBeingDragged为false时,重新检测一下当前是否符合左右滑动的条件(mIsBeingDragged为false时为啥会回调onTouchEvent呢?可能是触摸方向是上下滑动没触发到onInterceptTouchEvent的条件,事件分发给子view,子View又没处理,所以事件又回调到ViewPager的onTouchEvent);第二件事是调用performDrag对ViewPager进行滚动,performDrag本质上也是调用scrollTo进行滚动,细节可以去看看源码。

再来看up事件,主要就做一件事,就是根据松手时当前滑动的位置,计算出最终要切换到哪个item,最终调用setCurrentItemInternal进行切换,而且带动画。

       如果你想做出iOS多任务列表那种效果,就是快速滑动松手后,整个列表还能跟着惯性滚下去,可以考虑在这里做一个fling处理,微笑。

4. addView

       ViewPager的每一个item view都是通过Adapter返回的(严格说是Adapter返回Fragment,Fragment返回item view),那ViewPager是不是直接调用Adapter的getItem获取View,然后调用addView把view添加上去呢?

搜索ViewPager的addView方法,虽然覆写了,但ViewPager内部没有任何调用,真是神奇。

直接断点addView调试一下,调用堆栈如下:

从堆栈可以看出,起始方法是ViewPager的populate方法(第一部分讲onMeasure提到的那个方法),中间经过FragmentManager各种状态处理,最终到了moveToState,调用了ViewPager的addView方法把itemview添加了上去,下面讲一下这两个方法。

4.1 populate

这个方法有点长,这里不贴代码,其作用主要是,根据当前的item位置,把当前要显示的item填充到屏幕上,对于已经不需要显示的item,会调用adapter.destroyItem销毁,对于还没创建的item,会调用adapter.instantiateItem初始化。最后调用adapter.finishUpdate触发状态更新。

4.2 moveToState

这个方法是FragmentManager更新Fragment状态的地方,addView的调用也在这个地方。

可以看到,第一次创建Fragment后的状态就是Fragment.CREATED,这里的container在我们的例子里指的就是ViewPager,container.addView就把当前item的view添加到ViewPager里。

5. 总结

       ViewPager是一个很强大也很常用的View,其源码有3000多行,本篇只对核心的4个方面进行分析,如果需要对ViewPager进行源码修改来自定义某些行为,可以优先考虑从这4个方面去修改。

期待后续!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
自定义无限循环ViewPager(一)――ViewPager初始化源码解析
大部分app首页一般都会有个无限循环的广告轮播位,通常都是采用ViewPager来实现的,对此大家肯定不会感到陌生。而关于无限循环的ViewPager的实现,一般有下面三种实现方式。
用户3106371
2018/09/12
2.7K0
自定义无限循环ViewPager(一)――ViewPager初始化源码解析
自定义无限循环ViewPager(二)――ViewPager滑动原理解析
在前面一篇文章中,已经分析了ViewPager初始化的原理,而本篇文章开始分析ViewPager的滑动及页面切换的原理。在阅读本文之前,大家可以先去了解下Scroller的用法,以便大家更好的理解ViewPager的滑动原理。
用户3106371
2018/09/12
2.4K0
ViewPager,ScrollView 嵌套ViewPager滑动冲突解决
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/52939127
程序员徐公
2018/09/17
5.9K0
ViewPager,ScrollView  嵌套ViewPager滑动冲突解决
Android自定义控件总结
自定义控件分类: 1、使用系统控件,实现自定义的效果 2、自己定义一个类继承View ,如textView、ImageView等,通过重写相关的方法来实现新的效果 3、自己定义一个类继承ViewGroup,实现相应的效果 继承view类或viewgroup类,来创建所需要的控件。一般来讲,通过继承已有的控件来自定义控件要简单一点。 介绍下实现一个自定义view的基本流程 1.明确需求,确定你想实现的效果。 2.确定是使用组合控件的形式还是全新自定义的形式,组合控件即使用多个系统控件来合成一个新控件,你比如t
六月的雨
2018/05/14
1.3K0
Android面试官最爱问的12个自定义View的高级问题
在Android开发领域,自定义View是一个考察开发者深度功底和创造力的重要方面。本文将从Android面试官的角度出发,深入探讨自定义View面试中常见的12个高级疑难问题,帮助大家更好地准备面试,展示专业技能。
Rouse
2023/12/26
1.7K0
Android面试官最爱问的12个自定义View的高级问题
探秘Android手势事件机制与优化技巧
在Android开发中,手势操作被广泛应用于各种应用场景,如滑动、双击等。本文将介绍Android手势事件传递的原理,包括手势事件的类型、分发机制和处理流程等内容,并提供一些优化用户体验的技巧。
Rouse
2023/08/31
3960
探秘Android手势事件机制与优化技巧
这可能是2020大小厂问的最经典的Android面试题了——事件分发机制、View渲染过程
Activity和View只有两个方法控制事件传递:dispatchTouchEvent(),onTouchEvent ();
Android技术干货分享
2021/01/07
1.1K0
这可能是2020大小厂问的最经典的Android面试题了——事件分发机制、View渲染过程
Android开发艺术笔记 | View的事件分发机制原理详析与源码分析(ing)
(2)【处理事件,独一无二】 正常情况下,一个事件序列只能被一个View拦截且消耗!!! 这一条的原因可以参考(3), 因为一旦一个元素拦截了某此事件, 那么同一个事件序列内的所有事件都会直接交给它处理!!! 因此同一个事件序列中的事件不能分别由两个View同时处理!!! 除非, 将本该由某个View自己处理的事件 通过onTouchEvent强行传递给其他View处理。 (3)【事件序列,从一而终】 某个View一旦决定拦截,则这一个事件序列都只能由它来处理 (如果事件序列能够传递给它的话), 并且它的onInterceptTouchEvent不会再被调用!!! 当一个View决定拦截一个事件后, 那么系统会把同一个事件序列内的其他方法都直接交给它来处理, 因此 就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。 (4)【短期失信】 某个View一旦开始处理事件, 如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false), 那么同一事件序列中的其他事件都不会再交给它来处理, 【即,View放弃处理ACTION_DOWN,便放弃了整个事件序列!!!】 并且事件将重新交由它的父元素去处理, 即父元素的onTouchEvent会被调用。【事件向上“回传”】 即, 事件一旦交给一个View处理,那么它就必须消耗掉!!! 否则同一事件序列中剩下的事件就不再交给它来处理了!!! 好比上级交给程序员一件事,如果这件事没有处理好, 短期内上级就不敢再把事情交给这个程序员做。 (5)【余粮上缴】 如果View不消耗除ACTION_DOWN以外的其他事件, 那么这个点击事件会消失, 此时父元素的onTouchEvent并不会被调用, 并且当前View可以持续收到后续的事件, 最终这些消失的点击事件会传递给Activity处理。 (6)ViewGroup默认不拦截任何事件。 Android源码中 ViewGroup的onInterceptTouch-Event方法默认返回false。 (7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。 (8)View的onTouchEvent默认都会消耗事件(返回true)!!!!!!! 除非它是不可点击的(clickable 和longClickable同时为false)。 View的longClickable属性默认都为false, clickable属性要分情况, 比如Button的clickable属性默认为true, 而TextView的clickable属性默认为false。 (9)【enable无用,clickable居上】 View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的!!!!! 只要它的clickable或者longClickable有一个为true, 那么它的onTouchEvent就返回true!!! (10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。 (11)【由外而内;以下犯上】 事件传递过程是由外向内的, 即事件总是先传递给父元素,然后再由父元素分发给子View, 通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
凌川江雪
2020/04/01
1.1K0
Android开发艺术笔记 | View的事件分发机制原理详析与源码分析(ing)
高级 UI 成长之路 (二) 深入理解 Android 8.0 View 触摸事件分发机制
在上一篇文章中我们介绍了 View 的基础知识以及 View 滑动的实现,本篇将为大家带来 View 的一个核心知识点 事件分发机制。事件分发机制不仅仅是核心知识点也是 Android 中的一个难点,下面我们就从源码的角度来分析事件的传递还有最后是如何解决滑动冲突的。
做个快乐的码农
2021/11/12
7560
高级 UI 成长之路 (二) 深入理解 Android 8.0 View 触摸事件分发机制
View的滑动冲突的分析和处理实践
文中有用到 Scroller 来实现弹性滑动,不了解的可以先看下 View的滑动实现方式。
103style
2022/12/19
5630
View的滑动冲突的分析和处理实践
怎样在Android上实现一个iOS多任务列表效果
纵观Android标准的控件库,能想到的就只有ViewPager比较合适,其首先满足第1点,ViewPager又是直接使用Adapter来管理数据,然后通过Fragment来管理每个item,满足第4点(这一点很重要,Adapter+Fragment这种成熟的设计,会让调用代码很简洁),剩下的2,3点都是UI层面的效果,应该改改ViewPager的源码就可以实现了吧,微笑。
allanlin
2020/08/13
3.8K1
怎样在Android上实现一个iOS多任务列表效果
5个Android自定义View的深度面试题
解答: 自定义View是Android开发中一个核心的概念,它允许开发者根据应用的特定需求来创建新的视图组件。自定义View的重要性在于它提供了高度的灵活性和创新性,使得开发者可以创建出独特的用户界面和交互体验。自定义View通常涉及继承View或其子类,并重写onMeasure、onLayout和onDraw等方法来定义视图的行为和外观。
AntDream
2024/10/23
1790
5个Android自定义View的深度面试题
手把手教你读懂源码,View的Touch事件传递流程详细剖析
继上一篇分析,今天我们来接着分析Activity的Touch事件是如何分发传递的。 都知道在Android中的事件主要包括三部分内容:分发事件dispatchTouchEvent、拦截事件onInterceptTouchEvent、消费事件onTouchEvent。这几乎是所有开发者都要面临的问题,无论是解决一些事件冲突问题,还是自定义View,都会或多或少涉及到。由于其独特的重要性,大多数面试的时候也基本会有所涉及,所以很好的掌握View的Touch事件传递显得尤其重要。 1、Activi
分享达人秀
2018/02/02
1K0
手把手教你读懂源码,View的Touch事件传递流程详细剖析
Android开发笔记(四十五)手势事件
基本的手势事件主要有如下三个方法: dispatchTouchEvent : 判断该事件是否需要下发。返回true表示需要下发给下级视图,返回false表示不需要下发(交给自身的onTouchEvent处理)。但是否最终下发,还需根据onInterceptTouchEvent的拦截结果。 onInterceptTouchEvent : 判断当前容器是否需要拦截该事件。返回true表示予以拦截(交给自身的onTouchEvent处理)、不放给下级视图,返回false表示不拦截该事件。 onTouchEvent : 判断该事件是否处理完毕。返回true表示处理完毕,则无需处理上级视图的onTouchEvent,一路返回结束流程。返回false表示该事件未完成,则返回继续处理上级视图的onTouchEvent,然后再根据上级onTouchEvent的返回值判断是直接结束还是由再上级处理。
aqi00
2019/01/18
1.4K0
Android6.0源码分析之View(一)
目前对于view还处于学习阶段,本来打算学习结束之后再写一篇进行总结,但是发现自己自制力太差,学习效率太低,所以在此,边学边写博客,不仅督促自己完成对view的学习,而且还可以看看大家对于view有什么想知道的,顺便来看看自己需要研究些什么。 fang_fang_story 接下来就是对view的学习。对于view我的学习思路是先要对view相关的知识进行一个整体的系统的了解,然后在对view中各个知识模块进行详细的研究,如果有正在研究view并且遇到问题的可以留言,大家一起讨论讨论。想要对view先有个整
fanfan
2018/01/24
9770
android画廊无限轮播,ViewPager无限循环实现画廊式banner
主要属性:android:clipChildren=”false” //允许子布局超出父布局显示
全栈程序员站长
2022/08/10
2.5K0
android画廊无限轮播,ViewPager无限循环实现画廊式banner
Android开发之漫漫长途 番外篇——自定义View的各种姿势2
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。
LoveWFan
2018/08/07
5790
Android开发之漫漫长途 番外篇——自定义View的各种姿势2
View的工作原理
View的绘制流程是从ViewRoot的PerformTraversals方法开始的。它经过measure,layout,draw三个过程将view绘制出来。mesure用来测量view的宽高,layout用来确定位置,draw绘制。流程图如下
提莫队长
2019/02/21
5620
Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。
LoveWFan
2018/08/07
5600
Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)
仿淘宝、京东拖拽商品详情(可嵌套ViewPager、ListView、WebView、FragmentTabhost)实现效果图实现
对于电商App,商品详情无疑是很重要的一个模块,观察主流购物App的详情界面,发现大部分都是做成了上下两部分,上面展示商品规格信息,下面是H5商品详情,或者是嵌套了一个包含H5详情及评论列表的ViewPager界面,本文就是实现了一个兼容不同需求的上下滚动黏滞View-DragScrollDetailsLayout。 DragScrollDetailsLayout GitHub链接 实现效果图 首先看一下实现效果图 简单的ScrollView+Webview 当然,如果将Webview替换成其他的Lis
看书的小蜗牛
2018/06/29
1.3K0
推荐阅读
相关推荐
自定义无限循环ViewPager(一)――ViewPager初始化源码解析
更多 >
LV.1
互联网大厂高级工程师
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档