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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3591784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 02:18:03  来源:igfitidea点击:

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

javaandroidandroid-layoutgetter

提问by ngreenwood6

I am creating all of the elements in my android project dynamically. I am trying to get the width and height of a button so that I can rotate that button around. I am just trying to learn how to work with the android language. However, it returns 0.

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

I did some research and I saw that it needs to be done somewhere other than in the onCreate()method. If someone can give me an example of how to do it, that would be great.

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

Here is my current code:

这是我当前的代码:

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

Any help is appreciated.

任何帮助表示赞赏。

采纳答案by CommonsWare

You are calling getWidth()too early. The UI has not been sized and laid out on the screen yet.

你打电话getWidth()太早了。UI 尚未在屏幕上调整大小和布局。

I doubt you want to be doing what you are doing, anyway -- widgets being animated do not change their clickable areas, and so the button will still respond to clicks in the original orientation regardless of how it has rotated.

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

That being said, you can use a dimension resourceto define the button size, then reference that dimension resource from your layout file and your source code, to avoid this problem.

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

回答by kerem yokuva

As Ian states in this Android Developers thread:

正如 Ian 在此 Android 开发人员线程中所述

Anyhow, the deal is that layout of the contents of a window happens afterall 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.

无论如何,问题是窗口内容的布局发生 所有元素被构造并添加到它们的父视图之后。必须这样,因为在您知道 View 包含哪些组件以及它们包含什么等等之前,您没有合理的方式可以对其进行布局。

最重要的是,如果您在构造函数中调用 getWidth() 等,它将返回零。该过程是在构造函数中创建所有视图元素,然后等待调用视图的 onSizeChanged() 方法——这是您第一次找出实际大小的时间,也就是设置 GUI 元素大小的时间。

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

回答by Sana

We can use

我们可以用

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

回答by Tim Autin

I used this solution, which I think is better than onWindowFocusChanged(). If you open a DialogFragment, then rotate the phone, onWindowFocusChanged will be called only when the user closes the dialog):

我使用了这个解决方案,我认为它比 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 :)
        }
    });

Edit : as removeGlobalOnLayoutListener is deprecated, you should now do :

编辑:由于不推荐使用 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 :)
}

回答by sulu.dev

If you need to get width of some widget before it is displayed on screen, you can use getMeasuredWidth() or getMeasuredHeight().

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

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

回答by Patrick Favre

The basic problem is, that you have to wait for the drawing phase for the actual measurements (especially with dynamic values like wrap_contentor match_parent), but usually this phase hasn't been finished up to onResume(). So you need a workaround for waiting for this phase. There a are different possible solutions to this:

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



1. Listen to Draw/Layout Events: ViewTreeObserver

1. 监听绘制/布局事件:ViewTreeObserver

A ViewTreeObserver gets fired for different drawing events. Usually the OnGlobalLayoutListeneris what you want for getting the measurement, so the code in the listener will be called after the layout phase, so the measurements are ready:

ViewTreeObserver 因不同的绘图事件而被触发。通常OnGlobalLayoutListener是你想要得到测量的东西,所以在布局阶段之后会调用监听器中的代码,所以测量准备好了:

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

Note: The listener will be immediately removed because otherwise it will fire on every layout event. If you have to support apps SDK Lvl < 16use this to unregister the listener:

注意:侦听器将被立即删除,否则它将在每个布局事件上触发。如果您必须支持SDK Lvl < 16 的应用程序,请使用它来取消注册侦听器:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)



2. Add a runnable to the layout queue: View.post()

2.在布局队列中添加一个runnable:View.post()

Not very well known and my favourite solution. Basically just use the View's post method with your own runnable. This basically queues your code afterthe view's measure, layout, etc. as stated by Romain Guy:

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

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

UI 事件队列将按顺序处理事件。调用 setContentView() 后,事件队列将包含一条要求重新布局的消息,因此您发布到队列的任何内容都将在布局传递后发生

Example:

例子:

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

The advantage over ViewTreeObserver:

优势在于ViewTreeObserver

  • your code is only executed once and you don't have to disable the Observer after execution which can be a hassle
  • less verbose syntax
  • 你的代码只执行一次,你不必在执行后禁用观察者,这可能很麻烦
  • 不那么冗长的语法

References:

参考:



3. Overwrite Views's onLayout Method

3.覆盖Views的onLayout方法

This is only practical in certain situation when the logic can be encapsulated in the view itself, otherwise this is a quite verbose and cumbersome syntax.

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

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

Also mind, that onLayout will be called many times, so be considerate what you do in the method, or disable your code after the first time

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



4. Check if has been through layout phase

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

If you have code that is executing multiple times while creating the ui you could use the following support v4 lib method:

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

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.

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



Additional: Getting staticly defined measurements

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

If it suffices to just get the statically defined height/width, you can just do this with:

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

But mind you, that this might be different to the actual width/height after drawing. The javadoc describes the difference perfectly:

但请注意,这可能与绘制后的实际宽度/高度不同。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().

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

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

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

回答by Ayaz Alifov

I would rather use OnPreDrawListener()instead of addOnGlobalLayoutListener(), since it is called a bit earlier than other listeners.

我宁愿使用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;
        }
    });

回答by Odys

One liner if you are using RxJava& RxBindings. Similar approach without the boilerplate. This also solves the hack to suppress warnings as in the answer by Tim Autin.

如果您使用的是RxJavaRxBindings ,则为一个衬垫。没有样板的类似方法。这也解决了 hack 来抑制警告,如Tim Autin的回答。

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

This is it. No need to be unsubscribe, even if never called.

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

回答by Renetik

A Kotlin Extension to observe on the global layout and perform a given task when height is ready dynamically.

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

Usage:

用法:

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

Implementation:

执行:

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

回答by Renetik

Gone views returns 0 as height if app in background. This my code (1oo% works)

如果应用程序在后台,消失的视图返回 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)
            }
        }
    })
}