Çalışma zamanında bir Android görünümünün boyutunu belirleme


123

Etkinliğim oluşturulduktan sonra Android uygulamamdaki bir görünüme animasyon uygulamaya çalışıyorum. Bunu yapmak için, görünümün mevcut boyutunu belirlemem ve ardından mevcut boyuttan yeni boyuta ölçeklemek için bir animasyon ayarlamam gerekiyor. Görünüm, kullanıcıdan gelen girdiye bağlı olarak farklı boyutlara ölçeklendiğinden, bu bölüm çalışma zamanında yapılmalıdır. Düzenim XML olarak tanımlandı.

Bu kolay bir görev gibi görünüyor ve bununla ilgili pek çok SO sorusu var, ancak hiçbiri benim sorunumu çözmedi. Bu yüzden belki de bariz bir şeyi kaçırıyorum. Görüşümü şu şekilde ele alıyorum:

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

Bu cezayı çalışır ancak çağrılırken getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, vb, hepsi dönüş 0. Ayrıca arama elle denedim measure()bir çağrı izledi görünümde getMeasuredWidth(), ama bu hiçbir etkisi olmaz.

Bu yöntemleri çağırmayı ve etkinliğimdeki onCreate()ve içindeki hata ayıklayıcıdaki nesneyi incelemeyi denedim onPostCreate(). Çalışma zamanında bu görünümün tam boyutlarını nasıl anlayabilirim?


1
Ayrıca, görünümün kendisinin kesinlikle 0 genişlik / yüksekliğe sahip olmadığını da belirtmeliyim. Ekranda gayet iyi görünüyor.
Nik Reiman

Xml düzeninden <ImageView ... /> etiketi gönderebilir misiniz?
fhucho

Benim durumumda Görünüm boyutlarının fiziksel ekranla eşleştiğini fark edene kadar bu soruna birçok SO çözümü ile mücadele ettim (uygulamam "sürükleyici" ve Görünüm ve tüm ebeveynlerinin genişlik ve yüksekliklerinin ayarlandığını match_parent). Bu durumda, Görünüm çizilmeden önce bile güvenli bir şekilde çağrılabilen daha basit bir çözüm (örneğin, Aktivitenizin onCreate () yönteminden) sadece Display.getSize () kullanmaktır; Ayrıntılar için stackoverflow.com/a/30929599/5025060 adresine bakın. @NikReiman'ın istediği şeyin bu olmadığını biliyorum, sadece bu basit yöntemi kullanabilenlere bir not bırakıyorum.
CODE-REaD

Yanıtlar:


180

İlk düzeni beklemek için Görünüm üzerindeki ViewTreeObserver'ı kullanın. Yalnızca ilk düzenden sonraWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () çalışacaktır.

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
Altta yatan görünüm bir video / yüzey ise sürekli çağrılacağından bu yanıta dikkat edin
Kevin Parker

7
@ Kevin'i biraz daha detaylandırabilir misin? İki şey, her şeyden önce, bir dinleyici olduğu için, tek bir geri arama bekliyorum ve sonra, ilk geri aramayı alır almaz, dinleyiciyi kaldırıyoruz. Bir şey mi kaçırdım?
Vikram Bodicherla

4
Herhangi bir ViewTreeObserver yöntemini çağırmadan önce isAlive () 'yi kontrol etmeniz önerilir: if (view.getViewTreeObserver (). İsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran

4
herhangi bir alternatif removeGlobalOnLayoutListener(this);? çünkü kullanımdan kaldırıldı.
Sibbs Gambling

7
@FarticlePilter, bunun yerine removeOnGlobalLayoutListener () kullanın. Dokümanlarda bahsediliyor.
Vikram Bodicherla

64

Senaryoya bağlı olarak aslında birden fazla çözüm var:

  1. Güvenli yöntem, yerleşim aşaması bittikten sonra görünümü çizmeden hemen önce çalışacaktır:
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); 
}

Örnek kullanım:

    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. Bazı durumlarda, görünümün boyutunu manuel olarak ölçmek yeterlidir:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Kabın boyutunu biliyorsanız:

    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. Uzattığınız özel bir görünümünüz varsa, boyutunu "onMeasure" yönteminde alabilirsiniz, ancak bunun yalnızca bazı durumlarda iyi çalıştığını düşünüyorum:
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 ile yazarsanız, perde arkasında aynen runJustBeforeBeingDrawnyazdığım gibi çalışan sonraki işlevi kullanabilirsiniz :

    view.doOnPreDraw { actionToBeTriggered() }

    Not vermek için bunu eklemeniz gerektiğini unutmayın ( buradan ulaşabilirsiniz ):

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

1
Görünüşe göre 1 numaralı çözüm her zaman işe yaramayacak OnPreDrawListener. Aktivitelerden kod çalıştırmak istediğinizde onCreate()ve hemen arka planda uygulamayı çalıştırdığınızda durum test edilmiştir . Özellikle daha yavaş cihazlarda tekrar etmesi kolaydır. Eğer değiştirirseniz OnPreDrawListenerile OnGlobalLayoutListener- aynı sorunu görmezsiniz.
questioner

@questioner Anlamıyorum ama sonunda size yardımcı olduğu için mutluyum.
android geliştiricisi

Genellikle kullansam ViewTreeObserverda veya postbenim durumumda 2 numara yardımcı oldu ( view.measure).
CoolMind

3
@CoolMind Kotlin kullanmanız durumunda yeni bir yöntem de kullanabilirsiniz. Yanıt da eklenecek şekilde güncellendi.
android geliştiricisi

kotlin'in bunun için neden ek işlevleri var? google çılgın, bu dil işe yaramaz (sadece java ile kalmalı), hızlıdan çok daha eski, ancak yalnızca hızlı bir şekilde yeterince popülerlik kazandı tiobe.com/tiobe-index (swift 12 konumu ve kotlin 38)
user924

18

getWidth()Görüntü gerçekten ekrana yansıtılmadan önce mi arıyorsunuz?

Yeni Android geliştiricileri tarafından yapılan yaygın bir hata, yapıcısının içindeki bir görünümün genişliğini ve yüksekliğini kullanmaktır. Bir görünümün kurucusu çağrıldığında, Android görünümün ne kadar büyük olacağını henüz bilmediğinden boyutlar sıfıra ayarlanır. Gerçek boyutlar, inşaattan sonra ancak herhangi bir şey çizilmeden önce ortaya çıkan yerleşim aşamasında hesaplanır. onSizeChanged()Değerler bilindiğinde haberdar olmak için yöntemi kullanabilir veya yöntemde olduğu gibi daha sonra getWidth()ve getHeight()yöntemlerini kullanabilirsiniz onDraw().


2
Bu onSizeChange(), gerçek boyutu bilmek için olayı yakalamak için ImageView'u geçersiz kılmam gerektiği anlamına mı geliyor? Bu biraz abartılı gibi görünüyor ... gerçi bu noktada sadece kodu çalıştırmak için çaresizim, bu yüzden denemeye değer.
Nik Reiman

Daha çok onCreate () işleminizin Aktivitenizde bitmesini beklemek gibi bir şey düşünüyordum.
Mark B

1
Bahsedildiği gibi, onPostCreate()görünüm tamamen oluşturulduktan ve başlatıldıktan sonra çalıştırılan bu kodu yerleştirmeyi denedim .
Nik Reiman

1
Alıntılanan metin nereden geliyor?
Karmaşıklıklar

1
özel görünüm için bu alıntı, genel olarak görünüm boyutu hakkında sorduğumuzda bu tür bir cevap için göndermeyin
kullanıcı924

17

@ Mbaird'in tavsiyesine dayanarak, ImageViewsınıfı alt sınıflara ayırarak ve geçersiz kılarak uygulanabilir bir çözüm buldum onLayout(). Daha sonra, aktivitemin uyguladığı ve sınıfa kendisine bir referans gönderdiği bir gözlemci arayüzü oluşturdum, bu da aktivitenin boyutlandırmanın ne zaman bittiğini söylemesine izin verdi.

Bunun en iyi çözüm olduğuna% 100 ikna olmadım (bu nedenle bu yanıtı henüz doğru olarak işaretlemiyordum), ancak işe yarıyor ve belgelere göre ilk kez bir görünümün gerçek boyutunu bulabilirim.


Bunu bildiğim iyi oldu. Görünümü alt sınıfa ayırmak zorunda kalmanıza izin verecek bir şey bulursam, onu buraya göndereceğim. OnPostCreate () çalışmadığına biraz şaşırdım.
Mark B

Bunu denedim ve işe yarıyor gibi görünüyor. Bu yöntem başlangıçta yalnızca bir kez değil sık sık çağrıldığı için gerçekten en iyi çözüm değildir. :(
Tobias Reich

10

API <11 ise (API 11, View.OnLayoutChangedListener özelliğini içerir) bir görünümü geçersiz kılarak düzeni alma kodu:

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

Aşağıdaki kodu kullanın, görüş boyutunu verir.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}

5

Bu benim için çalışıyor 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
Tıklama dinleyicisindeyken Gecikmeli yayınlamanıza gerek yoktur. Görünümlerin tümü kendilerini ölçüp ekranda görünene kadar hiçbir şeye tıklayamazsınız. Böylece, onları tıklayabildiğiniz zamana göre zaten ölçülmüş olacaklar.
afollestad

Bu kod yanlış. Gönderim gecikmesi, bir sonraki tıklamada görünümünüzün ölçüleceğini garanti etmez.
Ian Wong

PostDelayed () için gitmemenizi tavsiye ederim. Olması muhtemel değil, ama ya görünümünüz 1 saniye içinde ölçülmezse ve bunu görünüm genişliğini / yüksekliğini elde etmek için çalıştırılabilir olarak adlandırırsanız. Yine de 0 ile sonuçlanır. Düzenleme: Oh ve btw 1 milisaniye cinsindendir, bu yüzden kesin olarak başarısız olur.
Alex

4

Ayrıca etrafında kayboldu getMeasuredWidth()ve getMeasuredHeight() getHeight()ve getWidth()uzun süre .......... sonra i görünümün genişlik ve yükseklik alma bulundu onSizeChanged()dinamik can ........ bunu yapmanın en iyi yolu onSizeChanged() yöntemini geçersiz kılarak MEVCUT genişliğinizi ve GÜNCEL görünümünüzün yüksekliğini alın .

ayrıntılı bir kod parçacığı olan buna bir göz atmak isteyebilirsiniz. Yeni Blog Gönderisi: Android'de bir customView (Görünümü genişletir) genişlik ve yükseklik boyutları nasıl alınır http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html


0

Ekrandaki görünümün hem Konumunu hem de Boyutunu alabilirsiniz

 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

benim için mükemmel çalışıyor:

 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;
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.