66

For an animation I need to know the height from a View. The Problem is, that the getHeight() method allways return 0 unless the View is drawn. So is there any way to get height without drawing it?

In this case the View is a LinearLayout.

EDIT: I try to adapt the expand Animation from https://github.com/Udinic/SmallExamples/blob/master/ExpandAnimationExample/src/com/udinic/expand_animation_example/ExpandAnimation.java

With it I want to expand some more informations for a list item. I wasn´t able to achieve the same effect via xml. At the moment the Animation only works when you know the layout size before drawing it.

anonymous
  • 1,141
  • 3
  • 11
  • 20

8 Answers8

139

Sounds like you want to get the height but hide the view before it is visible.

Have the visibility in the view set to visible or invisible to start with(just so that a height is created). Don't worry we will change it to invisible/gone in the code as shown below:

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

});
Jim Baca
  • 6,112
  • 2
  • 24
  • 30
  • 13
    Thank you, it works! Just a little addition: removeGlobalOnLayoutListener() is deprecated since API level 16, it's replaced by removeOnGlobalLayoutListener() – Yulia Rogovaya Dec 05 '12 at 13:36
  • Thanks helped me a lot. the measuring of the views are buggy. – string.Empty Jul 05 '13 at 08:17
  • 3
    Thanks for this tip, it worked very well. Side note, the fact that the argument's name for the removeOnGlobalLayoutListener is "victim" is fantastic. – PGMacDesign Jul 10 '17 at 20:09
  • 4
    This won't always work - first of all, the view will flash visible before disappearing, which is not good for UI. Second, if you start the app and immediately switch to another app, the height seems to not be calculated properly for whatever reason. – ArturT Feb 11 '18 at 21:44
34

I had a similar issue with a similar situation.

I had to create an anmiation for which I required the height of the view that is subject to the animation. Within that view, which is a LinearLayout may be child view of type TextView. That TextView is multi line and the number of lines actually required varies.

Whatever I did I just got the measured height for the view as if the TextView has just one line to fill. No wonder this worked fine every time when the text happend to be short enough for a single line, but it failed when the text was longer than on line.

I considered this answer: https://stackoverflow.com/a/6157820/854396 But that one did not work too well for me because I am not able to access the embedded TextView directly from here. (I could have iterated though the tree of child views though.)

Have a look at my code as it works for now:

view is the view that is to be animated. It is a LinearLayout (The final solution will be a bit more type save here) this is a custom view that contains view and inherits from LinearLayout too.

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

This was nearly it but I made another mistake. Within the view hierarchy there are two custom views involved which are subclasses of LinearLayout and their xml is merged into with a merge tag as xml root tag. Within one of these merge tags I overlooked to set the android:orientation attribute to vertical. That did not bother too much for the layout itself, because within this merged ViewGroup was just one child ViewGroup and therfore I could not actually see the wrong orientation on screen. But it was there and sort of broke this layout measurement logic a bit.

In addition to the above it may be required to get rid of all paddings associated with LinearLayouts and subclasses within the view hierarchy. Replace them by margins, even if you have to wrap another layout around it to make it look the same.

Hermann Klecker
  • 14,039
  • 5
  • 48
  • 71
  • 1
    I lost two days. Thanks. – aurelianr Mar 22 '19 at 14:33
  • Note that, after this code finishes, `view`'s `LayoutParams` are not the same anymore. After animation ends, view dimensions will be set to flat pixel value. Which may be problematic, if it was previously set to `WRAP_CONTENT`, and content inside view somehow changes afterwards. – Peter Jun 29 '20 at 12:56
  • Somehow your solution doesn't work anymore, I got it to work like this: (sorry for formatting) 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(); // Move my layout above the screen to prepare for drop down animation when needed float height = (float) layoutHeight ; layout.setTranslationY(-height); – Harmen Oct 25 '21 at 13:08
  • perfect answer. – Omid Naji Jan 28 '22 at 13:09
16

Technically, you can call measure method on the view and then get its height via getMeasureHeight. See this for more info: https://developer.android.com/guide/topics/ui/how-android-draws.html. You will need to give it a MeasureSpec though.

But in reality, the view's size is influenced by its parent layout, so you may get a different size than when it's actually drawn.

Also, you may try using RELATIVE_TO_SELF values in your animations.

Daniel F
  • 13,684
  • 11
  • 87
  • 116
Fixpoint
  • 9,619
  • 17
  • 59
  • 78
  • Indeed the height returned by getMeasuredSpec is different. At the moment 34 vs 137. I edited my first message with some more informations. – anonymous Mar 06 '12 at 00:09
  • 2
    This answer is the right answer, but the lack of code is the simple reason for less upvotes. – Warpzit Feb 17 '16 at 10:42
  • Make sure to call `getMeasuredHeight()`, not `getHeight()` after! I neglected this little bit and spent a while thinking this didn't work. – SMBiggs Oct 23 '19 at 20:25
  • Also, you can call `measure(0,0)` if you don't care about clipping. Works fine. – SMBiggs Oct 23 '19 at 20:25
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();
}
Anubhav
  • 1,984
  • 22
  • 17
8

just measure it yourself if the parent does not determine its size:

here is a kotlin extension on view to do it for you always:

fun View.getMeasurments(): Pair<Int, Int> {
    measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
    val width = measuredWidth
    val height = measuredHeight
    return width to height
}
j2emanue
  • 60,549
  • 65
  • 286
  • 456
3

While this answer seems to work at first, eventually I noticed that it messed up the view that is being measured. I fixed it by passing the parent view.

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

I also created a data class so it becomes more readable what the values represent. You can now access the result values like this

child.getMeasurements(parent).width

or this

child.getMeasurements(parent).height
Wirling
  • 4,810
  • 3
  • 48
  • 78
0

Ok, regarding the updated post, here is what will help you: Animation#initialize method. You should just put initialization there instead of the constructor, and you'll have all the sizes you need there.

Fixpoint
  • 9,619
  • 17
  • 59
  • 78
  • Hm... I don´t understand. There the height is also 0 unless the View is visible and not gone. – anonymous Mar 06 '12 at 16:35
  • 2
    Try using `INVISIBLE` instead of `GONE`. – Fixpoint Mar 06 '12 at 16:57
  • Could be a good hint. But now the animation doesn´t start allways immediately. Sometimes first if I scroll or interact with the ui in some way. – anonymous Mar 06 '12 at 20:01
  • How do you assign the animation to the view? In what method and with what method? – Fixpoint Mar 06 '12 at 22:09
  • With layout.startAnimation(anim); But I could fix the problem with calling layout.requestLayout(); Now the only problem is, that I have to set android:layout_marginBottom="-9999dip" for the layout which shell be expanded, because otherwise I can´t set it invisible instead of gone. – anonymous Mar 07 '12 at 00:23
-7

You could try to start with the View visible, and then add something like this:

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

Now when you will call view.getHeight() it should return the height you expect.

I'm not sure if it's really a good way to do it, but at least it should work.

YuviDroid
  • 1,546
  • 11
  • 12
  • If you do this then it will display the view first and then do this. In the case where we want to set the params based on a views params then we would want to do something else. – lokoko Jan 16 '13 at 17:42