关于java:View的getWidth()和getHeight()返回0

View's getWidth() and getHeight() returns 0

我正在动态创建我的Android项目中的所有元素。我想知道一个按钮的宽度和高度,这样我就可以旋转这个按钮了。我只是想学习如何使用Android语言。但是,它返回0。

我做了一些研究,发现需要在除onCreate()方法以外的其他地方进行。如果有人能给我一个如何做的例子,那就太好了。

这是我的当前代码:

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
package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

感谢您的帮助。


基本问题是,您必须等待实际测量的绘制阶段(尤其是动态值,如wrap_contentmatch_parent),但通常这个阶段还没有完成到onResume()。所以你需要一个变通方案来等待这个阶段。对此有不同的可能解决方案:

< BR>

1。侦听draw/layout事件:viewtreeobserver

对于不同的绘图事件,将激发一个viewtreeobserver。通常,OnGlobalLayoutListener是您要获取度量值的工具,因此监听器中的代码将在布局阶段之后调用,因此度量值准备就绪:

1
2
3
4
5
6
7
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

注意:监听器将立即被删除,否则它将在每个布局事件上触发。如果必须支持apps sdk lvl<16,请使用此项注销侦听器:

埃多克斯1〔5〕

< BR>

2。将可运行文件添加到布局队列:view.post()。

不是很有名,我最喜欢的解决方案。基本上,只需将视图的post方法与您自己的runnable一起使用。这基本上是在视图的度量、布局等之后对代码进行排队,如RomainGuy所述:

The UI event queue will process events in order. After
setContentView() is invoked, the event queue will contain a message
asking for a relayout, so anything you post to the queue will happen
after the layout pass

例子:

1
2
3
4
5
6
7
8
final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

ViewTreeObserver相比的优势是:

  • 您的代码只执行一次,并且您不必在执行后禁用观察者,这可能会很麻烦。
  • 不那么冗长的语法

参考文献:

  • https://stackoverflow.com/a/3602144/774398
  • https://stackoverflow.com/a/3948036/774398

< BR>

三。覆盖视图的OnLayout方法

只有在某些情况下,逻辑可以封装到视图本身中,这才是可行的,否则这是一个非常冗长和繁琐的语法。

1
2
3
4
5
6
7
view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

另外请注意,OnLayout将被多次调用,因此请考虑在方法中所做的操作,或者在第一次调用之后禁用代码。

< BR>

4。检查是否已通过布局阶段

如果在创建UI时有多次执行的代码,则可以使用以下支持v4 lib方法:

1
2
3
4
5
View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Returns true if view has been through at least one layout since it was
last attached to or detached from a window.

< BR>

附加:获取静态定义的测量值

如果只需获得静态定义的高度/宽度就足够了,那么只需使用以下方法即可:

  • 江户十一〔七〕号
  • 埃多克斯1〔8〕

但请注意,这可能与绘图后的实际宽度/高度不同。JavaDoc完美地描述了这一区别:

The size of a view is expressed with a width and a height. A view
actually possess two pairs of width and height values.

The first pair is known as measured width and measured height. These
dimensions define how big a view wants to be within its parent (see
Layout for more details.) The measured dimensions can be obtained by
calling getMeasuredWidth() and getMeasuredHeight().

The second pair is simply known as width and height, or sometimes
drawing width and drawing height. These dimensions define the actual
size of the view on screen, at drawing time and after layout. These
values may, but do not have to, be different from the measured width
and height. The width and height can be obtained by calling getWidth()
and getHeight().


我们可以用

1
2
3
4
5
@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }


你打电话给getWidth()太早了。用户界面还没有在屏幕上调整大小和布局。

我怀疑你是否想做你正在做的事情,无论如何——正在制作动画的小部件不会改变它们的可点击区域,因此不管按钮如何旋转,它仍然会对原始方向上的点击做出响应。

也就是说,可以使用维度资源定义按钮大小,然后从布局文件和源代码中引用该维度资源,以避免此问题。


我使用了这个解决方案,我认为它比OnWindowFocusChanged()更好。如果您打开一个对话框片段,然后旋转手机,只有当用户关闭对话框时才会调用onWindowFocusChanged):

1
2
3
4
5
6
7
8
9
10
    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

编辑:由于removeglobalonlayoutlistener已被弃用,现在应该执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}


正如Ian在Android开发者线程中所说:

Anyhow, the deal is that layout of the
contents of a window happens
after all the elements are constructed and added to their parent
views. It has to be this way, because
until you know what components a View
contains, and what they contain, and
so on, there's no sensible way you can
lay it out.

Bottom line, if you call getWidth()
etc. in a constructor, it will return
zero. The procedure is to create all
your view elements in the constructor,
then wait for your View's
onSizeChanged() method to be called --
that's when you first find out your
real size, so that's when you set up
the sizes of your GUI elements.

Be aware too that onSizeChanged() is
sometimes called with parameters of
zero -- check for this case, and
return immediately (so you don't get a
divide by zero when calculating your
layout, etc.). Some time later it
will be called with the real values.


如果需要在某个小部件显示在屏幕上之前获取其宽度,可以使用getMeasuredWidth()或getMeasuredHeight()。

1
2
3
myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();


我宁愿使用OnPreDrawListener()而不是addOnGlobalLayoutListener(),因为它在前面被称为。

1
2
3
4
5
6
7
8
9
10
11
12
    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });


在全局布局上观察并在动态准备高度时执行给定任务的Kotlin扩展。

用途:

1
view.height { Log.i("Info","Here is your height:" + it) }

实施:

1
2
3
4
5
6
7
8
9
10
fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}


如果您使用的是RxJava&RxBindings,则使用一个衬线。类似的方法没有样板文件。这也解决了黑客压制警告的问题,正如蒂姆·奥廷的回答一样。

1
2
3
4
RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

就是这样。即使从未打过电话,也无需取消订阅。


高度和宽度为零,因为在请求视图的高度和宽度时尚未创建该视图。最简单的解决方案是

1
2
3
4
5
6
7
view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

该方法短而脆,与其它方法相比效果较好。


如果你在使用Kotlin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })


我们要等着看会画出来。为此,请使用OnPredrawListener。Kotlin示例:

1
2
3
4
5
6
7
8
9
10
11
12
val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)


如果应用程序在后台,Gone视图将返回0作为高度。这是我的代码(100%有效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}