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

时间:2010-08-28 17:47:02

标签: java android android-layout getter

我正在动态创建我的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);
}

感谢任何帮助。

20 个答案:

答案 0 :(得分:710)

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

注意:将立即删除侦听器,否则将触发每个布局事件。如果您必须支持应用 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
            }
        });

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

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


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

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

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

如果视图已经通过至少一个布局,则返回true   最后附在窗户上或从窗户上拆下。


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

如果只需获得静态定义的高度/宽度就足够了,您可以使用以下方法执行此操作:

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

  

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

     

第一对称为测量宽度和测量高度。这些   维度定义视图在其父级中的大小(请参阅   布局了解更多细节。)测量尺寸可以通过获得   调用getMeasuredWidth()和getMeasuredHeight()。

     

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

答案 1 :(得分:258)

我们可以使用

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

答案 2 :(得分:178)

您过早地致电getWidth()。用户界面尚未调整大小并在屏幕上显示。

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

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

答案 3 :(得分:106)

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

正如伊恩在this Android Developers thread中所述:

  

无论如何,这笔交易就是布局了   窗口的内容发生   之后构造所有元素并将其添加到其父元素中   观点。它必须是这样的,因为   直到你知道View的组件   包含,以及它们包含的内容,以及   等等,你没有明智的方法   把它打好。

     

底线,如果你调用getWidth()   在构造函数中,它将返回   零。程序是创建所有   构造函数中的视图元素,   然后等待你的View   要调用的onSizeChanged()方法 -   那是你第一次发现你的时候   实际尺寸,所以当你设置   GUI元素的大小。

     

也要注意onSizeChanged()是   有时用参数调用   零 - 检查这种情况,并且   马上回来(所以你没有得到   计算你的时候除以零   布局等)。一段时间后呢   将被称为真实值。

答案 5 :(得分:62)

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

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

答案 6 :(得分:16)

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

答案 7 :(得分:5)

扩展以在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)
}

答案 8 :(得分:3)

如果您使用的是RxJava&amp; RxBindings。没有样板的类似方法。这也解决了黑客在Tim Autin的答案中抑制警告的问题。

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

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

答案 9 :(得分:3)

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

答案 10 :(得分:2)

也许这可以帮助某人:

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

答案 11 :(得分: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
            }
        })

答案 12 :(得分:1)

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

答案 13 :(得分:1)

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

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

与其他方法相比,该方法短而明快。

答案 14 :(得分: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)

答案 15 :(得分:0)

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

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

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

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

答案 16 :(得分: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)
            }
        }
    })
}

答案 17 :(得分:0)

这有点旧,但是我自己却遇到了麻烦(创建片段时需要动画化片段中的对象)。这种解决方案对我有用,我相信这是不言而喻的。

class YourFragment: Fragment() {
    var width = 0
    var height = 0


override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val root = inflater.inflate(R.layout.fragment_winner_splash, container, false)
    container?.width.let {
        if (it != null) {
            width = it
        }
    }
    container?.height.let {
        if (it != null) {
            height = it
        }
    }
    
    return root
}
 

答案 18 :(得分:0)

如果你担心 onDraw 方法过度工作,你可以在构造过程中将维度设置为 null,然后只在 onDraw 内部设置维度,如果它为空。

那样你就不用在 onDraw 里面做任何工作了

class myView(context:Context,attr:AttributeSet?):View(context,attr){
var height:Float?=null


override fun onDraw(canvas:Canvas){
    if (height==null){height=this.height.toFloat()}
    }
}

答案 19 :(得分:0)

好吧,你可以使用 addOnLayoutChangeListener

您可以在 onCreateonCreateView 中使用它


myView.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> 
            Log.i(TAG, "view : ${view.width}")
        }