Activity技术
Q7nl1s admin

Activity基本概念

◼ Activity:Android四大组件之一,主要负责为用户创建一个窗口,用户可与其提供的界面进
行交互。
◼ Activity 是一个界面的载体,通常一个界面对应一个Activity。
◼ 界面布局命名规范:activity_S***.xml, 其中***对应某个Activity名
◼ Activity使用:setContentView( R.layout.布局id ); 来加载布局

注:一个 Activity 不一定只对应一个界面(布局)

image-20230313190826410

Activity的创建和启动

image-20230313190854167

image-20230313191044417

MyActivity创建成功

image-20230313191111262

在MyActivity布局中添加组件:ImageView

image-20230313191238887

重要:如何设置新的Activity为启动程序?

◼ 首先,打开 AndroidManifest.xml 查看Activity配置:

image-20230313191301726

<action android:name="android.intent.action.MAIN" />:表明此 Activity 是启动程序
<category android:name="android.intent.category.LAUNCHER" />:表明显示在程序列表里

然后,将<intent-filter>内容配置给 MyActivity

image-20230313191438904

APP 运行结果:MyActivity 将作为启动程序

image-20230313191522672

关于项目配置文件(清单文件):AndroidManifest.xml
◼ 每一个 Android 项目都有一个名为 AndroidManifest.xml 的配置文件
◼ 该文件在所有 Android 项目中的名称不变
◼ 该文件是全局配置文件,所有在项目中使用的组件(如 Activity,Service,Content provider,Broadcast receivers 等)
都要在该文件中声明
◼ 该文件还可以声明一些 android 权限等信息

Android工程中各个文件之间的关系

image-20230313191636658

Activity的背景

◼ Activity 设置背景色和背景图片

image-20230313191707598

  1. Activity 设置背景色

◼ 首先在 res/values/colors.xml 中添加某个颜色值: <color name="bg">#FFC107</color>==注:颜色名称要保持唯一==

image-20230313191829243

◼ 然后给布局设置 background 属性值:@color/bg (bg为自定义的颜色名)

image-20230313191856827

  1. Activity设置背景图

◼ 将布局的 background 属性值设置为图片,如:@drawable/bg1080 ==注:先将背景图片复制到res/drawable文件夹中==
◼ 注:背景图默认会自动拉伸,建议使用与屏幕相同分辨率的图片

image-20230313191947974

示例:定时更新Activity背景图

image-20230315135036577

Activity更换背景图:
layout.setBackgroundResource(R.drawable.图片id);

定时器用法:Timer + TimerTask

定时器基本架构

1
2
3
4
5
6
7
8
9
10
11
Timer timer = new Timer(); //定时器
TimerTask task = new TimerTask() { //定时任务--子线程(TimerTask是一个抽象类)
public void run() {
//定时任务代码…
}
};
// 调度task的常用方法:
timer.schedule(task, delay); // 从现在起过delay毫秒后执行一次task任务(只执行1次)
timer.schedule(task, delay, period); // 从现在起过delay毫秒以后,每隔period毫秒执行一次task任务(多次执行)
// 停止定时器:
timer.cancel();

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends AppCompatActivity {
private int idx = 0; // 图片序号
private ConstraintLayout layout; // 布局

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// constrainLayout 为布局的 id
layout = (ConstraintLayout) findViewById( R.id.constrainLayout );
int[ ] resIds = new int[ ] { R.drawable.bg1080, R.drawable.bg1080_2 }; // 图片资源id数组
// begin TimerTask
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
// 在定时任务线程中再启动一个 runOnUiThread 线程去更新UI
runOnUiThread( new Runnable() {
@Override
public void run() {
// 说明:Android规定只有主线程才能更新UI,在TimerTask线程中是不能直接更新UI的,
// 但可调用runOnUiThread方法将一个Runnable子线程任务放到主线程中执行。
layout.setBackgroundResource(resIds[idx]); //更换背景图
if (idx < resIds.length - 1) idx++;
else idx = 0;
}
} ); //end runOnUiThread
}
}; //end TimerTask

//3秒之后执行task任务,每隔5秒再执行一次task
timer.schedule(task, 3000, 5000);
}
}

扩展:实现可调控的背景切换

设置私有变量(线程访问控制)

1
2
3
4
5
6
7
public class MyActivity extends AppCompatActivity {
private int idx = 0; // 图片序号
private ConstraintLayout layout; // 布局
private Timer timer;
private TimerTask task;
...
}

初始化相关数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void init(){
TextView tv = findViewById(R.id.tv);
timer = new Timer();
layout = (ConstraintLayout) findViewById( R.id.constrainLayout );
int[ ] resIds = new int[ ] { R.drawable.bg1080, R.drawable.bg1080_2 }; // 图片资源id数组
task = new TimerTask() {
@Override
public void run() {
// 在定时任务线程中再启动一个
// runOnUiThread 线程去更新UI

// 说明:Android规定只有主线程才能更新UI,在TimerTask
// 线程中是不能直接更新UI的,但可调用runOnUiThread方
// 法将一个Runnable子线程任务放到主线程中执行。
runOnUiThread( new Runnable() {
@Override
public void run() {
tv.setText("这是第"+ (idx+1) + "张");
layout.setBackgroundResource(resIds[idx]); //更换背景图
if (idx < resIds.length - 1) idx++;
else idx = 0;
}
} ); //end runOnUiThread
}
}; //end TimerTask
}

onCreate 里调用 init

1
2
3
4
5
6
7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
init();
...
}

在 onOptionsItemSelected 中通过选择来启动和终止线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_login:
Toast.makeText( MyActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
//3秒之后执行task任务,每隔5秒再执行一次task
timer.schedule(task, 3000, 5000);
break;
case R.id.menu_exit:
timer.cancel();
break;
}
return super.onOptionsItemSelected(item);
}

设置私有变量,并在 onCreate 中进行初始化是为了方便线程间的通信,否则可能会因为访问控制的原因,无法结束该线程

Activity选项菜单

◼ 选项菜单 (Options Menu):是 Activity 中位于标题栏右侧的菜单,点击之后可以展开选项,供
用户选择。
◼ 每个 Activity 有且只有一个选项菜单,它为整个 Activity 服务

选项菜单的创建:3 个步骤

◼ 步骤1:创建菜单资源
◼ 步骤2:重写onCreateOptionsMenu方法
◼ 步骤3:重写onOptionsItemSelected方法

步骤1:创建菜单资源

主要过程:
◼ 先在 res 文件夹中创建”menu”资源文件夹(android resource directory)
◼ 再在 menu 文件夹中创建菜单资源文件(menu resource file),文件名自定义,如:main_menu.xml
◼ 在菜单设计器中设计菜单项

image-20230313192152619

image-20230313192207318

image-20230313192245590

步骤2:重写onCreateOptionsMenu方法

– 在 ActionBar 中将显示菜单

◼ 作用:将创建的菜单资源(main_menu)注入到 Activity 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
…略
}

@Override
// 在Activity中重写onCreateOptionsMenu()方法
public boolean onCreateOptionsMenu(Menu menu) {
// 将创建的菜单资源(名为main_menu)注入到Activity中
getMenuInflater().inflate( R.menu.main_menu, menu );
// 此行为默认代码
return super.onCreateOptionsMenu(menu);
}
}

步骤3:重写onOptionsItemSelected方法

◼ 作用:为菜单项添加选中事件

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { // 菜单项参数
switch ( item.getItemId() ) { // 根据菜单id做分支
case R.id.menu_login:
Toast.makeText( MyActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); // 模拟一下"登录"功能
break;
case R.id.menu_exit:
finish(); // 结束Activity
}
return super.onOptionsItemSelected(item); // 此行为默认代码
}

运行结果

image-20230313192802303

ActionBar用法

◼ ActionBar:导航栏,是 Activity 顶端的一块区域

image-20230315141632493

ActionBar用法示例1:

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);

ActionBar actionBar = getSupportActionBar(); //获取ActionBar
// APP全屏显示
actionBar.hide(); //隐藏ActionBar
}
image-20230313193051928

ActionBar用法示例2:

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);

ActionBar actionBar = getSupportActionBar(); //获取ActionBar
actionBar.setTitle("欢迎使用"); //设置标题
actionBar.setDisplayHomeAsUpEnabled(true); //在ActionBar最左边显示返回箭头按钮
}

注:箭头按钮 id 为:android.R.id.home
响应时和菜单项编程一样,如:case android.R.id.home : finish();

image-20230313193426915

ActionBar用法示例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);

ActionBar actionBar = getSupportActionBar(); //获取ActionBar
actionBar.setTitle(" 欢迎使用"); //设置标题
//设置logo (3行代码)
actionBar.setDisplayShowHomeEnabled(true);
// 特别强调:logo图片尺寸要小一点,如48*48
actionBar.setLogo( R.drawable.twitter );
actionBar.setDisplayUseLogoEnabled(true);
}
image-20230313193207115

ActionBar用法示例4:

ActionBar上放置按钮 ==按钮实际是选项菜单项==

image-20230313193557666

ActionBar用法示例5:

Actionbar上添加查询按钮(查询菜单项)

1
2
3
4
5
<item
android:id = "@+id/menu_search"
android:title = "搜索"
app:actionViewClass = "android.widget.SearchView"
app:showAsAction = "always" />

image-20230313193712248

查询按钮编程框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
// 代码写在onCreateOptionsMenu方法中
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu,menu);
MenuItem menuItem = menu.findItem( R.id.menu_search );
SearchView searchView = (SearchView) menuItem.getActionView(); // 找到MenuItem中的SearchView
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { // SearchView添加查询监听器
@Override
public boolean onQueryTextSubmit(String s) { // 查询信息提交时触发,参数s为输入的查询信息
Toast.makeText(MyActivity.this, s, Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onQueryTextChange(String s) { // 查询信息修改时触发
return false;
}
});
return super.onCreateOptionsMenu(menu);
}

练习:将查询送到baidu搜索

1
2
3
4
5
6
7
public boolean onQueryTextSubmit(String s) {
Intent intent = new Intent();
intent.setAction( Intent.ACTION_VIEW );
intent.setData( Uri.parse("https://www.baidu.com/s?wd="+s) );
startActivity( intent );
return false;
}
Snipaste_2023-03-15_14-25-48

可以看到程序自动跳转 google 并进行搜索

Activity生命周期

◼ Activity 四种状态:

image-20230313193937680

Activity 栈示例:

image-20230313193956559

Activity 生命周期回调函数

随着 Activity 状态的变化,系统会调用不同的回调函数(主要7个)

函数 说明
onCreate() Activity 启动后第一个被调用的函数(且只被调用一次),常用来进行 Activity 的初始化,例如创建 View、绑定数据或 恢复信息等。强调:必须在此处调用 setContentView() 来设置 Activity 的界面布局。
onStart() 当 Activity 显示在屏幕上时(但用户尚不能交互) ,该函数被调用。表示 Activity 将进入”已启动”状
onResume() 当用户可以与 Activity 交互前,该函数被调用。 (此时 Activity 位于 Activity 栈顶部,成为前台程序)
onPause() 当 Activity 失去焦点进入暂停状态时(部分遮挡),该函数被调用。主要用来释放 CPU 资源、关闭动画等
onStop() 当 Activity 对用户不可见后,该函数被调用,Activity 进入停止状态。
onRestart() 当处于 stopped 状态的 Activity 即将重启时,该函数被调用。此回调后面总是跟着 onStart()
onDestroy() 在 Activity 被销毁前(即将进入非活动状态前),该函数被调用。Activity接收的最后一个回调 两种情况:
(1) Activity 中主动调用 finish() 函数;
(2) Activity 被 Android 系统终结

Activity 生命周期图

img

了解:Activity启动模式 节点配置

<activity>节点配置 android:launchMode 属性

◼ standard(默认):标准模式,每次激活 Activity,都会创建新的实例
◼ singleTop:栈顶唯一,表现为当 Activity 处于栈顶位置时,反复激活并不会创建新的实例,也不会出现
压栈操作,反之,当 Activity 不处于栈顶位置时,激活时会创建新的实例,并压栈
◼ singleTask:任务栈内唯一,表现为当任务栈中没有当前 Activity 时,会创建新的实例,并压栈,反之,
当任务栈中已经存在当前 Activity 时,不会创建新的实例,原本在栈内处于该 Activity 上方的其他
Activity 将全部会被强制出栈,使得该被激活的 Activity 获得栈顶位置
◼ singleInstance:实例唯一,该 Activity 的实例最多只存在1个,该 Activity 将强制独占1个新的任务栈,
且该任务栈中有且仅有1个 Activity
◼ singleInstancePerTask:略

Activity 生命周期测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MyActivity extends AppCompatActivity { 移动平台软件设计
private static final String tag = "flag"; // 定义一个日志标签

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Log.d(tag, "onCreate");
}

@Override
protected void onStart() {
super.onStart();
Log.d(tag, "onStart");
}

@Override
protected void onResume() {
super.onResume();
Log.d(tag, "onResume");
}

@Override
protected void onPause() {
super.onPause();
Log.d(tag, "onPause");
}

@Override
protected void onStop() {
super.onStop();
Log.d(tag, "onStop");
}

@Override
protected void onRestart() {
super.onRestart();
Log.d(tag, "onRestart");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(tag, "onDestroy");
}
}

测试1:正常启动程序(进入running状态)

image-20230313194805971

测试2:侧滑离开程序(进入stopped状态)

image-20230313194828645

测试3:重新进入程序(从stopped恢复进入running状态)

image-20230313194843955

测试4:结束程序(进入destroyed状态)

image-20230313194901698

测试5:切换为横屏(destroyed之后再重启)

image-20230313194920008

测试6:通话时和挂机后(从stopped恢复进入running状态)

image-20230313194932233

其他测试:

  1. 正常启动后返回 Home (桌面):
    ◼ onCreate → onStart → onResume → onPause → onStop
  2. 再从桌面返回应用程序:
    ◼ onRestart → onStart → onResume
  3. 锁屏后解锁
    ◼ onPause → onStop → onRestart → onStart → onResume

实战案例

image-20230318152359546

Timer + TimerTask 实现数字时钟

activity_clock.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".ClockActivity">


<TextView
android:id="@+id/time_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:text="00:00"
android:textSize="90sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.283"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.397" />

<TextView
android:id="@+id/weekday_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="周一"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.223"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.534" />

<TextView
android:id="@+id/ap_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AM"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.813"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.384" />

<TextView
android:id="@+id/second_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00"
android:textSize="25sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.806"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.441" />

<TextView
android:id="@+id/date_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0000/00/00"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.664"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.536" />
</androidx.constraintlayout.widget.ConstraintLayout>

ClockActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example.helloandroid;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

public class ClockActivity extends AppCompatActivity {
private Timer timer;
TimerTask task;

public void init(){
TextView second_text_view = (TextView) findViewById( R.id.second_text_view ); // 秒数
TextView time_text_view = (TextView) findViewById( R.id.time_text_view ); // 24小时制时钟
TextView ap_text_view = (TextView) findViewById( R.id.ap_text_view ); // 上下午
TextView date_text_view = (TextView) findViewById( R.id.date_text_view ); // 年份月份日期
TextView weekday_text_view = (TextView) findViewById( R.id.weekday_text_view ); // 星期几

timer = new Timer(); //定时器
task = new TimerTask() { //定时任务--子线程(TimerTask是一个抽象类)
@Override
public void run() {
// 在定时任务线程中再启动一个 runOnUiThread 线程去更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
// 说明:Android规定只有主线程才能更新UI,在TimerTask线程中是不能直接更新UI的,
// 但可调用runOnUiThread方法将一个Runnable子线程任务放到主线程中执行。

// 获取当前时间
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);

// 格式化时间和日期
Locale englishLocale = Locale.ENGLISH; // 硬编码为英语,否则会输出上下午而不是 AM/PM
SimpleDateFormat sdfTime = new SimpleDateFormat("hh:mm", Locale.getDefault());
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
SimpleDateFormat sdfSeconds = new SimpleDateFormat("ss", Locale.getDefault());
SimpleDateFormat sdfWeekday = new SimpleDateFormat("EEEE", Locale.getDefault());
SimpleDateFormat sdfAmPm = new SimpleDateFormat("a", englishLocale);
String time = sdfTime.format(now);
String date = sdfDate.format(now);
String seconds = sdfSeconds.format(now);
String weekday = sdfWeekday.format(now);
String amPm = sdfAmPm.format(now);

// 设置文本
time_text_view.setText(time);
date_text_view.setText(date);
second_text_view.setText(seconds);
// 把星期X替换为周X
String formattedWeekday;
if (weekday.startsWith("星期")) {
formattedWeekday = weekday.replace("星期", "周");
} else {
formattedWeekday = weekday;
}
weekday_text_view.setText(formattedWeekday);
ap_text_view.setText(amPm);
}
}); //end runOnUiThread
}
};
}



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clock);
init();
//0秒之后执行task任务,每隔1秒再执行一次task
timer.schedule(task, 0, 1000);
}
}

简单易懂不需要过多解释

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View