新建活动
[toc]
初始化
新建一个 ActivetyTest
选择 no activity
在 com.example.activitytest
中新建一个空的活动, 不要勾选 generate Layout File 和 launcher Activity
在 app/src/main/res/
下新建 layout 文件夹, 在次文件夹下, 新建 layout resource file , 命名为 first_layout, 根元素默认选择为LinearLayout .
绘制布局一个按钮
打开刚才新建的 first_layout.xml
文件, 切换到text视图. 然后修改如下
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button_1"
android:text="Button 1"
/>
</LinearLayout>
通过design视图可以看到, 这样就创建了一个新的按钮, 它位于屏幕顶端, 与屏幕一样宽, 高度取决于其内容. 其内容为”Button 1” , id的设置是指新定义了它的id.
加载布局
打开 FirstActivity.java
1
2
3
4
5
6
7
8
9
10
11
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 项目中添加任何资源都会在R文件中生成一个相应的资源id
// R.layout.first_layout 可获取到first_layout.xml 的布局id
// 添加下面这一行,将这个布局加载到活动中,传入的参数是布局的id
setContentView(R.layout.first_layout);
}
}
在 AndroidManifest 中注册
实际上 Android Studio 已经给注册完了. 也就是 <actvity>
标签
但是我们还要将其设置为主活动. 也就是运行程序时首先启动的活动.
其实程序没有主活动也可以, 这样它就无法被主动启动, 只能作为第三方服务在其他应用内部调用.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<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:roundIcon="@mipmap/ic_launcher_round"
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>
</application>
</manifest>
文件编写时一定一定要仔细, 尤其是activity标签内还有添加东西.
运行程序
运行成功之后, 会发现程序的左上角是 android:lable 的内容, 并且是启动器(可以理解为桌面)中应用程序显示的名称, 但是如果点开应用的信息的话会发现它是工程的名字.
Toast
Toast 就是安卓在下方的通知, 停留一段时间自动消失
在 FirstActivity.java
下添加监听器.
1
2
3
4
5
6
7
8
9
10
11
12
/* 设置按钮的点击事件 */
// findViewById 可以找到布局文件中定义的元素,传入xml中定义的id,返回view对象
Button button1 = (Button) findViewById(R.id.button_1);
// 注册监听器, 点击按钮时会执行onClick方法
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过静态方法创建toast对象,然后使用show,显示这个提示
// 第一个参数Context 上下文,此处传入活动, 第二个参数文本内容,第三个是时长,自带了short和long
Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_SHORT).show();
}
});
Menu
创建资源文件
创建 res/menu
文件夹, 创建 Menu Resource File 文件, 取名为main. 就得到了main.xml
修改此文件如下:
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- item 用以创建菜单项, id是标识符 title是显示名称-->
<item
android:id="@+id/add_item"
android:title="Add" />
<item
android:id="@+id/remove_item"
android:title="Remove" />
</menu>
这时候可以从design视图看到右上角多了菜单, 包含刚才创建的两项
重载菜单显示
在 FirstActivity.java 类中重载菜单显示函数
按ctrl+O可以快速重载
1
2
3
4
5
6
7
8
9
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 给当前活动创建菜单
// 第一个参数是指定创建菜单的资源文件也就是刚才创建的res/menu/main.xml
// 第二个参数是将菜单添加到哪个对象中,这个是传入的参数
getMenuInflater().inflate(R.menu.main, menu);
// 允许菜单显示,返回true
return true;
}
重载菜单选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// 通过传入的item的id号,执行不同的动作
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;
}
运行效果
运行虚拟机, 发现右上角有三个点, 然后点击之后弹出 Add
Remove
框, 点击之后有文字提示. 与onOptionsItemSelected中的一致.
Intent
intent 是一种能够切换活动的方法.
显式Intent
修改 FirstActivity.java 中的 onCreate() 中的按钮监听器, 添加如下内容.
1
2
3
// 显式Intent
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
隐式intent
创建 ` SecondActivity.java , 活动选择为
Empty Activity , 配置文件为
second_layout.xml` . 在 AndroidManifest.xml 下将其对应的部分修改如下.
1
2
3
4
5
6
7
8
9
10
11
<activity android:name=".SecondActivity">
<!-- 指定当前活动能够响应的action和category -->
<!-- action只能有一个,category可以有多个,当且仅当action和所有category都对上的时候,才能判定为可响应 -->
<!-- 若一个Intent没有任何可响应它的活动.则会导致程序崩溃 -->
<!-- 只有action和category中的内容能够同时匹配上Intent中指定的时,这个活动才能响应这个Intent -->
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
在 FirstActivity.java的监听器中加入如下内容(记得将刚才测试显式intent的代码注释掉)
1
2
3
4
// 若想要启动的活动的catalog是DEFAULT的话,则无需指定intent的catalog,startActivity会自动给他加上
// 隐式Intent ,通过action 和 catelog 才决定启动哪个活动,可广泛用于调用其他程序的活动
Intent intent = new Intent("com.example.activitytest.ACTION_START");
intent.addCategory("com.example.activitytest.MY_CATEGORY");
更多用法
调用浏览器
修改 FirstActivity.java 的监听器如下
1
2
3
4
5
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
// 不加下面这行无法调用ThirdActivity
intent.addCategory("android.intent.category.BROWSABLE");
startActivity(intent);
此时启动AVD, 会发现点击按钮之后, 浏览器(chrome)会自动被调用.
其中setData就意味着需要给 intent-filter 增加data标签, 可以用于指定 scheme(协议) host, port path mineType(数据类型)
创建 ` ThirdActivity.java , 活动选择为
Empty Activity , 配置文件为
third_layout.xml` . 在 AndroidManifest.xml 下将其对应的部分修改如下.
1
2
3
4
5
6
7
8
9
10
11
12
<activity android:name=".ThirdActivity">
<!-- 添加完下面的intent-filter 之后,就可在手机的浏览器默认应用选择里发现 ActivityTest 这个软件,选中之后,待会运行就是-->
<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="http" />
</intent-filter>
</activity>
拨号
修改监听器如下
1
2
3
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
启动AVD, 点击按钮就可以调出拨号界面.
通过Intent向其他活动传递消息
单向传递
修改FirstActivity 里面的监听器如下
1
2
3
4
5
6
7
// 向second传递消息
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// 第一个参数是tag,第二个元素是携带的信息,这两个可以理解为键值对,待会取信息的时候也要用键取值
intent.putExtra("extra_data", data);
startActivity(intent);
在 SecondActivity 的 onCreate 里面加入如下内容便可读取. 也就是可将intent看做类似字典的逻辑结构.
1
2
3
4
// 接受来自FirstActivity的消息
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity", data);
双向发送消息
修改 FirstActivity 的监听器, 去掉 startActivity , 修改为
1
startActivityForResult(intent, 1);
此处的1就是给这个intent的一标记, 用于有多个活动回调 FirstActivity时进行辨别.
修改 SecondActivity 的监听器如下
1
2
3
4
5
6
// 构造返回信息的内容
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
// 此处设置 Result的状态,一般就ok和cancel两种
setResult(RESULT_OK, intent);
finish();
然后重载 onBackPress(), 内容和onClick()的相同, 用于在使用返回键返回 FirstActivity 时能够返回同样的结果.
最后在 FirstActivity 中重载 onActivityResult, 内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
// 书上没有这个super的调用,但是没有就编译不过
super.onActivityResult(requestCode, resultCode, data);
// switch 用来判断此回调是来自哪个活动的,此处的1就是刚才 startActivityForResult里面定义的
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
运行程序, 就可以发现在 SecondActivity 无论是通过按钮还是返回键返回, 都可以在Logcat中发现打印的信息了.
但注意在 SecondActivity 的 onBackPress 中不能调用super, 否则后续的不会执行.
活动生命周期
运行状态 | 暂停状态 | 停止状态 | 销毁状态 | |
---|---|---|---|---|
在栈顶? | √ | x | x | x |
可见? | √ | √ | x | x |
未被销毁? | √ | √ | √ | x |
可见可以有部分可见.
onCreate 活动第一次创建 onStart 第一次创建时, 停止状态->运行状态 onStop 运行状态->停止状态, 销毁 onRestart 停止状态->运行状态 onPause 运行状态->暂停状态, 运行状态->停止状态, 销毁 onResume 第一次创建时, 暂停状态->运行状态, 停止状态->运行状态 onDestory 销毁
可见, onCreate 和 onDestory 是反义词, 只会在 产生/销毁 时调用一次. onStart 和 onStop 是反义词, 只会在 产生/销毁 运行/停止 时调用 onResume 和 onPause 是反义词, 只会在 产生/销毁 运行/停止 运行/暂停 时调用 onRestart 相比于 onStart , 不会在创建时调用, 只会在 停止->运行时调用
启动模式
重新打开 ActivityTest 项目
standard
此模式 只要调用新活动就一定新建, 即使已经存在于栈中. 让 FirstActivity 的按钮调用自身, 同时打印日志. 启动项目, 点击按钮两次, 从日志中发现创建了两个活动. 而且需要三次back才能退出.
1
2
3
// 尝试去调用自己,用以验证 standard启动模式
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
singleTop
此模式, 在栈顶活动调用自身时不会新建, 但其他情况下仍然会. 比如栈可能 FirstActivity , SecondActivity , FirstActivity 这种情况
singleTask
singleTask 会在启动新活动时先检查栈中有无活动, 如果有, 就找到它, 并且弹出它上方所有活动.
此情况可通过 FirstActivity重写 onRestart , SecondActivity重写 onDestory 来验证.
singleInstance
若某个活动的启动模式被设置为 singleInstance, 那么, 它在被调用时, 会另开一个栈.
我们令 SecondActivity 为此模式. 然后让 FirstActivity 调用 SecondActivity , SecondActivity 调用 ThirdActivity.
在各自的 onCreate 中调用
1
2
// 以FirstActivity举例
Log.d("FirstActivity", "Task id is " + getTaskId());
运行程序, 可发现 FirstActivity 与 ThirdActivity 处于同一个栈中, SecondActivity单独位于一个栈. 且返回的顺序是 ThirdActivity -> FirstActivity -> SecondActivity. 这是因为返回的顺序是先将当前栈一个一个弹空, 再切换到别的栈再弹出.
最佳实践
查看当前的活动
直接创建java类 BaseActivity(不要用创建活动的方式, 直接创建类即可).
使其继承 AppCompatActivity, 然后令之前的三个活动全部放弃原有的继承, 转而继承 BaseActivity.
重写 BaseActivity的onCreate, 写入如下语句
1
Log.d("BaseActivity", getClass().getSimpleName());
然后启动程序, 观察日志输出
1
2
3
BaseActivity: FirstActivity
BaseActivity: SecondActivity
BaseActivity: ThirdActivity
这样就可以实时看到当前是哪个活动了.
启动活动的最佳方法
其实就是多加一个 actionStart() 函数 以修改 SecondActivity 的启动为例, 加入如下代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* 调用方法
* 比如在FirstActivity中想要启动 SecondActivity
* 则使用如下语句
* SecondActivity.actionStart(FirstActivity.this,"data1","data2");
* */
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
按钮的点击事件
除了之前提到过的按钮加监听器的方式之外,还有另一种方式.
将活动类设置为实现View. OnClickListener 接口. 然后在onCreate中将此按钮加入this监听器. 然后重载 onClick方法, 在方法中用switch来指定每个按钮被点击后的行为.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DicomViewer extends Activity implements View.OnClickListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 给按钮加入监听器,监听器是类本身
Button button_next = (Button) findViewById(R.id.button_next);
button_next.setOnClickListener(this);
}
// 指定各个按钮被点击后的行为
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_next:
Log.d("button_next", "button_next clicked");
next(v);
break;
default:
}
}
}