ビューのgetWidth()およびgetHeight()は0を返します


513

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

どんな助けでもありがたいです。

回答:


197

あなたは呼んでいるgetWidth()のは時期尚早。UIはまだサイズ変更されておらず、画面上にレイアウトされていません。

とにかく、あなたがしていることをしたいのではないかと思います-アニメーション化されているウィジェットはクリック可能な領域を変更しないので、ボタンは回転の仕方に関係なく、元の方向のクリックに応答します。

つまり、ディメンションリソースを使用してボタンのサイズを定義し、レイアウトファイルとソースコードからそのディメンションリソースを参照して、この問題を回避できます。


82
ngreenwood6、あなたの他の解決策は何ですか?
Andrew

12
@Andrew-フォローアップの通知をnegreenwood6に送信したい場合は、私と同じようにメッセージを開始する必要があります(最初の3文字で十分だと思います)-CommonsWareは自動的に通知を受け取ります。あなたがそれらに対処しない限り、そうではありません。
Peter Ajtai

11
@Override public void onWindowFocusChanged(boolean hasFocus){// TODO自動生成されたメソッドスタブsuper.onWindowFocusChanged(hasFocus); //ここでサイズを取得できます!}
サナ

5
@ ngreenwood6では、あなたのソリューションは何でしたか?
Nima Vaziri

5
このリスナーを使用して、画面の準備ができたときにサイズを取得します。。view.getViewTreeObserver()addOnGlobalLayoutListener(新しいViewTreeObserver.OnGlobalLayoutListener(){}
シナンDizdarević

816

基本的な問題は、実際の測定の描画フェーズを待つ必要があることです(特にwrap_contentまたはのような動的な値の場合match_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.ランナブルをレイアウトキューに追加します:View.post()

あまり知られていない、私のお気に入りのソリューション。基本的には、独自のランナブルでビューのpostメソッドを使用するだけです。これは基本的に後にコードキューに入れますRomain Guyによって述べられているよう、ビューのメジャー、レイアウトなどのに。

UIイベントキューはイベントを順番に処理します。setContentView()が呼び出された後、イベントキューには再レイアウトを要求するメッセージが含まれるため、キューに投稿したものはすべてレイアウトパスの後に発生します

例:

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

上の利点 ViewTreeObserver

  • コードは一度だけ実行され、実行後にオブザーバーを無効にする必要はありません。
  • 冗長な構文

参照:


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

ウィンドウに最後にアタッチまたはデタッチされてからビューが少なくとも1つのレイアウトを通過した場合、trueを返します。


追加:静的に定義された測定値の取得

静的に定義された高さ/幅を取得するだけで十分な場合は、次のように実行できます。

ただし、これは描画後の実際の幅/高さとは異なる場合があることに注意してください。javadocは違いを完全に説明しています:

ビューのサイズは、幅と高さで表されます。ビューは実際には2組の幅と高さの値を持っています。

最初のペアは、測定された幅と測定された高さとして知られています。これらの寸法は、ビューが親の中にどの程度の大きさになるかを定義します(詳細については、「レイアウト」を参照してください)。測定寸法は、getMeasuredWidth()およびgetMeasuredHeight()を呼び出すことで取得できます。

2番目のペアは、単に幅と高さ、または描画幅と描画高さとして知られています。これらの寸法は、描画時およびレイアウト後の画面上のビューの実際のサイズを定義します。これらの値は、測定された幅と高さとは異なる場合があります。幅と高さはgetWidth()とgetHeight()を呼び出すことで取得できます。


1
フラグメントでview.postを使用しています。親の高さが0 dpで、重みが設定されているビューを測定する必要があります。私が試したものはすべて高さゼロを返します(グローバルレイアウトリスナーも試してみました)。125の遅延でonViewCreatedにview.postコードを配置すると、機能しますが、遅延がないわけではありません。アイデア?
Psest328 2015年

6
残念ながら、回答を1度だけ賛成できます。アプリを終了して最近のアプリから再起動すると、ビューの回転に問題が発生しました。そこからスワイプして再起動した場合、すべて問題ありませんでした。view.post()は私の一日を救った!
Matthias

1
ありがとう。通常はView.post()(2番目のメソッド)を使用しますが、バックグラウンドスレッドから呼び出された場合、動作しないことがあります。だから、書くことを忘れないでくださいrunOnUiThread(new Runnable() {...}。現在、1つ目の方法のバリエーションを使用していますaddOnLayoutChangeListener。内部で削除することを忘れないでください:removeOnLayoutChangeListener。バックグラウンドスレッドからも機能します。
CoolMind 2016

2
残念ながら、 'View.post()'は常に機能しているわけではありません。非常によく似た2つのプロジェクトがあります。1つのプロジェクトでは機能し、もう1つのプロジェクトでは機能しません。:(。どちらの状況でも、アクティビティから 'onCreate()'メソッドからメソッドを呼び出します。理由は何かありますか?
Adrian Buciuman

1
問題が見つかりました。1つのプロジェクトではビューがありandroid:visibility="gone"、もう1つのプロジェクトでは可視性プロパティがありましたinvisible。視認性があればgone幅は常に0になります
エイドリアンBuciuman

262

使用できます

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

10
では、どのようにしてフラグメントで作業できますか?これはアクティビティでのみ機能しますか?
Olkunmustafa 2015年

8
これでうまくいくはずですが、答えにはなりません。ユーザーは、ウィンドウに描画されるのではなく、いつでも寸法を取得したいと考えています。また、このメソッドは、ウィンドウに変更(ビューの非表示、削除、新しいビューの追加など)があるたびに、または毎回呼び出されます。したがって、慎重に使用してください:)
Dr. aNdRO、2015

1
これはハックであり、ViewTreeObserverを使用する方が良いようです。
i_am_jorf 2015年

失礼に聞こえるつもりはありませんが、自動生成されたコメント(特に2行のコード)を残しても、自分が何をしているのか実際にわかっているという自信はあまりありません。
Natix 2016

残念ながら、それは私にはうまくいきませんでした。viewTreeObserverを試すとうまくいきました。
Narendra Singh

109

私はこのソリューションを使用しましたが、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
これは断然最高の解決策です!これは、幅と高さが不明で計算されない動的なソリューションでも機能するためです。つまり、他の要素の位置/サイズに基づく相対レイアウトで。よくやった!
George Pligoropoulos 2013

2
これにはAPI 16以降(Jelly Bean)が必要です
tomsv 14

7
API 16は必要ありません。唯一の問題は、API 16から非推奨メソッドremoveGlobalOnLayoutListenerですが、シンプルなすべてのAPIレベルで互換性の解決策がある- stackoverflow.com/questions/16189525/...
gingoは

自分自身を何度も呼び出さないようにリスナーを削除する必要があり、さまざまなAPIで問題が発生している場合は、クラスの先頭にfalseブール値を設定し、値をキャプチャした後でtrueに設定して、再度値を取得することはありません。
Mansour Fahad

最初の実行後にリスナーを削除すると、誤った寸法になる可能性があります:02-14 10:18:53.786 15063-15063 / PuzzleFragment:height:1647 width:996 02-14 10:18:53.985 15063-15063 / PuzzleFragment:height :1500幅:996
Leos Literak、2016

76

このAndroid DevelopersスレッドでIanが述べているように

とにかく、契約はウィンドウのコンテンツのレイアウトが後に発生するということです すべての要素が作成されて親ビューに追加された。ビューに含まれるコンポーネント、コンポーネントに含まれるものなどがわかるまでは、レイアウトするための賢明な方法がないためです。

結論として、コンストラクタでgetWidth()などを呼び出すと、ゼロが返されます。手順は、すべてのビュー要素をコンストラクターで作成し、ビューのonSizeChanged()メソッドが呼び出されるのを待ちます。これは、実際のサイズを最初に見つけたときなので、GUI要素のサイズを設定します。

onSizeChanged()がゼロのパラメーターで呼び出される場合があることにも注意してください。この場合を確認して、すぐに戻ります(レイアウトの計算時にゼロで除算しないようにします)。しばらくすると、実際の値で呼び出されます。


8
onSizeChangedは私のために働いた。onWindowFocusedChangedがカスタムビューで呼び出されません。
aaronmarino 2013

これは良い解決策に見えますが、onSizeChangedメソッドをオーバーライドするために常にカスタムビューを作成する必要がありますか?
M.ElSaka 2013年

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.それはビンゴです。私の問題の根源。
MikeNereson 2015年

私の場合、他のいくつかのソリューションが0を返すため、フラグメントに対してこのソリューションをお勧めします。
WindRider 2015年

66

画面に表示される前にウィジェットの幅を取得する必要がある場合は、getMeasuredWidth()またはgetMeasuredHeight()を使用できます。

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

5
どういうわけか、これは私にとってうまくいった唯一の答えでした-ありがとう!
Farbod Salamat-Zadeh 2015

私に働いていた私に同じ、最も簡単な解決策、
TIMA

魅力のように働きます!
Morales Batovski 2017

1
@CommonsWareは、このソリューションがグローバルレイアウトコールバックでビューツリーオブザーバーの前に高さと幅をどのように与えるかを説明しています。
Mightian

22

他のリスナーより少し早く呼び出されるので、の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;
        }
    });

3
これreturn false、現在の描画パスキャンセルすることを意味ます。return true代わりに続行します。
2018

9

グローバルレイアウトで観察し、高さが動的に準備ができたときに特定のタスクを実行する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)
}

7

AndroidXには、この種の作業を支援する複数の拡張機能があります。 androidx.core.view

これにはKotlinを使用する必要があります。

ここに最も適合するものはdoOnLayout次のとおりです。

このビューがレイアウトされるときに、指定されたアクションを実行します。ビューがレイアウトされていて、レイアウトを要求していない場合、アクションはすぐに実行されます。それ以外の場合は、ビューが次にレイアウトされた後にアクションが実行されます。

アクションは次のレイアウトで1回だけ呼び出され、その後削除されます。

あなたの例では:

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

依存: androidx.core:core-ktx:1.0.0



1
これらの拡張機能は本当に重要で便利です
Ahmed na

5

RxJavaRxBindingsを使用している場合は1つのライナー。ボイラープレートなしの同様のアプローチ。これはまた、ハックを解決し、Tim Autinの回答のように警告を抑制します。

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

これだよ。呼び出されていなくても、登録を解除する必要はありません。


4

これは、ビューがインフレートされるまでにさらに時間がかかるためです。したがって、メインスレッドでview.widthand を呼び出す代わりにview.height、を使用view.post { ... }viewて、すでにインフレートされていることを確認する必要があります。コトリンでは:

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

JavaではgetWidth()、とのgetHeight()メソッドを呼び出してRunnableRunnableto view.post()メソッドを渡すこともできます。

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

投稿されたコードの実行時にビューが測定およびレイアウトされない可能性があるため、これは正解ではありません。これはエッジケース.postになりますが、ビューのレイアウトを保証するものではありません。
d.aemon

1
この答えは素晴らしく、私にとってはうまくいきました。Ehsanにありがとう
MMG

3

ビューの高さと幅を要求するまでにビューが作成されていないため、高さと幅はゼロです。最も簡単な解決策は

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

この方法は短くてカリッとするため、他の方法と比較して優れています。


3

多分これは誰かを助ける:

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
}

2

回答は、postサイズが再計算されない場合がありますので、間違っています。
もう1つの重要なことは、ビューとそのすべての祖先が可視でなければならないということです。そのために私はプロパティを使用します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)
}

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

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

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.