门——Activity(二)
这里我们主要讲几个问题
- Activity间数据的传递
- 特殊情况的生命周期即数据保存和恢复
- manifest的相关配置
Activity间数据的传递
上一章我们讲Activity的跳转的时候,使用了一个类,Intent ,翻译过来是意图的意思,像是一个中间的媒介,告诉系统我们想要干什么。就像是以前提亲,需要一个媒婆。过程大体如下:
- 王家大少爷和李家千金已经好了很长一段时间了,李千金让王少爷赶紧的来提亲,那么王少爷要怎么做呢?先找一个媒婆(初始化Intent对象);
- 然后告诉她我的要提亲的对象(设置意图,要跳转到BActivity);
- 提亲总不能空着手去吧,于是王少爷先跟李千金商量好了,李千金说我要黄金万两(需要数据)去之前跟媒婆交代了要带信回来或者不用了(我们关系好得很,肯定成。);
- 然后我就开始准备,准备的东西不一定就满足需求。准备好东西之后给到媒婆(使用Intent的put***方法来设置数据),让媒婆带到。
- 李千金收到媒婆带来的彩礼(从intent中取出数据),开始清点,然后家里开始处理(BActivity开始处理)。
- 这里就分2种情况了,如果彩礼对了(BActivity正常处理完所有任务)就告诉媒婆这门亲事答应了;如果彩礼不对(BActivity处理发生了异常)就告诉媒婆我女儿不嫁了。
- 如果之前要求带信回去那么媒婆就不管成不成都得要回信,如果不需要回信的话媒婆就什么都不用干了。(返回数据到前一个Activity)
下面我们用代码来演示一下这个流程
AActivity的代码
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("AActivity", "ZLog onCreate $savedInstanceState")
acMainTvJump.setOnClickListener {
//创建意图(聘请媒婆,告诉它我的对象是BActivity)
val intent = Intent(this, BActivity::class.java)
//设置数据(添加彩礼)
intent.putExtra("name", "BActivity")
//这种启动方式是不需要返回数据的
startActivity(intent)
//这种方式是需要返回数据的,第二个参数为标记位,取回数据的时候需要用到,用它来判断返回的数据是给当前Activity的
startActivityForResult(intent,0x1001)
}
}
//返回数据后系统会调用此回调,第一个参数为我们启动的时候传递的,用来判断是否是我们要的数据
//第二个参数是标记返回的是成功还是失败,成功时取值Activity.RESULT_OK
//第三个参数为Intent对象,里面包含返回的数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AActivity", "ZLog onActivityResult ")
if (requestCode == 0x1001 && resultCode == Activity.RESULT_OK){
}
}
AActivity的流程为 初始化Intent,同时设置需要跳转的Activity,然后通过putExtra方法设置数据,这里设置的数据可以是多种类型的。
除了基本数据类型的包装类以外还有几个需要注意,一个是实现了Serializable接口的子类,还有一个是Parcelable子类,这2个都是序列化的操作。另一个特殊的是Bundle,它可以看做是一个专门用来存放数据的地方,大家看它的方法基本类似的。
下面我们看看BActivity怎么处理
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
textView.setOnClickListener {
startActivity(Intent(this, CActivity::class.java))
}
val name = intent.getStringExtra("name")
Log.d("SecondActivity", "ZLog onCreate : $name")
}
我们在BActivity中使用intent的get方法来拿到数据,这里传入的参数“name”必须要跟AActivity中put的时候设置的key相同才能取到。如果我们不需要返回给A数据的话就不用做任何操作,如果需要的话就要处理。只需要在BActivity被销毁之前调用setResult方法即可。
/**
* Call this to set the result that your activity will return to its
* caller.
*
* @param resultCode The result code to propagate back to the originating
* activity, often RESULT_CANCELED or RESULT_OK
*
*/
public final void setResult(int resultCode)
/**
* Call this to set the result that your activity will return to its
* caller.
*
* @param resultCode The result code to propagate back to the originating
* activity, often RESULT_CANCELED or RESULT_OK
* @param data The data to propagate back to the originating activity.
*/
public final void setResult(int resultCode, Intent data)
如果我们需要带回数据就使用第二个方法设置Intent对象,不需要的话就直接使用第一个方法,只告诉AActivity成功与否。
修改一下代码然后查看运行效果
//这里是AActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("AActivity", "ZLog onCreate $savedInstanceState")
acMainTvJump.setOnClickListener {
//创建意图(聘请媒婆,告诉它我的对象是BActivity)
val intent = Intent(this, BActivity::class.java)
//设置数据(添加彩礼)
intent.putExtra("name", "嫁吗")
//这种启动方式是不需要返回数据的
// startActivity(intent)
//这种方式是需要返回数据的,第二个参数为标记位,取回数据的时候需要用到,用它来判断返回的数据是给当前Activity的
startActivityForResult(intent, 0x1001)
}
}
//返回数据后系统会调用此回调,第一个参数为我们启动的时候传递的,用来判断是否是我们要的数据
//第二个参数是标记返回的是成功还是失败,成功时取值Activity.RESULT_OK
//第三个参数为Intent对象,里面包含返回的数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AActivity", "ZLog Intent onActivityResult $requestCode $resultCode")
if (requestCode == 0x1001 && resultCode == Activity.RESULT_OK && data != null) {
val result = data.getStringExtra("result")
Log.d("AActivity", "ZLog Intent onActivityResult 取出BActivity返回的数据:$result")
}
}
//这里是BActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
textView.setOnClickListener {
// startActivity(Intent(this, CActivity::class.java))
val intent = Intent()
intent.putExtra("result", "嫁")
setResult(Activity.RESULT_OK, intent)
finish()
}
val name = intent.getStringExtra("name")
Log.d("SecondActivity", "ZLog Intent 取出AActivity传递过来的数据: $name")
}
至此我们就完成了Activity之间的数据传递
特殊情况的生命周期即数据保存和恢复
之前我们讲过activity的生命周期,其中主要的流程为6个回调。现在我们考虑一种情况,我们做了一个视频类的APP,在基本界面的时候是竖屏播放的,用户点击全屏后变成横屏了,但是播放的进度不能变。带着这个问题我们来用代码看看实际的横竖屏切换时发生了什么。
再次修改代码,去掉不需要的部分,添加打印信息
class AActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("AActivity", "ZLog onCreate $savedInstanceState")
}
override fun onStart() {
super.onStart()
Log.d("AActivity", "ZLog onStart ")
}
override fun onResume() {
super.onResume()
Log.d("AActivity", "ZLog onResume ")
}
override fun onRestart() {
super.onRestart()
Log.d("AActivity", "ZLog onRestart ")
}
override fun onPause() {
super.onPause()
Log.d("AActivity", "ZLog onPause ")
}
override fun onStop() {
super.onStop()
Log.d("AActivity", "ZLog onStop ")
}
override fun onDestroy() {
super.onDestroy()
Log.d("AActivity", "ZLog onDestroy ")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d("AActivity", "ZLog onSaveInstanceState ")
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
Log.d("AActivity", "ZLog onRestoreInstanceState ")
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.d("AActivity", "ZLog onConfigurationChanged $newConfig")
}
}
效果图:
可以看到切换横竖屏后打印的log一下多了很多
切换后的调用顺序是onPause——onStop——onSaveInstanceState——onDestroy——onCreate——onStart——onRestoreInstanceState——onResume
可以看到原本的Activity已经被销毁了,只是在销毁之前调用了一个onSaveInstanceState方法。
这个方法的参数我们看到就是一个Bundle,刚刚我们说了它就是一个专门用来存放数据的,由此我们就可以在这里保存一些数据而不至于数据丢失。恢复数据的地方其实有2个,细心的观察log,第一次onCreate调用的时候参数的bundle为空,第二次不为空,而且切换后有一个onRestoreInstanceState方法。所以我们可以在这2个地方来恢复数据。
实际操作一下看看:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("AActivity", "ZLog onCreate")
if (savedInstanceState != null) {
processValue = savedInstanceState.getInt("process")
}
Log.d("AActivity", "ZLog onCreate Bundle processValue:$processValue")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("process", 50)
Log.d("AActivity", "ZLog Bundle onSaveInstanceState ")
}
初始processValue为0,在保存的时候我们存入了一个50,在onCreate中判断如果Bundle不为空我们就把这个值取出来
可以看到第二次调用onCreate的时候已经去到了这个50了。
manifest的相关配置
这里想要讲的主要是manifest中activity标签的相关配置对activity的影响。
1、screenOrientation:刚刚我们的activity之所以能进行横竖屏的切换就是通过这个属性来进行配置的
screenOrientation的取值有以下几种,配以表格说明:
取值 | 说明 |
fullSensor | 显示的方向(4个方向)是由设备的方向传感器来决定的,除了它允许屏幕有4个显示方向之外,其他与设置为“sensor”时情况类似,不管什么样的设备,通常都会这么做。例如,某些设备通常不使用纵向倒转或横向反转,但是使用这个设置,还是会发生这样的反转。这个值在API Level 9中引入。 |
behind | 与前一个Activity相同 |
fullUser | 如果用户锁定了基于传感器的旋转,其行为与 user 相同,否则,其行为与 fullSensor 相同,允许所有 4 种可能的屏幕方向。 API 级别 18 中的新增配置。 |
landscape | 强制横屏显示 |
locked | 将方向锁定在其当前的任意旋转方向。API 级别 18 中的新增配置。 |
nosensor | 旋转设备时候,界面不会跟着旋转。初始化界面方向由系统控制。 |
protrait | 强制竖屏显示 |
sensor | 根据物理传感器方向转动,用户90度、180度、270度旋转手机方向,activity都更着变化 |
sensorLandscape | 横屏旋转,一般横屏游戏会这样设置 |
sensorPortrait | 竖屏旋转 |
unspecified | 默认值,由系统决定,不同手机可能不一致 |
user | 用户当前设置的方向 |
2、configChanges:这个标记的作用是如果系统发生一些配置上的变化不会从新初始化activity,而是调用onConfigurationChanged来重新加载新的配置。
举个例子
<activity
android:name=".AActivity"
android:screenOrientation="fullSensor"
android:configChanges="orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
class AActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("AActivity", "ZLog onCreate")
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.d("AActivity", "ZLog onConfigurationChanged $newConfig")
}
}
这里我们加上configChanges="orientation"表示如果发生屏幕切换后不从销毁界面
我们看到切换后onCreate并没有从新调用,而是调用了onConfigChanged。
取值 | 说明 |
locale | 用户所在区域发生变化,一般是用户切换了语言时,切换后的语言会显示出来 |
orientation | 屏幕方向改变了---横竖屏切换 |
fontScale | 字体比例发生了变化----选择了不同的全局字体 |
screenSize | 屏幕大小改变了,基本不会遇到 |
Keyboard | 键盘发生了改变----例如用户用了外部的键盘 |
keyboardHidden | 键盘的可用性发生了改变 |
mcc | 国际移动用户识别码所属国家代号是改变了,sim被侦测到了,去更新mcc MCC是移动用户所属国家代号 |
mnc | 国际移动用户识别码的移动网号码是改变了, sim被侦测到了,去更新mnc MNC是移动网号码,最多由两位数字组成,用于识别移动用户所归属的移动通信网 |
navigation | 导航发生了变化-----通常也不会发生 |
screenLayout | 屏幕的显示发生了变化------不同的显示被激活 |
smallestScreenSize | 屏幕的物理大小改变了,如:连接到一个外部的屏幕上 |
uiMode | 用户的模式发生了变化 |
其取值是可以添加多个的,用 | 隔开,例如
android:configChanges="orientation|locale|layoutDirection|fontScale"
其中需要注意的是,如果在设置中切换语言,单独添加locale是无效的,必须配合layoutDirection一起使用才能生效。
3、windowSoftInputMode:这个配置的作用是设置当前界面输入法相关属性。
android:screenOrientation="fullSensor"
取值 | 说明 |
adjustNothing | 布局不做任何调整 |
adjustPan | Window内容布局整体压缩,展示软件盘的地方不会展示内容布局 |
adjustResize | 如果Window内容布局可以滚动,尽量将输入框展示在软件盘之上,不能保证软件盘没有遮盖其他内容布局 |
adjustUnspecified | 根据情况做adjustPan或者adjustResize |
stateAlwaysHidden | 不论初次还是从别的Activity导航回来都不展示软件盘 |
stateAlwaysVisible | 不管是初次进入还是导航到别的Activity重新回来,都展示软件盘 |
stateHidden | 初次键入不展示软件盘 |
stateUnchanged | 跟前一个Activity里的状态一样 |
stateUnspecified | 未指定软键盘状态 |
stateVisible | 初次进入界面展示软键盘 |
下面我们主要看几个例子的效果
修改一下布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".AActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框1" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框2" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框3" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框4" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框5" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框6" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框7" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框8" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框9" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框10" />
</LinearLayout>
1、android:windowSoftInputMode="stateHidden|adjustPan"
我们设置了adjustPan,可以看到界面会上移,但是标题栏也被强制上移了。下面再试一下adjustResize
android:windowSoftInputMode="stateHidden|adjustResize"
我们可以看到界面并没有上移,而是直接被输入法挡住了…… 这里就需要再补充一下了,这个属性想要界面上移,界面的布局必须是要可以移动的,例如ScrollView。所以这里我们改一下布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".AActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框1" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框2" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框3" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框4" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框5" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框6" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框7" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框8" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框9" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入框10" />
</LinearLayout>
</ScrollView>
</LinearLayout>
我们在外面包了一层ScrollView,再来看看效果。
可以看到输入框并没有被遮挡,而且标题栏也没有被挤出去。
好了,我们基本上常用的几个属性就这些了,以后如果有用到其他的我们再说详细的介绍。