Activity是一种可以包含用户界面的组件,主要用于和用户进行交互。
一个应用程序中可以包含零个或多个Activity,但不包含任何Activity的应用程序则很少。
这里选择No Activity创建一个新的项目,并将项目命名为ActivityTest,此时会发现app\src\main\java\com\example\activitytest目录是空的,然后在该包下新建Empty Activity,并将之命名为FirstActivity,并不勾选Generate Layout File和Launcher Activity两个选项,即:
上面两个选项的作用为:
此时Android Studio会自动生成FirstActivity.kt文件,其内容为:
package com.example.activitytest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
项目中的任何Activity都应该重写onCreate方法,不过上面的onCreate是自动生成的,可以看到上面自动生成的onCreate方法很简单,就是调用父类的onCreate方法。
之前提到,Android程序设计讲究逻辑和视图分离,最好每一个Activity都能够对应一个布局。
而布局则是用来显示界面内容的,这里手动创建一个布局文件。
在app\src\main\res下新建一个目录,命名为layout,然后在layout下新建一个Layout resource file,命名为first_layout,根元素默认选择LinearLayout:
然后便会出现下面的界面:
上图的右上角有Design,Code和Split的选项卡:
其XML文件内容为:
之前在创建布局文件时选择了LinearLayout 作为根元素,因此现在布局文件中便存在一个LinearLayout元素。此时添加一个按钮:
上面添加了一个Button元素,并在Button元素内部增加了几个属性:
从预览中可以看出,该按钮已经添加成功,然后在Activity中加载该布局:
package com.example.activitytest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
}
}
可以看到,这里调用了setContentView方法来给当前的Activity加载一个布局,而在该方法中一般需要传入一个id。之前提到,项目中添加的任何资源都会在R文件中生成一个相应的资源id,这里传入的就是first_layout的id。
之前提到,所有的Activity都要在AndroidManifest.xml中注册才能够生效。实际上FirstActivity已经在AndroidManifest中注册过了:
从上面文件内容可以看出,Activity的注册声明位于application标签内,这里是通过activity标签来对Activity进行注册的,不过注册过程是Android Studio自动完成的。
而在activity标签中:
不过上面只是注册了Activity,程序仍然不能运行,因为还没有为程序配置主Activity,即程序运行起来的时候,不知道要先启动哪个Activity。而配置主Activity的方法就是在activity标签内部添加以下内容:
此外,还可以使用android:label指定Activity中标题栏的内容,标题栏是显示在Activity最顶部的。同时给主Activity指定的label不仅会生成标题栏中的内容,还会称为启动器(Launcher)中应用程序显示的名称。
此时,FirstActivity就称为该程序的主Activity了,点击桌面应用程序图标时首先打开的就是该Activity。不过,如果应用程序中没有声明任何一个Activity作为主Activity,该程序仍然是可以正常安装的,只是无法在启动器中看到或者打开该程序,此类程序一般是作为第三方服务供其它应用在内部进行调用的。
此时运行程序的结果为:
Toast是Android系统提供的一种很好的提醒方式,在程序中可以使用Toast将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。
首先需要定义一个弹出Toast的触发点,之前定义了一个按钮,这里可以使用该按钮的点击事件作为弹出Toast的触发点。此时需要在onCreate方法中添加代码:
package com.example.activitytest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
val button1: Button = findViewById
此时运行程序,并点击BUTTON 1的结果为:
在Activity中,通过findViewById方法获取在布局文件中定义的元素,这里传入R.id.button1获取之前定义的按钮的实例。而findViewById方法返回的是一个继承自View的泛型对象,因此Kotlin无法自动推导出其是Button还是其它控件,因此需要将button1变量显式地声明为Button类型。
得到按钮的实例之后,通过setOnClickListener方法为按钮注册一个监听器,点击按钮时就会指定监听器中的onClick方法,因此弹出Toast的功能是在onClick方法中编写的。
Toast通过静态方法makeText创建出了一个Toast对象,然后调用show方法将Toast显示出来。
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
return makeText(context, null, text, duration);
}
同时makeText方法需要三个参数,第一个参数是Context,是Toast要求的上下文,由于Activity本身就是一个Context对象,因此可以传入this,第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长,有两个内置常量可以选择:Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
而关于findViewById,该方法可以获取布局文件中控件的实例,在之前的例子中,只有一个按钮,而如果布局文件中存在多个控件,就需要多次调用findViewById方法,这种写法会很繁琐。
不过Kotlin中的kotlin-android-extensions插件可以根据布局文件中定义的控件id自动生成一个具有相同名称的变量,而用户可以直接在Activity中使用该变量,而不用调用findViewById方法。只是需要在app下的build.gradle文件中添加该插件:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
然后在Activity中导入该布局文件:
import kotlinx.android.synthetic.main.first_layout.*
此时便可以省略掉Button变量的创建,而直接使用该Button的id作为变量名:
package com.example.activitytest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import kotlinx.android.synthetic.main.first_layout.*
class FirstActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
// val button1: Button = findViewById
同时这也是Kotlin中推荐的写法。
手机的屏幕空间有限,因此需要充分利用屏幕空间进行显示,而如果有大量的菜单需要显示,界面设计就会比较麻烦,而Android则提供了一种方式,可以让菜单都能够得到展示,还不占用屏幕空间。
在res目录下新建文件夹,并命名为menu,然后在该目录下创建Menu resource file,并命名为main,添加如下代码:
上面的代码创建了两个菜单项:
然后在FirstActivity中重写onCreateOptionsMenu方法:
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}
这段代码中,menuInflater实际上调用的是父类的getMenuInflater方法,该方法能够得到一个MenuInflater对象,再调用它的inflate方法,就可以给当前Activity创建菜单了。
而inflate方法接收两个参数,第一个参数用于指定通过哪一个资源文件来创建菜单,这里传入R.menu.main,第二个参数用于指定菜单项要添加到的Menu对象,这里直接使用onCreateOptionsMenu方法中传入的menu参数。最后返回true,表示允许创建的菜单显示,如果返回了false,创建的菜单将无法显示。
/**
* Inflate a menu hierarchy from the specified XML resource. Throws
* {@link InflateException} if there is an error.
*
* @param menuRes Resource ID for an XML layout resource to load (e.g.,
* R.menu.main_activity
)
* @param menu The Menu to inflate into. The items and submenus will be
* added to this Menu.
*/
public void inflate(@MenuRes int menuRes, Menu menu)
菜单能够显示后,还要定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected方法:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add_item -> Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show()
R.id.remove_item -> Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show()
}
return true
}
在onOptionsItemSelected方法中,通过调用item.itemId来判断点击的是哪一个菜单项,实际上这里调用的item的getItemId方法,这种简洁写法其实也是Kotlin的语法糖。然后使用when条件语句进行逻辑处理。
上述代码运行的结果为:
之前提到的都是创建Activity,并在Activity中添加内容的操作,而如何销毁Acivity呢?
其实简单通过back键就可以销毁当前的Activity,而如果不想通过按键的方式,也可以通过代码销毁,即调用finish方法。比如修改一下按钮的触发代码:
button1.setOnClickListener {
// Toast.makeText(this, "You clicked Button 1.", Toast.LENGTH_SHORT).show()
finish()
}
此时点击按钮,就会退出当前Activity,效果和back键是一样的。