Android-入门学习笔记-Fragment

2.Android中的导航模式

导航模式指导用户在应用的不同部分之间切换。

应用内的导航有很多种模式,每种模式都应该与正在展示给用户的数据类型相匹配。使用过许多应用后, 你将注意到一些常见的导航模式。

“主详情布局”由主数据列表构成。当你点按某个数据项时,系统会显示该项的详情视图。

 
 
 

你可以轻松地更改该布局,使其更适合大屏幕设备。当有更多的屏幕空间可用时,那么可以在主数据列表旁边同时显示某项的详情视图。

 
 

查看数据的另一种方式是使用“抽屉式导航栏”。如果应用包含多个屏幕,相互之间是“兄弟姐妹”关系, 那么不同的屏幕可以列在“抽屉”中,并从屏幕左侧弹出来。在 Google News & Weather 应用 中,抽屉式导航栏提供了不同新闻类别的链接。

 
 

你将看到的另一种模式是“可滑动标签页”。你可以在不同的屏幕之间向左或向右滑动,或者可以 在屏幕顶部点按某个标签页。设计规范中提供了关于标签页的部分。下面是个 YouTube 应用 示例,该应用在每个标签页中都使用了图标:

 
 

还有其他多种类型的浏览方式。例如,Google 日历应用具有一个滚动日程视图,但是你可以 通过日历月份视图跳到特定的天,或者可以查看不同时长的时间,例如周视图。这些互动方式是 日历应用特有的。

 
 
 

在你构建自己的应用的时候,你可以查看 Google 材料设计规范的导航部分或学习这一课关于如何设计有效导航模式的指南,了解如何构建你的应用。请注意,你的应用需要适用于各种屏幕尺寸的设备,这被称为自适应设计,我们在上一门课程中第一次提到过。Android 应用必须适合各种手机、平板电脑和电视,甚至手表和汽车!

3.其他应用中的导航模式

分析另一款应用中的导航模式。

请花些时间分析下另一款应用中的导航模式!这么做将使你更深入地了解应用是如何设计的, 以及如何在未来设计更好的应用。为此,请按以下步骤操作:

  1. 下载一款应用。琢磨琢磨该应用。切换到不同的屏幕,并试试不同的功能。
  2. 在论坛上分享下简短的说明,描述你是如何导航该应用的。花些时间阅读下其他学员写下的描述内容!

4.“向上”按钮

你可以创建一个关于应用中不同屏幕之间关系的图表。

以下是 Miwok 应用中不同屏幕之间关系的示例图表。主屏幕(具有四个类别按钮)是“父” Activity。它会转到词汇列表,即“子”Activity。这种父子关系非常重要,因为有时候, 用户可能想要导航到父 Activity 或子 Activity。这是另一种导航应用的方式,如果 用户进入应用中的非主屏幕位置,则很有用。更多详情请参阅培训文档

 
 

在查看 Android 应用时,你可能注意到了应用栏中指向左侧的水平箭头,称为“向上”按钮。 在下一个编程任务中,请向 NumbersActivity、FamilyActivity、ColorsActivity 和 PhrasesActivity 添加一个“向上”按钮。

 
 

用户可以通过此按钮导航到父 Activity,我们将其称之为 MainActivity。

 
 

“向上”按钮与“返回”按钮

你可能会有疑问,“向上”按钮和“返回”按钮的作用不是一样的吗?

实际上不完全一样。“返回”按钮位于 Android 上的系统导航栏上(最左侧的三角 形按钮)。无论你位于哪个应用中,当你点按“返回”按钮时,都会返回到之前的位置。

 
 

但是,在某些情况下,“返回”按钮和“向上”按钮产生的行为却不同。“向上”按钮始终会使你 转到父 Activity。“返回”按钮可以使你转到父 Activity、主屏幕或其他应用,取决于你 是如何到达当前屏幕的。

下面是个示例情形。你正在应用中浏览网页。然后收到一个新邮件通知。你点击该通知,忽然就 进入电子邮件应用中了。阅读完该电子邮件后,如果点按“返回”,则将回到之前的网络应用。如果 点按“向上”按钮,则将转到父 Activity,其中列出了所有的电子邮件。

“向上”按钮和“返回”按钮之间的区别在于用户是否能够直接转到你应用中的某个屏幕,无需经过 主页面(即直接打开一封电子邮件,而无需通过所有电子邮件列表)。在 Miwok 应用中,用户 需要经过主屏幕,所以“返回”按钮和“向上”按钮的效果是一样的。但是,最佳做法是让用户能够通过“向上”,明确跳转到 MainActivity。

为了实现最佳做法,我们将练习实施“向上”按钮。有关此操作的信息,请参阅此教程

这是该编程任务之前应用的外观:

 
 

这是该编程任务之后应用的外观(请仔细看看应用栏):

 
 
Quiz to Update Manifest to include the parent

 

Checkbox to add "Up" button
  • 查看答案
  • 提交答案

当你操作完毕之后,AndroidManifest.xml 中单个类别 Activity 应该如下所示:

<activity
        android:name=".NumbersActivity"
        android:label="@string/category_numbers" android:parentActivityName=".MainActivity"> <!-- Parent activity meta-data to support 4.0 and lower --> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/> </activity> 

请参阅完整的 AndroidManifest.xml 文件

5.另一版本中的Miwork应用

该应用现在外观已经很美观惊艳!看起来已经创建完毕了,为何还要更改呢?

这在应用开发团队中是有可能发生的。你构建了第 1 个版本的应用,然后团队决定重新设计并提供更好的用户体验。然后你就开始构建第 2 个版本的应用!

当你与任何专业 Android 开发者交流时,他们可以跟你讲述各种关于他们的应用用户界面在过去的几个月内甚至几年里都在不断改进的故事。

作为开发者,需要掌握的重要技能是能够重构你的代码。在应用中保留相同的功能,但是外观上却看起来不同。在采用新的设计模式的时候,你不能破坏任何现有的功能(例如,你不能丢失图片或音频播放功能)。当你破坏了某些功能,用户无法再像在之前的版本中那样执行某些任务的话,就称之为退步。

为了确保我们没有破坏任何功能,我们可以一次完成一个小小的阶段。尽可能多次在设备上运行下应用。你肯定不希望花 5 天的时间编写出新的代码,然后发现应用不能在设备上运行了。

以下是新的设计模式。我们希望能在单词列表之间滑动。这样我们就不用再点按一次,才能打开单词列表。当我们启动应用后,就可以立即看到单词列表。

 
 
 
 
 

请保存下应用当前状态的副本

重要提示:因为我们要大幅改动代码,因此一定要保存下应用当前状态的副本。这样的话,至少有个可运转的应用作为恢复的备份版本。

在你的计算机上搜索“AndroidStudioProjects”文件夹。该文件夹内应该有个“Miwok”文件夹,其中包含了该应用的所有代码。复制该文件夹,并保存到某个位置。

6.Android开发者模式

现在,请观看这个简短的“Android 开发者模式(关于标签页和 ViewPager)”Youtube视频,熟悉下 Android ViewPager 组件。然后继续转到下一步!

如果你无法打开视频链接,可以在此下载观看。

 
 

背景信息

此视频属于开发者推广大使 Ian Lake 和 Joanna Smith 创建的“Android 开发者模式”视频系列之一(你在第 4 节课的视频和文章中见过他们了)。

“Android 开发者模式”将指导你如何构建更好的应用,该视频解释了 Android 开发的基本组件和背后的原因,以及在你的应用中使用这些组件的最佳做法。你可以在以后随时观看这些视频

 

7.接下来要完成的更改

我们将分多个阶段完成这一更改。

首先,你将研究一个包含 ViewPager 的示例应用。你将了解 FragmentPagerAdapter 会如何为你滑动到的每个“页面”提供不同的 Fragment。然后我们将进一步学习 Fragment,为下一个编程步骤做好准备。

 
 

请将 4 个 Activity(Numbers、Colors、Family、Phrases)的逻辑重构成 4 个 Fragment。应用外观将保持不变,但是用来展示单词列表的所有逻辑将存储到 Fragment 中,而不是 Activity 文件中。当这些逻辑进入 Fragment 中后,我们就可以进入下一步了。

  • NumbersActivity 将包含 NumbersFragment
  • FamilyActivity 将包含 FamilyFragment
  • ColorsActivity 将包含 the ColorsFragment
  • PhrasesActivity 将包含 PhrasesFragment
 
 

你可以通过 ViewPager 在不同的“页面”或屏幕之间滑动。我们将修改 MainActivity,使其包含一个 ViewPager,其中有 4 个页面,每个页面是一个 Fragment。我们可以在每个 Fragment 之间滑动,查看不同的单词列表。

此时,我们将删除包含每个类别按钮的布局,同时还将删除类别 Activity(NumbersActivity、FamilyActivity、ColorsActivity、PhrasesActivity),因为应用现在只包含 1 个 Activity (MainActivity) 了。你可以判断出我们位于 MainActivity 内,因为所有屏幕上的应用栏都显示为“Miwok”。

 
 

ViewPager 可用后,我们将在 ViewPager 的顶部添加标签页,使你能够点按并跳到特定页面。

 

8.ViewPage示例

我们通过一个示例应用来了解下 ViewPager 在简单情形下是如何工作的。

1. 请打开该 GitHub 链接,并点击“下载 zip”下载此示例应用(确保你位于 的是“quiz”分支)。

 
 

2. 将此项目导入到 Android Studio 中,并在你的设备上运行该应用。

3. 当你打开该应用时,ViewPager 中应该有 3 个页面可以滑动。首先应该是 “Monday”,然后是“Tuesday”,接着是“Wednesday”。

 
 
 
 

4. 浏览下代码库,看看布局和 Java 文件是如何协同工作的。为测试题做准备,届时你将 需要修改该示例应用。

工作原理是什么?

ViewPager 的运作方式是从 FragmentPagerAdapter 适配器那获取数据。

对我们来说,我们需要自定义该适配器,以便显示我们自己的 Fragment,因此我们需要使用 FragmentPagerAdapter 的子类。通过继承,我们免费获得了 FragmentPagerAdapter 的所有功能,并且可以在上面添加我们的自定义功能。我们创建一个 SimpleFragmentPagerAdapter 类,并继承自 FragmentPagerAdapter 类。

当你在设备上启动该应用时,首先 ViewPager 会询问该适配器将有多少页面。对我们来说, 适配器会说有 3 个页面。请参阅 SimpleFragmentPagerAdapter getCount() 方法。

为了让 ViewPager 能够显示第 0 页,ViewPager 会向适配器请求第 0 个 Fragment。 请参阅 SimpleFragmentPagerAdapter getItem(int position) 方法。当用户向左 滑动时,我们移到第 1 页,表示 ViewPager 向适配器寻求位置 1 的 Fragment。当我们 转到第 2 页时,ViewPager 会向适配器寻求位置 2 的 Fragment。因此,根据用户滑到的 页面(亦成为位置),应用就会显示相应的 Fragment。

 
Quiz on ViewPager Sample App
Checkbox to Modify sample app
  • 查看答案
  • 提交答案

为了包含 5 个页面,我们又添加了以下几个 Fragment java 文件:

同时还添加了相应的布局文件:

然后,我们修改了 SimpleFragmentPagerAdapter.java 文件,以便创建这 5 个 不同的 Fragment。

这是最新版本的应用的代码

深入提示:你可以向 Fragment 中传入一个字符串参数,而不是具有 5 个基本上功能差不多 的 Fragment 文件。然后,该 Fragment 可以包含一个逻辑,能够根据传入的字符串更改 显示的文本。请使用 Fragment setArguments(Bundle args) 方法。更多详情请参阅 Fragment 指南

9.Fragment简介

Fragment 是 Activity 的一部分。你可以使用占据部分屏幕或整个屏幕的 Fragment,或者你可以同时显示多个 Fragment 并占据了整个屏幕。在 Activity 中,你还可以在不同的 Fragment 之间切换(你还可以使用不可见的 Fragment 来执行与 Activity 相关的任务,但是在这门课程中我们将不再介绍这一内容了。)

当我们开始为大型屏幕(例如平板电脑)构建应用时,Android 中引入了 Fragment。我来举个例子。

在主/详情模式中,列表 Fragment 位于屏幕的左侧,而详情 Fragment 位于右侧,并根据所选的列表项显示出来。

 
 

来源

在这种情况下,当我们要使应用适用于小屏幕设备(例如手机)时,使用 Fragment 就很方便。当用户在手机上打开应用时,他们可以看到列表 Fragment。如果点按列表中的某项,就可以导航到详情 Fragment。

 
 

来源

在手机上,每次只显示一个 Fragment。在平板电脑上,会同时显示两个 Fragment,二者相互挨在一起,以便利用大屏幕的可用空间。

 
 

来源

这只是 Fragment 的一种使用情形。你可以将你的应用逻辑拆分为任意多个 Fragment。但是,如果拆分成了多个 Fragment,可能会导致额外的管理事宜,因为你的 Activity 将需要管理 Fragment 之间的通信事宜。

Fragment 是一种 Java 类。你可以创建自己的 Fragment,就像创建自己的 Activity 一样。你可以在 Android 框架中创建 Fragment 的子类。

审核继承效果

 
 

10.Fragment生命周期

因为 Fragment 可以创建,添加到屏幕,然后从屏幕上删除,因此它具有自己的生命周期。猜怎么着?当你获得这些状态变化的通知时,Android 会提供异步回调。这些回调的格式和 Activity 类似。

想象下 Activity 包含一个占据整个屏幕的 Fragment。当 Activity 被创建时,Fragment 也被创建了。当 Activity 恢复了并变得活跃时,Fragment 也恢复了并变得活跃起来。Activity 生命周期和 Fragment 生命周期之间的关系异常紧密。

Fragment 还具有额外的回调,作为开发者,当它附加到封装的 Activity 上或离开该 Activity 时,你都会获得通知。

如果多个 Fragment 占据了整个屏幕,那么它们都需要关联到同一 Activity。它们需要知道,何时该 Activity 恢复/暂停了,这样它们也可以恢复或暂停正在该 Fragment 中执行的操作。如果你在应用的生命周期内切换出来,并替换了不同的 Fragment,这些 Fragment 需要被创建并被恰当地销毁。

注意,这是 Activity 的生命周期。

 
 

来源

类似地,Fragment 具有一个与 Activity 的生命周期阶段相关联的生命周期。

 
 

来源

Fragment 是一个很难懂的概念。当你在 Android 开发者的道路上继续前行时,将遇到更多的 Fragment。如需立即了解详情,请参阅

Android 社区中的开发者还写了很多其他关于 Fragment 的教程。

11.重构类别 Activity

我们暂时先不使用 ViewPager。先准备 Fragment。目前,我们有 4 个类别 Activity 和 0 个 Fragment。

 
 

在此编程任务结束时,我们希望有 4 个 Activity 和 4 个 Fragment。每个 Activity 包含一个 Fragment。

 
 

为了将代码逻辑移到 Fragment java 文件中,我们将复制/粘贴大量代码。代码基本上是一样 的。但是,任何假设我们位于 Activity 类中的代码都需要稍加修改,考虑到现在代码位于 Fragment 类中。例如,当我们位于 Fragment 类中时,Activity 生命周期调用(onCreate、 onStop)将不存在。但是会有其他类似的方法(onCreateView、onStop),因此,我们需要 根据 Fragment 生命周期调用稍加修改。

 
 

如果该表格看起来让人困惑,也不用担心。我们一步一步地更改 NumbersActivity 的代码, 并希望你会更加明白这一流程。如下面的图表所示,我们将从具有 NumbersActivity(之前 的状态)转变为具有其中包含 NumbersFragment 的 NumbersActivity(之后的状态)。 对于接下来的测试题,请自己按照相同的流程将剩余的类别 Activity 转换一下。

 
 

创建 NumbersFragment 类

1) 首先,为 NumbersFragment 创建一个新的 Java 文件。右键点击 “com.example.android.miwok”文件夹。依次转到“新建”> “Fragment”>“Fragment (Blank)”。

 
 

2) 按照向导填写相关内容。为 Fragment 取一个名字:NumbersFragment 。取消 选中所有的方框,因为我们将自己向文件中添加代码。

 
 

3) Android Studio 将在 NumbersFragment.java 文件中自动为你新建一个 Fragment 类。代码类似于下面的内容:

package com.example.android.miwok;

import android.os.Bundle;
import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** * A simple {@link Fragment} subclass. */ public class NumbersFragment extends Fragment { public NumbersFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView textView = new TextView(getActivity()); textView.setText(R.string.hello_blank_fragment); return textView; } } 

复制 NumbersActivity 中的代码并粘贴到 NumbersFragment 中

4) 首先将 NumbersActivity 中的全局变量复制到 NumbersFragment 中(与此 同时,从 NumbersActivity 中删除这些变量。) 代码应该是这样的

5) 将 NumbersActivity 中的 releaseMediaPlayer() 辅助方法复制到 NumbersFragment 中。releaseMediaPlayer() 的代码。

根据 Fragment 生命周期(而不是 Activity 生命周期)调整下代码。

6) 重写该 Fragment 的 onStop() 方法。

你不能直接完全复制 NumbersActivity 中的 onStop() 方法,因为 Fragment onStop() 方法性质稍微不太相同。如果你想知道的话,其实 Activity 类对该方法使用的是“protected” 修饰符,而 Fragment 类对该方法使用的是“public”修饰符。但现在不用关心这些修饰符详情。

将光标移到类中的空白处,在这里你可以添加一个新方法。按下键盘快捷键 Ctl + O 弹出一个 对话框,并选择要重写的方法。输入“onStop”,找到该结果后,点击“确定”。

 
 

Android Studio 将自动向 NumbersFragment 类中添加该方法:

@Override
public void onStop() { super.onStop(); } 

修改 onStop() 方法,使其调用 releaseMediaPlayer 方法:

@Override
public void onStop() { super.onStop(); // When the activity is stopped, release the media player resources because we won't // be playing any more sounds. releaseMediaPlayer(); } 

7) 重写该 Fragment 的 onCreateView() 方法。

Activity 的 onCreate() 方法与 Fragment 的 onCreateView() 方法稍微不同 在 Activity 的 onCreate() 方法中,我们可以调用 setContentView() 来为 该 Activity 设置布局。在 Fragment 中,我们需要根据 XML 布局资源 ID 获得 视图,并在 onCreateView() 方法中返回该视图。注意,Fragment 的布局将使用 word_list XML 布局资源,因为它将显示一个单词列表。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                    Bundle savedInstanceState) {
   View rootView = inflater.inflate(R.layout.word_list, container, false); /** TODO: Insert all the code from the NumberActivity’s onCreate() method after the setContentView method call */ return rootView; } 

复制了 NumbersActivity 的 onCreate() 方法中的代码后,你将在 Android Studio 中遇到各种错误,因为代码认为是在 Activity 类中运行的,而不是 Fragment 类。下面是 关于如何解决每个错误的说明。如果你不想一个一个的解决每个错误,则可以跳到下面的代码段, 这段代码展示了完成后你的 NumbersFragment onCreateView() 应该是怎样的。

第 1 个错误: 你将遇到一个错误,提示无法解析“findViewById(int)”方法,因为 Fragment 没有 findViewById 方法,而 Activity 的确具有该方法(请参阅 此链接)。 ListView listView = (ListView) findViewById(R.id.list);

解决方法:对 rootView 对象调用 findViewById(int),其中应该包含 子视图,例如 ListView。 ListView listView = (ListView) rootView.findViewById(R.id.list);

第 2 个错误: 你将遇到一个错误,提示无法解析“getSystemService(String)”方法, 因为 Fragment 无法访问系统服务,而 Activity 可以(请参阅此链接)。 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

解决方法:首先获取 Activity 对象实例。这是封装当前 Fragment 的 Activity, 即 NumbersActivity 封装了 NumbersFragment。然后对该 Activity 对象调用 getSystemService(String)。

mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);

第 3 个错误: 传入 WordAdapter 构造函数的参数存在问题,因为第一个参数“this” 指的是这个类(即 NumbersFragment),而 Fragment 不是有效的 Context。但是, 当“this”指的是 NumbersActivity 时代码是可行的,因为 Activity 是个有效的 Context。 WordAdapter adapter = new WordAdapter(this, words, R.color.category_numbers);

解决方法:传入封装此 Fragment 的 Activity 的引用并作为 context。 WordAdapter adapter = new WordAdapter(getActivity(), words, R.color.category_numbers);

第 4 个错误: 在创建 MediaPlayer 对象时,我们需要传入 context。同样的, “this”指的是 NumbersFragment(而不是 NumbersActivity),但是 Fragment 不是有效的 Context。 mMediaPlayer = MediaPlayer.create(NumbersActivity.this, word.getAudioResourceId());

解决方法:对第一个输入参数传入相关 Activity。 mMediaPlayer = MediaPlayer.create(getActivity(), word.getAudioResourceId());

在解决了这 4 个错误后,文件中应该没有任何错误了!NumbersFragment onCreateView() 方法看起来应该是这样的

再检查下,NumbersFragment 类的最后结果是这样的。

更新 NumbersActivity

8) 在 res/layout 目录下,创建一个新的布局文件,叫做 activity_category.xml。 重要的是该视图具有一个 ID。我们选择为该视图提供一个 ID,叫做“container”。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"/> 

9) 现在,我们需要更新 NumbersActivity 来使用 NumbersFragment, 否则的话,两个类中将有重复的代码并执行相同的操作。

将 NumbersActivity 代码替换为下面的整段代码。我们将使用这个简化的NumbersActivity 来将 activity_category XML 布局资源设置为内容视图。然后创建一个新的 NumbersFragment,并使用 FragmentTransaction 将其插入 container 视图中 (暂时不需要了解其中的详情)。因为该 container 具有“match_parent”宽度和高度, 因此 NumbersFragment 将占据屏幕的整个宽度和高度。

package com.example.android.miwok;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; public class NumbersActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_category); getSupportFragmentManager().beginTransaction() .replace(R.id.container, new NumbersFragment()) .commit(); } } 

10) 解释下,之前,NumbersActivity 用来展示 word_list.xml 布局。 现在,NumbersActivity 用来展示 activity_category.xml 布局, 而 NumbersFragment 用来展示 word_list.xml 布局。

 
 

现在,NumbersActivity 使用了 NumbersFragment 了!运行下应用,确保 Numbers 列表依然能运转。外观应该是一样的,因为正如之前解释的,这只是朝着目标前进的其中一个中间点。

太棒了!在接下来的测试题中,请对其他类别重复 1 到 9 的相同步骤。所有类别 都可以使用 activity_category.xml 布局资源。

最终,你的应用应该看起来是一样的,但是每个类别 Activity 将包含不同的 Fragment。 测试下应用,确保正确的 Activity 显示了正确的 Fragment。每个 Fragment 都具有相同的背景颜色(当我第一次实施解决方案时,这两项都遇到了 bug)。同时确保 音频播放功能依然能工作。

将会有很多的复制/粘贴操作,祝你好运!!

 
Quiz to Refactor App
Checkbox to change from activities to fragments
  • 查看答案
  • 提交答案

对于该测试题,点击每个类别将打开新的 Activity,请参阅下方的图片。

注意: 所有 Activity 都可以使用 activity_category.xml 布局资源。

12.ViewPager 和 FragmentPagerAdapter

现在,显示单词列表的逻辑代码已经位于 Fragment 中,你可以 在 MainActivity 中使用 ViewPager 了。

请自己完成这项任务,但是可以参考之前观看过的“Android 开发模式”Youtube视频。你还可以复习下 之前的 ViewPager 示例应用。

当你完成后,应用应该是这样的。当应用打开时,你立即就能看到 Numbers 单词列表。然后, 你可以在单词列表之间水平滑动。在后台,我们有一个 Activity (MainActivity),其中包含 一个 ViewPager(具有 4 个不同的 Fragment)。判断出有一个 Activity,是因为 当你在屏幕之间滑动时,应用栏中一直写有“Miwok”字样。

 
 
 
 
 
Quiz to Add ViewPager to MainActivity
Checkbox to Modify MainActivity
Checkbox to create CategoryAdapter class
Checkbox to delete unused files
  • 查看答案
  • 提交答案

练习完成前后的差异。

要在 Miwok 应用中使用 ViewPager 和 FragmentPagerAdapter,你需要作出以下更改。

1) 首先修改 activity_main.xml 布局以包含 ViewPager。你可以删除此布局文件中之前具有的 4 个类别 TextView。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/tan_background" android:orientation="vertical" tools:context="com.example.android.miwok.MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> 

2) 要在 ViewPager 中填充页面,我们需要一个适配器。

为适配器创建一个新文件,方法是:在“项目 (Project)”目录面板上右击 com.example.android.miwok 文件夹,然后依次转到“New”>“Java Class”。 创建一个新类,叫做 CategoryAdapter。

CategoryAdapter

3) Android Studio 将自动在 CategoryAdapter.java 文件中 创建一个新的 Java 类,其中包含以下内容:

package com.example.android.miwok; public class CategoryAdapter { } 

4) 修改 CategoryAdapter 类的声明行,以便继承 FragmentPagerAdapter 类。 Android Studio 将报错,提醒你需要填写必要的方法。你可以按下 Alt + Enter 获得 修复该文件的快速方法,或者右击并尝试解决该错误。Android Studio 将为需要实施的 方法添加一个简单版本,同时还会为 CategoryAdapter(传入 FragmentManager) 添加一个构造函数。请参阅以下内容:

package com.example.android.miwok;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; /** * Created by katherinekuan on 4/14/16. */ public class CategoryAdapter extends FragmentPagerAdapter { public CategoryAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return null; } @Override public int getCount() { return 0; } } 

5) 用我们希望在 Miwok 应用中实现的逻辑重写各个方法。我们需要思考下:

问题: ViewPager 中需要有多少个页面? 答案: 4 个页面,所以我们应该在 CategoryAdapter getCount() 方法中返回 4 个。

问题: 如果位置是 0,我们应该显示哪个 Fragment?位置是 1 或 2 呢? 答案: 在 CategoryAdapter getItem(int position) 方法中,我们创建, 一个 if/else 条件语句,针对给定位置返回相应的类别 Fragment。

最终的 CategoryAdapter 类应该如下所示:

package com.example.android.miwok;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

/** * {@link CategoryAdapter} is a {@link FragmentPagerAdapter} that can provide the layout for * each list item based on a data source which is a list of {@link Word} objects. */ public class CategoryAdapter extends FragmentPagerAdapter { /** * Create a new {@link CategoryAdapter} object. * * @param fm is the fragment manager that will keep each fragment's state in the adapter * across swipes. */ public CategoryAdapter(FragmentManager fm) { super(fm); } /** * Return the {@link Fragment} that should be displayed for the given page number. */ @Override public Fragment getItem(int position) { if (position == 0) { return new NumbersFragment(); } else if (position == 1) { return new FamilyFragment(); } else if (position == 2) { return new ColorsFragment(); } else { return new PhrasesFragment(); } } /** * Return the total number of pages. */ @Override public int getCount() { return 4; } 

}

6) 然后在 MainActivity 中,我们可以使用 CategoryAdapter 来为 ViewPager 提供支持。删除与 4 个类别 TextView 相关的所有旧代码。

我们只需找到在 XML 布局中声明的 ViewPager。然后创建一个新的 CategoryAdapter, 并使用 setAdapter 方法将该适配器设置到 ViewPager 上。

package com.example.android.miwok;

import android.os.Bundle;
import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set the content of the activity to use the activity_main.xml layout file setContentView(R.layout.activity_main); // Find the view pager that will allow the user to swipe between fragments ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); // Create an adapter that knows which fragment should be shown on each page CategoryAdapter adapter = new CategoryAdapter(getSupportFragmentManager()); // Set the adapter onto the view pager viewPager.setAdapter(adapter); } } 

7) 测试代码,确保所有功能都能正常运行。

8) 如果代码能按预期得运行,则删除以下不必要的文件:

  • NumbersActivity.java
  • FamilyActivity.java
  • ColorsActivity.java
  • PhrasesActivity.java
  • activity_category.xml 布局文件

同时删除 AndroidManifest.xml 文件中的 Activity 声明。

9) 删除所有这些内容后,再次测试下应用,确保一切都正常运转,没有删除任何重要的内容。 并且确保音频播放功能依然能工作。

练习完成前后的差异

13.向 ViewPager 中添加标签页

现在我们添加一些标签页,这样用户就知道可以滑动打开更多的页面。

首先,你需要使用 Android Design 支持库。这样有助于你创建一个甚至可以在更早 版本的 Android 设备上运行的 材料设计应用。要详细了解该支持库,请参阅此 Blogspot 博文。 该支持库会定期更新,你可以参阅此网站获取最新的更新或订阅 Android Developers Blogspot 博客

向你的项目中添加 Android Design 支持库

1) 在 Android Studio 中的“项目 (Project)”目录面板中,依次转到 Miwok > app > build.gradle,打开 build.gradle 文件。

Android Studio 使用 Gradle 工具来生成安装到设备上的 apk(应用文件)。 要详细了解如何配置 build.gradle 文件,请参阅这篇文章。 我们还提供了关于这一概念的高级课程用 Gradle 构建 Android 和 Java,如果暂时不理解这一概念的话,也没关系。

 
 

2) 此 build.gradle 文件包含了应用该如何封装的信息。转到“dependencies” 部分,添加一行内容来包含 Android Design 支持库。这表明我们的应用依赖于 Android Design 支持库,并且我们的 Miwok 应用现在可以引用该设计库里的组件了。

compile 'com.android.support:design:23.3.0'

之后,代码应该类似于以下内容:

dependencies {
   compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:support-v4:23.2.1' compile 'com.android.support:design:23.3.0' } 

3) 你将在文件的顶部看到一个黄色警告,提示 Gradle 文件已更改,需要同步项目。 点击“立即同步 (Sync Now)”按钮。

 
 

现在,你可以使用 Android Design 支持库中的组件了,例如针对 Viewpager 使用 TabLayout!

在下一个编程任务中,请修改应用,并在 MainActivity 中向你的 ViewPager 添加标签页。 完成后,你的应用应该是这样的。你可以参阅此 Codepath 教程以获取帮助。

 
 
 
 
 
Quiz to add tabs to ViewPager
Checkbox to add tabs to ViewPager
Checkbox to add visual polish
  • 查看答案
  • 提交答案

练习完成前后的差异。

首先,我们需要让标签页显示在屏幕上。然后我们需要根据红色标示改善外观。

1) 因为 Design Library 应该已经在项目中关联为依赖项,请修改 MainActivity 布局,使其包含 ViewPager 和 TabLayout。 activity_main.xml 布局文件应该是这样的。注意,我们为 TabLayout 分配了视图 ID,因为我们将需要在 Java 代码中引用该视图。

xml 文件可以如下所示:

  <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/tan_background" android:orientation="vertical" tools:context="com.example.android.miwok.MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> 

2) 修改 MainActivity onCreate() 方法,使 TabLayout 与 ViewPager 相关联。

代码应该如下所示:

 package com.example.android.miwok;
 import android.os.Bundle;
 import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set the content of the activity to use the activity_main.xml layout file setContentView(R.layout.activity_main); // Find the view pager that will allow the user to swipe between fragments ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); // Create an adapter that knows which fragment should be shown on each page CategoryAdapter adapter = new CategoryAdapter(getSupportFragmentManager()); // Set the adapter onto the view pager viewPager.setAdapter(adapter); // Find the tab layout that shows the tabs TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); // Connect the tab layout with the view pager. This will // 1. Update the tab layout when the view pager is swiped // 2. Update the view pager when a tab is selected // 3. Set the tab layout's tab names with the view pager's adapter's titles // by calling onPageTitle() tabLayout.setupWithViewPager(viewPager); } } 

3) 现在我们需要告诉应用在每个标签页中显示什么文本。转到 CategoryAdapter.java 文件并重写 getPageTitle() 方法。 该方法本身是在超类(FragmentPagerAdapter)中定义的,但是 我们想要重写该方法,以便自定义标签页文本(即代码中的页面标题)。

要重写该方法,请使用键盘快捷键 Ctl + O。输入“getPageTitle” 然后点击“确定”。

重写 getPage

Android Studio 会自动将该方法添加到 CategoryAdapter 类中。

@Override
public CharSequence getPageTitle(int position) { return super.getPageTitle(position); } 

4) 修改默认的 CategoryAdapter getPageTitle(int position) 实现, 以便为每页返回正确的类别名称。我们可以返回硬编码的字符串,例如“Numbers”、 “Family”等等。但是,我们在第 4 节课结束时提到过,我们不希望我们的应用 仅限于支持英语。我们应该为这些类别名称使用字符串资源。

遗憾的是,这也意味着我们需要一个 Context 对象,以便让该字符串资源 ID 变成实际的字符串。因此我们修改下 CategoryAdapter 构造函数,使其还需要 一个 Context 输入参数,这样我们便能获得恰当的文本字符串。

添加正确的 getPageTitle(int position) 代码后,CategoryAdapter 类将是这样的, 我们在 Java 代码中依赖的是字符串资源而不是硬编码的字符串。

 package com.example.android.miwok;

 import android.content.Context;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentPagerAdapter; /** * {@link CategoryAdapter} is a {@link FragmentPagerAdapter} that can provide the layout for * each list item based on a data source which is a list of {@link Word} objects. */ public class CategoryAdapter extends FragmentPagerAdapter { /** Context of the app */ private Context mContext; /** * Create a new {@link CategoryAdapter} object. * * @param context is the context of the app * @param fm is the fragment manager that will keep each fragment's state in the adapter * across swipes. */ public CategoryAdapter(Context context, FragmentManager fm) { super(fm); mContext = context; } /** * Return the {@link Fragment} that should be displayed for the given page number. */ @Override public Fragment getItem(int position) { if (position == 0) { return new NumbersFragment(); } else if (position == 1) { return new FamilyFragment(); } else if (position == 2) { return new ColorsFragment(); } else { return new PhrasesFragment(); } } /** * Return the total number of pages. */ @Override public int getCount() { return 4; } @Override public CharSequence getPageTitle(int position) { if (position == 0) { return mContext.getString(R.string.category_numbers); } else if (position == 1) { return mContext.getString(R.string.category_family); } else if (position == 2) { return mContext.getString(R.string.category_colors); } else { return mContext.getString(R.string.category_phrases); } } } 

5) 因为我们修改了 CategoryAdapter 构造函数,所以我们还需要更新下 MainActivity(用到了该构造函数)。当我们创建 CategoryAdapter 时, 我们传入了一个 Context(即“this”或相关 Activity)和 FragmentManager。

该声明行可以如下所示:

 CategoryAdapter adapter = new CategoryAdapter(this, getSupportFragmentManager());

6) 现在如果运行应用的话,标签页应该已经成功地出现在屏幕上了!但是,因为应用 剩余部分的背景颜色导致的原因,外观效果不太好。我们根据设计师提供的红色标示,开始 改善下应用外观。

7) 首先我们注意到的是,与其他标签页标签相比,“Family Members”文本太长了。 在 strings.xml 文件中,我们可以将字符串“Family Members”改成“Family”。

当你修改字符串时,一定要小心所有使用了该字符串的地方。对我们来说,我们只在一个地方 使用了该字符串,所以可以放心地更改。

 <!-- Category name for the vocabulary words for family members [CHAR LIMIT=20] -->
 <string name="category_family">Family</string> 

8) 根据我们提供了链接的以下 Codepath 教程对应用外观做出改善。

修改 activity_main.xml 文件。同时更改根 LinearLayout 的背景颜色, 使标签页的颜色与应用栏的一样。我们还希望向标签页应用一种格式,改变它们的外观。

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/primary_color" android:orientation="vertical" tools:context="com.example.android.miwok.MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tabs" style="@style/CategoryTab" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> 

在 styles.xml 文件中定义标签页布局的主题背景。注意,应用栏下面不应该出现 黑影。请参阅此 StackOverflow 讨论贴

完成后,styles.xml 文件应该如下所示:

 <resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/primary_color</item> <item name="colorPrimaryDark">@color/primary_dark_color</item> <item name="actionBarStyle">@style/MiwokAppBarStyle</item> <item name="android:windowContentOverlay">@null</item> </style> <!-- App bar style --> <style name="MiwokAppBarStyle" parent="style/Widget.AppCompat.Light.ActionBar.Solid.Inverse"> <!-- Remove the shadow below the app bar --> <item name="elevation">0dp</item> </style> <!-- Style for a tab that displays a category name --> <style name="CategoryTab" parent="Widget.Design.TabLayout"> <item name="tabIndicatorColor">@android:color/white</item> <item name="tabSelectedTextColor">@android:color/white</item> <item name="tabTextAppearance">@style/CategoryTabTextAppearance</item> </style> <!-- Text appearance style for a category tab --> <style name="CategoryTabTextAppearance" parent="TextAppearance.Design.Tab"> <item name="android:textColor">#A8A19E</item> </style> </resources> 

9) 在更改了所有这些代码后(你猜到了),我们应该运行下应用, 确保外观和运行效果跟预期的一样。:)

练习完成前后的差异

这是应用的完整结束状态。(真不敢相信,已经结束了!)

日后,如果你想了解更多内容,请参阅开发者编程工程师 Chris Banes 的开源 Cheesesquare 应用。 下载应用代码,并导入到 Android Studio 中,然后在设备上运行 Cheesesquare 应用,了解下你可以通过 Android Design 设计库执行的其他各种很酷的任务。

14.恭喜!

恭喜!

现在你已经正式完成 Miwok 应用了!我们对你取得的成就感到非常骄傲。

我们很期待你的下一步计划,无论是继续完善 Miwok 应用,针对其他语言调整该应用,完成纳米学位实战项目(你所在社区的导游应用),还是展开一段完全不一样的冒险之旅,都请在论坛上分享你打算运用新的技能开启怎样的未来之路!

无论你选择哪种道路,我们都祝你好运,希望你能充满信心,因为你已经构建了一个非常完善的 Android 应用了!



转载于:https://www.cnblogs.com/infocodez/p/8375917.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值