ViewPager Kotlin 教程入门

原文: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 LibrarySupport 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.favoritemovies

import 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 替代第三方库。你可以参考这个方案

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值