源地址http://blog.csdn.net/nailsoul/article/details/25909313
总结
1. 界面的原点(0, 0)是除去status bar和title bar之后剩下的区域。如果使用了全屏,不显示状态栏,不显示标题栏这样的主题后,区域的原点位置会相应改变。
2. FrameLayout的widget中使用类似android:layout_marginLeft="65px"这样的属性,一定要加上android:layout_gravity,否则margin无效。还要注意FrameLayout的android:layout_width和android:layout_height对layout_gravity的影响。
3. 使用布局属性一定要分清谁是parent,parent用的是什么layout,layout_width和layout_height的值。
4. 不同的布局属性也可以实现相同的功能。例如layout_gravity="center"和android:layout_centerInParent ="true"。
5. Eclipse的Android开发工具插件ADT里面有一个所见即所得的开发UI的功能。利用Graphical Layout可以预览的效果。但是,有时会遇到以下问题:
测量控件尺寸(宽度、高度)是开发自定义控件的第一步,只有确定尺寸后才能开始画(利用canvas在画布上画,我们所使用的控件实际上都是这样画上去的)。当然,这个尺寸是需要根据控件的各个部分计算出来的,比如:padding、文字大小,间距等。
非容器控件的onMeasure
下面我们就来看看如何给非容器控件(即直接extends View)这只尺寸的:
1.
@Override
2.
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
3.
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
4.
}
通过重写onMeasure()方法来设置尺寸。这个方法实际是由容器控件(LinearLayout、RelativeLayout)来调用的,以便容器控件知道我需要分配给你多大空间或尺寸。
计算完尺寸后,最终调用setMeasuredDimension()来设置。比如,我要创建个 宽度是200px,高度是100px的控件,就可以setMeasuredDimension(200, 100)。
注意setMeasuredDimension()接收的值是px值,而不是dp值,所以我们不可能像上面那样将尺寸写固定的。如何做那:
参数 int widthMeasureSpec, int heightMeasureSpec 是指明控件可获得的空间以及关于这个空间描述的元数据,是与布局文件中Android:layout_width、android:layout_height相联系的。如何使用:
01.
/**
02.
* 计算组件宽度
03.
*/
04.
private
int
measureWidth(
int
widthMeasureSpec) {
05.
int
result;
06.
int
specMode = MeasureSpec.getMode(widthMeasureSpec);
07.
int
specSize = MeasureSpec.getSize(widthMeasureSpec);
08.
09.
if
(specMode == MeasureSpec.EXACTLY) {
//精确模式
10.
result = specSize;
11.
}
else
{
12.
result = getDefaultWidth();//最大尺寸模式,getDefaultWidth方法需要我们根据控件实际需要自己实现
13.
if
(specMode == MeasureSpec.AT_MOST) {
14.
result = Math.min(result, specSize);
15.
}
16.
}
17.
return
result;
18.
}
调用MeasureSpec.getMode(measureSpec),可以获得设置尺寸的mode(模式)。
mode共有三种情况,取值分别为:
MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST。
MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。
measureWidth() 方法是个固定实现,几乎不用改, 唯一需要我们实现的就是getDefaultWidth()。即,当mode处于最大模式下,此时父容器会将他所能给你的或者说目前剩余的最大空间给你。你只能在这个空间内设定控件尺寸。一个简单的类似TextView的getDefaultWidth()的实现:
1.
private
void
getDefaultWidth(){
2.
int
txtWidth = (
int
)
this
.paint.measureText(
this
.text);
3.
return
txtWidth +
this
.paddingLeft +
this
.paddingRight;
4.
}
上面说了宽度的测量,高度同理:
01.
/**
02.
* 计算组件高度
03.
*/
04.
private
int
measureHeight(
int
measureSpec) {
05.
int
result;
06.
int
specMode = MeasureSpec.getMode(measureSpec);
07.
int
specSize = MeasureSpec.getSize(measureSpec);
08.
09.
if
(specMode == MeasureSpec.EXACTLY) {
10.
result = specSize;
11.
}
else
{
12.
result = getDefaultHeight();
13.
if
(specMode == MeasureSpec.AT_MOST) {
14.
result = Math.min(result, specSize);
15.
}
16.
}
17.
return
result;
18.
}
有了上面的实现,我们就可以在布局文件中使用控件时,通过android:layout_width、android:layout_height来自定义控件的宽度、高度。
容器控件的onMeasure
容器控件一般是继承ViewGroup,ViewGroup是个抽象类,本身没有实现onMeasure,但是他的子类都有各自的实现,通常他们都是通过measureChildWithMargins函数或者其他类似于measureChild的函数来遍历测量子View,被GONE的子View将不参与测量,当所有的子View都测量完毕后,才根据父View传递过来的模式和大小来最终决定自身的大小.
在测量子View时,会先获取子View的LayoutParams,从中取出宽高,如果是大于0,将会以精确的模式加上其值组合成MeasureSpec传递子View,如果是小于0,将会把自身的大小或者剩余的大小传递给子View,其模式判定在前面表中有对应关系.
ViewGroup一般都在测量完所有子View后才会调用setMeasuredDimension()设置自身大小。
在activity中可以调用View.getWidth、View.getHeight()、View.getMeasuredWidth()、View.getgetMeasuredHeight()来获得某个view的宽度或高度,但是在onCreate()、onStrart()、onResume()方法中会返回0,这是应为当前activity所代表的界面还没显示出来没有添加到WindowPhone的DecorView上或要获取的view没有被添加到DecorView上或者该View的visibility属性为gone 或者该view的width或height真的为0 所以只有上述条件都不成立时才能得到非0的width和height
所以要想获取到的width和height为真实有效的 则有以下方法
1.在该View的事件回调里使用 这时候该view已经被显示即被添加到DecorView上 如点击事件 触摸事件 焦点事件等
2.在activity被显示出来时即添加到了DecorView上时获取宽和高如onWindowFocusChanged() 回调方法
3.或在onResume方法最后开线程300毫秒左右后获取宽和高 因为onResume执行完后300毫秒后 界面就显示出来了
当然地2种和地3种方法要保证获取宽高的view是在setContentView时设进去的View或它的子View
4.在onCreate()或onResume()等方法中需要获取宽高时使用getViewTreeObserver().addOnGlobalLayoutListener()来添为view加回调在回调里获得宽度或者高度获取完后让view删除该回调
在Android中,有时需要对控件进行测量,得到的控件宽度和高度可以用来做一些计算。在需要自适应屏幕的情况下,这种计算就显得特别重要。另一方便,由于需求的原因,希望一进入界面后,就能得到控件的宽度和高度。
可惜的是,根据我的验证,利用网上转载的那些方法在OnCreate函数中获取到的仍然是0(希望搞技术的能自己验证过再转载),例如Measure方法之后调用getMeasuredWidth的值还是0。
原因是因为当OnCreate函数发生时,只是提供了数据初始化的机会,此时还没有正式绘制图形。而绘制图形在OnDraw中进行,此时计算又显得太晚。容易想到的办法是:希望能在程序刚刚测量好某个指定控件后,拿到它的宽度和高度立刻进行计算或数据初始化。这就需要有一个方法来监听到这个事件的发生,幸好Android提供了这样的机制,利用View类中的getViewTreeObserver方法,可以获取到指定View的观察者,在绘制控件前的一刹那进行回调,这样速度上又不耽误,得到的数据由是准确的,但此方法在之后可能会被反复调用,因此需要加入限制,普通需求下,只计算一次就够了,代码如下(此代码在OnCreate回调函数中验证通过,实时上,因为它是监听器,所以发生事件时已经和OnCreate无关了):
layout = (MetroLayout)findViewById(R.id.layout);
ViewTreeObserver vto =layout.getViewTreeObserver();
vto.addOnPreDrawListener(newViewTreeObserver.OnPreDrawListener()
{
public boolean onPreDraw()
{
if (hasMeasured == false)
{
int height = metroLayout.getMeasuredHeight();
int width = metroLayout.getMeasuredWidth();
//获取到宽度和高度后,可用于计算
hasMeasured = true;
}
return true;
}
});