视图的getWidth()和getHeight()返回0


513

我正在动态创建我的android项目中的所有元素。我正在尝试获取按钮的宽度和高度,以便可以旋转该按钮。我只是想学习如何使用android语言。但是,它返回0。

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

这是我当前的代码:

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);
}

任何帮助表示赞赏。

Answers:


197

getWidth()来得太早了。UI尚未调整大小并在屏幕上布置。

无论如何,我怀疑您是否想做自己正在做的事情—动画化的小部件不会更改其可点击区域,因此无论按钮如何旋转,按钮仍将响应原始方向的单击。

话虽如此,您可以使用维度资源来定义按钮的大小,然后从布局文件和源代码中引用该维度资源,以避免出现此问题。


82
ngreenwood6,您的其他解决方案是什么?
Andrew

12
@Andrew-如果您希望收到negreenwood6的跟进通知,您必须像我对您所做的那样开始发送消息(我认为前三个字母就足够了)-CommonsWare会自动收到通知,因为他写了此回复,但ngreen却没有除非您解决这些问题。
彼得·阿杰泰

11
@Override public void onWindowFocusChanged(boolean hasFocus){// TODO自动生成的方法存根super.onWindowFocusChanged(hasFocus); //这里您可以获取尺寸!}
萨那2012年

5
@ ngreenwood6那么您的解决方案是什么?
尼玛瓦济里

5
屏幕准备就绪时,使用此侦听器获取大小。view.getViewTreeObserver()。addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){}
SinanDizdarević15年

814

基本问题是,您必须等待绘图阶段进行实际测量(尤其是使用诸如wrap_content或的动态值match_parent),但通常该阶段尚未完成onResume()。因此,您需要一种解决方案来等待此阶段。有一个不同的可能的解决方案:


1.收听绘图/布局事件:ViewTreeObserver

一个ViewTreeObserver被激发用于不同的绘图事件。通常,这OnGlobalLayoutListener是您想要获得测量值的,因此在布局阶段之后将调用侦听器中的代码,因此测量值已准备就绪:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

注意:监听器将被立即删除,因为否则它将在每个布局事件上触发。如果您必须支持SDK Lvl <16的应用程序,请使用此命令注销监听器:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2.将可运行对象添加到布局队列:View.post()

不是很出名,也是我最喜欢的解决方案。基本上,只需将View的post方法与您自己的runnable一起使用即可。如Romain Guy所述,这基本上是在视图的尺寸,布局等之后将您的代码排队:

UI事件队列将按顺序处理事件。调用setContentView()后,事件队列将包含一条消息,要求进行重新布局,因此您发布到队列中的所有内容都会在布局通过后发生

例:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

相对于ViewTreeObserver

  • 您的代码仅执行一次,而不必在执行后禁用观察器,这可能会很麻烦
  • 不太冗长的语法

参考文献:


3.覆盖Views的onLayout方法

仅在逻辑可以封装在视图本身中的情况下,这才是切实可行的,否则,这是一种非常冗长且繁琐的语法。

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将被调用多次,因此请考虑在方法中执行的操作,或者在第一次后禁用代码


4.检查是否已经过布局阶段

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

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

如果视图自从上次附着到窗口或从窗口分离以来已经经过至少一种布局,则返回true。


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

如果只需要获取静态定义的高度/宽度,则可以使用以下方法执行此操作:

但是请注意,这可能与绘制后的实际宽度/高度不同。Javadoc完美地描述了差异:

视图的大小用宽度和高度表示。一个视图实际上具有两对宽度和高度值。

第一对称为测量宽度和测量高度。这些尺寸定义了视图在其父视图中的大小(有关更多详细信息,请参见布局。)可以通过调用getMeasuredWidth()和getMeasuredHeight()获得测量尺寸。

第二对简称为宽度和高度,有时也称为图纸宽度和图纸高度。这些尺寸定义了屏幕上,绘制时和布局后视图的实际尺寸。这些值可以但不必与测量的宽度和高度不同。宽度和高度可以通过调用getWidth()和getHeight()获得。


1
我在片段中使用view.post。我需要测量其父级的高度为0dp并设置了权重的视图。我尝试的所有操作都返回高度为零(我也尝试过使用全局布局侦听器)。当我将view.post代码放入延迟为125的onViewCreated中时,它可以工作,但并非没有延迟。有想法吗?
Psest328

6
太可惜了,您只能投票一次,真的很好的解释。如果我退出应用程序并从最近的应用程序重新启动,则视图旋转会出现问题。如果我从那里刷卡并重新启动,一切都很好。view.post()保存了我的一天!
Matthias

1
谢谢。我通常使用View.post()(第二种方法),但是如果从后台线程中调用它,则有时无法正常工作。因此,不要忘了写runOnUiThread(new Runnable() {...}。目前,我使用第一种方法的一种变体:addOnLayoutChangeListener。不要忘记将其删除:removeOnLayoutChangeListener。它也可以从后台线程工作。
CoolMind

2
对我来说不幸的是,“ View.post()”并非一直都在工作。我有2个非常相似的项目。在一个项目中,它正在工作,而在另一个项目中,则没有工作。:(。在两种情况下,我都从Activity调用'onCreate()'方法。为什么有任何建议?
Adrian Buciuman

1
我发现了问题。在一个项目中,视图具有android:visibility="gone",而在另一个项目中,可见性为invisible。如果能见度gone宽度将始终为0。
阿德里安Buciuman

262

我们可以用

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

10
那么我们如何在“片段”中工作?这仅适用于活动吗?
Olkunmustafa 2015年

8
这应该可以解决问题,但不能解决问题。用户希望在任何时间点获取尺寸,而不是在窗口上绘制。同样,在窗口中发生多次更改(视图隐藏,消失,添加新视图等)时,也会多次调用此方法。因此请仔细使用它:)
aNdRO博士2015年

1
这是一个hack,看来使用ViewTreeObserver会更好。
i_am_jorf 2015年

并不是说听起来很粗鲁,但是留下一个自动生成的注释(尤其是两行代码)并不能使您对自己真正知道自己在做什么有很大的信心。
Natix

不幸的是,它对我没有用。尝试viewTreeObserver为我工作。
Narendra Singh

109

我使用了这种解决方案,我认为它比onWindowFocusChanged()更好。如果打开DialogFragment,然后旋转电话,则仅当用户关闭对话框时才会调用onWindowFocusChanged):

    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,现在您应该这样做:

@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 :)
}

4
这是迄今为止最好的解决方案!因为它也适用于宽度和高度未知且无法计算的动态解决方案。即基于其他元素的位置/大小的相对布局。做得好!
2013年

2
这需要API 16或更高版本(软
糖豆

7
它不需要API 16!唯一的问题是API 16的方法已过时removeGlobalOnLayoutListener,但有简单的解决方案兼容所有API级别- stackoverflow.com/questions/16189525/...
银杏

如果您需要删除侦听器以免多次调用,并且在使用不同的API时遇到问题,建议您在类的开头设置一个假布尔值,然后在捕获值之后将其设置为true,这样它不会再次捕获值。
曼苏尔·法哈德

如果在第一次运行后删除侦听器,则可能会得到错误的尺寸:02-14 10:18:53.786 15063-15063 / PuzzleFragment:高度:1647宽度:996 02-14 10:18:53.985 15063-15063 / PuzzleFragment:高度:1500宽度:996
Leos Literak '16

76

正如Ian在此Android Developers线程中指出的那样:

无论如何,关键是要在构造所有元素并将其添加到其父视图之后,对窗口内容进行布局 。它必须是这种方式,因为在您知道View包含哪些组件以及它们包含什么等等之前,没有明智的方法可以对其进行布局。

底线是,如果在构造函数中调用getWidth()等,它将返回零。该过程是在构造函数中创建所有视图元素,然后等待调用View的onSizeChanged()方法-那是您首次确定实际大小时,因此才是设置GUI元素的大小。

还要注意,有时会使用零参数调用onSizeChanged()-检查这种情况,然后立即返回(因此在计算布局等时不会除以零)。一段时间后,它将使用实际值进行调用。


8
onSizeChanged为我工作。在我的自定义视图中不会调用onWindowFocusedChanged。
aaronmarino

这看起来是一个很好的解决方案,但是我应该始终创建自己的自定义视图以覆盖onSizeChanged方法吗?
M.ElSaka

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.那是宾果游戏。我问题的根源。
MikeNereson

我建议为碎片使用此解决方案,因为其他一些解决方案在我的情况下返回0。
WindRider 2015年

66

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

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

5
由于某种原因,这是唯一对我有用的答案-谢谢!
Farbod Salamat-Zadeh

同样对我来说,最简单的解决方案,即工作对我来说
梯玛

像魅力一样工作!
莫拉莱斯·巴托夫斯基

1
@CommonsWare此解决方案如何在全局布局回调中的视图树观察者之前给出高度和宽度,对此进行解释将很有帮助。
Mightian

21

我宁愿使用OnPreDrawListener()代替addOnGlobalLayoutListener(),因为它比其他监听器更早被调用。

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

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

3
需要注意的是return false手段,以取消当前的图形通。要继续,请return true改为。

9

Kotlin扩展程序,用于观察全局布局,并在动态准备好高度时执行给定的任务。

用法:

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

实现方式:

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)
}

6

AndroidX在内部具有多种扩展功能,可帮助您完成此类工作 androidx.core.view

您需要为此使用Kotlin。

最适合这里的是doOnLayout

布置此视图时执行给定的操作。如果视图已被布局并且尚未请求布局,则将立即执行该操作,否则,将在下一个视图被布局后执行该操作。

该动作将仅在下一个布局上调用一次,然后删除。

在您的示例中:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

依赖关系: androidx.core:core-ktx:1.0.0



1
这些扩展确实非常重要和有用
艾哈迈德

5

如果您使用的是RxJavaRxBindings,则使用一根衬板。没有样板的类似方法。蒂姆·奥丁Tim Autin)的回答也解决了黑客抑制警告的问题

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

就是这个。即使从未调用,也无需取消订阅。


4

发生这种情况是因为视图需要更多的时间来膨胀。因此,而不是调用view.widthview.height主线程,你应该使用view.post { ... },以确保您view已经膨胀。在科特林:

view.post{width}
view.post{height}

在Java中,您还可以在中调用getWidth()getHeight()方法,Runnable并将Runnableto view.post()方法传递给。

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });

这是不正确的答案,因为在执行发布的代码时可能无法测量和布置视图。这将是一个极端的情况,但.post不能保证视图的布局。
d.aemon

1
这个答案很好,对我有用。非常感谢Ehsan
MMG

3

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

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

与其他方法相比,该方法短而明快,因此非常好。


3

也许这可以帮助某人:

为View类创建扩展功能

文件名:ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

然后可以将其用于以下任何视图:

view.afterLayout {
    do something with view.height
}

2

答案post为不正确,因为可能不会重新计算尺寸。
另一个重要的事情是,视图及其所有祖先必须可见。为此,我使用一个属性View.isShown

这是我的kotlin函数,可以将其放在utils中:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

用法是:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}

1

如果您使用的是Kotlin

  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
            }
        })

0

如果应用程序在后台运行,则视图消失将返回0作为高度。这是我的代码(1oo%有效)

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)
            }
        }
    })
}

0

我们需要等待视图将被绘制。为此,请使用OnPreDrawListener。Kotlin示例:

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.