View的getWidth()和getHeight()返回0

ngr*_*od6 487 java getter android android-layout

我动态地在我的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);
}
Run Code Online (Sandbox Code Playgroud)

任何帮助表示赞赏.

pat*_*ckf 765

基本问题是,您必须等待实际测量的绘制阶段(特别是动态值,如wrap_contentmatch_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
            }
        });
Run Code Online (Sandbox Code Playgroud)

注意:将立即删除侦听器,否则将触发每个布局事件.如果您必须支持SDK Lvl <16,请使用此方法取消注册侦听器:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2.将一个runnable添加到布局队列: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
            }
        });
Run Code Online (Sandbox Code Playgroud)

优势超过ViewTreeObserver:

  • 您的代码只执行一次,您不必在执行后禁用Observer,这可能很麻烦
  • 更简洁的语法

参考文献:


3.覆盖视图的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
    }
};
Run Code Online (Sandbox Code Playgroud)

还要注意,onLayout会被多次调用,所以要考虑你在方法中做了什么,或者在第一次之后禁用你的代码


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

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

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}
Run Code Online (Sandbox Code Playgroud)

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


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

如果只需要获得静态定义的高度/宽度就足够了,你可以这样做:

但请注意,这可能与绘图后的实际宽度/高度不同.javadoc完美地描述了这种差异:

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

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

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

  • 最后,view.post(new Runnable())hack为我工作 (24认同)
  • 太糟糕了,你只能回答一次,非常好的解释.如果我退出应用程序并从最近的应用程序重新启动它,我的视图轮换有问题.如果我从那里刷它并重新启动一切都很好.view.post()节省了我的一天! (5认同)
  • 我找到了问题。在一个项目中,视图具有`android:visibility="gone"`,而在另一个项目中,可见性属性是`invisible`。如果可见性“消失”,则宽度将始终为 0。 (3认同)
  • 我在片段中使用 view.post。我需要测量父高度为 0dp 并设置权重的视图。我尝试的一切都返回零高度(我也尝试过全局布局侦听器)。当我将 view.post 代码放入 onViewCreated 并延迟 125 时,它可以工作,但并非没有延迟。想法? (2认同)
  • 对我来说不幸的是,“ View.post()”并非一直都在工作。我有2个非常相似的项目。在一个项目中,它正在工作,而在另一个项目中,则没有工作。:(。在两种情况下,我都从Activity的'onCreate()'方法调用该方法。为什么有什么建议? (2认同)

San*_*ana 261

我们可以用

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

  • 那么我们如何在Fragments中工作呢?这仅适用于活动? (10认同)
  • 这应该做的事情,但它不应该是答案.用户希望在任何时间点获取尺寸,而不是在窗口上绘制尺寸.此方法也会多次调用,或者每次在窗口中发生更改(查看隐藏,消失,添加新视图等)时调用.所以仔细使用它:) (8认同)

Com*_*are 184

你打电话getWidth()太早了.UI尚未调整大小并在屏幕上显示.

我怀疑你想要做你正在做的事情,无论如何 - 动画小工具不会改变它们的可点击区域,因此按钮仍然会响应原始方向的点击,无论它如何旋转.

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

  • ngreenwood6,你的另一个解决方案是什么? (81认同)
  • @Andrew - 如果你想让negreenwood6得到你的跟进通知,你必须像我对你那样开始你的消息(我认为前三个字母就足够了) - CommonsWare自动得到通知,因为他写了这个回复,但ngreen没有除非你解决它们. (11认同)
  • @Override public void onWindowFocusChanged(boolean hasFocus){// TODO自动生成的方法stub super.onWindowFocusChanged(hasFocus); //在这里你可以得到大小!} (10认同)
  • @ ngreenwood6那么你的解决方案是什么? (5认同)
  • 使用此侦听器可以获得大小,屏幕何时准备就绪.view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){} (4认同)
  • 大声笑,他只是在确认不可能的情况下跑了出来,然后这里的人都陷入了死锁. (2认同)

Tim*_*tin 107

我使用这个解决方案,我认为它比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 :)
        }
    });
Run Code Online (Sandbox Code Playgroud)

编辑:由于不推荐使用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 :)
}
Run Code Online (Sandbox Code Playgroud)

  • 它不需要API 16!唯一的问题是从API 16弃用的方法removeGlobalOnLayoutListener,但是有一个与所有api级别兼容的简单解决方案 - http://stackoverflow.com/questions/16189525/ongloballayoutlistener-deprecation-and-compatibility (7认同)
  • 这是迄今为止最好的解决方案!因为它也适用于宽度和高度未知和计算的动态解决方案.即,基于其他元素的位置/大小的相对布局.做得好! (4认同)
  • 这需要API 16或更高(果冻豆) (2认同)

小智 75

正如Ian在这个Android开发者线程中所述:

无论如何,交易是在构造所有元素并将其添加到父视图之后,窗口内容的布局发生 .它必须是这种方式,因为直到你知道View包含哪些组件,它们包含什么,等等,你就没有明智的方法可以解决它.

最重要的是,如果在构造函数中调用getWidth()等,它将返回零.过程是在构造函数中创建所有视图元素,然后等待View的onSizeChanged()方法被调用 - 这是当你第一次找到你的实际大小时,所以当你设置GUI元素的大小时.

也要注意onSizeChanged()有时会被调用参数为零 - 检查这种情况,并立即返回(因此在计算布局时不要除以零).一段时间后,将使用真实值调用它.

  • onSizeChanged为我工作.在我的自定义视图中不会调用onWindowFocusedChanged. (8认同)

小智 64

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

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
Run Code Online (Sandbox Code Playgroud)

  • 出于某种原因,这是唯一对我有用的答案 - 谢谢! (4认同)
  • 聪明的解决方案. (2认同)
  • @CommonsWare 这个解决方案如何在全局布局回调的视图树观察者之前给出高度和宽度,解释会有所帮助。 (2认同)

Aya*_*fov 20

我宁愿使用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;
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 请注意,“ return false”的意思是[*要**取消**当前图形传递**](https://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html#onPreDraw())。要继续,请返回“ true”。 (3认同)

Vla*_*lad 18

AndroidX 有多个扩展功能可以帮助你完成这类工作,里面 androidx.core.view

为此,您需要使用 Kotlin。

最适合这里的是doOnLayout

在布局此视图时执行给定的操作。如果视图已经布局并且还没有请求布局,则将立即执行该操作,否则将在下一次布局视图之后执行该操作。

该动作只会在下一个布局上被调用一次,然后被删除。

在你的例子中:

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

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


Ren*_*tik 8

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

用法:

view.height { Log.i("Info", "Here is your height:" + it) }
Run Code Online (Sandbox Code Playgroud)

实现方式:

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)
}
Run Code Online (Sandbox Code Playgroud)


Ody*_*dys 5

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

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

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


Pep*_*ijn 5

也许这对某人有帮助:

为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()
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

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

view.afterLayout {
    do something with view.height
}
Run Code Online (Sandbox Code Playgroud)


Ehs*_*ndi 5

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

view.post{width}
view.post{height}
Run Code Online (Sandbox Code Playgroud)

在 Java 中,您还可以调用 a 中的getWidth()getHeight()方法Runnable并传递Runnabletoview.post()方法。

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 这不是正确的答案,因为在执行发布的代码时可能无法测量和布局视图。这将是一种边缘情况,但“.post”并不能保证视图的布局。 (2认同)