android开发布局从入门到精通
Android的UI组件都是继承View类,View表示一个空白的矩形区域。TextView、Button、EditText这些常用的组件等都是直接或间接继承自View。
此外,View还有一个重要的子类ViewGroup,该类可以用来包含多个View组件,本身也可以当做一个View组件被其他的ViewGroup所包含,由此,可以构建出非常复杂的UI界面。
常用的布局管理器如FrameLayout、LinearLayout、RelativeLayout等都直接继承自ViewGroup。
在Android应用中,Activity就相当于传统桌面开发中的Form,刚创建出来就是一个空白的屏幕,因此,要显示UI界面时,就需要调用setContentView()方法传入要显示的视图实例或者布局资源。
如:
传入一个布局资源:
setContentView(R.layout.main);
传入一个View实例:
TextView myTv = new TextView(this);
setContentView(myTv);
myTv.setText(“hello, world”);
因为setContentView()只能接受一个View实例,要显示复杂的UI界面,就需要用到ViewGroup来包含多个多个View实例,然后将ViewGroup实例传给setContentView。ViewGroup是个抽象类,一般直接使用的都是它的子类,被称之为布局管理器。
Android有两种方式编写UI界面,一种是在xml布局资源文件中,另一种是直接在代码中编写,如上面的传入一个View实例的做法就是直接在代码中编写,这是传统的Form编程的做法。现在比较推荐的是在xml布局资源文件中编写UI界面,这样一来就可以将应用表示层与逻辑层相分离,无需修改代码就可以修改表示层。
要编写复杂的UI界面,需要掌握android中常用的布局管理器。主要有:
AbsoluteLayout:绝对布局
FrameLayout:帧布局
LinearLayout:线性布局
RelativeLayout:相对布局
TableLayout:表格布局
GridLayou:网格布局(Android 4.0添加的新的布局管理器)
1.LinearLayout 线性布局
线性布局就是放在其中的View组件将进行线性对齐排列,可以设置是垂直排列还是水平排列。
新建一个布局资源文件的方法:
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建LinearLayout布局文件,就选择LinearLayout作为其根节点即可。
linear_layout.xml代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="aaaaaa" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="dddddd" /> </LinearLayout>
activity中代码如下:
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.linear_layout); }
显示效果:
常用的几个属性:
1)orientation属性:设置LinearLayout中组件的排列方式,可以取值vertical或者horizontal表示垂直排成一列或者水平排成一行。
上面代码中,如果把orientation设置为horizontal。显示则变为:
因为只显示一行,而第一个Button的宽度就是充满父元素,所以只显示出来了第一个Button。
2)layout_width属性:设置在父元素中该组件的宽度,可以取值wrap_content、match_parent或者fill_parent。其中wrap_content表示宽度能够包裹该组件中的内容即可,fill_parent和match_parent含义相同表示宽度充满父元素,现在,更常使用match_parent,而很少用fill_parent。
如上面代码中把所有的Button的layout_width都设置为wrap_content,则显示效果如下:
3)layout_height属性:设置在父元素中该组件的宽度,取值同layout_width。
4)grativity属性:设置该容器内组件的对齐方式。
如在LinearLayout节点中添加属性:android:gravity="center_vertical"
则显示效果如下:
该属性的取值可以是:top、bottom、left、right、center、center_vertical、center_horizontal等值,或者这些值相或(即位或运算 | )
如:android:gravity="bottom|right" 显示效果
5)layout_gravity属性:当前控件在父元素的位置。
如将aaaaaa那个Button中layout_gravity设置为”center”,其效果将会与其所处容器即LinearLayout中的gravity属性效果进行叠加,显示如下:
垂直上进行了居中,水平上还是排在bbbbbb的左边
6)layout_weight属性:在子控件中设置父元素中多出来的额外空间的分配权重。
此时,如果只在aaaaaa这个button中设置layout_weight属性,可以设置为任意值,习惯设置为1。则aaaaaa这个button会拉伸占据剩下的空间,显示如下:
如果同时在aaaaaa和dddddd两个button中都设置layout_weight属性,且第一个设置为1,第二个设置为2,则之前多出来的剩余空间会分给aaaaaa 1/3,分给dddddd 2/3,即各自的权重值/总的权重值,即为各自所分得的剩余空间的比例,显示如下:
7)weightSum属性:设置容器中剩余空间的总的权重值,这个属性是LinearLayout中的属性,而layout_weight是各个子控件中的属性,若不设置,则默认为各个子控件layout_weight属性值的总和。
若如上面aaaaaa的layout_weight值为1,dddddd的layout_weight的值为2,同时在LinearLayout中设置weightSum值为6,则仍会有一半的剩余空间,aaaaaa只分得原来剩余空间的1/6,dddddd分得2/6,显示如下:
8)visibility属性:控制是否显示,取值可以是invisible、visible、gone。visible表示显示出来,invisible和gone不显示出来,其中invisible不显示,但控件仍然存在,占用着空间,而gone表示控件不存在了,也就不占用空间了。
如:cccccc设置visibility属性为gone,显示如下:
若改为invisible:
LinearLayout设置invisible:
2.RelativeLayout:相对布局
顾名思义,即根据各控件的相对位置进行布局,相对位置,可以是子控件A相对父控件的位置,也可以是子控件A相对于子控件B的位置。
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建RelativeLayout布局文件,就选择RelativeLayout作为其根节点即可。文件名为relative_layout.xml。
代码如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/aa" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="aaaaaa" /> <Button android:id="@+id/bb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/aa" android:layout_alignTop="@id/aa" android:text="bbbbbb" /> <Button android:id="@+id/cc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/aa" android:layout_alignBottom="@id/aa" android:text="cccccc" /> <Button android:id="@+id/dd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/aa" android:layout_alignLeft="@id/aa" android:text="dddddd" /> <Button android:id="@+id/ee" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/aa" android:layout_alignLeft="@id/aa" android:text="eeeeee" /> </RelativeLayout>
修改FirstActivity中setContentView(R.layout.relative_layout);
显示效果:
aaaaaa在父容器中居中显示
bbbbbb在aaaaaa的右边显示,并且与aaaaaa顶部对齐
ccccccc在aaaaaa的左边显示,并且与aaaaaa顶部对齐
dddddd在aaaaaa的上面显示,并且与aaaaaa左对齐
eeeeee在aaaaaa的下面显示,并且与aaaaaa左对齐
主要属性:均为设置父子相对位置,或者子控件与子控件的相对位置
android:layout_toRightOf 在指定控件的右边
android:layout_toLeftOf 在指定控件的左边
android:layout_above 在指定控件的上边
android:layout_below 在指定控件的下边
android:layout_alignBaseline 跟指定控件水平对齐
android:layout_alignLeft 跟指定控件左对齐
android:layout_alignRight 跟指定控件右对齐
android:layout_alignTop 跟指定控件顶部对齐
android:layout_alignBottom 跟指定控件底部对齐
android:layout_alignParentLeft 是否跟父布局左对齐
android:layout_alignParentTop 是否跟父布局顶部对齐
android:layout_alignParentRight 是否跟父布局右对齐
android:layout_alignParentBottom 是否跟父布局底部对齐
android:layout_centerVertical 在父布局中垂直居中
android:layout_centerHorizontal 在父布局中水平居中
android:layout_centerInParent 在父布局中居中
3.FrameLayout:帧布局
如同Flash或者photoshop中图层的概念,在上面的图层遮盖下面的图层,没被遮到的地方仍然显示出来。
右击res/layout,然后在弹出的菜单中选择new,然后选择Android Xml File,要新建FrameLayout布局文件,就选择FrameLayout作为其根节点即可。文件名为frame_layout.xml。
代码如下:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:src="@drawable/bg" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <ImageView android:src="@drawable/hero" android:layout_width="wrap_content" android:layout_gravity="center" android:layout_height="wrap_content"/> </FrameLayout>
依次放置两个ImageView用于显示两张图片,第一张为背景图片,第二张为一个人物图片。
修改FirstActivity中setContentView(R.layout.frame_layout);
显示效果如下:
先添加的控件位于下面,后添加的控件位于上面。
4.AbsoluteLayout:绝对布局
根据绝对坐标位置进行布局,不灵活,故而很少使用。
eclipse中也提示:AbsoluteLayout is deprecated,即不建议使用绝对布局。
新建一个layout文件,名为absolute_layout.xml,代码入下:
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="100dp" android:layout_y="100dp" android:text="aaaaaa" /> </AbsoluteLayout>
修改FirstActivity中代码:setContentView(R.layout.absolute_layout);
显示如下:
属性:
android:layout_x 指定控件在父布局的x轴坐标
android:layout_y 指定控件在父布局的y轴坐标
5.TableLayout:表格布局
需要配合TableRow进行使用,也不是太常用。
在TableLayout中每加入一个TableRow子节点,就表示在表格中加入了一行,之后在TableRow中每加入一个控件,就表示加入了一列。注意TableRow单元行里的单元格的宽度小于默认的宽度时就不起作用,其默认是fill_parent,高度可以自定义大小。
如果直接在TableLayout中添加控件,那么该控件将会占用一行。
新建一个layout布局文件,名为table_layout.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="aaaaaa" /> <TableRow > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="bbbbbb" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cccccc" /> </TableRow> <TableRow > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="dddddd" /> </TableRow> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="eeeeee" /> </TableLayout>
修改setContentView(R.layout.table_layout);
显示效果如下:
第0行一个按钮aaaaaa
第1行6个按钮,但是父控件宽度有限,只显示了5个
第2行一个TableRow,里面加了一个按钮dddddd
第3行也是一个按钮eeeeee
主要属性:
android:shrinkColumns 设置收缩的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。
如:android:shrinkColumns="0,2",表示第0和第2列收缩,显示效果如下:
可以看出没有放在TableRow中的行没有被收缩。
android:stretchColumns 设置拉伸的列,其值为要收缩的列的索引,从0开始,多个时用逗号分隔。如上显示中第1行有6个按钮,父容器宽度不够用,此时拉伸任何一列都不会有效果。若第1行只有两个按钮,此时,设置android:stretchColumns="1",则会把第1列拉伸,充满父容器剩下的空间。显示效果如下:
android:collapseColumns 设置要隐藏的列,这里的隐藏于visibility设置为gone效果相同的。隐藏之后不占用父容器的空间。
如:android:collapseColumns="1,3,5",则第一行6个按钮,只剩下3个bbbbbb
6.GridLayout:网格布局
Android4.0中新增的布局管理器。因此,在android4.0之后的版本才可以直接使用。
新建项目设置最小SDK为14,其他也要高于14。
新建一个layout文件,名为grid_layout.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="5" android:rowCount="4"> <Button android:layout_row="0" android:text="aaaaaa" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="1" android:layout_column="0" android:text="bbbbbb" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="2" android:layout_column="2" android:text="cccccc" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:layout_row="3" android:layout_column="1" android:text="dddddd" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </GridLayout>
显示效果如下:
整个布局被分为4行5列。
主要属性:
android:columnCount 设置GridLayout的列数
android:rowCount设置GridLayou的行数
每个添加到GridLayout中的子控件都可以设置如下属性:
android:layout_row 设置该元素所在行,从0开始
android:layout_column 设置该元素所在列,从0开始
android:layout_rowSpan 设置该元素所跨的行数
android:layout_columnSpan 设置该元素所跨的列数。
Activity是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器)之一。它间接继承自android.content.Context,因此,有些时候都直接把Activity实例当做Context的实例来使用。
如前面所提到的要在应用程序中使用Activity,必须在Android Manifest.xml中配置它。
新建一个Android工程,新建过程中勾选create activity,让系统自动帮我们创建一个Activity并在Android Manifest.xml中配置它。
AndroidManifest.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.csc.activity" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="14" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".FirstActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
配置中,Activity节点比较重要的属性有:
android:name属性:指明该Activity节点对应的Activity定义所在的类名,这里默认简写为.FisrtActivity,完整的类名需要与Manifest节点的package属性进行拼接,也可以直接写完整的类名,这里即为cn.csc.activity.FirstActivity。注意,name属性是必须配置的,否则报错,毕竟不配置name属性本身就没有任何意义了。
android:label属性:指明该Activity的标题栏显示的内容。
此外,比较重要的还有一个:
android:launchMode属性:指明该Activity的加载模式。取值可以是standard、singleTop、singleTask和singleInstance。这个属性在提到Activity生命周期时会用到。
回到FirstActivity.java的代码来看:
public class FirstActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); } }
Activity中常用的方法:
void onCreate(Bundle savedInstanceState) :这个方法在该Activity被创建时回调,进行相关的初始化工作。如,我们最常做的setContentView();设置一个UI界面。
void setContentView(int layoutResID)
void setContentView(View view) :这两个方法用于给Activity设置UI界面,只是传入的参数类型不同,一个是传入一个layout资源id,一个是直接在代码中编写UI界面。
View findViewById(int id) :根据控件的id找到该id,返回值是一个View实例,通常需要进行向下转型到具体类型,如Button、TextView等,以便对控件进行操作,如设置控件属性值,进行事件绑定等。
在first_layout.xml中添加一个按钮,需要设置按钮的id属性,在FirstActivity中的onCreate()方法中根据id获取该按钮,然后设置它的单击响应,弹出一个Toast信息。
代码如下:
first_layout.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btnText" /> </RelativeLayout>
FirstActivity.java:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); } }); }
void startActivity(Intent intent) :用于启动一个新的Activity,参数intent中指定了要启动Activity的相关信息。
void finish() :用于结束,并销毁当前Activity。
新建一个Activity,名为SecondActivity;新建一个layout文件,名为second_layout.xml
修改first_layout中按钮的点击事件,使它启动SecondActivity。
代码如下:
second_layout.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/close"/> </LinearLayout>
SecondActivity.java:
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.second_layout); Button btn2 = (Button) findViewById(R.id.btn2); btn2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub finish(); } }); }
FirstActivity.java:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent); } }); }
注意:使用SecondActivity一定要在Manifest.xml中配置
<activity android:name=".SecondActivity" android:label="@string/second">
</activity>
否则会出现如下错误:
点击FirstActivity中的按钮,会启动SecondActivity,点击SecondActivity中的按钮会销毁SecondActivity,然后又回到FirstActivity中。跟点击模拟器上的返回键效果一样。
点击I am a button
点击Close或者返回键:
关于Intent的使用,将在之后详细说明。
void startActivityForResult(Intent intent, int requestCode) :用于启动一个新的Activity,并期望在这个新的Activity结束时返回数据。
void onActivityResult(int requestCode, int resultCode, Intent data) :用于接收处理启动的新的Activity结束时返回的数据。
这两个函数在Intent在Android之间传递数据时会用到。
Intent getIntent() :获取启动该Activity的意图实例,该方法可以实现获取该Activity的启动者所要传递给自己的存放在Intent中的数据。
关于管理Activity的任务栈:
Activity中有一个int getTaskId()方法 :用于获取当前Activity所处的栈的id。
修改FirstActivity中的onCreate():
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); Log.i("TaskId","First:"+getTaskId()); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent); } }); }
及SecondActivity中的onCreate():
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.second_layout); Button btn2 = (Button) findViewById(R.id.btn2); Log.i("TaskId","Second:"+getTaskId()); btn2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub finish(); } }); }
注意:Log.i("TaskId","First:"+getTaskId());用于在LogCat中输出程序运行信息,第一个为Tag参数,第二个要输出的字符串信息。
打开LogCat界面:window ----> show view ----> other ---->即可找到LogCat。
由于显示的运行信息比较多,可以添加一个信息过滤器,只显示我们所关心的信息。
点击绿色的加号
由于我们在之前的Log.i()中设定了Tag参数为TaskId,这时选择by Log Tag,然后填入我们所设置的TaskId即可,随便给过滤器取个名字,然后OK。
启动应用程序,若发现仍有很多运行信息:
此时,单击下TaskId会发现只剩下一条了
此时运行的是FirstActivity,显示的是FirstActivity所在的任务栈的id为15。然后点击程序中的按钮,启动SecondActivity,发现又多出一条信息:
发现SecondActivity所在的任务栈id同样为15。
一个android应用中,不可能只有一个Activity,如上面启动了两个Activity。Android中使用任务栈来管理多个Activity。
当一个Activity被创建启动时,就会把它放入一个任务栈的栈顶。当该Activity结束被销毁时,就会从所在任务栈弹出,其下的Activity变为栈顶,切换到活动状态。当有新的Activity被启动时,它会入栈称为新的栈顶,之前活动的Activity被强制切换到暂停或者停止状态。
任意时刻,只有栈顶的Activity处于活动状态,可以与用户进行交互。栈中其他Activity若是仍然可见或部分可见,即没有被当前活动Activity完全遮盖时,则处于暂停状态。若完全不可见,则处于停止状态。
一般来说,一个android应用中所有的Activity都会被放到同一个任务栈中进行统一管理,但是也有例外,如上面提到的在Manifest.xml中配置launchMode时,配置不同的值就会有所差别。
关于Activity的状态:
任意时刻,一个Activity都处于下面四个状态之一:
运行状态:位于任务栈的栈顶,此时能够与用户进行交互。
暂停状态:不再处于任务栈的栈顶,不能与用户交互,但是仍然有部分可见。
停止状态:不再处于任务栈的栈顶,不能与用户交互,而且完全不可见。
销毁状态:已从任务栈中弹出。
当系统内存不足时,会优先回收处于销毁状态的Activity所占用的资源;仍然不足时,会回收处于停止状态的Activity所占用的资源;仍然不足时,会回收处于暂停状态的Activity的资源;最不愿意回收的是运行状态的Activity资源。
关于Activity的加载模式与任务栈的关联:
前面提到android:launchMode属性:指明该Activity的启动模式。取值可以是standard、singleTop、singleTask和singleInstance。
standard模式:是默认的启动模式,若没有明确指定启动模式,则为standard模式。如上面的FirstActivity和SecondActivity都没有设置launchMode属性,即为standard模式。
在该模式下,每当新启动一个Activity时,都会为之创建一个新的实例,然后放入栈顶,而不在乎任务栈中是否已然存在该Activity的实例。
修改FirstActivity代码:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); Log.i("TaskId","First:"+this+" "+getTaskId()); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(FirstActivity.this, FirstActivity.class); startActivity(intent); } }); }
Log.i("TaskId","First:"+this+" "+getTaskId());此处,加上this,打印出当前实例引用this的值。
按钮的点击响应改为启动FirstActivity自身。
运行信息:
发现每次this的值都不同,可见每次都新建了一个FirstActivity实例,即便当前FirstActivity实例已然位于任务栈中,且位于任务栈的栈顶。
singleTop模式:standard模式很多时候明显不太合理,当前Activity已然有实例位于任务栈的栈顶,直接使用不就得了,干嘛非要再创建一个实例浪费资源呢。singleTop模式就是当要启动的Activity已然有实例位于任务栈的栈顶就直接使用当前栈顶,而不重新创建实例。
修改Manifest.xml:
<activity android:name=".FirstActivity" android:label="@string/first" android:launchMode="singleTop" >
修改FirstActivity代码:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); Log.i("TaskId","Create:"+this+" "+getTaskId()); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(FirstActivity.this, FirstActivity.class); Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId()); startActivity(intent); } }); }
Log.i("TaskId","Create:"+this+" "+getTaskId());表示是onCreate()中调用
在button的onclick中Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId());表明是点击按钮调用。
运行信息:
发现只有一个Create,即onCreate()方法只调用了一次,只创建了一个FirstActivity实例。
若FirstActivity实例在任务栈中,但是不是在栈顶,又会如何呢?
修改代码,FirstActivity中启动SecondActivity,而SecondActivity又启动FirstActivity,两者均配置为singleTop。
修改FirstActivity代码:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button btn = (Button) findViewById(R.id.btn); Log.i("TaskId","Create First:"+this+" "+getTaskId()); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent); } }); }
修改SecondActivity代码:
protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.second_layout); Button btn2 = (Button) findViewById(R.id.btn2); Log.i("TaskId","Create Second:"+this + " "+getTaskId()); btn2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent intent = new Intent(SecondActivity.this, FirstActivity.class); startActivity(intent); } }); }
运行信息:
发现每次都新建了一个实例,即使当前Activity在任务栈中已然存在,但由于并没与处于栈顶,就要再次创建新的实例。
singleTask模式:可能会觉得singleTop模式还是浪费资源,明明栈中已然存在实例,只因为它不在栈顶便要重新创建。若是想重用不在栈顶的Activity实例,则需要使用singleTask模式,该模式下,新启动一个Activity时,检查当前任务栈中是否存在实例,若存在,但是不在栈顶也没关系,系统会把所有在这个实例之上的Activity实例统统出栈,这样这个实例就处于栈顶,然后就可以直接重用了。
修改Manifest.xml将两个Activity的launchMode都改为singleTask,源代码不需要改动,此时观察运行信息:
启动FirstActivity时,输出一条信息,点击FirstActivity中的按钮,由于SecondActivity在栈中不存在,创建了一个实例,然后点击SecondActivity中的按钮,发现FirstActivity实例已然存在,但是栈顶是SecondActiviy,则会将SecondActivity实例出栈,直接重用已存在的FirstActivity实例,所以,此时没有回调onCreate(),故而没有输出信息。
singleInstance模式:用于共享Activity时使用。设置为该模式的Activity不会与当前应用中的其他Activity公用一个任务栈,而是出于自己单独的任务栈中。而几个公用这个Actiivty的应用,共享这个单独的任务栈,就实现了该Activity实例的共享。不然的话,每个应用都有自己的任务栈,启动的Activity肯定在自己的任务栈中管理,根本做不到Activity实例的共享。
修改程序,添加一个ThirdActivity,程序运行的效果FirstActivity为入口,在其中点击按钮可以启动SecondActivity,SecondActivity的launchMode设置为singleInstance,点击SecondActivity中的按钮,可以启动ThirdActivity。ThirdActivity中的按钮可以启动SecondActivity。
FirstActivity和ThirdActivity的launchMode均设置为singleTask。
运行信息:
FirstActivity和ThirdActivity都在同一个栈中,id为28
而SecondActivity在id为29的栈中。
注意,此时,点击ThirdActivity中的按钮启动SecondActivity时,不会输出任何信息,因为直接重用了id为29的任务栈中的SecondActivity实例。若在ThirdActivity时,按下模拟器的返回按钮,销毁ThirdActivity,则会直接回到FirstActivity中,因为它俩是处在同一个栈中的。
关于Activity的生命周期:
下面是Activity整个生命周期中,状态发生变化时所回调的方法,它们对应着Activity完整的生命过程。
void onCreate(Bundle savedInstanceState):Activity被创建时回调
void onStart() :在onCreate()或者onRestart()之后被调用,即Activity第一次创建或者从不可见变为可见状态时调用。
void onResume() :恢复到活动状态时回到,在onStart()之后一定会调用该方法。之后该活动就处于活动状态了,处于任务栈的栈顶。
void onPause() :失去焦点,但是仍然部分可见时回调。
void onStop() :Activity变为完全不可见时回调
void onRestart() :Activity重新启动时回调
void onDestroy() :Activity被销毁前回调
上面的7个方法,除了onRestart()之外,在生命周期的图中都是成对出现的。分为三对,也就出现了三种生存期。
从onCreate()到onDestroy(),一个Activity实例经历了创建到销毁的所有过程,被称之为完整生存期。
从onStart()到onStop(),一个Activity实例从可见状态变为不可见状态,被称之为可见生存期。注意,可见并不一定处于栈顶,因而并一定能与用户交互。
从onResume()到onPause(),一个Activity实例经历了从活动状态到暂停状态,这两个方法之间的过程,该Activity实例都处于活动状态,被称之为前台生存期,或者活动状态生存期。
完整生命周期程序演示,参考《第一行代码》
程序有三个Activity:MainActivity是入口,放置两个按钮,分别用于启动另外两个Activity,实现7个生命周期回调方法,分别输出一条运行信息;NormalActivity就是一个普通的Activity;DialogActivity在Manifest.xml中配置了theme属性,使其成为一个对话框样式的Activity,<activity android:name=".DialogActivity" android:theme="@android:style/Theme.Dialog"></activity>。
具体代码:
main_layout.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/normal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/start_normal_activity"/> <Button android:id="@+id/dialog" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/start_dialog_activity"/> </LinearLayout>
MainActivity.java:
public class MainActivity extends ActionBarActivity implements OnClickListener { @Override public void onClick(View view) { // TODO Auto-generated method stub switch (view.getId()) { case R.id.normal: Intent intent1 = new Intent(this, NormalActivity.class); startActivity(intent1); break; case R.id.dialog: Intent intent2 = new Intent(this, DialogActivity.class); startActivity(intent2); default: break; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); Log.i("LIFECYCLE","onCreate"); Button btnNormal = (Button) findViewById(R.id.normal); Button btnDialog = (Button) findViewById(R.id.dialog); btnNormal.setOnClickListener(this); btnDialog.setOnClickListener(this); } @Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); Log.i("LIFECYCLE","onStop"); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("LIFECYCLE","onDestroy"); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); Log.i("LIFECYCLE","onPause"); } @Override protected void onStart() { // TODO Auto-generated method stub super.onStart(); Log.i("LIFECYCLE","onStart"); } @Override protected void onRestart() { // TODO Auto-generated method stub super.onRestart(); Log.i("LIFECYCLE","onRestart"); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); Log.i("LIFECYCLE","onResume"); } }
运行信息:
首先启动该应用程序,依次输出:
可见,正如生命周期图中所示,依次调用了onCreate()、onStart()、onResume()。
然后点击第一个按钮,启动那个普通的Activity,依次输出:
可见,当NormalActivity启动时,MainActivity调用onPause()进入暂停状态,由于NormalActivity启动后,MainActivity被NormalActivity完全遮住时,又要调用onStop()进入停止状态。
然后,点击模拟器的返回按钮,依次输出:
可见,由于按下返回键后,NormalActivity被销毁,MainActivity由不可见状态变为可见状态,则依次调用onRestart()、onStart(),又由于MainActivity当前处于任务栈栈顶,所以又调用onResume()进入活动状态。
然后,点击第二个按钮,启动DialogActivity,依次输出:
由于MainActivity仍然有部分可见,只是当前不再处于任务栈栈顶而已,所以调用了onPause()进入暂停状态。
然后,按下模拟器上的返回按钮,依次输出:
DialogActivity被销毁,MainActivity重新回到栈顶,调用onResume()进入活动状态。
然后,再按下模拟器上的返回按钮,依次输出:
MainActivity要被销毁,从活动状态到销毁状态,依次调用了onPause()、onStop()和onDestroy()。
以上就是一个完整的Activity生命周期演示。
此外,由于停止状态和暂停状态的Activity有可能被系统回收资源,当一个Activity从暂停或者停止状态重新回到活动状态时,由于可能已经被回收依次,之前的操作、数据等,如填写了好大一张表单,全都要重新开始,用户体验极差。这时,就要用到涉及Activity实例状态保存的回调函数:
onSaveInstanceState(Bundle bundle):用于在被系统回收之前,将需要保存的一些Activity实例状态信息,重要数据等保存到bundle对象中。当该Activity实例下次被创建时,调用onCreate(Bundle bundle)方法时,这个bundle对象会传递给onCreate()方法,则可以在onCreate方法中,获取到上次保存的数据,进行相应的初始化,恢复工作。
在手机app应用中我们经常会看到图片轮播动画效果,Android中想要实现图片轮播,主要用到ViewPager这个控件来实现,这个控件的主要功能是实现图片的滑动效果。
那么有了滑动,在滑动的基础上附上图片也就实现了图片轮播的效果...这个控件类似于ListView,需要使用到适配器这个东西,适配器在这里的作用是为轮播时设置一些效果...这里需要使用到PagerAdapter适配器...下面来一个例子,这个例子的效果是在图片轮播的同时显示播放的是第几张图片的信息...并且下面的点也是会随之进行变化的...
先上一下布局文件的代码...这个布局文件其实还是有点说道的...这句话必须要引进...否则会出现错误...意思就是我设置了一个滑动的效果,这个效果填充整个FrameLayout...每一个View表示一个控件,这个控件的显示方式在另外的xml文件当中...下面是两个xml文件...
上面通过配置xml文件来完成View的显示方式,因为这五个点的形状,大小,甚至是显示方式基本都是相同的,如果再去找5个点图片或者是一个点图片,然后通过Drawable资源的调用完成图片的显示...通过加载5次的方式...这样显然是没有必要的,会浪费不必要的资源..因此我们可以使用xml提供的自定义图形来完成这个过程...xml为我们提供了shape属性,自定义控件..这里我定义了一个实心圆...这个实心圆来完成随着图像的滑动,这个点也随之进行相应的变化...看起来并不是什么难理解的东西...
重要的部分还是如何去实现这个过程...这个过程的实现就再下面的代码中,详细解释也在代码当中...
package com.example.picture_change; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.annotation.SuppressLint; import android.app.Activity; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /* * HashMap存储的键是不允许重复的...但是值是可以重复的... * * */ public class MainActivity extends Activity { ArrayList imageSource=null; //存放图像控件... ArrayList dots=null; //存放5个点... int []images=null; //存放图像的资源... String []titles=null; //伴随着图像的变动,标题也会随之变动... TextView tv=null; //TextView来来显示title的变化... ViewPager viewpager; //ViewPager来完成滑动效果... MyPagerAdapter adapter; //适配器... Mapmap=new HashMap(); @SuppressLint("UseSparseArrays") MapmapValues=new HashMap(); private int curr=0; private int old=0; int o=0; int mapsize; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show(); /* 下面利用反射来完成Drawable的资源获取... * 在这里我获取了5张图片的资源数据..这5张图片分别为a.jpg b.jpg c.jpg d.jpg e.jpg * 这里使用了一个length<=1来完成数据的获取...其实这个方式并不好,是我自己想出来的... * 暂时没有更好的方法...我这里使用反射的目的在下面会进行介绍... * */ Field [] field=R.drawable.class.getFields(); for(Field f:field){ if(f.getName().length()<=1){ try { o++; String str="image"+"_"+o; map.put(str, f.getInt(R.drawable.class));//使用map以键值对的形式来保存图片的数据资源... } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } mapsize=map.size()-1; /* 这里我再次使用了一个map以键值对的形式只保存上一个map的Value值... * 这么做的目的在下面进行说明... * * */ for(Entry entry:map.entrySet()){ mapValues.put(mapsize, entry.getValue()); mapsize--; } init(); } public void init(){ //数据信息的初始化... images=new int[]{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e}; titles=new String[]{"this is the one picture","this is two picture","this is three picture","this is four picture","this is five picture"}; imageSource=new ArrayList(); //这里初始化imageSource... for(int i=0;i<images.length;i++){ ImageView iamgeview =new ImageView(this); iamgeview.setBackgroundResource(images[i]); imageSource.add(iamgeview); } //这里使用了一个方法...我们没有必要一次一次的findViewById()...使用下面的方法很有效的解决了多次findViewById()函数的引用... dots=new ArrayList(); for(int j=0;j<5;j++){ String dotid="dot"+"_"+j; int resId=getResources().getIdentifier(dotid, "id", "com.example.picture_change"); dots.add(findViewById(resId)); } tv=(TextView) findViewById(R.id.tv); tv.setText(titles[0]); viewpager=(ViewPager) findViewById(R.id.vp); adapter=new MyPagerAdapter(); //这里定义了一个适配器对象... viewpager.setAdapter(adapter); //传递对象,绑定适配器... viewpager.setOnPageChangeListener(new onpagelistener()); //这里设置了一个当图片发生滑动后的一个监听效果... ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor();//这里我们开启一个线程... scheduled.scheduleAtFixedRate(new Runnable() { @Override public void run() { // TODO Auto-generated method stub curr=(curr+1)%images.length; handler.sendEmptyMessage(0);//将信息发送给Handler,让Handler处理数据,完成一些操作... } }, 2, 2, TimeUnit.SECONDS); //实现内部方法,设置播放时间... } private class MyPagerAdapter extends PagerAdapter{ @Override public int getCount() { // TODO Auto-generated method stub return images.length; } @Override public boolean isViewFromObject(View arg0, Object arg1) { // TODO Auto-generated method stub //判断前后两张的显示图片是否相同... return arg0==arg1; } @Override public void destroyItem(ViewGroup container, int position, Object object) { //销毁...释放内存... container.removeView(imageSource.get(position)); } @Override public Object instantiateItem(ViewGroup container, int position) { /* 这个方法表示的是滑动到了第几张图片的定位...通过传递一个ViewGroup来完成数据的传递... * 我们上面使用到了一个Map来保存上一个Map的Value值,这个的真正目的就在这里..目的是为了 * 获取当前显示图片的资源信息..说白了就是要获取(R.drawable.属性),为什么要实现这个目的 * 因为我们要实现,当这个显示的图片被点击的时候,我们应该进行哪些操作... * */ ImageView v=imageSource.get(position);//获取当前图片... //position是从0-4的值...因此可以获取到Map中的值了... v.setClickable(true); //设置图片是可以点击的... final int values=(Integer)mapValues.get(position); //这里我们获取map中保存的Values值... System.out.println(values); //下面就是实现触发图片时的监听... v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub switch(values){ case R.drawable.a: Toast.makeText(MainActivity.this, "a", Toast.LENGTH_LONG).show(); break; case R.drawable.b: Toast.makeText(MainActivity.this, "b", Toast.LENGTH_LONG).show(); break; case R.drawable.c: Toast.makeText(MainActivity.this, "c", Toast.LENGTH_LONG).show(); break; case R.drawable.d: Toast.makeText(MainActivity.this, "d", Toast.LENGTH_LONG).show(); break; case R.drawable.e: Toast.makeText(MainActivity.this, "e", Toast.LENGTH_LONG).show(); break; } } }); container.addView(imageSource.get(position)); //将所有的图片都加载到了container中... return imageSource.get(position); } } //定义一个内部类实现图片在变化的时候的监听... class onpagelistener implements OnPageChangeListener{ @Override public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } @Override public void onPageSelected(int arg0) { // TODO Auto-generated method stub //当发生滑动后,要完成的一些相应操作... tv.setText(titles[arg0]); dots.get(arg0).setBackgroundResource(R.drawable.dot); dots.get(old).setBackgroundResource(R.drawable.dot_1); old=arg0; curr=arg0; } } @SuppressLint("HandlerLeak") private Handler handler=new Handler(){ public void handleMessage(Message msg) { //接收到消息后,更新页面 viewpager.setCurrentItem(curr); }; }; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
这里我使用了反射机制来获取Drawable的图像资源,然后通过switch方法来完成了当图片被点击的时候需要完成的操作...这是笔者我自己想出来的一种方法...因为Android没有提供ImageClickListener()这类的方法,因此我们只能够自己去进行书写图片被点击的方法...至于更好的方法,我是还没有发现...也是确实是能力有限制了,这个方法今天也想了整整一个下午才折腾出来的...
注意:给自己的一个提醒,HashMap的键是绝对不能够重复保存的...但是值是可以保存重复的数据的,如果保存了重复的键,那么在map只会保存第一个数据,不会对后续数据进行保存...这个也是一个很大的注意点,自己就栽这里很久,虽然很低级的错误,但是很有可能在不注意的情况下就犯下了...因此在这里也算是给自己提个醒...下次不会再犯下这样的错误的
Android图片轮播效果的几种实现方法
第一种:使用动画的方法实现:(代码繁琐)
这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧:
public class IamgeTrActivity extends Activity { /** Called when the activity is first created. */ public ImageView imageView; public ImageView imageView2; public Animation animation1; public Animation animation2; public TextView text; public boolean juage = true; public int images[] = new int[] { R.drawable.icon, R.drawable.expriment, R.drawable.changer, R.drawable.dataline, R.drawable.preffitication }; public int count = 0; public Handler handler = new Handler(); public Runnable runnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub AnimationSet animationSet1 = new AnimationSet(true); AnimationSet animationSet2 = new AnimationSet(true); imageView2.setVisibility(0); TranslateAnimation ta = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, -1f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f); ta.setDuration(2000); animationSet1.addAnimation(ta); animationSet1.setFillAfter(true); ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f); ta.setDuration(2000); animationSet2.addAnimation(ta); animationSet2.setFillAfter(true); //iamgeView 出去 imageView2 进来 imageView.startAnimation(animationSet1); imageView2.startAnimation(animationSet2); imageView.setBackgroundResource(images[count % 5]); count++; imageView2.setBackgroundResource(images[count % 5]); text.setText(String.valueOf(count)); if (juage) handler.postDelayed(runnable, 6000); Log.i(handler, handler); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); imageView = (ImageView) findViewById(R.id.imageView); imageView2 = (ImageView) findViewById(R.id.imageView2); text=(TextView)findViewById(R.id.text); text.setText(String.valueOf(count)); //将iamgeView先隐藏,然后显示 imageView2.setVisibility(4); handler.postDelayed(runnable, 2000); } public void onPause() { juage = false; super.onPause(); } }
布局代码:
android:orientation=vertical android:layout_width=fill_parent android:layout_height=fill_parent android:id=@+id/rl> android:id=@+id/imageView android:layout_width=fill_parent android:background=@drawable/icon android:layout_below=@+id/rl android:layout_height=120dp /> android:id=@+id/imageView2 android:layout_width=fill_parent android:background=@drawable/expriment android:layout_below=@+id/rl android:layout_height=120dp /> android:id=@+id/text android:layout_width=fill_parent android:layout_height=wrap_content android:layout_below=@id/imageView/>
第二种:使用ViewFlipper实现图片的轮播
Android系统自带的一个多页面管理控件,它可以实现子界面的自动切换:
首先 需要为ViewFlipper加入View
(1) 静态导入:在layout布局文件中直接导入
(2) 动态导入:addView()方法
ViewPlipper常用方法:
setInAnimation:设置View进入屏幕时候使用的动画
setOutAnimation:设置View退出屏幕时候使用的动画
showNext:调用该函数来显示ViewFlipper里面的下一个View
showPrevious:调用该函数来显示ViewFlipper里面的上一个View
setFlipInterval:设置View之间切换的时间间隔
startFlipping使用上面设置的时间间隔来开始切换所有的View,切换会循环进行
stopFlipping:停止View切换
讲了这么多,那么我们今天要实现的是什么呢?
(1) 利用ViewFlipper实现图片的轮播
(2) 支持手势滑动的ViewFlipper
我们需要先准备几张图片:把图片放进drawable中
创建两个动画:在res下面新建一个folder里面新建两个xml:
left_in:
android:duration=5000 android:fromXDelta=100%p android:toXDelta=0/>
left_out:
android:fromXDelta=0 android:toXDelta=-100%p android:duration=5000/>
一个布局文件:
xmlns:tools=http://schemas.android.com/tools android:layout_width=match_parent android:layout_height=match_parent tools:context=.MainActivity > android:id=@+id/flipper android:layout_width=fill_parent android:layout_height=fill_parent/>
一个主类:
public class MainActivity extends Activity { private ViewFlipper flipper; private int[] resId = {R.drawable.pc1,R.drawable.pc2,R.drawable.pc3,R.drawable.pc4}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); flipper = (ViewFlipper) findViewById(R.id.flipper); /* * 动态导入的方式为ViewFlipper加入子View * */ for (int i = 0; i < resId.length; i++) { flipper.addView(getImageView(resId[i])); } /* * 为ViewFlipper去添加动画效果 * */ flipper.setInAnimation(this, R.anim.left_in); flipper.setOutAnimation(this, R.anim.left_out); flipper.setFlipInterval(5000); flipper.startFlipping(); } private ImageView getImageView(int resId){ ImageView image = new ImageView(this); image.setBackgroundResource(resId); return image; } }
那么这样就实现了一个图片轮询的功能效果了
我们还可以添加点击,滑动效果:
我们还需要添加两个向右的滑动效果:
right_in:
android:fromXDelta=0 android:toXDelta=-100%p android:duration=2000/>
right_out:
android:fromXDelta=100%p android:toXDelta=0 android:duration=2000/>
然后我们还需要在主类里面添加(如果你不想让图片自动播放,只想通过手势来实现图片播放那么你需要把“为ViewFlipper添加动画效果的代码”删掉):
public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = event.getX(); break; case MotionEvent.ACTION_MOVE://判断向左滑动还是向右滑动 if (event.getX() - startX > 100) { flipper.setInAnimation(this, R.anim.left_in); flipper.setOutAnimation(this, R.anim.left_out); flipper.showPrevious(); }else if (startX - event.getX() > 100) { flipper.setInAnimation(this, R.anim.right_in); flipper.setOutAnimation(this, R.anim.right_out); flipper.showNext(); } case MotionEvent.ACTION_UP: break; } return super.onTouchEvent(event); }
这样我们利用我们的ViewFlipper完成的图片轮询的功能就做完了。
Android中View绘制流程以及invalidate(),requsetLaytout()以及requestFocus()方法分析
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘(draw),其框架过程如下:
步骤其实为host.layout()
接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。
流程一: mesarue()过程
主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调
View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性: mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;
2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程
measure函数原型为 View.java 该函数不能被重载
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //.... //回调onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); //more }
为了大家更好的理解,采用“二B程序员”的方式利用伪代码描述该measure流程
//回调View视图里的onMeasure过程 private void onMeasure(int height , int width){ //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight) //1、该方法必须在onMeasure调用,否者报异常。 setMeasuredDimension(h , l) ; //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个measure()过程就是个递归过程 //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都 measureChildWithMargins(child , h, i) ; //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下: //child.measure(h, l) } } //该方法具体实现在ViewGroup.java里 。 protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }
流程二、 layout布局过程:
主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
layout函数原型为 ,位于View.java
/* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴 * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴 if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局 mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; }
同样地, 将上面layout调用流程,用伪代码描述如下:
// layout()过程 ViewRoot.java // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout() private void performTraversals(){ //... View mView ; mView.layout(left,top,right,bottom) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void onLayout(int left , int top , right , bottom){ //如果该View不是ViewGroup类型 //调用setFrame()方法设置该控件的在父视图上的坐标轴 setFrame(l ,t , r ,b) ; //-------------------------- //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个layout()过程就是个递归过程 child.layout(l, t, r, b) ; } }
流程三、 draw()绘图过程
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
5、绘制滚动条
于是,整个调用链就这样递归下去了。
同样地,使用伪代码描述如下:
// draw()过程 ViewRoot.java // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图 private void draw(){ //... View mView ; mView.draw(canvas) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void draw(Canvas canvas){ //该方法会做如下事情 //1 、绘制该View的背景 //2、为绘制渐变框做一些准备操作 //3、调用onDraw()方法绘制视图本身 //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。 // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。 //5、绘制渐变框 } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 @Override protected void dispatchDraw(Canvas canvas) { // //其实现方法类似如下: int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ View child = getChildAt(i) ; //调用drawChild完成 drawChild(child,canvas) ; } } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 protected void drawChild(View child,Canvas canvas) { // .... //简单的回调View对象的draw()方法,递归就这么产生了。 child.draw(canvas) ; //......... }
强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可 。
这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。
invalidate()方法 :
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括该调用者本身。
一般引起invalidate()操作的函数如下:
1、setVisibility()方法:
当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
requestFocus()函数说明:
说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。
下面写个简单的小Demo吧,主要目的是给大家演示绘图的过程以及每个流程里该做的一些功能。截图如下:
1、 MyViewGroup.java 自定义ViewGroup类型
/** * @author http://http://blog.csdn.net/qinjuning */ //自定义ViewGroup 对象 public class MyViewGroup extends ViewGroup{ private static String TAG = "MyViewGroup" ; private Context mContext ; public MyViewGroup(Context context) { super(context); mContext = context ; init() ; } //xml定义的属性,需要该构造函数 public MyViewGroup(Context context , AttributeSet attrs){ super(context,attrs) ; mContext = context ; init() ; } //为MyViewGroup添加三个子View private void init(){ //调用ViewGroup父类addView()方法添加子View //child 对象一 : Button Button btn= new Button(mContext) ; btn.setText("I am Button") ; this.addView(btn) ; //child 对象二 : ImageView ImageView img = new ImageView(mContext) ; img.setBackgroundResource(R.drawable.icon) ; this.addView(img) ; //child 对象三 : TextView TextView txt = new TextView(mContext) ; txt.setText("Only Text") ; this.addView(txt) ; //child 对象四 : 自定义View MyView myView = new MyView(mContext) ; this.addView(myView) ; } @Override //对每个子View进行measure():设置每子View的大小,即实际宽和高 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView int childCount = getChildCount() ; Log.i(TAG, "the size of this ViewGroup is ----> " + childCount) ; Log.i(TAG, "**** onMeasure start *****") ; //获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec) ; int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec) ; Log.i(TAG, "**** specSize_Widht " + specSize_Widht+ " * specSize_Heigth *****" + specSize_Heigth) ; //设置本ViewGroup的宽高 setMeasuredDimension(specSize_Widht , specSize_Heigth) ; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 child.measure(50, 50) ; //简单的设置每个子View对象的宽高为 50px , 50px //或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法 //this.measureChild(child, widthMeasureSpec, heightMeasureSpec) ; } } @Override //对每个子View视图进行布局 protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub //通过init()方法,我们为该ViewGroup对象添加了三个视图 , Button、 ImageView、TextView int childCount = getChildCount() ; int startLeft = 0 ;//设置每个子View的起始横坐标 int startTop = 10 ; //每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:margin=10px ; Log.i(TAG, "**** onLayout start ****") ; for(int i=0 ;i<childCount ; i++){ View child = getChildAt(i) ; //获得每个对象的引用 child.layout(startLeft, startTop, startLeft+child.getMeasuredWidth(), startTop+child.getMeasuredHeight()) ; startLeft =startLeft+child.getMeasuredWidth() + 10; //校准startLeft值,View之间的间距设为10px ; Log.i(TAG, "**** onLayout startLeft ****" +startLeft) ; } } //绘图过程Android已经为我们封装好了 ,这儿只为了观察方法调用程 protected void dispatchDraw(Canvas canvas){ Log.i(TAG, "**** dispatchDraw start ****") ; super.dispatchDraw(canvas) ; } protected boolean drawChild(Canvas canvas , View child, long drawingTime){ Log.i(TAG, "**** drawChild start ****") ; return super.drawChild(canvas, child, drawingTime) ; } }
2、MyView.java 自定义View类型,重写onDraw()方法 ,
//自定义View对象 public class MyView extends View{ private Paint paint = new Paint() ; public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } public MyView(Context context , AttributeSet attrs){ super(context,attrs); } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ //设置该View大小为 80 80 setMeasuredDimension(50 , 50) ; } //存在canvas对象,即存在默认的显示区域 @Override public void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Log.i("MyViewGroup", "MyView is onDraw ") ; //加粗 paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); paint.setColor(Color.RED); canvas.drawColor(Color.BLUE) ; canvas.drawRect(0, 0, 30, 30, paint); canvas.drawText("MyView", 10, 40, paint); } }
主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。
总的来说: 整个绘制过程还是十分十分复杂地,每个具体方法的实现都是我辈难以立即的,感到悲剧啊。对Android提供的一些ViewGroup对象,比如LinearLayout、RelativeLayout布局对象的实现也很有压力。 本文重在介绍整个View树的绘制流程,希望大家在此基础上,多接触源代码进行更深入地扩展。
Android中将布局文件/View添加至窗口过程分析
本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的。
下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口),一个Dialog,一个Toast,一个Menu菜单等。
首先对相关类的作用进行一下简单介绍:
Window 类 位于 /frameworks/base/core/java/android/view/Window.java
说明:该类是一个抽象类,提供了绘制窗口的一组通用API。可以将之理解为一个载体,各种View在这个载体上显示。
源文件(部分)如下:
public abstract class Window { //... //指定Activity窗口的风格类型 public static final int FEATURE_NO_TITLE = 1; public static final int FEATURE_INDETERMINATE_PROGRESS = 5; //设置布局文件 public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); //请求指定Activity窗口的风格类型 public boolean requestFeature(int featureId) { final int flag = 1<<featureId; mFeatures |= flag; mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag; return (mFeatures&flag) != 0; } //... }
PhoneWindow类 位于/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java
说明: 该类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
源文件(部分)如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback { //... // This is the top-level view of the window, containing the window decor. private DecorView mDecor; //该对象是所有应用窗口的根视图 , 是FrameLayout的子类 //该对象是Activity布局文件的父视图,一般来说是一个FrameLayout型的ViewGroup // 同时也是DecorView对象的一个子视图 // This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. private ViewGroup mContentParent; //设置标题 @Override public void setTitle(CharSequence title) { if (mTitleView != null) { mTitleView.setText(title); } mTitle = title; } //设置背景图片 @Override public final void setBackgroundDrawable(Drawable drawable) { if (drawable != mBackgroundDrawable || mBackgroundResource != 0) { mBackgroundResource = 0; mBackgroundDrawable = drawable; if (mDecor != null) { mDecor.setWindowBackground(drawable); } } } //... }
DecorView类 该类是PhoneWindow类的内部类
说明: 该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。
如下所示 :
源文件(部分)如下:
private final class DecorView extends FrameLayout { //... //触摸事件处理 @Override public boolean onTouchEvent(MotionEvent event) { return onInterceptTouchEvent(event); } //... }
打个不恰当比喻吧,Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画 (具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。
DecorView呈现在PhoneWindow上。
当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包括Activity对象和Window对象信息)后,就会回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面,整个调用链由此铺垫开来。setContentView()的三个构造方法调用流程本质上是一样的,我们就分析setContentView(intresId)方法。
Step 1 、Activity.setContentView(intresId) 该方法在Activity类中
该方法只是简单的回调Window对象,具体为PhoneWindow对象的setContentView()方法实现 。
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); } public Window getWindow() { return mWindow; //Window对象,本质上是一个PhoneWindow对象 }
Step 2 、PhoneWindow.setContentView() 该方法在PhoneWindow类中
@Override public void setContentView(int layoutResID) { //是否是第一次调用setContentView方法, 如果是第一次调用,则mDecor和mContentParent对象都为空 if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } }
方法根据首先判断是否已经由setContentView()了获取mContentParent即View对象, 即是否是第一次调用该PhoneWindow对象setContentView()方法。如果是第一次调用,则调用installDecor()方法,否则,移除该mContentParent内所有的所有子View。最后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。
PS:因此,在应用程序里,我们可以多次调用setContentView()来显示我们的界面。
Step 3、 PhoneWindow. installDecor() 该方法在PhoneWindow类中
private void installDecor() { if (mDecor == null) { //mDecor为空,则创建一个Decor对象 mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); } if (mContentParent == null) { //generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件 //并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent mContentParent = generateLayout(mDecor); //... }
首先、该方法首先判断mDecor对象是否为空,如果不为空,则调用generateDecor()创建一个DecorView(该类是FrameLayout子类,即一个ViewGroup视图) ;
generateDecor()方法原型为:
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }
其次、继续判断mContentParent对象是否为空,如果不为空,则调用generateLayout()方法去创建mContentParent对象。
generateLayout()方法如下:
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. //...1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值 //2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件 int layoutResource; //窗口修饰布局文件 int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { layoutResource = com.android.internal.R.layout.dialog_title_icons; } else { layoutResource = com.android.internal.R.layout.screen_title_icons; } // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = com.android.internal.R.layout.screen_progress; // System.out.println("Progress!"); } //... //3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgressBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } //... return contentParent; }
该方法会做如下事情:
1、根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图)。这些窗口修饰布局文件指定一个用来存放 Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content"。
例如窗口修饰类型包括FullScreen(全屏)、NoTitleBar(不含标题栏)等。选定窗口修饰类型有两种:
①、指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;
②、为我们的Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法
获取值。
举例如下,隐藏标题栏有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);
或者 为Activity配置xml属性:android:theme=”@android:style/Theme.NoTitleBar”。
PS:因此,在Activity中必须在setContentView之前调用requestFeature()方法。
确定好窗口风格之后,选定该风格对应的布局文件,这些布局文件位于 frameworks/base/core/res/layout/ ,典型的窗口布局文件有:
R.layout.dialog_titile_icons R.layout.screen_title_icons
R.layout.screen_progress R.layout.dialog_custom_title
R.layout.dialog_title
R.layout.screen_title // 最常用的Activity窗口修饰布局文件
R.layout.screen_simple //全屏的Activity窗口布局文件
分析Activity最常用的一种窗口布局文件,R.layout.screen_title :
<!-- This is an optimized layout for a screen, with the minimum set of features enabled. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <FrameLayout android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"> <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
该布局文件很简单,一个LinearLayout下包含了两个子FrameLayout视图,第一个FrameLayout用来显示标题栏(TitleBar),该TextView 视图id为title(android:id="@android:id/title");第二个FrameLayout用来显示我们Activity的布局文件的父视图,该FrameLayoutid为content(android:id="@android:id/content") 。
全屏的窗口布局文件 R.layout.screen_simple:
<--This is an optimized layout for a screen, with the minimum set of features enabled. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/content" android:fitsSystemWindows="true" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" />
该布局文件只有一个FrameLayout,用来显示我们Activity的布局文件,该FrameLayoutid为
android:id="@android:id/content"
2、前面一步我们确定窗口修饰布局文件后,mDecor做为根视图将该窗口布局对应的视图添加进去,并且获取id为content的View,将其赋值给mContentParent对象,即我们前面中提到的第二个FrameLayout。
At Last、产生了mDecor和mContentParent对象后,就将我们的Activity布局文件直接添加至mContentParent父视图中即可。
我们再次回到 Step 2 中PhoneWindow.setContentView() 该方法在PhoneWindow类中
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } }
整个过程主要是如何把Activity的布局文件添加至窗口里,上面的过程可以概括为:
1、创建一个DecorView对象,该对象将作为整个应用窗口的根视图
2、创建不同的窗口修饰布局文件,并且获取Activity的布局文件该存放的地方,由该窗口修饰布局文件内id为content的FrameLayout指定 。
3、将Activity的布局文件添加至id为content的FrameLayout内。
最后,当AMS(ActivityManagerService)准备resume一个Activity时,会回调该Activity的handleResumeActivity()方法,
该方法会调用Activity的makeVisible方法 ,显示我们刚才创建的mDecor 视图族。
//系统resume一个Activity时,调用此方法 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { ActivityRecord r = performResumeActivity(token, clearHide); //... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } }
handleResumeActivity()方法原型如下: 位于ActivityThread类中
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); // 获取WindowManager对象 wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); //使其处于显示状况 }
接下来就是,如何把我们已经创建好的窗口通知给WindowManagerService ,以便它能够把这个窗口显示在屏幕上。
<pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <pre></pre> <div style="padding-top:20px"> <p style="font-size:12px;">版权声明:本文为博主原创文章,未经博主允许不得转载。</p> </div>
相关文章
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- 这篇文章主要介绍了C#窗体布局方式详解的相关资料,需要的朋友可以参考下...2020-06-25
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
- 下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
- 首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
- 下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
用Intel HAXM给Android模拟器Emulator加速
Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20- 在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
Android开发中布局中的onClick简单完成多控件时的监听的利与弊
本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20- 这篇文章主要介绍了从零开始在vue-cli4配置自适应vw布局,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-09