在安卓王国的边境,有一座特别的滑动城堡。城堡里藏着无数珍贵的 "故事卷轴"(比如图片、文章、表单),访客们希望能左右滑动切换卷轴,就像翻阅魔法书一样方便。但城堡以前的管理者总是笨手笨脚:每次切换卷轴都要把旧的烧掉(销毁 View),再重新画新的(创建 View),不仅浪费墨水(内存),还经常卡顿。
直到有一天,城堡来了位叫ViewPager的新管家。他带着神奇的 "滑动魔法" 和三个助手,让城堡的运转变得丝滑又高效。让我们跟着访客的一次参观,看看他们是怎么工作的吧~
第一章:管家和他的核心团队
在开始工作前,先认识下城堡的核心成员:
- ViewPager(维帕) :滑动城堡的总管家,负责统筹所有工作,让卷轴切换顺畅,还特别擅长 "旧物利用"(缓存复用)。
- PagerAdapter(帕姐) :卷轴管理员,手里握着所有 "故事卷轴" 的清单,知道每个位置藏着什么卷轴,还会制作新卷轴。
- 滑动精灵(OnTouchListener + Scroller) :维帕的小助手,能感知访客的滑动手势(手指拖动),还会用魔法让卷轴平滑移动(惯性滑动)。
- 卷轴休息室(缓存池) :存放暂时不用的卷轴的房间,避免重复制作浪费墨水。
第二章:第一次展示 ------ 卷轴的 "初次亮相"
清晨,第一位访客来到城堡,想看看收藏的 5 卷 "动物故事"(老虎、兔子、猴子、熊猫、大象)。维帕开始安排工作了。
第一步:维帕的 "地盘规划"
维帕先测量了城堡展示厅的大小(onMeasure()
):"展示厅宽 300 厘米,高 200 厘米,刚好能放下 1 卷卷轴(全屏展示)。"
(源码对应:ViewPager.onMeasure()
会计算自身大小,确定每个卷轴的展示区域)
第二步:问帕姐要 "卷轴清单"
维帕喊来帕姐:"帕姐,这次要展示多少卷故事呀?"
帕姐翻开清单(getCount()
):"报告维帕,一共 5 卷!"
(源码对应:PagerAdapter.getCount()
返回卷轴总数,告诉维帕有多少页需要展示)
第三步:制作初始卷轴
维帕说:"先把第 1 卷(老虎)展示出来吧。"
帕姐立刻行动:
- 她找了一张空白卷轴(
instantiateItem()
):"这是第 1 卷的位置,我来画老虎!"(其实是创建一个 View,比如 ImageView 或 LinearLayout)。 - 画好后,把卷轴交给维帕:"卷好啦,放在展示厅的 0 号位置!"(
instantiateItem()
会返回创建的 View,并通过addView()
添加到 ViewPager 中)。
(源码对应:PagerAdapter.instantiateItem(ViewGroup container, int position)
是核心方法,负责创建指定位置的 View 并添加到容器)
第四步:标记 "当前展示" 的卷轴
维帕在展示厅门口挂了块牌子:"当前展示第 1 卷"(setCurrentItem(0)
),然后让卷轴在展示厅正中间站好(布局位置)。
(源码对应:ViewPager.setCurrentItem(int item)
设置当前显示的页面,内部会触发布局和滑动逻辑)
第三章:滑动切换 ------ 卷轴的 "魔法移动"
访客想看下一卷(兔子),于是用手指向右滑动(这是城堡的规则:向右滑看后一卷,向左滑看前一卷)。滑动精灵立刻行动起来~
第一步:滑动精灵的 "手势感知"
滑动精灵摸到访客的手指(onTouchEvent()
):"手指在动!从左向右滑,距离是 100 厘米。"
它立刻告诉维帕:"访客想切换到第 2 卷!"
(源码对应:ViewPager.onTouchEvent(MotionEvent ev)
处理触摸事件,计算滑动方向和距离)
第二步:平滑移动的 "魔法"
维帕对滑动精灵说:"让卷轴慢慢移过去,别太急。"
滑动精灵掏出 "平滑魔法棒"(Scroller
):"收到!我会用 300 毫秒让卷轴从 0 号位置滑到 1 号位置。"
(源码对应:Scroller
是实现平滑滚动的核心,ViewPager.computeScroll()
会不断计算滚动位置,直到停止)
第三步:卷轴的 "新旧交替"
当第 2 卷(兔子)滑进展示厅时,第 1 卷(老虎)会慢慢滑出。这时候:
- 维帕不会把老虎卷轴烧掉,而是说:"老虎卷轴暂时不用了,送到休息室(缓存池)保管吧!"(
destroyItem()
其实不销毁 View,只是从当前容器移除,放入缓存)。 - 如果访客又向左滑回第 1 卷,维帕会直接从休息室把老虎卷轴拿出来,不用麻烦帕姐重新画(复用缓存,减少创建成本)。
(源码对应:PagerAdapter.destroyItem(ViewGroup container, int position, Object object)
移除 View 但不销毁,ViewPager 内部会缓存最近的 1-2 个 View,默认缓存当前页的前 1 页和后 1 页,可通过setOffscreenPageLimit()
调整)
第四章:特殊的 "卷轴制作法"(ViewPager2 与 ViewHolder)
后来,城堡来了位更聪明的新管家叫ViewPager2(维帕二世),他是维帕的升级版,改用了 "RecyclerView 的卷轴制作法":
- 帕姐的工作交给了
RecyclerView.Adapter
(就是之前列表小镇的阿蛋),卷轴的创建和复用更高效。 - 每个卷轴都由
ViewHolder
(小框)保管,彻底避免了重复画卷轴的麻烦。
(补充说明:ViewPager2 内部其实是用 RecyclerView 实现的,所以支持垂直滑动、更灵活的缓存策略,推荐优先使用)
第五章:团队分工总结
故事讲完了,我们看看每个角色在源码里的真实职责:
童话角色 | 源码类 / 接口 | 核心职责 |
---|---|---|
维帕(ViewPager) | ViewPager |
协调测量、布局、滑动逻辑,管理卷轴缓存,调用 PagerAdapter 创建和移除 View。 |
帕姐(PagerAdapter) | PagerAdapter |
通过getCount() 提供卷轴总数,instantiateItem() 创建指定位置的 View,isViewFromObject() 判断 View 是否对应某个位置。 |
滑动精灵 | OnTouchListener + Scroller |
处理触摸事件,计算滑动参数,实现平滑滚动。 |
卷轴休息室 | 内部缓存机制 | 保存最近使用的 1-2 个 View(默认),避免频繁创建和销毁,优化性能。 |
最后一句话总结:ViewPager 就像一个聪明的卷轴管家,通过让 PagerAdapter 提供卷轴、滑动精灵处理手势、缓存池保存旧卷轴,实现了平滑的页面切换,而这一切的核心都是 "减少重复劳动"(复用 View)和 "流畅的移动魔法"(平滑滚动)~