Activity是一种包含用户界面的组件,主要用于和用户进行交互。接下来详细了解一下。
首先,手动创建Activity加深一下我们的理解。
新建Android项目,选择“Add No Activity”,点击Next,项目名输入ActivityTest,包名使用默认值com.example.activity,点击finish,创建成功。
现在项目创建完成。这里我们将Android模式的项目结果手动改为Project模式。创建Activity文件
右击com.example.activity包->New->Empty Activity,弹出一个Activity的对话框,将Activity命名为FirstActivity,并不要勾选generate layout file 和launcher activity,点击finish。
Android程序设计的理念是一个Activity对应一个布局文件,接下来我们创建一个布局文件:
右击app/src/main/res->New->Directory,弹出一个新建目录的窗口,这里先创建一个layout的目录。右击layout目录->New->Layout resource file,弹出一个新建布局文件的窗口,布局文件命名为first_layout,根元素默认为LinearLayout,点击OK完成创建。
由于我们选择了LinnerLayout作为根元素,我们现在对这个布局稍作修改,添加一个按钮,如下所示
<Button
android:id="@+id/bt1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button1"
></Button>
这里添加了button元素,并在button元素里添加了几个属性。现在按钮已经显示出来了,布局已经编写完成,接下来就是要在Activity中加载这个布局。回到FirstActivity,在OnCreate()方法中添加如下代码:
setContentView(R.layout.first_layout)
这里调用了setContentView()方法给当前Activity加载布局,在这个方法中,我们一般会传入一个布局文件的id。
所有的Activity都要在AndroidManifest.xml中进行注册才能生效,此时FirstActivity已经注册过了,可以看到Activity的注册申明要放在<application>标签内,这里是通过<activity>标签对Activity进行注册的。
但现在只是对Activity注册了,程序仍不能运行,因为没有设定主Activity。只需要在<activity>内添加这段代码就行了。
<intent-filter>
<action android:name="android intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
这样,FirstActivity就成了这个程序的主Activity了。点击应用图标后首先显示的就是这个Activity。
Android中的Toast是一种非常好的提醒方式,在程序中可以使用他将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,且不会占用任何屏幕空间。
首先需要定义一个Toast的触发点,我们将上述的按钮作为弹出Toast的触发点,在OnCreate()方法中添加如下代码:
val bt1:Button=findViewById(R.id.bt1)
bt1.setOnClickListener {
Toast.makeText(this,"You click bt1",Toast.LENGTH_SHORT).show()
}
在Activity中,可以通过findViewById()方法获取在布局文件中定义的元素,这里我们传入R.id.bt1来得到按钮的实例,这个值是在first_layout.xml中通过android:id属性指定的。Kotlin无法推断findViewById()方法返回的是Button还是其他控件,所以我们需要将bt1变量显示的声明成Button类型。得到按钮的实例之后,通过调用setOnClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的OnClick()方法。因此弹出Toast的功能要写在OnClick()方法中。
Toast的用法也很简单,通过静态方法makeTest()创建一个Toast对象,然后调用show()函数将Toast函数显示出来。makeTest()方法需要传入三个参数,,第一个参数是Context即Toast的上下文,这里我们填入this即可,第二个参数是Toast显示的文本内容,第三个参数是显示的时长。
如果布局文件的控件过多,那么我们调用findViewById()方法就会比较麻烦,于是就滋生出了第三方开源库,来简化这个方法的调用。目前最新的是viewBinding插件,由于多次失败我放弃了。
如果你的Activity中有大量菜单需要展示,页面设计就会比较尴尬。Android提供了一种方式可以让菜单都显示还不占用任何空间。
首先在res目录下新建一个menu文件夹,右击res->New->Directory,输入文件名“menu”,再建一个菜单文件“main”,右击menu->New->Menu resource file。然后在main.xml中添加如下代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
这里我们创建了两个菜单项,其中<item>标签用来创建具体的某个菜单项,然后通过android:id给这个菜单项指定一个唯一的标识符,通过Android:title给这个 菜单项指定一个名称。
接着在FirstActivity中重写onCreateOptionsMenu()方法,重写方法使用Ctrl+O,然后在onCreateOptionsMenu()方法中编写如下代码:
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main,menu)
return true
}
这里就涉及到了Kotlin提供的语法糖,Kotlin会自动将代码转换成调用set()和get()方法。
我们在onCreateOptionsMenu()方法中编写的menuInflater就使用了这种语法糖,他实际上是调用了父类的getMenuInflater()方法。getMenuInflater()方法能够得到一个MenuInflater对象,在调用它的inflate()方法,就可以给当前Activity创建菜单了。inflate()方法接收两个参数:第一个用于指定我们提供哪一个资源文件来创建菜单;第二个参数用于指定我们的菜单项将添加到哪个Menu对象中,这里直接使用onCreateOptionsMenu方法中传入的参数,最后给这个方法返回true,表示允许创建的菜单显示出来。
当然仅仅让菜单显示出来还不行,关键要让菜单可以用起来,因此还要再定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected()方法,如图:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.add_item->Toast.makeText(this,"you click add",Toast.LENGTH_SHORT).show()
R.id.remove_item->Toast.makeText(this,"you click remove",Toast.LENGTH_SHORT).show()
}
return true
}
在onOptionsItemSelected() 方法中,我们通过调用item.itemid来判断点击的是哪个菜单项。这里也应用了刚刚说到的语法糖,kotlin实际上在背后调用的是item的getItemId()方法。这里我们将item.itemid传入when语句中,然后给每个菜单项加入自己的逻辑处理,这里我们弹出toast。
可以看到,菜单里的菜单项默认是不会显示的,只有点击菜单按钮才会弹出里面具体的内容,因此他不会占用任何Activity的空间。
现在我们已经会创建Activity了,那么如何销毁呢?只需按下Back键即可销毁了,如果你想通过代码来销毁的话也可以,调用finish()方法就可以销毁当前Activity了。
修改按钮监听器账号的代码
bt1.setOnClickListener {
finish()
}
这时点击以下按钮,当前activity就被销毁了。
一个应用不会只有一个Activity,,那么如何在多个Activity中跳转呢?
Intent是Androi程序中各组件之间进行交互的一种重要方式,他不仅可以执行当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity、启动Service以及发送广播等场景。
Intent分为显式Intent和隐式Intent,首先介绍的是显示Intent。Intent有多个构造函数的重载,其中一个是Intent(Context packageContext,class<?>cls),如下
我们再次创建一个Activity,命名为SecondActivity。修改FirstActivity中按钮的点击事件:
bt1.setOnClickListener {
val intent= Intent(this,SecondActivity::class.java)
startActivity(intent)
}
我们首先构建了一个Intent对象,第一次参数传入this也就是FirstActivity作为上下文,第二个参数传入SecondActivity::class.java作为目标Activity,这样我们的意图也就明显了,就是在FirstActivity的基础上打开SecondActivity。接下来再通过startActivity()方法执行这个Intent就行了。
相比于显式Intent,隐式Intent并不明确指出要启动哪个Activity,而是指定了一系列更加抽象的category和action等信息,然后交由系统去分析这个Intent,并帮我们找出可以响应这个隐式Intent的Activity启动。
可以在<activity>标签下配置<intent-fliter>的内容,
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在<action>标签中我们指明了当前Activity可以响应com.example.activitytest.ACTION_START这个action,而<category>标签则包含了一些附加信息,更明确的指明了当前Activity能够响应的Intent中还可能带有的category。只有这两项中的内容同时匹配到intent中指定的action和category时,这个activity才能响应该Intent。
修改FirstActivity中按钮的点击事件,可以看到,我们使用了Intent的另一个构造函数,直接将action的字符串传了进去,表明我们想要启动这个action的Activity。现在重新运行也同样可以启动SecondActivity了:
bt1.setOnClickListener {
val intent=Intent("com.example.activitytest.ACTION_START")
startActivity(intent)
}
每个Intent中只能指定一个action,但能指定多个category,目前我们的Intent中只有一个默认的category,那么我们再次增加一个:
bt1.setOnClickListener {
val intent=Intent("com.example.activitytest.ACTION_START")
intent.addCategory("com.example.activitytest.MY_CATEGORY")
startActivity(intent)
}
可以调用intent中的addCategory()方法来添加一个category,这里我们指定了一个自定义的category。
现在重新运行程序,在界面点击一下按钮会发现程序崩溃了,在Logcat处查看日志,可以发现上面显示没有一个Activity可以响应我们的intent,因为我们新增了一个category,而<intent-fliter>标签中没有没有声明可以响应这个category,所以就出现了这种情况。现在我们再在<intent-fliter>标签添加一个category的声明(),再次运行,一切就正常了。
隐式Intent不仅可以启动自己程序内的Activity,还可以启动其他程序的Activity,这就使多个应用程序之间的功能共享成为可能。比如你的程序中需要展示一个网页,可以调用系统的浏览器打开这个网页,修改代码:
bt1.setOnClickListener {
val intent=Intent(Intent.ACTION_VIEW)
intent.data= Uri.parse("https://www.baidu.com")
startActivity(intent)
}
这里我们首先定义了intent的action,这是一个Android系统内置的一个动作,其常量值为android.intent.action.VIEW.然后通过Uri.parse()方法将一个网址字符串解析成一个Uri对象,在调用Intent的setData()方法将这个对象传递进去。重新运行代码,点击按钮就可以看到打开了系统浏览器。
我们 还可在<intent-fliter>标签中配置一个<data>标签,用于更精确的响应当前Activity能够响应的数据。<data>标签中主要可配置以下几项:
- android:scheme。用于指定用户的协议部分
- android:host。用于指定数据的主机名部分
- android:port。用于指定数据的端口部分,一般在主机名之后
- android:path。用于指定主机名和端口之后的部分
- android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定
只有当<data>标签中指定的内容和intent中携带的data完全一致时,当前Activity才能响应Intent。不过data中一般不会指定过多内容,如上述的浏览器事例中,只需要指定android:scheme为hhtps,就可以响应所有HTTPS协议的intent了。
我们再次新建一个Activity,命名一个按钮为bt3,在AndroidMainifest.xml中修改这个Activity的注册信息:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"/>
</intent-filter>
我们在ThirdActivity的<intent-fliter>中配置了当前Activity能够响应的action,而category指定了默认的值。另外在<data>标签中我们通过android:scheme指定了数据的协议必须是https协议,这样这个Activity就能和浏览器一样打开一个网页了。另外由于AndroidStudio认为所有能够响应ACTION_VIEW的Activity都应该加上BROWSABLE的category,否则就会发出警告。
打开程序点击bt1系统会自动弹出一个列表,显示了目前能够响应这个intent的所有程序。
除了https协议外,我们还可以指定很多其他协议,比如geo表示地理位置,tel表示拨打电话。
在启动Activity时向下一级传递数据的思路很简单,intent提供了一系列putExtra()方法的重载,可以把数据暂存在intent,在启动另一个Activity时,把这些数据从intent中取出就可以了。
比如你想将FirstActivity中的字符串传递到SecondActivity可以这样写:
bt1.setOnClickListener {
val data="Hello SecondActivity"
val intent=Intent(this,SecondActivity::class.java)
intent.putExtra("extra_data",data)
startActivity(intent)
}
这里我们使用显式Intent的方法,并通过putExtra()方法传递了一个字符串。这里putExtra()方法接收俩个参数,第一个参数是键,用于之后从Intent处取值,第二个参数才是真正要传递的参数。
在SecondActivity中取出数据并打印出来
val extradata=intent.getStringExtra("extra_data")
Log.d("SecondActivity","extra data is $extradata")
这里的intent实际上调用的是父类的getIntent()方法,该方法会获取用于启动SecondActivity的Intent,然后调用getStringExtra()方法(另:传入整型数据则调用getIntExtra()方法,传入布尔型数据则调用getBOOleanExtra()方法,以此类推)并传入键值,就可以得到传递的数据了。
既然可以传递数据到下一个Activity,那么同样可以返回数据到上一个Activity。Activity类中有一个用于启动Activity的startActivityForResult()方法,他期望在Activity销毁的时候能够返回一个结果给上一个Activity。
startActivityForResult()方法接收两个参数:第一个参数还是Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
修改FirstActivity中的按钮事件为:
val intent=Intent(this,SecondActivity::class.java)
startActivityForResult(intent,1)
这里我们使用了startActivityForResult()方法来启动SecondActivity,请求码只要是一个唯一值即可,这里传入1。
接下来我们在SecondActivity给按钮注册点击事件,并在点击事件中添加返回数据的逻辑:
bt2.setOnClickListener {
val intent=Intent()
intent.putExtra("data_return","hello FirstActivity")
setResult(RESULT_OK,intent)
finish()
}
我们还是构建了个Intent,不过这个Intent仅用于传递数据。紧接着把要传送的数据存放在Intent中,然后调用setResult()方法向上一个Activity返回数据,setResult()接收俩个参数:第一个参数向上一个Activity返回处理结果;第二个参数则把带有数据的Intent传递回去。最后调用了finish()方法来销毁当前Activity。
由于我们是使用startActivityForResult()启动SecondActivity的,在SecondActivity被销毁之前会回调上一个Activity的OnActivityResult()方法,因此我们需要在FirstActivity中重写这个方法来得到返回的数据:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
1 ->if (resultCode== RESULT_OK){
val returnedData=data?.getStringExtra("data_return")
Log.d("FirstActivity","return data is $returnedData")
}
}
}
OnActivityResult()方法带有三个参数:第一个参数requestCode即我们启动Activity时传入的请求码;第二个参数resultCode即返回数据时传入的处理结果;第三个参数data即携带着返回数据的Intent。由于一个Activity中可能会有多次调用多个请求码,所以首先需要判断的就是请求码值的数据来源,之后在判断结果是否成功,最后将data数据打印出来。
现在SecondActivity的数据已经通过按钮返回给FirstActivity了,那如果不通过点击按钮数据能返回吗,还是可以的,我们可以在SecondActivity中重写OnBackPressed()来解决问题:
override fun onBackPressed() {
val intent=Intent()
intent.putExtra("data_return","hello FirstActivity")
setResult(RESULT_OK,intent)
finish()
}
这样,当用户按下back键就会执行这个代码。