在运行时确定Android视图的大小


123

创建活动后,我正在尝试将动画应用于Android应用程序中的视图。为此,我需要确定视图的当前大小,然后设置动画以从当前大小缩放到新大小。这部分必须在运行时完成,因为视图会根据用户的输入缩放为不同的大小。我的布局以XML定义。

这似乎是一项容易的任务,显然有很多关于SO的问题,尽管没有一个能解决我的问题。所以也许我缺少明显的东西。我可以通过以下方式了解我的观点:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

这工作得很好,但打电话时getWidth()getHeight()getMeasuredWidth()getLayoutParams().width,等,他们都返回0。我也试过手动调用measure()视图上,然后到一个呼叫getMeasuredWidth(),但没有效果。

我试过调用这些方法,并在活动的onCreate()和中检查调试器中的对象onPostCreate()。如何在运行时找出该视图的确切尺寸?


1
哦,我也应该注意到,视图本身肯定不会具有0宽度/高度。它出现在屏幕上就很好。
Nik Reiman 2010年

您可以从xml布局发布<ImageView ... />标记吗?
fhucho

我一直在为解决这个问题而苦苦挣扎,直到我意识到我的情况下View的尺寸与物理屏幕匹配(我的应用程序是“沉浸式”并且View及其所有父级的宽度和高度都设置为match_parent)后,我才为之奋斗。在这种情况下,即使在绘制View之前,也可以安全地调用一个更简单的解决方案(例如,通过Activity的onCreate()方法),只需使用Display.getSize();有关详细信息,请参见stackoverflow.com/a/30929599/5025060。我知道这不是@NikReiman要求的,只是给那些可能使用此更简单方法的人留下了笔记。
CODE-READ

Answers:


180

在视图上使用ViewTreeObserver等待第一个布局。只有在第一个布局之后,getWidth()/ getHeight()/ getMeasuredWidth()/ getMeasuredHeight()才能工作。

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}

21
请谨慎使用此答案,因为如果基础视图是视频/表面,则会连续调用它
Kevin Parker

7
您能详细说明一下@Kevin吗?首先,有两件事,因为它是一个侦听器,所以我希望有一个回调,然后,一旦我们获得第一个回调,就删除侦听器。我错过了什么?
Vikram Bodicherla 2011年

4
在调用任何ViewTreeObserver方法之前,建议先检查isAlive():if(view.getViewTreeObserver()。isAlive()){view.getViewTreeObserver()。removeGlobalOnLayoutListener(this); }
Peter Tran 2012年

4
有什么选择removeGlobalOnLayoutListener(this);吗?因为它已被弃用。
西伯斯赌博

7
@FarticlePilter,请改用removeOnGlobalLayoutListener()。在文档中提到过。
Vikram Bodicherla 2014年

63

根据情况,实际上有多种解决方案:

  1. 安全的方法将在绘制视图之前,布局阶段完成后起作用:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

用法示例:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. 在某些情况下,手动测量视图的大小就足够了:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

如果您知道容器的大小:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. 如果您具有扩展的自定义视图,则可以通过“ onMeasure”方法获取其大小,但是我认为它仅在某些情况下有效:
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. 如果您使用Kotlin编写,则可以使用下一个函数,该函数在幕后的工作方式与runJustBeforeBeingDrawn我所写的完全相同:

    view.doOnPreDraw { actionToBeTriggered() }

    请注意,您需要将此添加到gradle中(可通过here找到):

    implementation 'androidx.core:core-ktx:#.#'

1
似乎解决方案#1不能总是与一起使用OnPreDrawListener。当您想从Activity的代码中运行代码onCreate()并立即后台应用程序时进行了测试。易于重复,尤其是在速度较慢的设备上。如果替换OnPreDrawListenerOnGlobalLayoutListener-您将不会看到相同的问题。
提问者

@questioner我听不懂,但我很高兴它最终对您有所帮助。
android开发人员

尽管我通常使用ViewTreeObserverpost,但在我的情况下,第2个帮助了(view.measure)。
CoolMind

3
@CoolMind如果您使用Kotlin,也可以使用新方法。更新的答案也包括在内。
Android开发人员

为什么kotlin为此具有其他功能?谷歌很疯狂,这种语言是没有用的(只应该与Java一起使用),它比swift年代久远,但只有swift获得了足够的知名度tiobe.com/tiobe-index(swift 12位和kotlin 38)
user924

18

getWidth()是在视图实际显示在屏幕上之前打电话吗?

新的Android开发人员经常犯的一个错误是在其构造函数中使用视图的宽度和高度。调用视图的构造函数时,Android尚不知道视图的大小,因此将大小设置为零。实际尺寸是在布局阶段计算的,该阶段发生在构造之后但未绘制任何东西之前。您可以使用onSizeChanged()方法在知道值时通知它们,也可以稍后使用getWidth()getHeight()方法,例如在onDraw()方法中。


2
那么这是否意味着我需要重写ImageView只是为了捕获onSizeChange()事件以了解实际大小?这似乎有点矫kill过正……尽管在这一点上,我很想让代码起作用,所以值得一试。
Nik Reiman'9

我在想的更多像是等到您的Activity中的onCreate()完成。
Mark B 2010年

1
如前所述,我尝试将这段代码放置onPostCreate()在视图完全创建和启动后运行的地方。
Nik Reiman

1
引用的文字来自哪里?
复杂

1
此自定义视图的报价单,当我们一般询问视图大小时,请勿将其发布为此类答案
user924

17

根据@mbaird的建议,我通过对ImageView类进行子类化和重写来找到了可行的解决方案onLayout()。然后,我创建了一个观察器接口,我的活动实现了该观察器接口,并将对自身的引用传递给该类,从而使该类可以告知活动实际何时完成大小调整。

我不是100%确信这是最好的解决方案(因此我还没有将此答案标记为正确的答案),但是它确实有效,并且根据文档,这是人们第一次可以找到视图的实际大小。


很高兴知道。如果我发现任何可以让您绕开子类化View的方法,我将在此处发布。我有点惊讶onPostCreate()没有起作用。
Mark B

好吧,我尝试了这个,它似乎起作用了。并不是真正的最佳解决方案,因为此方法在启动时不仅被频繁调用一次。:(
Tobias Reich 2014年

10

如果API <11(API 11包括View.OnLayoutChangedListener功能),则以下代码通过重写视图来获取布局:

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}


5

使用下面的代码,它给出视图的大小。

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}

5

这对我有效onClickListener

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);

4
当它在点击监听器中时,您无需postDelayed。在视图全部测量完毕并显示在屏幕上之前,您无法单击任何内容。因此,它们将在您单击它们的时间进行度量。
afollestad 2015年

该代码是错误的。发布延迟不保证在下一个滴答中测量您的视图。
Ian Wong

我建议不要去postDelayed()。这不太可能发生,但是如果您的视图在1秒钟之内没有被测量并且您将其称为runnable以获取视图的宽度/高度,该怎么办。它仍然会产生0。编辑:哦,顺便说一句,1以毫秒为单位,所以肯定会失败。
亚历克斯

4

我也失去了身边getMeasuredWidth(),并getMeasuredHeight() getHeight()getWidth()很长一段时间..........后来我发现,越来越视图的宽度和高度onSizeChanged()是做到这一点的最好办法........你可以动态通过覆盖onSizeChanged()方法获取视图的CURRENT宽度和CURRENT高度。

可能想看看其中有一段精心设计的代码段。新博客文章:如何获取Android中customView的宽度和高度尺寸(扩展视图)http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html


0

您可以在屏幕上同时获得视图的位置和尺寸

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}

-1

为我工作的表现:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
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.