原文:ViewPager Tutorial: Getting Started in Kotlin
作者:Diana Pislaru
译者:kmyhy

ViewPager 是一个强大的布局管理工具,允许你在 app 中使用滑动手势进行导航。通常用于创建幻灯片效果、启动引导,或者 tab view。通过左右滑动在两个 ViewPage 页面之间切换,从而节省屏幕空间,创建更加迷你的界面。

在本教程中,你将通过修改一个现成的 app 让 UI 变得更有趣,并学习 ViewPager 的使用。在这个过程中,你将学习:

  • ViewPager 的工作原理
  • 如何用它节省内存
  • 如何在 ViewPager 中添加更多特性

注意:本教程假设你拥有 Kotlin 和 Android 开发经验。如果你不熟悉这门语言,请阅读这篇教程。如果你刚刚接触 Android,请阅读我们的入门和其它教程。

开始

下载开始项目,并打开 Android Studio ,然后选择 Open an existing Android Studio project。

找到示例项目目录,然后点击 Open。

首先看一眼现有代码。在 assets 目录,有一个 JSON 文件,包含了一些数据,它们是最流行的 5 个电影类 Android App。

你可以在 MoviewHelper.kt 中找到读取 JSON 数据的助手方法。Picasso 库用于下载和显示图片。

本教程使用 fragments。如果你不熟悉它,请阅读这个教程。

Build & run。

这个 app 有几个页面,每个页面会显示一些电影信息。我敢打赌,你一定想左右滑动以便查看下一部电影!或者只有我一个人会这样想?现在,我们还只能通过底部的上一部、下一部按钮来进行“不那么优雅的”页面切换。

ViewPager 简介

在 UI 中添加一个 ViewPager,将允许用户前后切换电影,通过在屏幕上左右骚动。你无需处理滑动动画、手势识别,因此实现起来比你想的还要简单。

我们可以将 ViewPager 实现分成 3 个步骤:

  1. 添加 ViewPager
  2. 为 ViewPager 创建 Adapter
  3. 将 ViewPager 和 Adapter 绑定

准备 ViewPager

第一步,打开 MainActivity.kt 删除 onCreate() 中这句之后的所有内容:

val movies = MovieHelper.getMoviesFromJson("movies.json", this)

从类中删除 replaceFragment() 方法。

打开 activity_main.xml 将 RelativeLayout 替换成:

<android.support.v4.view.ViewPager
    android:id="@+id/viewPager"android:layout_height="match_parent"android:layout_width="match_parent" />
Here you created the ViewPager view, which is now the only child of the RelativeLayout. Here’s how the xml file should look:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_height="match_parent"android:layout_width="match_parent"tools:context="com.raywenderlich.favoritemovies.MainActivity"><android.support.v4.view.ViewPager
      android:id="@+id/viewPager"android:layout_height="match_parent"android:layout_width="match_parent" /></RelativeLayout>

ViewPager 只在 Android Support 库中有效。Android Support Library 实际上是一系列库的集合,提供了对 widgets 和其它 标准 Android 特性的向下兼容。这些库提供了一些常见的 API,允许你在只支持低版本 API 级别的设备上使用高版本的 Android SDK 特性。你可以自己了解一下 Support Library 和 Support Library Packages。

回到 MainActivity.kt,导入 ViewPager:

import android.support.v4.view.ViewPager

现在你可以添加一个 ViewPager 属性了:

private lateinit var viewPager: ViewPager

注意:关键字 lateinit 的使用避免了在延迟初始化时 view 为空的问题。关于 lateinit 和其它 Kotlin 修饰符请阅读这里。

在 onCreate() 方法底部添加这句,以将 ViewPager 和你之前写的 xml 视图绑定:

viewPager = findViewById(R.id.viewPager)

实现 PagerAdapter

第一步完成了。你现在有一个 ViewPager,但没有 Adapter 来告诉它怎么显示的话,它上面也做不了。如果你运行 app,你不会看到任何电影。

ViewPager 通常显示用 fragment 构成的“页面”,但如果你想显示静态内容的话,也可以用于显示简单视图比如 ImageView 上。在这个项目中,你将在每个页面中显示多个内容。这里,我们将使用 Fragment。

我们需要通过 PagerAdapter 将 Fragment 对象和 ViewPager 关联,PagerAdapter 是一个对象,它位于 ViewPager 和包含你想显示的内容的数据集(在这里指的就是电影数组)之间。PagerAdapter 将创建一个个的 Fragment,将电影数据填充到 Fragment 然后返回给 ViewPager。

PagerAdapter 是一个抽象类,因此你将使用它的子类(FragmentPagerAdapter 和 FragmentStatePagerAdapter)而不是这个类自身。

FragmentPagerAdapter or FragmentStatePagerAdapter?

有两种用于管理每个 fragment 生命周期的标准 PagerAdapter 类型: FragmentPagerAdapter 和 FragmentStatePagerAdapter。它们都使用 fragment,但它们分别适用于不同场景:

  • FragmentPageAdapter 将 fragment 保存到内存,以便用户能够在它们之间进行导航。当一个 fragment 不可见时,PagerAdapter 会放开它,但不会销毁它,因此这个 fragment 对象仍然存活在 FragmentManager 中。只有 Activity 关闭时才释放它的内存。这使得页面间的动画更流畅和快速,但也会导致 app 的内存占用问题,如果 fragment 比较多的话。

  • FragmentStatePagerAdapter 如它的名称所暗示的,它会在用户不可见时会释放所有 fragment,只是将它们的状态保存在 FragmentManager 中。当用户回到一个 fragment,它会用所保存的状态恢复它。这种 PagerAdapter 对内存的需要更低,但可能两个页面之间的切换会更慢一点。

是时间做出选择了。你的电影只有 5 部,因此用 FragmentPagerAdapter 就足够了。如果你对这个教程感到无聊,想看看谁有的哈利.波特的电影时怎么办?你必须在 JSON 文件中再添加 8 部电影。这个数组更大。这样,你最好是用 FragmentStatePagerAdapter。

创建一个自定义的 FragmentStatePagerAdapter

在项目导航面板中,右键点击 com.raywenderlich.favoritemovies,选择 New->Kotlin File/Class。命名为 MoviesPagerAdapter 然后选择类型为 Class。点击 OK。

编辑内容为:

package com.raywenderlich.favoritemoviesimport android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter// 1
class MoviesPagerAdapter(fragmentManager: FragmentManager, private val movies: ArrayList<Movie>) : FragmentStatePagerAdapter(fragmentManager) {// 2   override fun getItem(position: Int): Fragment {return MovieFragment.newInstance(movies[position])}// 3  override fun getCount(): Int {return movies.size}
}

分段解释如下:

  1. 这个类继承自 FragmentStatePagerAdapter。这个父类需要一个 FragmentManager,因此我们自己的 PagerAdapter 类也需要这个。我们还需要一个电影数组作为参数。
  2. 返回指定位置所对应的 fragment 对象。
  3. 返回数组元素的个数。

当 ViewPager 需要显示一个 fragment 时,它会和 PagerAdapter 进行一系列会话。首先,它用 getCount 方法询问 PagerAdapter 数组中有多少部电影。然后在某个新页面即将显示时调用 getItem(int position)。在这个方法中,PagerAdapter 会创建一个新的 fragment 用于显示数组中和这个位置对应电影的信息。

关联 PagerAdapter 和 ViewPager

打开 MainActivity.kt 声明一个 MoviesPagerAdapter:

private lateinit var pagerAdapter: MoviesPagerAdapter

然后在 onCreate() 的已有代码下面添加:

pagerAdapter = MoviesPagerAdapter(supportFragmentManager, movies)
viewPager.adapter = pagerAdapter

初始化 MoviewsPagerAdapter 对象并将它和 ViewPager 关联。

注意:supportFragmentManager 等于 Java 中的 getSupportFragmentManager() 方法,viewPager.adapter = pagerAdapter 则等于 viewPager.setAdapter(pagerAdapter)。关于 Kotlin 的 getter/setter 访问器请看这里。

Build & run。app 表面上和之前一样,但你可以用轻扫手势而不是按钮来进行导航切换了。

注意:FragmentStatePagerAdapter 能省去你处理当前页面在运行时期间的配置改变的工作,比如设备的旋转。在这种情况下 activity 的状态会丢失,你必须通过 onCreate(savedInstanceState:Bundle?) 的方式将状态保存在 Bundle 对象中。幸好,你使用这种 PagerAdapter 能够为你自动完成这些工作。关于 savedInstanceState 对象和 activity 的生命周期,你可以参考这里。

无限循环

你经常会看到这样一种特性,也就是能够在页面之间进行往复循环式的无限切换。在第一页右扫会向最后一页循环,当在最后一页上左扫则向第一页循环。例如有 3 页,这样切换:

当当前索引到达 getCount() 返回的数组对象数目时,FragmentStatePagerAdapter 会停止创建新的 fragment。因此你需要修改这个方法,让它返回一个非常大的数字,使用户在同一方向扫动时不可能达到这个数。这样在索引达到 getCount() 返回的值之前, PagerAdapter 会不停地创建新页面。

打开 MoviesPagerAdapter.kt 一个常量保存一个数值:

private const val MAX_VALUE = 200

现在将返回结果从 movie.size 替换成 getCount():

return movies.size * MAX_VALUE

将数组大小乘上 MAX_VALUE,轻扫限制将以电影部数的整数倍进行增长。这种方式你就不必担心当电影数组变大时,返回的数字小于电影数组的大小。

在 Adapter 的 getItem(position:Int) 方法中还有一个问题。因为 getCount() 现在返回的数字远比数组大小要大,当用户扫动次数超过最后一部电影时,ViewPager 会越界访问数组元素。

将 getItem(position:Int) 方法修改为:

return MovieFragment.newInstance(movies[position % movies.size])

这将确保 ViewPager 不会越界访问 movies 数组中的元素,因为对 postion 以 movies.size 为模进行取模后,值只可能大于 0 并小于 movies.size。

现在,只有用户翻过整个数组后页面才会无限滚动(左扫)。这是因为,当 app 启动后,ViewPager 显示第 0 部电影。要解决这个问题,请打开MainActivity.kt 在 onCreate() 中 将 PageAdapter 关联到 ViewPager 之后添加一句:

viewPager.currentItem = pagerAdapter.count / 2

这将告诉 ViewPager,显示数组中间的电影。现在无论是哪个方向,都有大量的扫动次数可用。要保证一开始显示的电影仍然是数组中的第一部电影,可以将 MAX_VALUE 设置为一个偶数(这里,保持 200 就好)。这样,除以 2 之后,pagerAdapter.count % movies.size 还是等于 0(也就是当 app 启动时拿到的仍然是第一部电影的索引)。

Build & run。你现在可以左扫、右扫任意多次了,当你到达最后一部电影后又会从头开始,当你返回到第一部电影后又从最后开始,或者当你翻到最后一部电影后又会从第一部开始。

添加 Tab

TabLayout 是一个很好用的功能,允许你在多个页面间进行浏览和切换。TabLayout 为每个页面保留一个 tab,这个 tab 一般会显示页标题。用户可以点击 tab 切换到对应的页面或者用轻扫手势切换页面。

如果你将一个 TabLayout 添加到你的 ViewPager 中,你是无法看到 tab 的,因为自动布局使用 getCount() 方法作为 tab 的数目,但是现在 getCount() 方法返回的是一个超大的数字,要解决这个问题,你需要将数字缩小。

幸好,还有一个第三方库,叫做 RecyclerTabLayout 解决了这个问题。这个库在实现中使用了 RecycleView。你可以在这篇教程中学习这个神秘的 RecyclerView。要安装这个库,请打开 build.grad( app 的 Module 中),在 dependencies 中添加:

implementation 'com.nshmura:recyclertablayout:1.5.0'

Recyclertablayout 库使用了老版本的 Android Support Libraries 库,因此你必须添加这句才能让 Gradle 同步过程中不报错:

implementation 'com.android.support:recyclerview-v7:26.1.0'

现在点击 Sync Now,等 Android Studio 安装完这个库。

打开 activity_main.xml,在 ViewPager 之上加入下列代码:

<com.nshmura.recyclertablayout.RecyclerTabLayout
    android:id="@+id/recyclerTabLayout"android:layout_height="@dimen/tabs_height"android:layout_width="match_parent" />

现在在 ViewPager 中添加下列属性,让 ViewPager 在 RecyclerTabLayout 的下方对齐:

android:layout_below="@id/recyclerTabLayout"

整个布局文件变成这个样子:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_height="match_parent"android:layout_width="match_parent"tools:context="com.raywenderlich.favoritemovies.MainActivity"><com.nshmura.recyclertablayout.RecyclerTabLayout
      android:id="@+id/recyclerTabLayout"android:layout_height="@dimen/tabs_height"android:layout_width="match_parent" /><android.support.v4.view.ViewPager
      android:id="@+id/viewPager"android:layout_below="@id/recyclerTabLayout"android:layout_height="match_parent"android:layout_width="match_parent" /></RelativeLayout>

打开 MainActivity.kt,导入 RecyclerTabLayout:

import com.nshmura.recyclertablayout.RecyclerTabLayout

在类头部声明一个 RecyclerTabLayout 变量:

private lateinit var recyclerTabLayout: RecyclerTabLayout

在 onCreate() 方法的设置 viewPager.currentItem 的代码之上添加:

recyclerTabLayout = findViewById(R.id.recyclerTabLayout)
recyclerTabLayout.setUpWithViewPager(viewPager)

第一句将 RecyclerTabLayout 对象和 xml 视图绑定,第二句将 RecyclerTabLayout 和 ViewPager 关联。

最后,你必须让 RecyclerTabLayout 知道在 Tab 上分别都显示些什么标题。打开 MoviesPagerAdapter.kt 添加这个方法:

override fun getPageTitle(position: Int): CharSequence {return movies[position % movies.size].title
}

这个方法告诉 TabLayout 在对应位置的 Tab 上需要显示什么标题。它会针对用 getItem(postion:Int) 方法创建的 fragment 找到对应的电影来告诉 TabLayout 该显示什么。

运行 app。你会发现当你左右滑动 tab 时页面会做跳转。点击某个 tab,ViewPager 会自动滚动到相应的电影。

接下来做什么?

你可以下载完整示例项目。

干得不错!你修改了一个 app,通过 ViewPager 来改进它的 UI。你还添加了 TabLayout,实现了无限滑动。另外,你学习了 PagerAdapter 以及如何根据需要来选择 FragmentPagerAdapter 和 FragmentStatePagerAdapter。

如果你想进一步了解 ViewPager,可以参考这篇文档。你可以通过 PagerTransformer 自定义转换动画,你可以参考这篇教程。

加分项:你可以实现一个圆点指示器,就像其他照片流 APP 中一样。你可以参考这里的创建圆点指示器的方法。注意这个方法不能用于教程最后实现的 ViewPager,因为这种方法需要用 PagerAdapter 的 getCount() 方法来返回真实的页数。你可以实现这种指示器,而不要用无限滑动。同时可以用默认的 TabLayout 替代第三方库。你可以参考这个方案。

有任何问题和建议,请在论坛中留言。

ViewPager Kotlin 教程入门相关推荐

  1. kotlin coroutines 协程教程-入门用法

    kotlin coroutines 协程教程-入门用法 Coroutine 协程,是kotlin 上的一个轻量级的线程库,对比 java 的 Executor,主要有以下特点: 更轻量级的 api 实 ...

  2. Kotlin 教程(一):走进 Kotlin 的世界

    今年 Google I/O 大会上,官方扶正 Kotlin 的举动火遍整个 Android 开发圈.不过,有些开发者似乎过度解读 Google 的意图,认为 Kotlin 要取代 Java 成为 An ...

  3. 一篇就够——Kotlin快速入门

    文章内容主要是基于传智播客<kotlin从零基础到进阶>的视频做的笔记. 标题中的 V 是Video的缩写,V4 就是对应视频中的第四个视频. V4.程序入口--main函数 image ...

  4. 【python教程入门学习】Python实现自动玩贪吃蛇程序

    这篇文章主要介绍了通过Python实现的简易的自动玩贪吃蛇游戏的小程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学一学 实现效果 先看看效果 这比我手动的快多了,而且是单机的,自动玩没惹 ...

  5. Kotlin教程(九)泛型

    写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kotlin的同学.系列文章的知识点会以<Kotlin实战>这本书中顺序编写,在 ...

  6. 【helloworld】-微信小程序开发教程-入门篇【1】

    1. 开篇导言 本节目标:旨在演示如何用开发者工具构建并运行简单的 helloworld 应用. 目标用户:无编程经验,但对微信小程序感兴趣的同学. 学习目标:开发者工具的基本使用流程,即创建.导入. ...

  7. wxpython使用实例_wxPython中文教程入门实例

    wxPython中文教程入门实例 wx.Window 是一个基类,许多构件从它继承.包括 wx.Frame 构件. 可以在所有的子类中使用 wx.Window 的方法. wxPython的几种方法: ...

  8. Vue-cli3配置教程入门

    Vue-cli3配置教程入门 vue-cli3推崇零配置,其图形化项目管理也很高大上. 但是vue-cli3推崇零配置的话,导致了跟之前vue-cli2的配置方式都不一样了. 别名设置,sourcem ...

  9. 【python教程入门学习】普通人学python有意义吗

    普通人学python有意义吗?普通人能不能学习python语言,难不难,是否容易上手,学了python能做那些事情,能挣多少钱?这些问题是很多同学关心的问题,今天python教程入门学习就从小白同学的 ...

  10. 【python教程入门学习】学python要多久,0基础学python有多难

    学python要多久,0基础学python有多难,这是很多想学习python语言同学绕不开的问题,都害怕花完钱最终没有应有的回报!对于毫无经验0基础的同学来说学习python什么最重要,方向选对坚持下 ...

最新文章

  1. Python3中参数*args和**kwargs介绍
  2. iPhone开发:通过NSURLRequest获得服务器返回的http header和http status
  3. java 网络编程简单聊天_网络编程之 TCP 实现简单聊天
  4. 5教程 watchout_Unit 5单元复习学案设计
  5. C# 中的var关键字
  6. tkinter之事件绑定
  7. 2019考研调剂信息 计算机专业,2019考研分数线还未公布,已公布的调剂信息是真的吗?...
  8. contentprovider java_创建Contentprovider,
  9. web developer tips (38):如何用请求失败记录追踪重写规则
  10. 重新安装MySQL5.7.21教程_CentOS6.9安装mysql5.7.21教程
  11. 盘点 2017 年度最受欢迎的十大 Linux 服务器发行版
  12. div css将文字居中显示图片,css文字居中、图片居中、div居中解决方案
  13. js 打开新窗口 修改 窗口大小
  14. 电脑新固态硬盘ssd安装win7系统教程
  15. 《OpenGL ES 3.x游戏开发(下卷)》一2.3 风吹椰林场景的开发
  16. linux 两块硬盘合并成一块
  17. 机器学习第五章之决策树模型
  18. Java SE到Java EE的学习转换
  19. Qt静态函数中的信号和槽问题
  20. 手机token记录、支付宝、个推、goeasy、手机前端框架、阿里大于、百度编辑器、秀米集成解决方案

热门文章

  1. 鼎捷t100架构_鼎捷 T100 ERP 系统.pdf
  2. 线性回归 T检验P值计算
  3. 微信红包软件可测试,微信抢红包神器测试g2020
  4. 单片机交通灯c语言实验报告,模拟交通灯单片机实验报告.doc
  5. 闹钟Android实验报告,单片机实验报告(闹钟).doc
  6. 模拟抖音推荐算法检测视频原创度
  7. 千万流量大型分布式系统架构设计实战(干货)
  8. 上海大华条码称代码_上海大华条码秤的调试方法
  9. 需求文档:自营电商后台管理系统
  10. 苹果电脑装系统出现未能与服务器取得联系,Mac您的磁盘未能分区 Mac磁盘分区出错解决办法...