Android:在视图绘制之前获取视图的高度

67

为了进行动画,我需要知道视图的高度。问题在于,除非视图被绘制,否则getHeight()方法总是返回0。

那么有没有办法在不绘制视图的情况下获取高度呢?

在这种情况下,该视图是LinearLayout。

编辑: 我试图调整来自https://github.com/Udinic/SmallExamples/blob/master/ExpandAnimationExample/src/com/udinic/expand_animation_example/ExpandAnimation.java 的扩展动画。

通过它,我想为列表项扩展一些更多的信息。 我无法通过xml实现相同的效果。 目前,只有在绘制之前已知布局大小时,动画才能正常工作。

8个回答

139

看起来您想要获取高度,但在视图可见之前隐藏它。

先将视图的可见性设置为visible或invisible(这样就会创建一个高度)。 不用担心,我们将在代码中将其更改为invisible/gone,如下所示:

private int mHeight = 0;
private View mView;

class...

// onCreate or onResume or onStart ...
mView = findViewByID(R.id.someID);
mView.getViewTreeObserver().addOnGlobalLayoutListener( 
    new OnGlobalLayoutListener(){

        @Override
        public void onGlobalLayout() {
            // gets called after layout has been done but before display
            // so we can get the height then hide the view


            mHeight = mView.getHeight();  // Ahaha!  Gotcha

            mView.getViewTreeObserver().removeGlobalOnLayoutListener( this );
            mView.setVisibility( View.GONE );
        }

});

13
谢谢,它有效了!只需要加一点:removeGlobalOnLayoutListener()自API 16起就已经被弃用了,已由removeOnGlobalLayoutListener()取代。 - Yulia Rogovaya
非常感谢,帮了我很多。测量视图的功能有些问题。 - string.Empty
3
谢谢这个技巧,它非常有效。顺便说一下,“removeOnGlobalLayoutListener”参数的名称是“victim”,这太棒了。 - PGMacDesign
4
这种方法并不总是有效 - 首先,该视图在消失前会闪烁可见,这对于用户界面不好。其次,如果您启动应用程序并立即切换到另一个应用程序,则由于某种原因似乎无法正确计算高度。 - ArturT

34

我有一个类似的问题和情况。

我需要创建一个动画,其中我需要获取正在进行动画效果的view的高度。在这个view中,可能会有一些子视图,例如TextView的类型。该TextView是多行的,并且所需的行数实际上是不确定的。

无论我做了什么,我都只能够获得当TextView只有一行时的视图高度。当文本长度超过一行时,这种方法就会失效。

我考虑了这个答案:https://dev59.com/SG025IYBdhLWcg3wNC51#6157820 但是对我来说效果不是很好,因为我无法直接从这里访问嵌套的TextView。(尽管我可以遍历子视图树。)

请查看我的代码,因为它目前可以正常工作:

view 是要进行动画的视图。它是一个LinearLayout。(最终解决方案将更加类型安全。)this是一个自定义视图,包含view并继承自LinearLayout

   private void expandView( final View view ) {

      view.setVisibility(View.VISIBLE);

      LayoutParams parms = (LayoutParams) view.getLayoutParams();
      final int width = this.getWidth() - parms.leftMargin - parms.rightMargin;

      view.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
              MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

      final int targetHeight = view.getMeasuredHeight();

      view.getLayoutParams().height = 0;

      Animation anim = new Animation() {
         @Override
         protected void applyTransformation( float interpolatedTime, Transformation trans ) {
            view.getLayoutParams().height =  (int) (targetHeight * interpolatedTime);
            view.requestLayout();
         }

         @Override
         public boolean willChangeBounds() {
            return true;
         }
      };

      anim.setDuration( ANIMATION_DURATION );
      view.startAnimation( anim );
   }

这几乎就是问题所在了,但我又犯了一个错误。在视图层次结构中涉及到两个自定义视图,它们都是LinearLayout的子类,并且它们的xml与merge标签合并为xml根标签。在其中一个merge标签中,我忽略了设置android:orientation属性为vertical

对于布局本身,这并不太影响,因为在这个合并的中只有一个子,因此我实际上看不到屏幕上错误的方向。但是它确实存在,并且有点破坏了这种布局测量逻辑。

除了以上内容外,可能需要摆脱视图层次结构中与LinearLayout和其子类相关的所有填充。将它们替换为边距,即使您必须包装另一个布局才能使其看起来相同。


1
我浪费了两天时间。谢谢。 - aurelianr
请注意,此代码完成后,“view”的“LayoutParams”将不再相同。动画结束后,视图尺寸将设置为平面像素值。如果之前将其设置为“WRAP_CONTENT”,并且视图内的内容在此之后发生了变化,则可能会出现问题。 - Peter
你的解决方案似乎不再起作用了,我是这样让它工作的:(对于格式化抱歉) int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(parentLayout.getWidth(), View.MeasureSpec.UNSPECIFIED); int heigthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); layout.measure(widthMeasureSpec, heigthMeasureSpec); int layoutHeight = layout.getMeasuredHeight();// 在屏幕上方移动我的布局,以便在需要下拉动画时进行准备 float height = (float) layoutHeight ; layout.setTranslationY(-height); - Harmen
完美的答案。 - Omid Naji

16

从技术上讲,您可以在视图上调用measure方法,然后通过getMeasuredHeight获取其高度。有关更多信息,请参见:https://developer.android.com/guide/topics/ui/how-android-draws.html。不过您需要给它一个MeasureSpec

但实际上,视图的大小受其父布局的影响,因此您可能会得到与实际绘制时不同的尺寸。

此外,您可以尝试在动画中使用RELATIVE_TO_SELF值。


确实,getMeasuredSpec返回的高度是不同的。目前为34与137。 我在我的第一条消息中编辑了更多信息。 - anonymous
2
这个答案是正确的,但缺少代码是点赞较少的简单原因。 - Warpzit
一定要调用 getMeasuredHeight(),而不是 getHeight()!我忽略了这一点,浪费了一些时间以为它不起作用。 - SMBiggs
另外,如果您不关心剪辑,也可以调用 measure(0,0)。 这样也能正常工作。 - SMBiggs

12
static void getMeasurments(View v) {

    v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), 
      View.MeasureSpec.EXACTLY),
         View.MeasureSpec.makeMeasureSpec(0, 
      View.MeasureSpec.UNSPECIFIED));

    final int targetHeight = v.getMeasuredHeight();
    final int targetWidth = v.getMesauredWidth();
}

3
虽然这个解决方案对我有效,但请下次解释一下代码的作用和原因。 - dedda1994
3
PARENT_VIEW是什么? - Leo DroidCoder
这个可以运行。但是,在第一次计算时,它会将隐藏视图的高度也计算在内。 - Nigam Patro
真棒的解决方案! - G_MAN
非常棒的答案,是唯一一个对我有效的! - Alex_Skvortsov

8

如果父元素没有确定其大小,请自行测量:

这里有一个 Kotlin 扩展可以始终为您完成此操作:

fun View.getMeasurments(): Pair<Int, Int> {
    measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
    val width = measuredWidth
    val height = measuredHeight
    return width to height
}

3

虽然此答案一开始似乎是有效的,但最终我注意到它会影响被测量的视图。 我通过传递父视图来解决了这个问题。

/**
 * class to map width and height values.
 */
data class Point(val width: Int, val height: Int)

/**
 * Measures the view and returns the width and height.
 */
fun View.getMeasurements(parent: View): Point {
    measure(View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED))

    return Point(measuredWidth, measuredHeight)
}

我还创建了一个数据类,以便更容易地理解值所代表的含义。 现在可以像这样访问结果值:

child.getMeasurements(parent).width

或者这样

child.getMeasurements(parent).height

0

好的,关于更新的帖子,以下是可以帮助你的内容: Animation#initialize 方法。你应该在那里放置初始化而不是构造函数,这样你就可以得到所有需要的尺寸。


嗯...我不明白。除非视图可见且未消失,否则高度也为0。 - anonymous
2
尝试使用INVISIBLE而不是GONE - Fixpoint
可能是一个不错的提示。但现在动画并不总是立即开始。有时只有在我滚动或以某种方式与UI交互后才会开始。 - anonymous
你如何将动画分配给视图?使用哪种方法和什么方法? - Fixpoint
通过layout.startAnimation(anim);我无法解决问题; 但是,通过调用layout.requestLayout(),我成功地解决了问题。现在唯一的问题是,我必须为要扩展的布局设置android:layout_marginBottom="-9999dip",否则我无法将其设置为不可见而非消失。 - anonymous

-7

你可以尝试从可见视图开始,然后添加类似以下的内容:

view.post(new Runnable() {      
    @Override
    public void run() {
        // hide view here so that its height has been already computed
        view.setVisibility(View.GONE);
    }
});

现在当您调用view.getHeight()时,它应该返回您期望的高度。

我不确定这是否真的是一个好方法,但至少它应该可以工作。


如果你这样做,它会先显示视图,然后再执行操作。在我们想要根据视图参数设置参数的情况下,我们需要做一些其他的事情。 - lokoko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接