뷰의 getWidth () 및 getHeight ()는 0을 반환합니다.


513

내 안드로이드 프로젝트의 모든 요소를 ​​동적으로 만들고 있습니다. 버튼의 너비와 높이를 얻으려고 노력하여 버튼을 회전시킬 수 있습니다. 안드로이드 언어로 작업하는 법을 배우려고합니다. 그러나 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, 다른 솔루션은 무엇입니까?
앤드류

12
@Andrew-negreenwood6에게 후속 조치를 알리려면 메시지를 시작해야합니다 (처음 세 글자로 충분하다고 생각합니다)-CommonsWare는이 응답을 썼기 때문에 자동으로 알림을 받지만 ngreen은 당신이 그들을 해결하지 않는 한.
피터 아지 타이

11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO 자동 생성 메소드 스텁 super.onWindowFocusChanged (hasFocus); // 크기를 얻을 수 있습니다! }
Sana

5
@ ngreenwood6 그래서 당신의 해결책은 무엇입니까?
Nima Vaziri

5
화면이 준비되면 크기를 얻으려면이 리스너를 사용하십시오. view.getViewTreeObserver (). addOnGlobalLayoutListener (신규 ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević 2016.

815

기본적인 문제는 (특히 같은 동적 값과 실제 측정 도면 상 기다릴 필요가 있다는 것입니다 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. 레이아웃 대기열에 Runnable을 추가합니다 : View.post ()

잘 알려지지 않은 솔루션과 내가 가장 좋아하는 솔루션. 기본적으로 View의 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();
}

뷰가 창에 마지막으로 연결되거나 분리 된 후 하나 이상의 레이아웃을 통과 한 경우 true를 리턴합니다.


추가 : 정적으로 정의 된 측정 값 얻기

정적으로 정의 된 높이 / 너비를 얻는 것으로 충분하다면 다음과 같이하면됩니다.

그러나 이것은 드로잉 후 실제 너비 / 높이와 다를 수 있습니다. javadoc은 차이점을 완벽하게 설명합니다.

뷰의 크기는 너비와 높이로 표시됩니다. 뷰에는 실제로 두 쌍의 너비 및 높이 값이 있습니다.

첫 번째 쌍은 측정 된 너비와 측정 된 높이라고합니다. 이러한 차원은 뷰가 부모 내에 얼마나 큰지를 정의합니다 (자세한 내용은 레이아웃 참조). getMeasuredWidth () 및 getMeasuredHeight ()를 호출하여 측정 된 차원을 얻을 수 있습니다.

두 번째 쌍은 단순히 너비와 높이 또는 때때로 그리기 너비와 그리기 높이라고합니다. 이러한 치수는 도면 시간과 배치 후 화면에서 실제보기 크기를 정의합니다. 이 값들은 측정 된 폭과 높이와 다를 수 있지만 반드시 그런 것은 아닙니다. 너비와 높이는 getWidth () 및 getHeight ()를 호출하여 얻을 수 있습니다.


1
조각에서 view.post를 사용하고 있습니다. 부모의 높이가 0dp이고 가중치가 설정된보기를 측정해야합니다. 내가 시도하는 모든 것은 높이를 0으로 반환합니다 (글로벌 레이아웃 리스너도 시도했습니다). 125의 지연으로 view.post 코드를 onViewCreated에 넣으면 지연없이 작동하지 않습니다. 아이디어?
Psest328

6
너무 나쁘면 대답을 한 번만 올릴 수 있습니다. 정말 좋은 설명입니다. 앱을 종료하고 최근 앱에서 다시 시작하면 뷰 회전에 문제가있었습니다. 내가 거기에서 그것을 강타하고 새로 다시 시작하면 모든 것이 잘되었습니다. view.post ()가 내 하루를 구했습니다!
Matthias

1
감사. 일반적으로 View.post () (두 번째 방법)를 사용하지만 백그라운드 스레드에서 호출하면 때로는 작동하지 않습니다. 따라서 작성하는 것을 잊지 마십시오 runOnUiThread(new Runnable() {...}. 현재 첫 번째 방법의 변형을 사용합니다 addOnLayoutChangeListener. removeOnLayoutChangeListener 내부에서 제거해야합니다. 백그라운드 스레드에서도 작동합니다.
CoolMind

2
불행히도 'View.post ()'는 항상 작동하지 않습니다. 나는 매우 비슷한 2 개의 프로젝트가 있습니다. 한 프로젝트에서는 작동하고 다른 프로젝트는 작동하지 않습니다. . :( 두 상황에서 나는 '에서 onCreate ()'액티비티에서 방법에서 메소드를 호출 어떤 제안 이유.?
아드리안 Buciuman

1
문제가 있습니다. 한 프로젝트에서는 뷰가 android:visibility="gone"있고 다른 프로젝트에서는 가시성 속성이있었습니다 invisible. 가시성이 경우 gone폭은 항상 0이 될 것이다
아드리안 Buciuman

262

사용할 수있다

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

10
조각에서 어떻게 작업 할 수 있습니까? 이것은 활동에서만 작동합니까?
Olkunmustafa

8
이것은 일을해야하지만 대답이되어서는 안됩니다. 사용자는 창에 표시되는 대신 임의의 시점에서 치수를 얻으려고합니다. 또한이 메소드는 여러 번 또는 창에 변경 (보기 숨기기, 사라짐, 새보기 추가 등)이있을 때마다 호출됩니다. 따라서 신중하게 사용하십시오 :)
Dr. aNdRO

1
이것은 해킹이며 ViewTreeObserver를 사용하는 것이 더 좋을 것 같습니다.
i_am_jorf

무례하게 들리는 것은 아니지만 자동 생성 된 주석 (특히 두 줄의 코드)을 남겨 두더라도 실제로 수행중인 작업을 실제로 알고 있다는 확신이 없습니다.
Natix

불행히도, 그것은 나를 위해 작동하지 않았습니다. 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

2
API 16 이상이 필요합니다 (Jelly bean)
tomsv

7
API 16이 필요하지 않습니다! 유일한 문제는 API (16)에서 사용되지 않는 방법 removeGlobalOnLayoutListener이지만, 간단한 모든 API 레벨과 호환 해결책이 - stackoverflow.com/questions/16189525/...
gingo는

여러 번 호출되지 않고 다른 API에 문제가있는 리스너를 제거해야하는 경우 클래스 헤드에서 잘못된 부울을 설정 한 다음 값을 캡처 한 후 값을 캡처하여 true로 설정하는 것이 좋습니다. 값을 다시 캡처하지 않습니다.
Mansour Fahad

첫 실행 후 리스너를 제거하면 크기가 잘못 될 수 있습니다. 02-14 10 : 18 : 53.786 15063-15063 / PuzzleFragment : 높이 : 1647 너비 : 996 02-14 10 : 18 : 53.985 15063-15063 / PuzzleFragment : 높이 : 1500 폭 : 996
Leos Literak

76

Ian 이이 Android Developers 스레드 에서 언급했듯이 :

어쨌든, 모든 요소가 구성되고 부모 뷰에 추가 된 창의 내용 레이아웃이 발생 합니다. View에 포함 된 구성 요소와 그 구성 요소 등을 알 때까지 배치 할 수있는 합리적인 방법이 없기 때문에이 방식이어야합니다.

결론적으로 생성자에서 getWidth () 등을 호출하면 0이 반환됩니다. 절차는 생성자에서 모든 뷰 요소를 생성 한 다음 뷰의 onSizeChanged () 메소드가 호출 될 때까지 기다리는 것입니다. 즉, 실제 크기를 처음 찾을 때가되므로 GUI 요소의 크기를 설정할 때입니다.

onSizeChanged ()는 때때로 0의 매개 변수로 호출됩니다.이 경우를 확인하고 즉시 반환하십시오 (그래서 레이아웃 등을 계산할 때 0으로 나누지 않습니다). 얼마 후 실제 값으로 호출됩니다.


8
onSizeChanged가 나를 위해 일했습니다. 내 창에서 onWindowFocusedChanged가 호출되지 않습니다.
aaronmarino 2019

이것은 좋은 해결책처럼 보이지만 onSizeChanged 메서드를 재정의하려면 항상 사용자 정의보기를 만들어야합니까?
M.ElSaka

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.빙고입니다. 내 문제의 근원.
MikeNereson

필자의 경우 다른 솔루션은 0을 반환하기 때문에 조각에 대한이 솔루션을 제안합니다.
WindRider

66

화면에 표시되기 전에 일부 위젯의 너비를 가져와야하는 경우 getMeasuredWidth () 또는 getMeasuredHeight ()를 사용할 수 있습니다.

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

5
어떤 이유로 든, 이것이 나를 위해 일한 유일한 대답이었습니다-감사합니다!
Farbod Salamat-Zadeh

나에게 가장 효과적인 솔루션 인 나와 동일
Tima

매력처럼 일하십시오!
Morales Batovski

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대신 진행하십시오 .
Pang

9

전체 레이아웃을 관찰하고 높이가 동적으로 준비되면 주어진 작업을 수행하기위한 Kotlin Extension.

용법:

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

6

AndroidX에는 이러한 종류의 작업을 도와주는 여러 가지 확장 기능이 있습니다. androidx.core.view

이를 위해 Kotlin을 사용해야합니다.

여기에 가장 적합한 것은 다음과 doOnLayout같습니다.

이 뷰가 배치 될 때 지정된 조치를 수행합니다. 뷰가 배치되었고 레이아웃을 요청하지 않은 경우, 조치가 바로 수행되고 그렇지 않으면 뷰가 다음에 배치 된 후에 조치가 수행됩니다.

액션은 다음 레이아웃에서 한 번만 호출 된 다음 제거됩니다.

귀하의 예에서 :

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

RxJava & RxBindings를 사용하는 경우 하나의 라이너 . 상용구가없는 비슷한 방법. 이것은 또한 Tim Autin 의 답변에서와 같이 경고를 억제하는 핵을 해결합니다 .

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

이거 야. 전화하지 않아도 구독을 취소 할 필요가 없습니다.


4

뷰를 팽창시키는 데 더 많은 시간이 필요하기 때문에 발생합니다. 그래서 그 대신 호출하는 view.widthview.height메인 스레드에, 당신은 사용해야 view.post { ... }확실히 당신이 있는지 확인하기 위해 view이미 팽창하고있다. 코 틀린에서 :

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

Java에서는 a 및 메소드를 호출 getWidth()하고 to 메소드를 전달할 수도 있습니다 .getHeight()RunnableRunnableview.post()

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

게시 된 코드가 실행될 때 뷰가 측정되고 배치되지 않을 수 있으므로 정답이 아닙니다. 엣지 케이스 일 수 있지만 .post뷰의 배치를 보장하지는 않습니다.
d.aemon

1
이 답변은 훌륭했고 저에게 효과적이었습니다. 대단히 감사합니다 Ehsan
MMG

3

height 및 width를 요청하는 시점까지 view가 작성되지 않았으므로 height 및 width는 0입니다. 가장 간단한 해결책은

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크기가 다시 계산되지 않을 수 있으므로로 답 이 잘못되었습니다.
또 다른 중요한 점은 조망과 조망이 모두 보여야한다는 것 입니다. 이를 위해 나는 속성을 사용합니다 View.isShown.

utils 어딘가에 배치 할 수있는 kotlin 함수는 다음과 같습니다.

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를 사용하십시오. 코 틀린 예 :

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.