런타임시 Android보기의 크기 결정


123

내 활동이 생성 된 후 Android 앱의보기에 애니메이션을 적용하려고합니다. 이렇게하려면 뷰의 현재 크기를 확인한 다음 현재 크기에서 새 크기로 크기를 조정할 애니메이션을 설정해야합니다. 뷰가 사용자의 입력에 따라 다른 크기로 조정되므로이 부분은 런타임에 수행해야합니다. 내 레이아웃은 XML로 정의됩니다.

이것은 쉬운 작업처럼 보이며 분명히 내 문제를 해결하지 못했지만 이것에 관한 많은 질문이 있습니다. 그래서 아마도 나는 분명한 것을 놓치고 있습니다. 나는 다음과 같이 내 견해에 대한 핸들을 얻습니다.

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

이 잘 작동하지만, 호출 할 때 getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, 등, 그들은 모두 0을 반환은 또한 전화를 수동으로 시도 measure()를 호출 다음에보기에 getMeasuredWidth(),하지만 아무 효과가 없습니다.

이 메서드를 호출하고 내 활동 onCreate()과 .NET의 디버거에서 개체를 검사 해 보았습니다 onPostCreate(). 런타임에이 뷰의 정확한 치수를 어떻게 알 수 있습니까?


1
아, 또한 뷰 자체의 너비 / 높이가 0 이 아니라는 점에 유의해야합니다 . 화면에 잘 나타납니다.
Nik Reiman

xml 레이아웃에서 <ImageView ... /> 태그를 게시 할 수 있습니까?
fhucho

필자의 경우 View의 크기가 실제 화면과 일치한다는 것을 깨달을 때까지이 문제에 대한 많은 SO 솔루션으로 고생했습니다 (내 앱은 "몰입 형"이고 View 및 모든 부모의 너비 및 높이는로 설정 됨 match_parent). 이 경우보기를 그리기 전에 안전하게 호출 할 수있는 더 간단한 솔루션 (예 : Activity의 onCreate () 메서드에서)은 Display.getSize ()를 사용하는 것입니다. 자세한 내용은 stackoverflow.com/a/30929599/5025060 을 참조하십시오. 나는 이것이 @NikReiman이 요청한 것이 아니라는 것을 알고 있습니다.이 간단한 방법을 사용할 수있는 사람들에게 메모를 남깁니다.
CODE-REaD

답변:


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);? 더 이상 사용되지 않기 때문입니다.
Sibbs는 도박

7
@FarticlePilter, 대신 removeOnGlobalLayoutListener ()를 사용하십시오. 문서에 언급되어 있습니다.
Vikram Bodicherla 2014 년

64

시나리오에 따라 실제로 여러 솔루션이 있습니다.

  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에 추가해야합니다 ( 여기 에서 찾을 수 있음 ).

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

1
솔루션 # 1이 항상 OnPreDrawListener. 활동에서 코드를 실행 onCreate()하고 즉시 앱을 백그라운드 로 실행하려는 경우 테스트 된 사례 입니다. 특히 느린 장치에서 반복하기 쉽습니다. 당신이 바꿀 경우 OnPreDrawListenerOnGlobalLayoutListener- 당신은 동일한 문제가 표시되지 않습니다.
질문자

@questioner 이해가 안되지만 결국 도움이되어 기쁩니다.
안드로이드 개발자

난 보통 사용하지만 ViewTreeObserverpost, 내 경우에는 2 번 도움이 ( view.measure).
CoolMind

3
@CoolMind Kotlin을 사용하는 경우 새로운 방법을 사용할 수도 있습니다. 그것을 포함하도록 답변을 업데이트했습니다.
안드로이드 개발자

kotlin에 추가 기능이있는 이유는 무엇입니까? Google은 미쳤습니다.이 언어는 쓸모가 없습니다 (Java와 함께 있어야 함), Swift 보다 훨씬 오래되었지만 Swift만이 충분한 인기를 얻었습니다. tiobe.com/tiobe-index(swift 12 위치 및 kotlin 38)
user924

18

getWidth()보기가 실제로 화면에 배치되기 전에 전화 하고 있습니까?

새로운 Android 개발자가 범하는 일반적인 실수는 생성자 내부에서 뷰의 너비와 높이를 사용하는 것입니다. 뷰의 생성자가 호출되면 Android는 뷰의 크기를 아직 알지 못하므로 크기가 0으로 설정됩니다. 실제 크기는 레이아웃 단계에서 계산되며, 이는 시공 후 아무것도 그려지기 전에 발생합니다. onSizeChanged()메소드를 사용하여 값이 알려진 경우 알림을 받거나 메소드에서와 같이 나중에 getWidth()getHeight()메소드를 사용할 수 있습니다 onDraw().


2
그렇다면 onSizeChange()실제 크기를 알기 위해 이벤트를 포착하기 위해 ImageView를 재정의해야 함을 의미 합니까? 그것은 약간의 과잉 인 것처럼 보입니다.하지만이 시점에서 저는 코드가 작동하게하기 위해 필사적이므로 한 번 시도해 볼 가치가 있습니다.
Nik Reiman

나는 당신의 onCreate ()가 당신의 활동에서 끝날 때까지 기다리는 것보다 더 생각했습니다.
Mark B

1
언급했듯이 onPostCreate()뷰가 완전히 생성되고 시작된 후에 실행되는 이 코드를 넣어 보았습니다 .
Nik Reiman

1
인용 된 텍스트는 어디에서 왔습니까?
Intrications

1
우리가 일반적으로 뷰의 크기에 대해 질문 할 때 사용자 지정보기이 인용, 같은 대답을 게시하지 않습니다
user924

17

@mbaird의 조언에 따라 ImageView클래스 를 서브 클래 싱하고을 재정 의하여 실행 가능한 솔루션을 찾았습니다 onLayout(). 그런 다음 내 활동이 구현 된 관찰자 인터페이스를 만들고 클래스에 자신에 대한 참조를 전달하여 실제로 크기 조정이 완료되었을 때 활동에 알릴 수있었습니다.

나는 이것이 최선의 해결책이라고 100 % 확신하지는 못하지만 (따라서이 답변을 아직 올바른 것으로 표시하지 않음) 작동하며 문서에 따르면 뷰의 실제 크기를 찾을 수있는 것은 처음입니다.


알아 둘만 한. View를 서브 클래 싱해야하는 상황을 해결할 수있는 것이 있으면 여기에 게시하겠습니다. onPostCreate ()가 작동하지 않아서 조금 놀랐습니다.
Mark B

글쎄, 나는 이것을 시도했고 작동하는 것 같습니다. 이 메서드는 처음에 한 번뿐 아니라 자주 호출되기 때문에 실제로 최상의 솔루션은 아닙니다. :(
Tobias Reich

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이됩니다. 편집 : 오, btw는 1이 밀리 초 단위이므로 확실히 실패합니다.
Alex

4

또한 주변의 분실 getMeasuredWidth()getMeasuredHeight() getHeight()getWidth()오랜 시간 동안 .......... 나중에 내가있는 뷰의 폭과 높이를 점점 것을 발견 onSizeChanged()동적 수 ........이 작업을 수행하는 가장 좋은 방법입니다 onSizeChanged() 메서드 를 재정 의하여 뷰의 CURRENT 너비와 CURRENT 높이를 가져옵니다 .

정교한 코드 스 니펫이있는 이것을보고 싶을 것입니다. 새 블로그 게시물 : Android에서 customView (View 확장)의 너비 및 높이 치수를 얻는 방법 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;
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.