先从看得到的入手-探究活动

通过上一章的学习,你已经成功创建了你的第一个Android项目。不过仅仅满足于此显然是不够的,是时候学点新东西了。作为你的导师,我有义务帮你制定好后面的学习路线,那么今天我们应该从哪儿入手呢?现在你可想象一下,假如你已经写出了一个非常优秀的应用程序,然后推荐给你的第一个用户,你会从哪里开始介绍呢?毫无疑问,当然是从界面开始介绍了!因为即使你的程序算法再高效,架构再出色,用户根本不会在乎这些,他们一开始只会对看得到的东西感兴趣,那么我们今天的主题自然也要从看得到的入手了。

2.1活动是什么

活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动,但不包含任何活动的应用程序很少见,谁也不想让自己的应用永远无法被用户看到吧?

其实上一章中,你已经和活动打过交道了,并且对活动也有了初步的认识。不过上一章我们的重点是创建你的第一个Android项目,对活动的介绍并不多,在本章中我将对活动进行详细的介绍。

2.2活动的基本用法

到现在为止,你还没有手动创建过活动吧,因为上一章的HelloWorldActivity是Android Studio帮我们自动创建的。手动创建活动可以加深我们的理解,因此现在是时候应该自己反动手了。

由于Android Studio在一个工作区间只充许打开一个项目,因此首先你需要将当前的项目关闭,点击导航栏File-Close Project。然后再新建一个Android项目,项目名可以叫作ActivityTest,包名我们就使用默认值com.example.activitytest。新建项目的步骤你已经在上一章学习过了,不过图1.12中的那一步需要稍做修改,我们不再选择Empty Activity这个选项,而是选择Add No Activity,因为这次我们准备手动创建活动,如图2.1所示。

图2.1 选择不添加活动

图2.1 选择不添加活动

点击Finish,等待Gradle构建完成后,项目就创建成功了。

2.2.1手动创建活动

项目创建成功后,仍然会默认使用Android模式的项目结构,这里我们手动改成Project模式,本书中后面的所有项目都要这样修改,以后就不再赘述了。目前ActivityTest项目虽然还会自动生成很多文件,但是app/src/main/java/com.example.activitytest目录应该是空间了,如图2.2所示。

初始项目结构

图2.2 初始项目结构

现在右击com.example.activitytest包-New-Activity-Empty Activity,会弹出一个创建活动的对话框,我们将活动命名为FirstActivity,并且不要勾选Generate Layout File和Launcher Activity这两个选项,如图2.3所示。

新建活动对话框

图2.3 新建活动对话框

勾选Generate Layout File表示会自动为FirstActivity创建一个对应的布局文件,勾选Launcher Activity表示会自动将FirstActivity设置为当前项目的主活动,这里由于你是第一次手动创建活动,这些自动生成的东西暂时都不要勾选,下面我们将会一个个手动来完成。勾选Backwards Compatibility表示会为项目启用向下兼容的模式,这个选项要勾上。点击Finish完成创建。

你需要知道,项目中的任何活动都应该重写Activity的onCreate()方法,而目前我们的FirstActivity中已经重写了这个方法,这是由Android Studio自动帮我们完成的,代码如下所示:

public class FirstActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

}

}

可以看到,onCreate()方法非常简单,就是调用了父类的oncreate()方法。当然这只是默认的实现,后面我们还需要在里面加入很多自己的逻辑。

2.2.2创建和加载布局

前面我们说过,Android程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局就是用来显示界面内容的,因此我们现在就来手动创建一个布局文件。

右击app/src/main/res目录-new-Directory,会弹出一个新建目录的窗口,这里先创建一个名为layout的目录。然后对着layout目录右键-New-Layout resource file,又会弹出一个新建布局资源文件的窗口,我们将这个布局文件命名为first_layout,根元素就默认选择为LinearLayout,如图2.4所示。

新建布局资源文件

图2.4 新建布局资源文件

点击OK完成布局的创建,这时候你会看到如图2.5所示的布局编辑器。

布局编辑器

图2.5 布局编辑器

这是Android Studio为我们提供的可视化编辑器,你可以在屏幕的中央区域预览当前的布局。在窗口的最下方有两个切换卡,左边是Design,右边是Text。Design是当前的可视化布局编辑器,在这里你不仅可以预览当前的布局,还可以通过拖放的方式编辑布局。而Text则是通过XML文件的方式来编辑布局的,现在点击一下Text切换卡,可以看到如下代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent"

</LinearLayout>

由于我们刚才在创建布局文件时选择了LinearLayout作为根元素,因此现在布局文件中已经有一个LinearLayout元素了。那我们现在对这个布局稍做编辑,添加一个按钮,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="match_parent"

android:layout_width="match_parent"

android:layout_height="match_parent">

<Button

android:id="@+id/button_1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Button 1"

></Button>

这里添加了一个Button元素,并在Button元素的内部增加了几个属性。android:id是给当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作。你可能会对@+id/button_1这种语法感到陌生,但如果把加号去掉,变成@id/button_1,这样你就会觉得有些熟悉了吧,这不就是在XML中引用资源的语法吗?只不过是把string替换成了id。是的,如果你需要在XML中引用一个id,就使用@id/id_name这种语法,而如果你需要在XML中定义一个id,则要使用@+id/id_name这种语法。随后android:layout_width指定了当前元素的宽度,这里使用match_parent表示使用wrap_content表示当前元素的高度只要能刚好包含里面的内容就行。android:text指定了元素中显示的文字内容。如果你还不能完全明白,没有关系,关于编写布局的详细内容我会在下章中重点讲解,本章只是先简单涉及一些。现在按钮已经添加加完了,你可以通过右侧工具栏的Preview来预览一下当前布局,如图2.6所示。

预览当前布局

图2.6 预览当前布局

可以看到,按钮已经成功显示出来了,这样一个简单的布局就编写完成了。那么接下来我们要做的,就是在活动中加载这个布局。

重新回FirstActivity 在onCreate()方法中加入如下代码:

public class FirstActivity extends AppCompatActivity {

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layot.first_layout);

}

}

可以看到,这里调用了setContentView()方法来给当前的活动加载一个布局,而在setContentView()方法中,我们一般都会侵入一个布局文件的id。在第1章介绍项目资源的时候我曾提到过,项目中添加的任何资源都会在R文件中生成一个相应资源id,因此我们刚才创建first_layout.xml布局的id现在应该是已经添加到R文件中了。在代码中去引用布局文件的方法你也已经学过了,只需要调用R.layout.first_layout就可以得到first_layout.xml布局的id,然后将这个值传入setContentView()方法即可。

2.2.3在AndroidManifest文件中注册

别忘了在上一章我们学过,所有的活动都要在AndroidManifest.xml中进行注册才能生效,而实际上FirstActivity已经在AndroidManifest.xml中注册过了,我们打开app/src/main/Android-Manifest.xml文件睢一睢,代码如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.activitytest">

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme">

<activity android:name=".FirstActivity"></activity>

</application>

</manifest>

可以看到,活动的注册声明要放在<application>标签内,这里是通过<activity>标签来对活动进行注册的。那么又是谁帮我们自动完成了对FirstActivity的注册呢?当然是Android Studio了,之前在使用Eclipse创建活动或其他系统组件时,很多人都会忘记要去Android Manifest.xml中注册一下,从而导致程序进行崩溃,很显然Android Studio在这方面做理更加人性化。

在<activity>标签中我们使用了android:name来指定具体注册哪一个活动,那么这里填入的.FirstActivity是什么意思呢?其实这不过就是com.example.activitytest.FirstActivity的缩写而已。由于在最外层的<manifest>标签中已经通过package属性指定程序的包名是com.example.activitytest,因此在注册活动时这一部分就可以省略了,直接使用.FirstAc-tivity就够了。

不过,仅仅是这样注册了活动,我们的程序仍然是不能运行的,因为还没有为程序配置主活动,也就是说,当程序运行起来的时候,不知道要首先启动哪个活动。配置主活动的方法其实在第1章中已经介绍过了,就是在<activity>标签的内部加入<intent-filter>标签,并在这个标签添加<action android:name="android.intent.action.MAIN"/>和<category android:name="android.intent.category.LAUNCHET" />这两句声明即可。

除此之外,我们还可以使用android:label指定活动中标题栏的内容,标题栏是显示在活动顶部的,待会儿运行的时候你就会看到。需要注意的是,给主活动指定的label不仅会成为标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.activitytest">

<application

...>

<activity android:name=".FirstActivity"

android:label="This is firstActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER"/>

</intent-filter>

</activity>

</application>

</manifest>

这样的话,FirstActivity就成为我们这个程序的主活动了,即点击桌面应用程序图标时首先打开的就是这个活动。另外需要注意,如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如支付宝快捷支付服务。

好了,现在一切都已准备就绪,让我们来运行一下程序吧,结果如图2.7所示。

首次运行结果

图2.7 首次运行结果

手机毕竟和电脑不同,它的屏幕空间非常有限,因此充分地利用屏幕空间在手机界面设计中就显得非常重要了。如果你的活动中有大量的菜单需要显示,这个时候界面设计就会比较尴尬,因为仅这些菜单就可能占用屏幕将近三分之一的空间,这该怎么办呢?不用担心,Android给我们提供了一种方式,可以让菜单都能得到展示的同时,还能不占用任何屏幕空间。

首先在res目录下新建一个menu文件夹,右击res目录-New-Directory,输入文件夹名menu,点击OK。接着在这个文件夹下再新建一个名叫main的菜单文件,右击menu文件夹-New-Menu resurce file,如图2.9所示。

新建Menu资源文件

图2.9 布局编辑器

文件名输入main,点击OK完成创建。然后在main.xml中添加如下代码:

新建Menu资源文件

图2.9 布局编辑器

新建Menu资源文件

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item

android:id="@+id/add_item"

android:title="Add">

</item>

<item

android:id="@+id/remove_item"

android:title="Remove">

</item>

</menu>

这里我们创建了两个苹果单项,其中<item>标签就是用来创建具体的某一个菜单项,然后通过android:id给这个菜单项指定一个唯一的标识符,通过android:title给这个菜单项指定一个名称。

接着重新回到FirstActivity中来重写onCreateOptionsMenu()方法,重写方式可以使用Ctrl+O快捷键(Mac 系统是 control+O),如图2.10所示。

重写onCrerateOptionsMenu()方法

然后在onCreateOptonsMenu()方法中编写如下代码:

public boolean onCreateOptionsWenu(Menu menu){

getMenuInflater().inflate(R.menu.main,menu);

return true;

}

通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单了。inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单,这里当然传入R.menu.main。第二个参数用于指定我们的菜单项将添加到哪一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数然后给这个方法返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。

当然,仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真正可用才行,因此还要再定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected()方法:

public boolean onOptionsItemSelected(MenuIem item){

switch (item.getItemId()){

case R.id.add_item:

Toast.makeText(this, "you clicked Add", Toast.LENGTH_SHORT).show();

break;

case R.id.remove_item:

Toast.makeText(this, "you clicked Remove", Toast.LENGTH_SHORT).show();

break;

default:

}

return true;

}

在onOptionsItemSelected()方法中,通过调用item.getItemId()来判断我们点击的是哪一个菜单项,然后给每个菜单项加入自己的逻辑处理,这里我们就活学活用,弹出一个刚刚学会的Toast。

重新运行程序,你会发现在标题栏的右侧多了一个三点的符号,这个就是菜单按钮了,如图2.11所示。

可以看到,菜单里的菜单项默认是不会显示出来的,只有点击一下菜单按钮才会弹出里面具体内容,因此它不会占用任何活动的空间,如图2.12所示。

销毁一个活动

通过上一节的学习,你已经掌握了手动创建活动的方法,并学会了如何在活动中创建Toast和创建菜单。或许你现在心中会有个疑惑,如何销毁一个活动呢?

其实答案非常简单,只要按一下Back键就可以销毁当前的活动了。不过如果你不想通过按键的方式,而是希望在程序中通过代码来销毁活动,当然也可以,Activity类提供了一个finish()方法,我们在活动中调用一下这个方法就可以销毁活动了。

修改按钮监听器中的代码,如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

finish();

}

});

重新运行程序,这时点击一下按钮,当前的活动就被成功销毁了,效果和按下Back键是一样的。

使用Intent在活动之间穿梭

只有一个活动的应用也太简单了吧?没错,你的追求应该更高一点。不管你想创建多少个活动,方法都和上一节中介绍的是一样的。唯一的问题是在于,你在启动器中点击应用的图标只会进入到该应用的主活动,那么怎样才能由主活动转到其他活动呢?我们现在就来一起看一看。

使用显式Intent

你应该已经对创建活动的流程比较熟悉了,那我们现在快速地在ActivityTest项目中再创建一个活动。

仍然还是右击com.example.activitytest包-New-Activity-Empty Activity。会弹出一个创建活动的对话框,我礼物这次将活动命名为SecondActivity,并勾选Generate Layout File,给布局文件起名为second_layout,但不要勾选Launcher Activity选项,如图2.14所示。

创建SecondActivity

图2.14 创建SecondActivity

点击Finish完成创建,android Studio会为我们自动生成SecondActivity.java和second_layout.xml这两个文件。不过自动生成的布局代码目前对你来说可能有些复杂,这里我们仍然还是使用最熟悉的LinearLayout,编辑second_layout.xml,将里面的代码替换成如下内容:

<LijnearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

<Button

android:id="@+id/button_2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Button 2"

/>

</LinearLayout>

我们还是定义了一个按钮,按钮上显示Button2。

然后SecondActivity中的代码已经有自动生成了一部分,我们保持默认不变就好,如下所示:

public class SecondActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.second_layout);

}

}

另外不要忘记,任何一个活动都是需要在AndroidManifest.xml中注册的,不过幸运的是,Android Studio已经帮我们自动完成了,你可以打开AndroidManifest.xml瞧一瞧:

<application

android:allowBackup="true"

android:icon="@mipmap/ic_Launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme">

<activity

android:name=".FirstActivity"

android:label="This is FirstActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".SecondActivity"></activity>

</application>

由于SeconActivity不是主活动,因此不需要配置<intent-filter>标签里的内容,注册活动的代码也简单了许多。现在第二个活动已经创建完成,剩下的问题就是如何去启动这第二个活动了,这里我们需要引入一个新的概念:Intent。

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景,由于服务、广播等概念你暂时还未涉及,那么本章我们的目光无疑就锁定在了启动活动上面。

Intent大致可以分为两种:显式Intent和隐式Intent,我们先来看一下显式Inatent如何使用。

Intent有多个构造函数的重载,其中一个是Intent(context packageContext,Class<?>cls)。这个构造函数接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定发启动的目标活动,通过这个构造函数就可以构建出Intent的“意图”。然后我们应该怎么使用这[ 个Intent呢?Activity类中提供了一个startaActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数,这里我们将构建好的Intent传入starActivity()方法就可以启动目标活动了。

修改FirstActivity中按钮的点击事件,代码如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent = nwe Intent(FirstActivity.this,SecondActivity.class);

startActivity(intent);

}

});

我们首先构建出了一个Intent,传入FirstAcivity.this,SecondActivity.class);

startActivity(intent);传入FirstActivity.this作为上下文,传入Second-Activity.class作为目标活动,这样我们的“意图”就非常明显了,即在FirstActivity这个活动的基础上打开SecondActivity这个活动。然后通过startActivity()方法来执行这个Intent。

重新运行程序,在FirsActivity的界面点击一下按钮,结果如图2.15所示。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

<Button

android:id="@+id/button_2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Button 2"></Button/>

</LinearLayout>

我们还是定义了一个按钮,按钮上显示Button2

然后secondActivity中的代码已经自动生成了一部分,我们保持默认不变就好,如下所示:

public class SecondActivity extends AppCompatActivity{

@Override

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.second_layout);

}

}

另外不要忘记,任何一个活动都是需要在AndroidManifest.xml中注册的,不过幸运的是,Android Studio已经帮我们自动完成了,你可以打开AndroidManifest.xml瞧一瞧:

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme">

<activity

android:name=".FirstActivity"

android:label="This is FirstActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".SecondActivity"></activity>

</application>

由于SecondActivity不是主活动,因此不需要配置<intent-filter>标签里的内容,注册活动的代码也简单了许多。现在第二个活动已经创建完成,剩下的问题就是如何去启动这第二个活动了,这里我们需要引入一个新的概念:Intent。

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景,由于服务、广播等概念你暂时还未涉及,那么本章我们的目光无疑就锁定在了启动上面。

Intent大致可以分为两种:显式Intent和隐式INtent,我们先来看一下显示Intent如何使用。

Intent大致可以分为两种:显示Intent和稳式Intent,我们先来看一下显示Intent如何使用。

INtent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?>cls)。这个构造函数接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent的“意图”。然后我们应该怎么使用这个Intent呢?Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数,这里我们将构建好的Intent传入startActivity()方法就可以启动目标活动了。

修改FirstActivity中按钮点击事件,代码如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);

startActivity(intent);

}

});

我们首先构建出一个Intent,传入FirstActivity.this作为上下文,传入Second-Activity.class作为目标活动,这样我们的“意图”就非常明显了,即在FirstActivity这个活动的基础上打开SeconActivity这个活动。然后通过startActivity()方法来执行这个Intent。

重新运行程序,在FistActivity的界面点击一下按钮,结果如图2.15所示。

可以看到,我们已经成功启动SeconActivity这个活动了。如果你想要回到上一个活动怎么办呢?很简单,按下Back键就可以销毁当前活动,从而回到上一个活动了。

使用这种方式来启动,Intent的“意图”非常明显,因此我们称之为显示Intent。

2.3.2使用隐式Intent

相比于显式Intent,隐式Intent则含蓄了许多,它并不明显指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。

什么叫作合适的活动呢?简单来说就是可以响应我们这个隐式Intent的活动,那么目前SecondActivity可以响应什么样的隐式Intent呢?额,现在好像还什么都响应不了,不过很快就会有了。

通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action和category,打开AndroidManifest.xml,添加如下代码:

<activity android:name=".SecondActivity">

<intent-filter>

<action android:name="android.intent.category.DEFAULT" />

</intent-filter>

</activity>

在<action>标签中我们指明了当前活动可以响应com.example.activitytest.ACTION_START这个action,而<category>标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category,只有<action>和<category>时,这个活动才能响应该Intent。

修改FirstActivity中按钮的点击事件,代码如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent = new Intent("com.example.activitytest.ACTION_START");

startActivity(intent);

}

});

可以看到,我们使用了Intent的另一个构造函数,直接将action的字符串传了进去,表明我们想要启动能够响应com.example.activitytest.ACTION_START 这个 action 的活动,那前面不是说要<action>和<category>同时匹配上才能响应的吗?怎么没看到哪里有指定category呢?这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中。

重新运行程序,在FirstActiivity的界面点击一下按钮,你同样成功启动SecondActivity了。不同的是,这次你是使用了隐式Intent的方式来启动的,说明我们在<activity>标签下配置的action和category的内容已经生效了!

每个Intent中只能指定一个action,但却能指定多个category。目前我们的Intent中只有一个默认的category。那么现在再来增加一个吧。

修改FirstActivity中按钮点击事件,代码如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent = new Intent("com.example.activitytest.ACTION_START");

intent.addCategory("com.example.activitytest.MY_CATEGORY");

startActivity(intent);

}

});

可以调用Intent中的addCategory()方法来添加一个category,这里我们指定了一个自定义category,值为com.example.activitytest.MY_CATEGORY。

现在重新运行程序,在FistActivity的界面点击一按钮,你会发现,程序崩溃了!这是你第一次遇到崩溃,可能会有些束手无策。别紧张,其实大多数的崩溃问题都是很好解决的,只要你善于分析。在logcat界面查看错误日志,你会看到如图2.16所示的错误信息。

错误信息中提醒我们,没有任何一个活动可以响应我们的Intent,为什么呢?这里因为我们刚刚在Intent中新增了一个category,而SecondActivity的<intent-filter>标签中并没有声明可以响应这个category,所以就出现了没有任何活动可以响应该Intent的情况。现在我们在<intent-filter>中再添加一个category的声明,如下所示:

<activity android:name=".SecondActivity">

<intent-filter>

<action android:name="com.example.activitytest.ACTION_START" />

<category andeoid:name="android.intent.category.DEFAULT" />

<category android:name="com.example.activitytest.MY_CATEGORY" />

</intent-filter>

</activity>

再次重新运行程序,你就会发现一切正常了。

2.3.3更多隐式Intent的用法

上一节中,你掌握了通过隐式Intent来启动活动的方法,但实际上隐式Intent还有更多的内容需要你去了解,本节我们就来展开介绍一下。

使用稳式Intent,我们不仅可以启动自己的程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示一个网页,这时你没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器打开这个网页就行了。

修改FirstActivity中按钮点击事件的代码,如下所示:

button1.setOnClickListener(new View.OnclickListener(){

@Override

public void onClick(View v) {

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.setData(Uri.parse("http://www.luolimin.cn"));

startActivity(intent);

}

});

这里我们首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字符串解析一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。

重新运行程序,在FirstActivity界面点击按钮就可以看到打开了系统浏览器,如图2.17所示。

以上述代码中,可能你会对setData()部分感觉到陌生,这是我们前面没有讲到的。这个方法其实并不复杂,它接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。

与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前活动能够响应什么类型的数据。<data>标签中主要可以配置以下内容。

android:scheme。用于指定数据的协议部分,如上例中的http部分。

android:host。用于指定数据的主机部分,如上例中的www.luolimin.cn部分。

android:port。用于指定数据的端口部分,一般紧随在主机名之后。

android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。

android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

只有<data>标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。不过一般在<data>标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了。

为了让你能够更加直观地理解,我们来自己建立一个活动,让它也能响应打开网页的Intent。

右击com.example.activitytest包-New-Activity-Empty Activity,新建ThirdActivity,并勾选Generate Layout File,给布局文件起名为third_layout,点击Finish完成创建。然后编辑third_layout.xml,将里面的代替换成如下内容:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

<Button

android:id="@+id/button_3"

android:layout_width="mathch_parent"

android:layout_height="wrap_content"

android:layout_height="wrap_content"

android:text="Button 3"

/>

</LinearLayout>

ThirdActivity中的代码保持不变就可以了,最后在AndroidManifest.xml中修改ThirdActivity的注册信息:

<activity android:name=".ThirdActivity">

<intent-filter>

<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="http" />

</intent-filter>

</activity>

我们在ThirdActivity的<intent-filter>中配置了当前活动能够响应的action是Intent.ACTION_VIEW的常量值,而category则毫无疑问指定了默认的category值,另外在<data>标签中我们通过android:scheme指定了数据的协议必须是http协议,这样ThirdActivity应该就和浏览器一样,能够响应一个打开网页的Intent了。让我们运行一下程序试试吧,在FirstActivity的界面点击一下按钮,结果如图2.18所示。

可以看到,系统自动弹出了一个列表,显示了目前能够响应这个Intent的所有程序。选择Browser还会像之前一样打开浏览器,并显示IE的主页,而如果选择了AcrivityTest,则会启动这次选择的程序打开。需要注意的是,虽然我们声明了ThirdActivity是可以响应打开网页的Intent可能误误导用户的行为,不然会让用户对我们的应用产生负面的印象。

除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示拨打电话。下面的代码展示了如何在我们的程序中调用系统拨号界面。

button1.setOnClickListener(new View.OnClickListener(){

@Override

public Void OnClick(View v){

Intent intent = new Intent(Intent.ACTION_DIAL);

startActivity(intent);

}

});

首先指定了Intent的action是Intent.ACTTON_DIAL,这又是一个Android系统的内置动作。然后在data部分指定了协议是tel,号码是10086。重新运行一下程序,在FirstActivity的界面点击一下按钮,结果如图2.19所示。

2.3.4向下一个活动传递数据

经过前面几节的学习,你已经对Intent有了一定的了解。不过到目前为止,我们都只是简单地使用Intent来启动一个活动,其实Intent还可以在启动活动的时候传递数据,下面我们来一起看一下。

在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了。比如说FirstActivity中有一个字符,现在想把这个字符传递到SEcond-Activity中,你这可以这样编写:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public Void onClick(View v){

string data = "Hello SecondActivity";

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);

intent.putExtra("extra_data",data);

startActivity(intent);

}

});

广播intent,提示应用程序音频信号由于音频输出的变化将变得“嘈杂”。例如,当拔出一个有线耳机,或断开一个支持A2DP的音频接收器,这个intent就会被发送,且音频系统将自动切换音频线路到扬声器。收到这个intent后,控制音频流的应用程序会考虑暂停,减小音量或其他措施,以免扬声器的声音使用户惊奇。

常量值:"android.media.AUDIO_BECOMING_NOISY"

这里我们还是使用显示Intent的方式来启动SecondActivity,并通过putExtra()方法传递了一个字符串。注意这里putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数是真正要传递的数据。

然后我们在SecondActivity中将传递的数据取出,并打印出来来,代码如下所示:

public SecondActivity extends AppCompatActivity{

@Override

proteced void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

Intent intent = getIntent();

String data = intent.getStringExtra("extra_data");

Log.d("SecondActivity",data);

}

]

首先可以通过getIntent()方法获取到用于启动SecondActivity的Intent,然后调用getStringExtra()方法,传入相应的键值,就可以得到传递的数据了。这里由于我们传递的是字符串,所以使用getStringExtra()方法来获取传递的数据。如果传递是整型数据,则使用getIntExtra()方法;如果传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推。

重新运行程序,在FirstActivity的界面点击一下按钮会跳转到SecondActivity,查看logcat打印信息,如图2.20所示。

图2.20SecondActivity中的打印信息

可以看到,我们在SecondActivity中成功得到了从FirstActivity传递过来的数据。

返回数据给上一个活动

既然可以传递数据给下一个活动,那么能不能够返回数据给上一个活动呢?答案很肯定的。不过不同的是,返回上一个活动只需要按一下Back键就可以了,并没有一个用于启动活动的Intent来传递数据。通过查阅文档你会发现,Activity中还有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。毫无疑问,这就是我们所需要的。

startActivityForResult()方法接收两个参数,第一个参数还是Intent,第二个参数是请求码,用于在以后的回调中判断数据的来源。我们还是来实战一下,修改FirstActivity中按钮的点击事件,代码如下所示:

button1.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(view v) {

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);

startActivityForResult(intent,1);

}

});

这里我们使用了startActivityForResult()方法来启动SecondActivity中给按钮注册点击事件,并在点击事件添加返回数据逻辑,代码如下所示:

public class SecondActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

setContentView(R.layout.second_layout);

Button button2 = (Button) findViewById(R.id.button_2);

button2.setOnClickListener(new View.OnClickListener(){

@Override

public void onClick(View v){

Intent intent = new Intent();

intent.putExtra("data_return","Hello FirstActivity");

setResult(RESULT_ok,intent);

finish();

}

});

}

}

可以看到,我们还是构建了一个Intent,只不过这个Intent仅仅是用于传递数据而已,它没有指定任何的“意图”。紧接着把要传递的数据存放在Intent中,然后调用了setResult()方法。这个方法非常重要,是专门用于向上一个活动返回数据的。setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的Inteent传递回去,然后调用了finish()方法来销毁当前活动。

由于我们是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity被销毁之后回调上一个活动的onActivityResult()方法,因此我们需要在FistActivity中重写这个方法来得到返回的数据,如下所示:

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode){

case 1:

if (resultCode == RESULT_OK){

string returnedData = data.getstringExtra("data_return");

Log.d("FirstActivity", returnedData);

}

break;

default:

}

}

onActivityResult()方法带有三个参数,第一个参数requestCode,即我们在启动活动时传入的请求码。第二个参数resultCode,即我们在返回数据时传入的处理结果。第三个参数data,即携带着返回数据的Intent。由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调用onActivityResult()这个方法中,因此我们首先要做的就是通过检查requestCode的值来判断数据来源。确定数据是从SecondActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。

重新运行程序,在FirstActivity的界面点击按钮会打开SecondActivity,然后在SecondActivity界面点击Button2按钮会回到FirstActivity,这时查看logcat的打印信息,如图2.21所示。

图2.21FirstActivity中的打印信息

可以看到,SecondActivity已经成功返回数据给FirstActivity了。

这时候你可能会问,如果用户在SecondActivity中并不是通过点击按钮,而是通过按下Back键回到FirstActivity,这样数据不就没法返回了吗?没错,不过这种情况还是很好处理的,我们可以通过在SecondActivity中重写onBackPressed()方法来解决这个问题,代码如下所示:

@Override

public void onBackPressed() {

Intent intent = new Intent();

intent.putExtra("data_return","Hello FirstActivity");

setResult(RESULT_OK,intent);

finish();

}

这样的话,当用户按下Back键,就会去执行onBackPressed()方法中的代码,我们在这里添加返回数据的逻辑就行了。

返回
首页
开发
环境
控制
ui
UI
界面
王国
简介
返回
顶部