Android最初在2007年被谷歌公司收购后发布。起初,Android主要应用于手机。Android3.0增加了利用不断增长的平板市场的功能。
2014年,谷歌宣布Android活跃用户超过10亿!在谷歌应用商店有超过100万的应用程序,现在正是加入Android社区的最佳时机!
随着我们在2016年的开始,我们迎来了最近发布的Android6.0,为用户和开发者带来了激动人心的新功能。
第一章,活动,讨论了活动,这是大多数应用程序的基本构建块。查看最常见的任务示例,例如创建活动以及从一个活动传递控制到另一个活动。
第二章,布局,讨论了布局选项;虽然活动是UI的基础,但布局实际上定义了用户在屏幕上看到的内容。了解可用的主要布局选项和最佳实践。
第三章,视图、小部件和样式,探索了所有布局构建的基本UI对象。小部件包括从按钮和文本框到更复杂的NumberPicker和日历对话框等一切。
第四章,菜单,教你如何在Android中使用菜单。学习如何创建菜单以及如何在运行时控制它们的行为。
第五章,探索片段、应用小部件和系统UI,展示了如何通过重用UI组件的片段创建更灵活的用户界面。利用新的操作系统功能,如半透明系统栏,甚至使用沉浸模式完全隐藏系统UI。
第六章,处理数据,帮助你发现Android提供的多种持久化数据的方法,并了解何时使用每种选项最佳。Loader类示例展示了一种无需绑定UI线程的高效数据呈现解决方案。
第七章,警报和通知,展示了向用户显示通知的多种选项。选项包括应用内的警报、使用系统通知和浮动通知。
第八章,使用触摸屏和传感器,帮助你学习处理标准用户交互的事件,例如按钮点击、长按和手势。访问设备硬件传感器以确定方向变化、设备移动和指南针方位。
第九章,图形和动画,帮助你通过动画让你的应用程序生动起来!利用Android提供的多种创建动画的选项,从简单的位图到自定义属性动画。
第十章,初识OpenGLES,讨论了OpenGL;当需要高性能的2D和3D图形时,可以转向开放图形库。Android支持跨平台的OpenGL图形API。
第十一章,多媒体,利用硬件特性播放音频。使用Android意图调用默认的相机应用程序,或者深入研究相机API以直接控制相机。
第十三章,获取位置和使用地理围栏,指导你如何确定用户的位置,以及最佳实践以防止你的应用程序耗尽电池。使用新的位置API接收位置更新并创建地理围栏。
第十五章,后端即服务选项,探讨了后端即服务提供商可以为你的应用程序提供什么。比较几个提供原生Android支持和免费订阅选项的顶级提供商。
开发Android应用程序需要AndroidSDK,它支持多个平台,包括Windows、Mac和Linux。
尽管不是必须的,但本书使用了AndroidStudio,这是官方的AndroidIDE。如果你是Android开发新手,请访问以下链接查看当前的系统要求,并下载适用于你平台的带有SDK捆绑包的AndroidStudio:
AndroidSDK和AndroidStudio都是免费提供的。
本书假设你对编程概念和Android基础知识有一定的了解。否则,如果你是Android新手,并且通过直接进入代码学习效果最佳,这本书提供了最常见任务的广泛范围。
作为一本“食谱”,您可以轻松跳转到您感兴趣的主题,并尽快使代码在您自己的应用程序中运行。
在这本书中,你会发现几个经常出现的标题(准备工作、如何操作、工作原理、还有更多、另请参阅)。
为了清楚地说明如何完成一个食谱,我们按照以下方式使用这些部分:
本节告诉您对食谱的期望,并描述如何设置食谱所需的任何软件或初步设置。
本节包含遵循食谱所需的步骤。
这一节通常包含对前一节发生情况的详细解释。
本节包含关于食谱的额外信息,以使读者对食谱有更多了解。
本节为食谱提供其他有用的信息链接。
在这本书中,你会发现多种文本样式,用于区分不同类型的信息。以下是一些样式示例及其含义的解释。
文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL和用户输入将如下显示:"使用JsonObjectRequest()请求JSON响应基本上与StringRequest()相同。"
代码块设置如下:
警告或重要注意事项会像这样出现在一个框里。
提示和技巧会像这样出现。
我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者的反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。
如需发送一般反馈,只需将电子邮件发送至
既然您是Packt图书的骄傲拥有者,我们有许多方法可以帮助您充分利用您的购买。
您可以通过以下步骤下载代码文件:
文件下载后,请确保您使用最新版本的以下软件解压或提取文件夹:
如果您发现疑似盗版材料,请通过
我们感谢您帮助保护我们的作者以及我们为您提供有价值内容的能力。
如果您对这本书的任何方面有疑问,可以联系
本章节包括以下食谱:
AndroidSDK提供了一个强大的工具来编程移动设备,掌握这个工具的最佳方式是直接开始。虽然你可以从头到尾阅读这本书,因为这是一本食谱,但它特别设计成允许你跳转到特定任务并立即获得结果。
活动是大多数Android应用的基本构建块,因为活动类提供了应用与屏幕之间的接口。大多数Android应用至少会有一个活动,如果不是几个(但并非必须)。如果后台服务应用没有用户界面,则不一定需要活动。
本章节还简要探讨了通常与活动结合使用的意图对象。意图可以用于在您自己的应用程序中的活动之间传输数据,以及在外部应用程序中,如Android操作系统(一个常见的例子是使用意图启动默认的网页浏览器)。
要开始开发Android应用程序,请访问AndroidStudio页面下载新的AndroidStudioIDE和AndroidSDK捆绑包:
AndroidStudio是新的用于开发Android应用程序的工具,取代了现在已弃用的EclipseADT解决方案。本书将使用AndroidStudio展示所有食谱,因此如果你还没有安装它,请访问AndroidStudio网站(链接已提供)以安装IDE和SDK捆绑包。
在这个第一个示例中,我们将指导你创建一个新项目。AndroidStudio提供了一个快速入门向导,使得这个过程非常简单。按照以下步骤开始:
完成向导后,AndroidStudio将创建项目文件。对于此示例,我们将要检查的两个文件是MainActivity.java(对应于第6步中提到的活动名称)和AndroidManifest.xml。
在前面的代码中,还有一个属性—android:label。此属性表示屏幕上显示的标题以及如果这是启动器活动的话图标标签。
要查看可用应用程序属性的全部列表,请查看以下资源:
Android应用程序模型可以看作是一种面向服务的模型,活动作为组件,意图作为它们之间传递的消息。这里,一个意图用于启动显示用户通话记录的活动,但意图可以用作很多事情,我们将在本书中多次遇到它们。
为了简化事情,我们将使用一个意图对象来启动Android的一个内置应用程序,而不是创建一个新的应用程序。这只需要一个非常基础的应用程序,因此用AndroidStudio启动一个新的Android项目,并将其命名为ActivityStarter。
为了让示例简单,以便我们专注于手头的任务,我们将创建一个函数来展示一个意图操作,并从活动中的按钮调用这个函数。
在AndroidStudio中创建新项目后,请按照以下步骤操作:
尽管这个应用很简单,但它展示了Android操作系统背后的许多强大功能。意图对象只是一个消息对象。意图可以用于在应用程序的组件之间(如服务和广播接收器)以及与其他设备上的应用程序进行通信(正如本例中所做的那样)。
要在物理设备上测试,你可能需要为你的设备安装驱动程序(驱动程序针对硬件制造商是特定的)。你还需要在设备上启用开发者模式。启用开发者模式根据Android操作系统版本而有所不同。如果你在设备设置中看不到开发者模式选项,打开关于手机选项,并开始点击构建号。点击三次后,你应该会看到一个Toast消息,告诉你正在成为开发者的路上。再点击四次将启用该选项。
在本例中,我们通过指定ACTION_VIEW作为我们想要执行的操作(我们的意图)来创建一个意图对象。你可能已经注意到,当你输入Intent然后输入句点时,AndroidStudio提供了一个弹出式可能性的列表(这是自动完成功能),如下所示:
ACTION_VIEW与数据中的URL一起,表示意图是查看网站,因此会启动默认浏览器(不同的数据可能会启动不同的应用)。在这个例子中,我们的意图只是查看URL,所以我们仅使用startActivity()方法调用意图。根据我们的需求,还有其他调用意图的方法。在从活动中返回结果的食谱中,我们将使用startActivityForResult()方法。
对于Android用户来说,下载他们喜欢的网页浏览、拍照、发短信等应用是非常常见的。使用意图,您可以允许您的应用利用用户喜欢的应用,而不是试图重新发明所有这些功能。
若要从菜单选择启动一个活动,请参考第四章中的处理菜单选择部分,菜单。
这种活动切换并不能构成一个令人兴奋的应用程序。我们的活动除了演示如何从一个活动切换到另一个活动之外,什么也不做,这当然将成为我们开发几乎所有应用程序的基本方面。
如果我们手动创建活动,我们需要将它们添加到清单中。通过使用这些步骤,AndroidStudio已经处理了XML。要查看AndroidStudio的操作,请打开AndroidManifest.xml文件并查看
意图对象被定义为消息对象。作为消息对象,其目的是与应用程序的其他组件进行通信。在这个食谱中,我们将向您展示如何使用意图传递信息以及如何再次获取它。
这个食谱将从上一个食谱结束的地方开始。我们将这个项目称为SendData。
由于此食谱基于上一个食谱,因此大部分工作已经完成。我们将在主活动中添加一个EditText元素,以便我们有一些内容发送到SecondActivity。我们将使用(自动生成的)TextView视图来显示消息。以下是完整的步骤:
如预期的那样,意图对象正在完成所有工作。我们像在之前的食谱中一样创建了一个意图,然后添加了一些额外的数据。你注意到putExtra()方法调用了吗?在我们的例子中,我们使用了已经定义的Intent.EXTRA_TEXT作为标识符,但我们并不一定要这么做。我们可以使用我们想要的任何键(如果你熟悉名称/值对,你之前应该已经见过这个概念)。
使用名称/值对的关键点在于,你必须使用相同的名称来获取数据。这就是为什么我们在使用getStringExtra()读取额外数据时使用相同的键标识符。
第二个活动是用我们创建的意图启动的,所以只需获取意图并检查随它发送的数据。我们在onCreate()中进行这项操作:
textView.setText(getIntent().getStringExtra(Intent.EXTRA_TEXT));还有更多...我们不仅限于发送String数据。意图对象非常灵活,并且已经支持基本数据类型。回到AndroidStudio,点击putExtra方法。然后按下Ctrl和空格键。AndroidStudio将会弹出自动完成列表,这样你就可以看到你可以存储的不同数据类型了。
能够从一个活动启动另一个活动是很好的,但我们经常需要知道被调用的活动在任务中的表现,甚至需要知道哪个活动被调用了。startActivityForResult()方法提供了这个解决方案。
从活动中返回结果与我们在之前的食谱中调用活动的方式并没有太大不同。你可以使用之前食谱中的项目,或者开始一个新项目并将其命名为GettingResults。无论如何,一旦你有一个带有两个活动以及调用第二个活动所需代码的项目,你就可以开始了。
获取结果只需要进行少量更改:
如你所见,获取结果回来相对简单。我们只需使用startActivityForResult调用意图,这样它就知道我们想要一个结果。我们设置onActivityResult()回调处理程序以接收结果。最后,我们确保在关闭活动之前,第二个活动使用setResult()返回一个结果。在这个例子中,我们只是用静态值设置一个结果。我们仅显示我们收到的内容以演示这个概念。
检查结果码以确保用户没有取消操作是一个好习惯。它从技术上来说是一个整数,但系统将其作为布尔值使用。检查RESULT_OK或RESULT_CANCEL并根据情况进行处理。在我们的示例中,第二个活动没有取消按钮,那么为什么要检查呢?如果用户点击了返回按钮怎么办?系统会将结果码设置为RESULT_CANCEL,并将意图设置为null,这将导致我们的代码抛出异常。
我们使用了Toast对象,这是一种便捷的弹出式消息,可以用来不打扰地通知用户。它还作为一种方便的调试方法,因为它不需要特殊的布局或屏幕空间。
除了结果码,onActivityResults()还包括一个请求码。你可能想知道这是从哪里来的?它只是与startActivityForResult()调用一起传递的整数值,形式如下:
如果使用负请求码调用startActivityForResult(),它将表现得就像调用startActivity()一样——也就是说,它不会返回结果。
移动环境非常动态,用户更换任务比在桌面上更频繁。由于移动设备通常资源较少,可以预期你的应用程序在某个时刻会被中断。系统完全关闭你的应用程序以提供更多资源给当前任务也是非常可能的。这是移动设备的天性。
只需旋转设备,操作系统就会销毁并重新创建你的活动。这可能看起来有些过激,但这样做是有原因的——通常需要为纵向和横向提供不同的布局,这样可以确保你的应用程序使用正确的资源。
在这个教程中,你将看到如何处理onSaveInstanceState()和onRestoreInstanceState()回调来保存应用程序的状态。我们将通过创建一个计数器变量,并在每次按下计数按钮时增加它来演示这一点。我们还将有一个EditText和一个TextView小部件,以观察它们默认的行为。
在AndroidStudio中创建一个新项目,并将其命名为StateSaver。我们只需要一个活动,所以自动生成的MainActivity就足够了。但是,我们需要一些小部件,包括EditText、Button和TextView。它们的布局(在activity_main.xml中)将如下所示:
所有活动在其生命周期中都会经历多个状态。通过设置回调来处理事件,我们可以在活动被销毁之前让代码保存重要信息。
第3步是实际保存和恢复状态发生的地方。系统会向这些方法发送一个Bundle(一个数据对象,也使用名称/值对)。我们使用onSaveInstanceState()回调来保存数据,并在onRestoreInstanceState()回调中取出。
但是等等!你在旋转设备之前尝试在EditText视图中输入文本了吗?如果是,你会注意到文本也被恢复了,但我们没有任何代码来处理这个视图。默认情况下,系统会自动保存状态,前提是它有一个唯一的ID(并非所有视图都会自动保存状态,比如TextView,但如果我们想要,可以手动保存)。
请注意,如果你希望Android自动保存和恢复视图的状态,该视图必须有一个唯一的ID(在布局中使用android:id=属性指定)。注意,并非所有视图类型都会自动保存和恢复视图的状态。
onRestoreInstanceState()回调不是唯一可以恢复状态的地方。看看onCreate()的签名:
onCreate(BundlesavedInstanceState)这两个方法接收同一个名为savedInstanceState的Bundle实例。你可以将恢复代码移动到onCreate()方法中,效果是一样的。但需要注意的是,如果没有数据,比如在活动初次创建时,savedInstanceState包将为空。如果你想从onRestoreInstanceState()回调中移动代码,只需确保数据不是空的,如下所示:
if(savedInstanceState!=null){mCounter=savedInstanceState.getInt(KEY_COUNTER);}另请参阅存储持久活动数据能够在临时基础上存储关于我们活动的信息非常有用,但通常我们希望应用程序能够跨多个会话记住信息。
Android支持SQLite,但对于简单的数据来说,这可能会带来很多开销,比如用户的名字或高分。幸运的是,Android还提供了SharedPreferences这样的轻量级选项,适用于这些场景。
你可以使用上一个菜谱的项目,或者启动一个新项目并称之为PersistentData(在实际应用中,你可能无论如何都会这样做)。在之前的菜谱中,我们将mCounter保存在会话状态中。在这个菜谱中,我们将添加一个新方法来处理onPause()并将mCounter保存到SharedPreferences中。我们将在onCreate()中恢复该值。
我们只需做两个更改,都在MainActivity.java文件中:
如你所见,这与保存状态数据非常相似,因为它也使用名称/值对。这里,我们只存储了一个int,但我们同样可以轻松地存储其他基本数据类型。每种数据类型都有相应的获取器和设置器,例如,SharedPreferences.getBoolean()或SharedPreferences.setString()。
保存我们的数据需要SharedPreferences.Editor的服务。这是通过edit()调用的,接受remove()和clear()过程以及如putInt()的设置器。请注意,我们必须在这里用commit()语句结束任何存储操作。
getPreferences()访问器的稍微复杂一点的变体是getSharedPreferences()。它可以用来存储多个偏好设置。
使用getSharedPreferences()与使用其对应的方法没有区别,但它允许使用不止一个偏好文件。它的形式如下:
getSharedPreferences(Stringname,intmode)在这里,name是文件。mode可以是MODE_PRIVATE、MODE_WORLD_READABLE或MODE_WORLD_WRITABLE,描述了文件的访问级别。
对于一个活动来说,Android操作系统是一个充满危险的地方。系统对电池供电平台上的资源需求管理非常严格。当内存不足时,我们的活动可能会被从内存中清除,不会有任何预兆,同时包含的任何数据也会丢失。因此,理解活动生命周期至关重要。
下图显示了活动在其生命周期内经历的各个阶段:
除了阶段,图表还显示了可以覆盖的方法。如您所见,在之前的食谱中我们已经利用了这些方法中的大部分。希望了解全局情况将有助于您的理解。
在AndroidStudio中创建一个带有空白活动的新项目,并将其命名为ActivityLifecycle。我们将使用(自动生成)的TextView方法来显示状态信息。
为了观察应用程序经历各个阶段的过程,我们将为所有阶段创建方法:
我们的活动可以处于这三种状态之一:active、paused或stopped。还有一种第四状态,destroyed,但我们可以安全地忽略它:
在实现这些方法时,请在进行任何操作之前调用超类。
要关闭一个活动,直接调用它的finish()方法,这又会进而调用onDestroy()。要从子活动执行相同操作,请使用finishFromChild(Activitychild),其中child是调用子活动。
了解活动是正在关闭还是仅仅暂停,通常很有用,isFinishing(boolean)方法返回的值可以指示活动处于这两种状态中的哪一种。
在本章中,我们将涵盖以下主题:
Android提供了有用的Layout类,这些类包含和组织活动的各个元素(如按钮、复选框和其他Views)。ViewGroup对象是一个容器对象,它作为Android的Layout类家族的基础类。放置在布局中的视图形成一个层次结构,最顶层的布局是父布局。
Android提供了多种内置布局类型,专为特定目的设计,如RelativeLayout,它允许视图相对于其他元素定位。LinearLayout可以根据指定的方向堆叠视图或将它们水平对齐。TableLayout可用于布局视图网格。在各种布局中,我们还可以使用Gravity对齐视图,并通过Weight控制提供比例大小。布局和ViewGroups可以相互嵌套,以创建复杂的配置。提供了十几种不同的布局对象,用于管理小部件、列表、表格、画廊和其他显示格式,此外,您还可以从基类派生以创建自己的自定义布局。
使用AndroidStudio向导创建新项目时,它会自动创建res/layout/activity_main.xml文件(如下截图所示)。然后在onCreate()回调中使用setContentView(R.layout.activity_main)填充XML文件。
在这个示例中,我们将创建两个略有不同的布局,并通过按钮在它们之间切换。
在AndroidStudio中创建一个新项目,并将其命名为InflateLayout。创建项目后,展开res/layout文件夹,以便我们可以编辑activity_main.xml文件。
这里的关键是调用setContentView(),我们在之前自动生成的onCreate()代码中遇到过。只需将布局ID传递给setContentView(),它就会自动膨胀布局。
此代码旨在让概念易于理解,但对于仅更改按钮属性(在这个例子中,我们只需在按钮点击时更改对齐方式)来说可能过于复杂。通常在onCreate()方法中只需要对布局进行一次膨胀,但有时你可能需要像我们这里一样手动膨胀一个布局。(如果你要手动处理方向变化,这将是一个很好的例子。)
除了像我们在这里用资源ID标识布局,setContentView()还可以接受一个视图作为参数,例如:
findViewById(R.id.myView)setContentView(myView);参阅以下内容使用RelativeLayout如引言中所述,RelativeLayout允许视图相对于彼此和父视图定位。RelativeLayout特别有用,因为它可以减少嵌套布局的数量,这对于降低内存和处理要求非常重要。
创建一个名为RelativeLayout的新项目。默认布局使用RelativeLayout,我们将用它来水平和垂直对齐视图。
这是一个非常简单的练习,但它展示了RelativeLayout的几种选项:layout_centerVertical、layout_centerHorizontal、layout_below、layout_alignParentBottom等。
最常用的RelativeLayout布局属性包括:
与我们之前看到的相比,下面是仅使用LinearLayout来居中TextView的示例(创建与RelativeLayout的layout_center参数相同的效果):
另一个常见的布局选项是LinearLayout,它根据指定的方向,将子视图排列在单列或单行中。默认方向(如果未指定)是垂直,将视图对齐在单列中。
LinearLayout有一个RelativeLayout没有的关键特性——权重属性。在定义视图时,我们可以指定一个layout_weight参数,让视图根据可用空间动态调整大小。选项包括让视图填充所有剩余空间(如果视图具有更高的权重),让多个视图在给定空间内适应(如果所有视图权重相同),或者按权重比例分配视图空间。
我们将创建一个包含三个EditText视图的LinearLayout,以演示权重属性如何使用。在这个例子中,我们将使用三个EditText视图——一个用于输入收件人地址参数,另一个用于输入主题,第三个用于输入消息。收件人和主题视图各占一行,剩余空间留给消息视图。
创建一个新项目,将其命名为LinearLayout。我们将用LinearLayout替换在activity_main.xml中创建的默认RelativeLayout。
当使用LinearLayout的垂直方向时,子视图会在单列中创建(一个叠在另一个上面)。前两个视图使用android:layout_height="wrap_content"属性,使它们各占一行。editTextMessage使用以下属性来指定高度:
android:layout_height="0dp"android:layout_weight="1"使用LinearLayout时,它会告诉Android根据权重计算高度。权重为0(如果未指定,则为默认值)表示视图不应该扩展。在这个例子中,只有editTextMessage视图被定义了权重,因此它将独自扩展以填充父布局中的任何剩余空间。
当使用水平方向时,指定android:layout_height="0dp"(连同权重),让Android计算宽度。
将权重属性视为百分比可能有助于理解。在这种情况下,定义的总权重是1,所以这个视图获得了剩余空间的100%。如果我们给另一个视图分配了1的权重,那么总权重将是2,所以这个视图将获得50%的空间。尝试给其他视图之一添加权重(确保也将高度更改为0dp)以查看效果。
如果你给其他视图之一(或两者)添加了权重,你注意到文本位置了吗?没有为gravity指定值时,文本只会保持在视图空间的中心。editTextMessage指定了:android:gravity="top",这将强制文本位于视图的顶部。
可以使用按位OR组合多个属性选项。(Java使用管道字符(|)表示OR)。例如,我们可以结合两个重力选项,使其既沿着父视图的顶部对齐,又在可用空间内居中:
android:layout_gravity="top|center"需要注意的是,layout_gravity和gravity标签不是一回事。layout_gravity决定了视图在其父视图中的位置,而gravity控制视图内内容的位置——例如,按钮上文本的对齐方式。
当你在UI中需要创建一个表格时,Android提供了两种方便的布局选项:TableLayout(以及TableRow)和GridLayout(在API14中添加)。这两种布局选项可以创建看起来相似的表格,但每个都使用不同的方法。使用TableLayout时,行和列是动态添加的,随着表格的构建而添加。使用GridLayout时,行和列的大小在布局定义中定义。
这两种布局没有绝对的好坏,只是根据你的需求选择最适合的布局。我们将使用每种布局创建一个3x3网格以进行比较,因为你可以轻易地在同一个应用程序中使用这两种布局。
为了专注于布局并提供更简单的比较,我们将为这个食谱创建两个独立的应用程序。创建两个新的Android项目,第一个名为TableLayout,另一个名为GridLayout。
如您在查看创建的表格时所见到的那样,屏幕上的表格基本看起来是一样的。主要区别在于创建它们的代码。
在TableLayout的XML中,每一行都是通过TableRow添加到表格中的。每个视图都成为一个列。这不是必须的,因为单元格可以跳过或留空。(在下一节中了解如何在TableRow中指定单元格位置。)
GridLayout使用相反的方法。在创建表格时指定行数和列数。我们不必指定行或列的信息(尽管我们可以,下面会讨论)。Android会自动按顺序将每个视图添加到单元格中。
android:stretchColumns="1"stretchColumns指定了需要拉伸的列的(基于零的)索引。(android:shrinkColumns是一个可以收缩的列的基于零的索引,以便表格可以适应屏幕。)
为了在GridLayout中实现相同的效果,请在B列中的所有视图上添加以下属性(textView2、textView5和textView8):
android:layout_columnWeight="1"注意给定列中的所有单元格必须定义权重,否则它不会拉伸。
现在,让我们来看一下它们之间的不同之处,因为这确实是决定针对特定任务使用哪种布局的关键。首先要注意的是列和行是如何定义的。在TableLayout中,行是明确定义的,使用TableRow。(Android会根据拥有最多单元格的行来确定表格中的列数。)在定义视图时,使用android:layoutColumn属性来指定列。
相比之下,在GridLayout中,在定义表格时(如前所示使用columnCount和rowCount)指定行数和列数。
在前面的示例中,我们只是将TextViews添加到GridLayout中,并让系统自动定位它们。我们可以通过在定义视图时指定行和列的位置来更改此行为,例如:
android:layout_row="2"android:layout_column="2"提示在添加每个视图后,Android会自动增加单元格计数器,因此下一个视图也应该指定行和列,否则,您可能无法得到预期的结果。
与LinearLayout配方中显示的LinearLayout一样,GridLayout也提供了支持水平和垂直(默认)方向的orientation属性。方向决定了单元格的放置方式。(水平方向首先填充列,然后向下移动到下一行。垂直方向则先填充每行的第一列,然后移动到下一列。)
ListView和GridView都是ViewGroup的后代,但它们更像是一个View,因为它们是由数据驱动的。换句话说,在设计时,你不需要定义可能填充ListView(或GridView)的所有可能的View,而是从传递给View的数据动态创建内容。(ListItem的布局可以在设计时创建,以控制数据在运行时的外观。)
例如,如果你需要向用户展示一个国家列表,你可以创建一个LinearLayout并为每个国家添加一个按钮。这种方法有几个问题:确定可用国家、保持按钮列表更新、有足够的屏幕空间来容纳所有国家等等。否则,你可以创建一个国家列表来填充ListView,它将为每个条目创建一个按钮。
我们将使用第二种方法创建一个示例,从一个国家名称数组中填充ListView。
在AndroidStudio中创建一个新项目,并将其命名为ListView。默认的ActivityMain类扩展了Activity类。我们将改为让它扩展ListActivity类。然后,我们将创建一个简单的字符串列表并将其绑定到ListView上,以在运行时派生按钮。
我们首先创建一个简单的国家名称数组,然后使用它来填充ListAdapter。在这个例子中,我们在构造ListAdapter时使用了ArrayAdapter,但Android还有其他几种适配器类型可用。例如,如果你的数据存储在数据库中,你可以使用CursorAdapter。如果内置类型之一不符合你的需求,你总是可以使用CustomAdapter。
我们用以下这行代码创建适配器:
ListAdaptercountryAdapter=newArrayAdapter
一旦我们准备好适配器,只需通过setListAdapter()调用将其传递给底层的ListView。(底层的ListView来自扩展ListViewActivity。)最后,我们实现setOnItemClickListener以在用户按下列表中的按钮(代表一个国家)时显示一个Toast。
ListViews在Android中非常常见,因为它们通过滚动视图高效地利用屏幕空间,这对于小屏幕非常有用。ScrollView布局提供了一种替代方法来创建类似的滚动效果。这两种方法的主要区别在于,ScrollView布局在显示给用户之前会完全展开,而ListView只展开用户将看到的部分视图。对于有限的数据,这可能不是问题,但对于较大的数据集,在列表显示之前应用程序可能会耗尽内存。
由于ListView是由数据适配器驱动的,因此数据可以轻松更改。即使在我们的有限示例中,向屏幕添加一个新国家也只需将该名称添加到国家列表中。更重要的是,在用户使用应用程序时,列表可以在运行时更新(例如,从网站下载更新后的列表以显示实时选项)。
ListView还支持通过setChoiceMode()方法设置多选模式。要查看其效果,请在setListAdapter()之后添加以下代码行:
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);然后,将ListItem布局从android.R.layout.simple_list_item_1更改为android.R.layout.simple_list_item_checked。
尽管大多数需要滚动列表的应用程序都使用ListView,但Android也提供了GridView。它们在功能上非常相似,甚至使用相同的数据适配器。主要区别在于视觉效果,它允许多列显示。为了更好地理解,让我们将ListView示例更改为GridView。
首先,我们需要将MainActivity修改为再次继承自Activity,而不是ListActivity。(这将撤销之前的第1步。)然后,用以下代码替换onCreate():
protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);GridViewgridView=newGridView(this);setContentView(gridView);String[]countries=newString[]{"China","France","Germany","India","Russia","UnitedKingdom","UnitedStates"};ListAdaptercountryAdapter=newArrayAdapter
ListViewActivity基类处理了其中大部分工作,但GridView没有相应的活动类来继承。
在Android开发中,通常推荐的做法是用XML定义UI,用Java定义应用程序代码,将用户界面代码与应用程序代码分开。有时,从Java代码中修改(甚至构建)UI要容易或高效得多。幸运的是,这在Android中很容易实现。
在上一个示例中,我们看到了一个从代码中修改布局的小例子,我们设置了GridView列的数量以在代码中显示。在这个示例中,我们将获取对LayoutParams对象的引用,以在运行时改变边距。
在这里,我们将使用XML设置一个简单的布局,并使用LinearLayout.LayoutParams对象在运行时改变视图的边距。
每个视图(因此也包括ViewGroup)都有一组与其关联的布局参数。特别是,所有视图都有参数来告诉它们的父视图期望的高度和宽度。这些通过layout_height和layout_width参数定义。我们可以使用getLayoutParams()方法从代码中访问此布局信息。布局信息包括布局高度、宽度、边距以及任何类特定的参数。在这个例子中,我们通过获取按钮的LayoutParams并改变边距,在每次点击时移动按钮。
在开始优化你的布局之前,了解Android布局过程是有帮助的。布局的膨胀开始于活动首次显示时。发生以下三个步骤:
这个过程从父视图开始,然后遍历其所有子视图。这些子视图再遍历它们的子视图。这样就创建了布局树(LayoutTree),父视图成为树中的根节点。
层次查看器(HierarchyViewer)是AndroidSDK中包含的一个用于检查布局的工具。它以图形化的方式显示布局树,并附带了每个视图/节点的计时结果。通过检查树状布局和计时,你可以查找低效的设计和瓶颈。拥有这些信息,你就可以优化你的布局了。
对于这个示例,我们将使用层次查看器(HierarchyViewer)检查在使用RelativeLayout示例中给出的布局。
层次结构查看器只能连接到已获得root权限的设备,例如模拟器。
如果在AndroidStudio中检查此布局的Lint警告,你会在第二个LinearLayout元素上看到以下警告:
ViewStub还可以用来优化布局。将ViewStub视为布局的“懒加载”。ViewStub中的布局在需要之前不会展开,这减少了需要展开的视图数量。布局将更快渲染并使用更少的内存。这是在需要时提供不常用功能(如打印功能)的一种好方法,但在不需要时不占用内存。以下是一个ViewStub的示例:
一旦ViewStub被展开,ViewStub的ID将从布局中移除,并替换为展开后的ID。
在本章中,我们将介绍以下主题:
控件一词在Android中可以指代几个不同的概念。当大多数人谈论控件时,他们指的是应用控件,通常出现在主屏幕上。应用控件本身就像迷你应用程序,因为它们通常提供基于它们主要应用程序的功能子集。(通常,大多数应用控件随应用程序一起安装,但这不是必需的。它们可以是独立的应用,以控件格式存在。)一个常见的应用控件示例是提供多种不同主屏幕控件的风weather应用程序。第五章,探索片段、应用控件和系统UI,将讨论主屏幕应用控件并提供创建你自己的食谱。
在为Android开发时,控件一词通常指的是在布局文件中放置的专用视图,如Button、TextView、CheckBox等。在本章中,我们将专注于应用开发中的控件。
要查看AndroidSDK提供的控件列表,请在AndroidStudio中打开一个布局文件,并点击设计标签。在设计视图的左侧,你会在布局部分下方看到控件部分,如下面的屏幕截图所示:
如你所见,AndroidSDK提供了许多有用的控件——从简单的TextView、Button或Checkbox到更复杂的控件,如Clock、DatePicker和Calendar。内置控件虽然很有用,但扩展SDK提供的内容也非常容易。我们可以扩展现有控件来自定义其功能,或者通过扩展基础的View类来从头创建我们自己的控件。(我们将在后面的创建自定义组件食谱中提供一个示例。)
控件的视觉外观也可以自定义。这些设置可以用来创建样式,进而用来创建主题。就像在其他开发环境中一样,创建主题可以轻松地改变我们整个应用程序的外观,而无需付出太多努力。最后,AndroidSDK还提供了许多内置主题和变体,如来自Android3/4的Holo主题和来自Android5的Material主题。(Android6.0没有发布新主题。)
要将小部件插入到布局中,请按照以下步骤操作:
使用AndroidStudio创建UI就像拖放Views一样简单。您还可以直接在设计选项卡中编辑Views的属性。切换到XML代码只需点击文本选项卡。
这里我们所做的是在Android开发中非常常见的操作——在XML中创建UI,然后在Java代码中将UI组件(Views)连接起来。要从代码中引用一个View,它必须有一个与之关联的资源标识符。这是通过使用id参数完成的:
android:id="@+id/button"我们的onClickListener函数会在按钮被按下时在屏幕上显示一个名为Toast的弹出消息。
再次看看我们之前创建的标识符格式,@+id/button。@表示这是一个资源,而+符号表示新资源。(如果我们忘记包含加号,将会在编译时出现错误,提示Noresourcematchedtheindicatedname(没有资源与指定的名称匹配))。
我们讨论了Android视图的灵活性以及行为和视觉外观如何定制。在本教程中,我们将创建一个可绘制的状态选择器,这是一个在XML中定义的资源,它根据视图的状态指定要使用的可绘制资源。最常用的状态以及可能的值包括:
要定义状态选择器,请创建一个带有
对于本教程,我们将使用状态选择器根据ToggleButton的状态改变背景颜色。
在AndroidStudio中创建一个名为StateSelector的新项目,使用默认的手机&平板选项。当提示选择活动类型时,选择空活动。为了便于输入本教程的代码,我们将使用颜色作为表示按钮状态的图形。
我们将从创建状态选择器开始,这是一个用XML代码定义的资源文件。然后我们将设置按钮使用新的状态选择器。以下是步骤:
这里需要理解的主要概念是Android状态选择器。如第二步所示,我们创建了一个资源文件,根据state_checked指定了一个可绘制资源(在这种情况下是颜色)。
除了选中状态,Android还支持许多其他状态条件。在输入android:state时,查看自动完成下拉列表以查看其他选项。
创建好可绘制资源(第一步的XML)后,我们只需告诉视图使用它。由于我们希望根据状态改变背景颜色,因此我们使用android:background属性。
state_selector.xml是一个可传递给任何接受可绘制资源的属性的可绘制资源。例如,我们可以使用以下XML替换复选框中的按钮:
android:button="@drawable/state_selector"还有更多...如果我们想要实际的图片作为图形,而不仅仅是颜色变化呢?这就像更改项状态中引用的可绘制资源一样简单。
一旦你有了想要的图像,将它们放在res/drawable文件夹中。然后,在XML中更改状态项行以引用你的图像。以下是一个示例:
当Android遇到@drawable引用时,它会期望在res/drawable文件夹之一中找到目标。这些是为不同的屏幕密度设计的:ldpi(低每英寸点数)、mdpi(中等)、hdpi(高)和xhdpi(超高),它们允许我们为特定目标设备创建资源。当应用程序在特定设备上运行时,Android将从与实际屏幕密度最接近的指定文件夹加载资源。
如果它发现这个文件夹是空的,它会尝试下一个最接近的匹配,以此类推,直到找到命名的资源。出于教程目的,不需要为每种可能的密度创建一组单独的文件,因此将我们的图像放在drawable文件夹中是在任何设备上运行练习的简单方法。
有关在Android上选择资源的另一个示例,请参阅关于根据操作系统版本选择主题的食谱。
前一章中的GridView示例是在代码中创建的。但与GridView食谱不同,在这个食谱中,我们将向在activity_main.xml中定义的现有布局中添加一个视图。
在AndroidStudio中创建一个新项目,并将其命名为RuntimeWidget。在选择活动类型时,选择空活动选项。
我们将从为现有布局添加ID属性开始,这样我们就可以在代码中访问布局。一旦我们在代码中有了对布局的引用,我们就可以向现有布局中添加新视图。以下是步骤:
这段代码应该非常直观。首先,我们使用findViewById获取父布局的引用。我们在第一步中为现有的RelativeLayout添加了ID,以便更容易引用。我们在代码中创建一个DatePicker,并使用addView()方法将其添加到布局中。
如果我们想完全从代码创建整个布局呢?尽管这可能不是最佳实践,但在某些时候,从代码创建布局肯定更容易(也更简单)。让我们看看如果我们不使用activity_main.xml中的布局,这个例子会是什么样子。以下是onCreate()的样子:
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);RelativeLayoutlayout=newRelativeLayout(this);DatePickerdatePicker=newDatePicker(this);layout.addView(datePicker);setContentView(layout);}在这个例子中,其实并没有太大区别。如果你在代码中创建了一个视图,并且稍后想要引用它,你需要保留对对象的引用,或者给视图分配一个ID以使用findViewByID()。要给视图分配ID,请使用setID()方法,传入View.generateViewId()(以生成唯一ID)或在xml中使用定义ID。
正如我们在之前的教程中所看到的,AndroidSDK提供了广泛的组件。但是当你找不到符合你独特需求的预建组件时会发生什么呢?你可以随时创建自己的组件!
在本教程中,我们将介绍如何创建一个自定义组件,该组件从View类派生,就像内置小部件一样。以下是一个高级概述:
虽然重写onMeasure()和onDraw()不是严格要求的,但默认行为很可能不是你想要的。
在AndroidStudio中开始一个新项目,并将其命名为CustomView。使用默认的向导选项,包括Phone&TabletSDK,并在提示选择Activity类型时选择EmptyActivity。一旦项目文件在AndroidStudio中创建并打开,你就可以开始了。
我们将为自定义组件创建一个新类,从AndroidView类派生。我们的自定义组件可以是现有类的子类,比如Activity,但我们将在一个单独的文件中创建它,以便更容易维护。以下是步骤:
我们首先扩展了View类,正如内置组件所做的。接下来,我们创建默认构造函数。这很重要,因为我们需要将上下文传递给超类,我们通过以下调用实现:
super(context);我们需要重写onDraw(),否则,如引言中所述,我们的自定义视图将不会显示任何内容。当调用onDraw()时,系统会传递一个画布对象。画布是我们视图的屏幕区域。(因为我们没有重写onMeasure(),我们的视图将是100x100,但由于我们的整个活动仅包含这个视图,因此我们的整个屏幕都是我们的画布。)
我们在类级别创建了Paint对象,并作为final,以更有效地分配内存。(onDraw()应该尽可能高效,因为它每秒可能会被调用多次。)从运行程序中可以看出,我们的onDraw()实现只是将背景色设置为青色,并使用drawText()将文本打印到屏幕上。
实际上,还有很多内容。我们只是触及了自定义组件能做什么的皮毛。幸运的是,从本例中可以看出,实现基本功能并不需要太多代码。我们可以很容易地用一整章来讨论诸如将布局参数传递给视图、添加监听器回调、重写onMeasure()、在IDE中使用我们的视图等主题。这些功能都可以根据你的需求添加。
尽管自定义组件应该能够处理任何解决方案,但可能还有其他编码量更少的选项。扩展现有小部件通常足以满足需求,无需从头开始自定义组件的开销。如果你需要的解决方案包含多个小部件,还有复合控件。复合控件(如组合框)只是将两个或多个控件组合在一起作为一个单独的小部件。
复合控件通常会从布局扩展而来,而不是从视图,因为你将添加多个小部件。你可能不需要重写onDraw()和onMeasure(),因为每个小部件都会在其各自的方法中处理绘制。
样式是一组属性设置的集合,用于定义视图的外观。正如在定义布局时你已经看到的,一个视图提供了许多设置以决定它的外观及功能。我们已经设置了视图的高度、宽度、背景颜色和内边距,还有更多的设置,比如文字颜色、字体、文字大小、边距等等。创建样式就像把这些设置从布局中提取出来,然后放入一个样式资源中一样简单。
在这个食谱中,我们将通过创建样式并将其连接到视图的步骤。
与级联样式表类似,Android样式允许你将设计设置与UI代码分开指定。
创建一个新的AndroidStudio项目,命名为Styles。使用默认的向导选项创建一个Phone&Tablet项目,并在提示选择Activity时选择EmptyActivity。默认情况下,向导还会创建一个styles.xml文件,我们将在这个食谱中使用它。
我们将创建自己的样式资源,以改变TextView的外观。我们可以按照以下步骤将新的样式添加到AndroidStudio创建的styles.xml资源中:
样式是一个资源,通过在xml文件的