Activity
-
Activity概述
Activity的4种状态:运行状态、暂停状态、停止状态、销毁状态。
Activity的生命周期
- 创建、配置、启动和关闭Activity
创建Activity
创建继承自Activity的Activity
- 创建、配置、启动和关闭Activity
public class MainActivity extends Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
启动ActivityActivity分为入口Activity和其他Activity在AndroidManifest.xml设置入口Activity通过startActivity()启动其他Activity
单击按钮启动其他Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = this.findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,DetailActivity.class);
startActivity(intent);
}
});
}
关闭Activity
finish()方法
刷新Activity
onCreate(null)
-
多个Activity的使用
使用Bundle在Activity之间交换数据
什么是Bundle?
能够存储要传输的数据将数据传输给另一个Activity
过程:新建Bundle对象,将要存储的数据存入该对象中,如putCharSequence(,),将该对象通过Intent传输到其他Activity中,
intent.putExtras(bundle),在其他Activity类中获取其Itent对象getItent()
,通过itent获取传输过来的Bundle对象getExtras(),获取其中存储的数据,bundle.get(),获取要显示的文本框组件,将数据设置进去setText()>调用另一个Activity并返回结果
startActivityForResult(Intent intent,int requestCode),启动一个Activity,并设置请求码。
过程:新建Intent对象,启动其他Activity(startActivityForResult),
在另一个Activirt类中,获取itent对象getIntent(),新建Bundle对象,将数据设置设置进该对象中,如putInt(),将该对象通过itent传输,itent.putExtras(),设置返回结果setResult(resultCode,itent)。重写主Activity的 onActivityResult(int requestCode, int resultCode, @Nullable Intent data)方法,判断请求码和结果码是否为自己设置进去的值,若为true,则通过itent.getExtras()获取bundle对象,并通过设定的key值获取存储的数据,将其设置进需要的组件里。实现头像选择
activity_main.xml
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:src="@drawable/icon1"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择头像"
android:layout_below="@id/imageview"
android:gravity="center_horizontal"/>
activity_head.xml
<GridView
android:id="@+id/gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:horizontalSpacing="3dp"
android:verticalSpacing="3dp"/>
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode ==0x11&&resultCode==0x11){
Bundle bundle = data.getExtras();
int imageId = bundle.getInt("imageId");
ImageView imageView = findViewById(R.id.imageview);
imageView.setImageResource(imageId);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = this.findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, HeadActivitys.class);
startActivityForResult(intent,0x11);
}
});
}
}
HeadActivity
public class HeadActivitys extends AppCompatActivity {
private int[] picture = new int[]{R.drawable.icon1,R.drawable.icon2,R.drawable.icon1
,R.drawable.icon1,R.drawable.icon1,R.drawable.icon1};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_head_activitys);
GridView gridView = this.findViewById(R.id.gridview);
BaseAdapter adapter = new BaseAdapter() {
@Override
public int getCount() {
return picture.length;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView ==null){
imageView = new ImageView(HeadActivitys.this);
imageView.setAdjustViewBounds(true);
imageView.setMaxHeight(150);
imageView.setMaxWidth(158);
imageView.setPadding(5,5,5,5);
}else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(picture[position]);
return imageView;
}
};
gridView.setAdapter(adapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = getIntent();
Bundle bundle = new Bundle();
bundle.putInt("imageId",picture[position]);
intent.putExtras(bundle);
setResult(0x11,intent);
finish();
}
});
}
}
- 随时关闭所有Activity
有时我们可能会打开了很多个Activity,突然来个这样的需求,在某个页面可以关掉 所有的Activity并退出程序!好吧,下面提供一个关闭所有Activity的方法, 就是用一个list集合来存储所有Activity!
- 完全退出App的方法
/**
1. 退出应用程序
*/
public void AppExit(Context context) {
try {
ActivityCollector.finishAll();
ActivityManager activityMgr = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
activityMgr.killBackgroundProcesses(context.getPackageName());
System.exit(0);
} catch (Exception ignored) {}
}
- 双击退出程序的两种方法:
1)定义一个变量,来标识是否退出
// 定义一个变量,来标识是否退出
private static boolean isExit = false;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
isExit = false;
}
};
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (!isExit) {
isExit = true;
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
// 利用handler延迟发送更改状态信息
mHandler.sendEmptyMessageDelayed(0, 2000);
} else {
exit(this);
}
return false;
}
return super.onKeyDown(keyCode, event);}
2)保存点击时间:
//保存点击的时间
private long exitTime = 0;
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if ((System.currentTimeMillis() - exitTime) > 2000) {
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
} else {
exit();
}
return false;
}
return super.onKeyDown(keyCode, event);
}
- 为Activity设置过场动画
所谓的过场动画就是切换到另外的Activity时加上一些切换动画,比如淡入淡出,放大缩小,左右互推等! 当然,我们并不在这里详细讲解动画,后面有专门的章节来讲解这个,这里只教大家如何去加载动画,另外 给大家提供了一些比较常用的过渡动画,只要将相关动画文件添加到res/anim目录下,然后下述方法二选一 就可以实现Activity的切换动画了!
1)方法一:
2)方法二:
通过style进行配置,这个是全局的哦,就是所有的Activity都会加载这个动画!
实现代码如下:
①在style.xml中自定义style:
解释:
4个item分别代表:
Activity A跳转到Activity B时Activity B进入动画;
Activity A跳转到Activity B时Activity A退出动画;
Activity B返回Activity A时Activity A的进入动画
Activity B返回Activity A时ActivityB的退出动画
②然后修改下AppTheme:
③最后在appliction设置下:
好的,动画特效就这样duang一声设置好了~
3)其他
好的,除了上面两种方法以外,还可以使用TransitionManager来实现,但是需求版本是API 19以上的, 另外还有一种addOnPreDrawListener的转换动画,这个用起来还是有点麻烦的,可能不是适合初学者 这里也不讲
此图表明,当旋转,平移时,上下变化的起点为图片上部,左右变化的起点为图片右部。
动画文件常用的属性
<scale>标签:缩放
fromXSCale=“1.0” toXScale=“0” 将X轴按比例压缩从1至0。
fromYSCale=“1.0” toYScale=“0” 将Y轴按比例压缩从1至0。
duration=“2000"效果持续时间
pivotX=“50%p” pivotY=“50%p"已屏幕的中心为坐标原点进行缩放
<translate>标签:平移
android:fromXDelta=“100%p” android:toXDelta=“0” 将整个页面从右向左推入
android:fromXDelta=“0” android:toXDelta=”-100%p” 将整个页面从右向左推出
android:fromXDelta=“0” android:toXDelta=“100%p” 将整个页面从左向右推出
android:fromXDelta="-100%p" android:toXDelta=“0” 将整个页面从左向左推入
android:fromYDelta=“100%p” android:toYDelta=“0” 将整个页面从下向上推入
android:fromYDelta=“0” android:toYDelta="-100%p" 将整个页面从下向上推出
android:fromYDelta="-100%p" android:toYDelta=“0” 将整个页面从上往下推入
android:fromYDelta=“0” android:toYDelta=“100%p” 将整个页面从上往下推出
duration=“2000"效果持续时间
<rotate>标签:旋转
android:fromDegrees=“0” android:toDegrees=”+360"
android:pivotX=“50%“android:pivotY=“50%” 以屏幕中心为坐标原点顺时针旋转360°
android:fromDegrees=”-360” android:toDegrees=“0”
android:pivotX=“50%“android:pivotY=“50%” 以屏幕中心为坐标原点顺时针旋转360°
android:fromDegrees=”+360” android:toDegrees=“0”
android:pivotX=“50%“android:pivotY=“50%” 以屏幕中心为坐标原点逆时针旋转360°
android:fromDegrees=“0” android:toDegrees=”-360”
android:pivotX="50%"android:pivotY=“50%” 以屏幕中心为坐标原点逆时针旋转360°
duration="2000"效果持续时间
<alpha>标签:透明度
android:fromAlpha=“1.0” android:toAlpha="0"从不透明到完全透明
duration="2000"效果持续时间
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<scale android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="0.0" android:toXScale="1.0" android:fromYScale="0.0"
android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%"
android:duration="2000"></scale>
<translate android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXDelta="120" android:toXDelta="30" android:fromYDelta="30"
android:toYDelta="250" android:duration="2000" />
<rotate android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0" android:toDegrees="+355" android:pivotX="50%"
android:pivotY="50%" android:duration="2000" />
<alpha android:fromAlpha="1.0" android:toAlpha="0"
android:duration="4000"/>
</set>
- 设置Activity全屏的方法:
1)代码隐藏ActionBar
在Activity的onCreate方法中调用getActionBar.hide();即可
2)通过requestWindowFeature设置
requestWindowFeature(Window.FEATURE_NO_TITLE); 该代码需要在setContentView ()之前调用,不然会报错!!!
注: 把 requestWindowFeature(Window.FEATURE_NO_TITLE);放在super.onCreate(savedInstanceState);前面就可以隐藏ActionBar而不报错。
3)通过AndroidManifest.xml的theme
在需要全屏的Activity的标签内设置 theme = @android:style/Theme.NoTitleBar.FullScreen
- 定义对话框风格的Activity
在某些情况下,我们可能需要将Activity设置成对话框风格的,Activity一般是占满全屏的, 而Dialog则是占据部分屏幕的!实现起来也很简单!
直接设置下Activity的theme:
android:theme="@android:style/Theme.Dialog" 这样就可以了,当然你可以再设置下标题,小图标!
//设置左上角小图标
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.main);
getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, android.R.drawable.ic_lion_icon);
//设置文字:
setTitle(R.string.actdialog_title); //XML代码中设置:android:label="@string/activity_dialog"
要使得点击该对话框外的空白区域不会退出的方法
方法一: setFinishOnTouchOutside(false);
方法二:首先在View创建之前设置两个Flag,一个设置窗口为非模式的,这样除窗口外的内容就可以获得touch事件,然后设置窗口外部touch事件发生时的通知。最后重写onTouchEvent,监听窗口外的Touch事件。这样就可以在监听方法中自定义窗口外点击事件的响应,是否关闭窗口或者其他操作。
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make us non-modal, so that others can receive touch events.
getWindow().setFlags(LayoutParams.FLAG_NOT_TOUCH_MODAL, LayoutParams.FLAG_NOT_TOUCH_MODAL);
// ...but notify us that it happened.
getWindow().setFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
// Note that flag changes must happen *before* the content view is set.
setContentView(R.layout.my_dialog_view);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// If we've received a touch notification that the user has touched
// outside the app, finish the activity.
if (MotionEvent.ACTION_OUTSIDE == event.getAction()) {
finish();
return true;
}
// Delegate everything else to Activity.
return super.onTouchEvent(event);
}
}
- Activity,Task和Back Stack的一些概念
接着我们来了解Android中Activity的管理机制,这就涉及到了两个名词:Task和Back Stack了!
概念解析:
我们的APP一般都是由多个Activity构成的,而在Android中给我们提供了一个Task(任务)的概念, 就是将多个相关的Activity收集起来,然后进行Activity的跳转与返回!当然,这个Task只是一个 frameworker层的概念,而在Android中实现了Task的数据结构就是Back Stack(回退堆栈)! 相信大家对于栈这种数据结构并不陌生,Java中也有个Stack的集合类!栈具有如下特点:
后进先出(LIFO),常用操作入栈(push),出栈(pop),处于最顶部的叫栈顶,最底部叫栈底
而Android中的Stack Stack也具有上述特点,他是这样来管理Activity的:
当切换到新的Activity,那么该Activity会被压入栈中,成为栈顶! 而当用户点击Back 键,栈顶的Activity出栈,紧随其后的Activity来到栈顶!
我们来看下官方文档给出的一个流程图:
流程解析:
应用程序中存在A1,A2,A3三个activity,当用户在Launcher或Home Screen点击应用程序图标时, 启动主A1,接着A1开启A2,A2开启A3,这时栈中有三个Activity,并且这三个Activity默认在 同一个任务(Task)中,当用户按返回时,弹出A3,栈中只剩A1和A2,再按返回键, 弹出A2,栈中只剩A1,再继续按返回键,弹出A1,任务被移除,即程序退出!
接着在官方文档中又看到了另外两个图,处于好奇,我又看了下解释,然后跟群里的人讨论了下:
然后总结下了结论:
Task是Activity的集合,是一个概念,实际使用的Back Stack来存储Activity,可以有多个Task,但是 同一时刻只有一个栈在最前面,其他的都在后台!那栈是如何产生的呢?
答:当我们通过主屏幕,点击图标打开一个新的App,此时会创建一个新的Task!举个例子:
我们通过点击通信录APP的图标打开APP,这个时候会新建一个栈1,然后开始把新产生的Activity添加进来,可能我们在通讯录的APP中打开了短信APP的页面,但是此时不会新建一个栈,而是继续添加到栈1中,这是 Android推崇一种用户体验方式,即不同应用程序之间的切换能使用户感觉就像是同一个应用程序, 很连贯的用户体验,官方称其为seamless (无缝衔接)! ——————这个时候假如我们点击Home键,回到主屏幕,此时栈1进入后台,我们可能有下述两种操作:
1)点击菜单键(正方形那个按钮),点击打开刚刚的程序,然后栈1又回到前台了! 又或者我们点击主屏幕上通信录的图标,打开APP,此时也不会创建新的栈,栈1回到前台!
2)如果此时我们点击另一个图标打开一个新的APP,那么此时则会创建一个新的栈 2,栈2就会到前台, 而栈1继续呆在后台;
3) 后面也是这样...以此类推!
- Task的管理
1)文档翻译:
好的,继续走文档,从文档中的ManagingTasks开始,大概的翻译如下:
1)文档翻译
继续走文档,从文档中的ManagingTasks开始,翻译如下:
如上面所述,Android会将新成功启动的Activity添加到同一个Task中并且按照以"先进先出"方式管理多个Task 和Back Stack,用户就无需去担心Activites如何与Task任务进行交互又或者它们是如何存在于Back Stack中! 或许,你想改变这种正常的管理方式。比如,你希望你的某个Activity能够在一个新的Task中进行管理; 或者你只想对某个Activity进行实例化,又或者你想在用户离开任务时清理Task中除了根Activity所有Activities。你可以做这些事或者更多,只需要通过修改AndroidManifest.xml中 < activity >的相关属性值或者在代码中通过传递特殊标识的Intent给startActivity( )就可以轻松的实现 对Actvitiy的管理了。
< activity >中我们可以使用的属性如下:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
你能用的主要的Intent标志有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
好的,接下来逐个介绍这些怎么用:
2)taskAffinity和allowTaskReparenting
默认情况下,一个应用程序中的所有activity都有一个Affinity,这让它们属于同一个Task。 你可以理解为是否处于同一个Task的标志,然而,每个Activity可以通过 < activity>中的taskAffinity属性设置单独的Affinity。 不同应用程序中的Activity可以共享同一个Affinity,同一个应用程序中的不同Activity 也可以设置成不同的Affinity。 Affinity属性在2种情况下起作用:
1)当启动 activity的Intent对象包含FLAG_ACTIVITY_NEW_TASK标记: 当传递给startActivity()的Intent对象包含 FLAG_ACTIVITY_NEW_TASK标记时,系统会为需要启动的Activity寻找与当前Activity不同Task。如果要启动的 Activity的Affinity属性与当前所有的Task的Affinity属性都不相同,系统会新建一个带那个Affinity属性的Task,并将要启动的Activity压到新建的Task栈中;否则将Activity压入那个Affinity属性相同的栈中。
2)allowTaskReparenting属性设置为true 如果一个activity的allowTaskReparenting属性为true, 那么它可以从一个Task(Task1)移到另外一个有相同Affinity的Task(Task2)中(Task2带到前台时)。 如果一个.apk文件从用户角度来看包含了多个"应用程序",你可能需要对那些 Activity赋不同的Affinity值。
3)launchMode:
四个可选值,启动模式我们研究的核心,下面再详细讲! 他们分别是:standard(默认),singleTop,singleTask,singleInstance
4)清空栈
当用户长时间离开Task(当前task被转移到后台)时,系统会清除task中栈底Activity外的所有Activity 。这样,当用户返回到Task时,只留下那个task最初始的Activity了。我们可以通过修改下面这些属性来 改变这种行为!
alwaysRetainTaskState: 如果栈底Activity的这个属性被设置为true,上述的情况就不会发生。 Task中的所有activity将被长时间保存。
clearTaskOnLaunch 如果栈底activity的这个属性被设置为true,一旦用户离开Task, 则 Task栈中的Activity将被清空到只剩下栈底activity。这种情况刚好与 alwaysRetainTaskState相反。即使用户只是短暂地离开,task也会返回到初始状态 (只剩下栈底acitivty)。
finishOnTaskLaunch 与clearTaskOnLaunch相似,但它只对单独的activity操 作,而不是整个Task。它可以结束任何Activity,包括栈底的Activity。 当它设置为true时,当前的Activity只在当前会话期间作为Task的一部分存在, 当用户退出Activity再返回时,它将不存在。
- Activity的四种加载模式详解:
接下来我们来详细地讲解下四种加载模式: 他们分别是:standard(默认),singleTop,singleTask,singleInstance
先来看看总结图:
模式详解:
standard模式:
标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A–>A。
singleTop模式:
如果一个以singleTop模式启动的Activity的实例已经存在于任务栈的栈顶, 那么再启动这个Activity时,不会创建新的实例,而是重用位于栈顶的那个实例, 并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。 举例来说,如果A的启动模式为singleTop,并且A的一个实例已经存在于栈顶中, 那么再调用startActivity(new Intent(this,A.class))启动A时, 不会再次创建A的实例,而是重用原来的实例,并且调用原来实例的onNewIntent()方法。 这时任务栈中还是这有一个A的实例。如果以singleTop模式启动的activity的一个实例 已经存在与任务栈中,但是不在栈顶,那么它的行为和standard模式相同,也会创建多个实例。
singleTask模式:
只允许在系统中有一个Activity实例。如果系统中已经有了一个实例, 持有这个实例的任务将移动到顶部,同时intent将被通过onNewIntent()发送。 如果没有,则会创建一个新的Activity并置放在合适的任务中。
singleInstance模式
保证系统无论从哪个Task启动Activity都只会创建一个Activity实例,并将它加入新的Task栈顶 也就是说被该实例启动的其他activity会自动运行于另一个Task中。 当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例 的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同, 同一时刻在系统中只会存在一个这样的Activity实例。