您的位置:首页 > 移动开发 > Android开发

Android代码中如何获取控件宽高

2016-05-22 14:13 363 查看
源地址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上 如点击事件  触摸事件  焦点事件等
 

[java] view plaincopy

View view=findViewById(R.id.tv);  
    view.setOnClickListener(new OnClickListener() {  
          
        @Override  
        public void onClick(View v) {  
            int width = v.getWidth();  
        }  
    });  

 
2.在activity被显示出来时即添加到了DecorView上时获取宽和高如onWindowFocusChanged() 回调方法
 

[java] view plaincopy

@Override  
  public void onWindowFocusChanged(boolean hasFocus) {  
    View iv1 = findViewById(R.id.iv1);  
View iv2=findViewById(R.id.iv2);  
String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
i("onWindowFocusChanged() "+msg1);  
i("onWindowFocusChanged() "+msg2);  
    super.onWindowFocusChanged(hasFocus);  
  }  

 
3.或在onResume方法最后开线程300毫秒左右后获取宽和高  因为onResume执行完后300毫秒后 界面就显示出来了  
当然地2种和地3种方法要保证获取宽高的view是在setContentView时设进去的View或它的子View
 

[java] view plaincopy

view.postDelayed(new Runnable() {  
              
            @Override  
            public void run() {  
                View iv1 = findViewById(R.id.iv1);  
                View iv2=findViewById(R.id.iv2);  
                String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
                String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
                i("onWindowFocusChanged() "+msg1);  
                i("onWindowFocusChanged() "+msg2);  
            }  
        }, 300);  

 
4.在onCreate()或onResume()等方法中需要获取宽高时使用getViewTreeObserver().addOnGlobalLayoutListener()来添为view加回调在回调里获得宽度或者高度获取完后让view删除该回调
 

[java] view plaincopy

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  
          
        @Override  
        public void onGlobalLayout() {  
            view.postDelayed(new Runnable() {  
                  
                @Override  
                public void run() {  
                    View iv1 = findViewById(R.id.iv1);  
                    View iv2=findViewById(R.id.iv2);  
                    String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
                    String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
                    i("onWindowFocusChanged() "+msg1);  
                    i("onWindowFocusChanged() "+msg2);  
                }  
            }, 300);  
        }  
    }); 

在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;

}

});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: