View's getWidth()和getHeight()返回0

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

任何帮助都是感激的。

393589 次浏览

你过早地调用getWidth()。UI还没有在屏幕上调整大小和布局。

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

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

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

无论如何,交易是那种布局 发生窗口内容 所有元素都被构造并添加到它们的父元素中 的观点。必须这样,因为 直到你知道什么组件是视图 包含,以及它们包含的内容 因此,你没有明智的办法

底线,如果你调用getWidth() 在构造函数中,它将返回 零。步骤是创建全部 构造函数中的视图元素, 然后等待您的视图 onSizeChanged()方法被调用—— 那是你第一次发现你的 实际大小,这就是你设置的时候 你的GUI元素的大小 还要注意onSizeChanged()是 的参数 零,检查这种情况,然后 立即返回(这样就不会得到一个 计算时除以0 布局,等等)。一段时间后

我们可以用

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

我使用了这个解决方案,我认为它比onWindowFocusChanged()更好。如果你打开一个对话片段,然后旋转电话,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 :)
}

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

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

基本的问题是,您必须等待实际测量的绘制阶段(特别是使用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,使用这个来注销监听器:

# EYZ0


2. 添加一个可运行对象到布局队列:

不是很有名,也是我最喜欢的解决方案。基本上只需要使用视图的post方法和你自己的runnable。这基本上是排队你的代码,视图的测量,布局等,如罗曼的家伙所述:

UI事件队列将按顺序处理事件。后 setContentView()被调用时,事件队列将包含一条消息 请求重新布局,这样你发布到队列中的任何事情都会发生 在布局传递

例子:

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

相对于ViewTreeObserver的优势:

  • 你的代码只执行一次,你不必在执行后禁用观察者,这可能是一个麻烦
  • 更少的详细语法

引用:

  • https://stackoverflow.com/a/3602144/774398 < a href = " https://stackoverflow.com/a/3602144/774398 " > < / >
  • https://stackoverflow.com/a/3948036/774398 < a href = " https://stackoverflow.com/a/3948036/774398 " > < / >

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时执行了多次,你可以使用下面的support v4 lib方法:

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

如果视图已经经过至少一次布局,则返回true

.最后附加到或从窗口分离

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

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

但请注意,这可能与绘制后的实际宽度/高度不同。javadoc更详细地描述了两者的区别:

视图的大小用宽度和高度表示。一个视图 实际上拥有两对宽度和高度值 第一对被称为测量宽度和测量高度。这些 维度定义了一个视图在其父视图中的大小(参见 详情请浏览网页。)测量尺寸可由 调用getMeasuredWidth()和getMeasuredHeight().

第二对简单地称为宽度和高度,或者有时 绘图宽度和绘图高度。这些维度定义了实际 在绘制时和布局后屏幕上视图的大小。这些 数值可以(但不必)与测量宽度不同 和身高。宽度和高度可以通过调用getWidth()来获得 和获得(). < / p >

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

根据@Pang的评论调整了代码onPreDraw方法应返回true以继续当前的绘图过程。

如果你使用RxJava &# EYZ1。类似的方法没有样板。这也解决了黑客压制警告的答案由蒂姆Autin

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

就是它了。不需要取消订阅,即使从未调用。

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

消失视图返回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)
}
}
})
}

如果您正在使用Kotlin

  customView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {


override fun onGlobalLayout() {


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


// Here you can get the size :)
viewWidth = customView.width
}
})

高度和宽度为零,因为当你请求它的高度和宽度时视图还没有创建。 一个最简单的解决方案是

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

与其他方法相比,这种方法是很好的,因为它是短而脆。

我们需要等待绘制视图。为此,请使用OnPreDrawListener。芬兰湾的科特林的例子:

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)

也许这能帮助到一些人:

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

AndroidX有多个扩展函数可以帮助你完成这类工作,在androidx.core.view

为此,您需要使用Kotlin。

最适合这里的是doOnLayout:

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

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

在你的例子中:

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

依赖:# EYZ0

这是因为视图需要更多的时间来膨胀。因此,不要在主线程中调用view.widthview.height,而应该使用view.post { ... }来确保view已经被填充。在芬兰湾的科特林:

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

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

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

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

这是一个有点旧,但我自己有麻烦(需要在一个片段中创建动画对象)。这个解决方案对我来说很有效,我相信这是不言自明的。

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
}
 

如果你担心过度工作的onDraw方法,你可以总是设置维度为空在构造过程中,然后只设置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()}
}
}

你可以使用addOnLayoutChangeListener

你可以使用它在onCreateActivityonCreateViewFragment

< p > @Edit 不要忘记删除它,因为在某些情况下它会触发无限循环


myView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener{
override fun onLayoutChange(
v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int
) {
if (v?.width > 0 && v?.height > 0){
// do something
Log.i(TAG, "view : ${view.width}")
// remove after finish
v?.removeOnLayoutChangeListener(this)
}
}
})


芬兰湾的科特林:

由于使用view.height/ view.width导致了NaN,而我使用View.post()有时会查看以0值返回的尺寸,因此我面临了生产崩溃。

所以,

使用view.doOnPreDraw { // your action here},即:

  • OneShotPreDrawListener,所以它只调用一次。
  • 实现OnPreDrawListener,确保视图被布局和测量

在我的例子中,我不能通过post或addOnGlobalLayoutListener获得视图的高度,它总是0。因为我的视图在一个片段中,而这个片段是MainActivity中的第二个选项卡。当我打开MainActivity时,我输入第一个选项卡,所以第二个选项卡不会显示在屏幕上。但是onGlobalLayout()或post()函数仍然有一个回调。

当第二个片段在屏幕上可见时,我得到了视图的高度。这次我得到了正确的高度。

用法:

imageView.size { width, height ->
//your code
}

延伸:


fun <T : View> T.size(function: (Int, Int) -> Unit) {
if (isLaidOut && height != 0 && width != 0) {
function(width, height)
} else {
if (height == 0 || width == 0) {
var onLayoutChangeListener: View.OnLayoutChangeListener? = null
var onGlobalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null


onGlobalLayoutListener = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (isShown) {
removeOnLayoutChangeListener(onLayoutChangeListener)
viewTreeObserver.removeOnGlobalLayoutListener(this)
function(width, height)
}
}
}


onLayoutChangeListener = object : View.OnLayoutChangeListener {
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
val width = v?.width ?: 0
val height = v?.height ?: 0
if (width > 0 && height > 0) {
// remove after finish
viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener)
v?.removeOnLayoutChangeListener(this)
function(width, height)
}
}
}


viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
addOnLayoutChangeListener(onLayoutChangeListener)
} else {
function(width, height)
}
}
}


public final class ViewUtils {


public interface ViewUtilsListener {
void onDrawCompleted();
}


private ViewUtils() {
}


public static void onDraw(View view, ViewUtilsListener listener) {
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (view.getHeight() != 0 && view.getWidth() != 0) {
if (listener != null) {
listener.onDrawCompleted();
}
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}
}

你可以这样用;

ViewUtils.onDraw(view, new ViewUtils.ViewUtilsListener() {
@Override
public void onDrawCompleted() {
int width = view.getWidth();
int height = view.getHeight();
}
});

最简洁的方法是使用post method of view:

芬兰湾的科特林:

  view.post{
var width = view.width
var height = view.height
}

Java:

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