View's getWidth () dan getHeight () mengembalikan 0

513

Saya membuat semua elemen dalam proyek android saya secara dinamis. Saya mencoba mendapatkan lebar dan tinggi tombol sehingga saya dapat memutar tombol itu. Saya hanya mencoba belajar bagaimana bekerja dengan bahasa android. Namun, mengembalikan 0.

Saya melakukan riset dan saya melihat bahwa itu perlu dilakukan di tempat lain selain di onCreate()metode. Jika seseorang bisa memberi saya contoh bagaimana melakukannya, itu akan bagus.

Ini kode saya saat ini:

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

Bantuan apa pun dihargai.

ngreenwood6
sumber

Jawaban:

197

Anda menelepon getWidth()terlalu dini. UI belum berukuran dan ditata di layar.

Saya ragu Anda ingin melakukan apa yang Anda lakukan - widget yang dianimasikan tidak mengubah area yang dapat diklik, dan tombol tersebut akan tetap merespons klik dalam orientasi asli terlepas dari bagaimana ia diputar.

Yang sedang berkata, Anda dapat menggunakan sumber daya dimensi untuk menentukan ukuran tombol, kemudian referensi sumber daya dimensi dari file tata letak dan kode sumber Anda, untuk menghindari masalah ini.

CommonsWare
sumber
82
ngreenwood6, apa solusi Anda yang lain?
Andrew
12
@Andrew - jika Anda ingin negreenwood6 diberitahu tentang tindak lanjut Anda, Anda harus memulai pesan Anda seperti yang saya lakukan kepada Anda (saya pikir tiga huruf pertama sudah cukup) - CommonsWare mendapat pemberitahuan secara otomatis, karena ia menulis respons ini, tetapi ngreen tidak kecuali Anda mengatasinya.
Peter Ajtai
11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Metode yang dihasilkan secara otomatis rintisan super.onWindowFocusChanged (hasFocus); // Di sini kamu bisa mendapatkan ukurannya! }
Sana
5
@ ngreenwood6 Jadi apa solusi Anda?
Nima Vaziri
5
Gunakan pendengar ini untuk mendapatkan ukuran, kapan layar Anda siap. view.getViewTreeObserver (). addOnGlobalLayoutListener (baru ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević
816

Masalah dasarnya adalah, Anda harus menunggu fase menggambar untuk pengukuran aktual (terutama dengan nilai dinamis seperti wrap_contentatau match_parent), tetapi biasanya fase ini belum selesai onResume(). Jadi Anda perlu solusi untuk menunggu fase ini. Ada beberapa solusi yang mungkin untuk ini:


1. Dengarkan Draw / Layout Events: ViewTreeObserver

ViewTreeObserver dipecat untuk berbagai peristiwa menggambar. Biasanya OnGlobalLayoutListeneradalah apa yang Anda inginkan untuk mendapatkan pengukuran, sehingga kode di pendengar akan dipanggil setelah fase tata letak, sehingga pengukuran siap:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Catatan: Pendengar akan segera dihapus karena jika tidak maka akan menyala pada setiap acara tata letak. Jika Anda harus mendukung aplikasi SDK Lvl <16 gunakan ini untuk membatalkan pendaftaran pendengar:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Tambahkan runnable ke antrian tata letak: View.post ()

Tidak terlalu terkenal dan solusi favorit saya. Pada dasarnya cukup gunakan metode posting View dengan runnable Anda sendiri. Ini pada dasarnya mengantri kode Anda setelah ukuran tampilan, tata letak, dll. Seperti yang dinyatakan oleh Romain Guy :

Antrian acara UI akan memproses acara secara berurutan. Setelah setContentView () dipanggil, antrian acara akan berisi pesan yang meminta relai, sehingga apa pun yang Anda poskan ke antrian akan terjadi setelah tata letak berlalu

Contoh:

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

Keuntungan lebih dari ViewTreeObserver:

  • kode Anda hanya dieksekusi sekali dan Anda tidak perlu menonaktifkan Observer setelah eksekusi yang dapat merepotkan
  • kurang sintaksis verbose

Referensi:


3. Timpa Metode onLayout Views

Ini hanya praktis dalam situasi tertentu ketika logika dapat diringkas dalam pandangan itu sendiri, jika tidak, ini adalah sintaksis yang sangat verbose dan rumit.

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

Selain itu, onLayout akan dipanggil berkali-kali, jadi pertimbangkan apa yang Anda lakukan dalam metode ini, atau nonaktifkan kode Anda setelah pertama kali


4. Periksa apakah telah melalui fase tata letak

Jika Anda memiliki kode yang menjalankan beberapa kali saat membuat ui Anda bisa menggunakan metode lib dukungan v4 berikut:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Mengembalikan nilai true jika tampilan telah melalui setidaknya satu tata letak sejak terakhir kali dilampirkan atau dilepaskan dari jendela.


Tambahan: Mendapatkan pengukuran yang ditentukan secara statis

Jika cukup untuk mendapatkan tinggi / lebar yang ditentukan secara statis, Anda bisa melakukan ini dengan:

Tapi ingat, ini mungkin berbeda dengan lebar / tinggi sebenarnya setelah menggambar. Javadoc menjelaskan perbedaan dengan sempurna:

Ukuran tampilan dinyatakan dengan lebar dan tinggi. Tampilan sebenarnya memiliki dua pasang nilai lebar dan tinggi.

Pasangan pertama dikenal sebagai lebar terukur dan tinggi terukur. Dimensi ini menentukan seberapa besar tampilan ingin berada di dalam induknya (lihat Layout untuk detail lebih lanjut.) Dimensi yang diukur dapat diperoleh dengan memanggil getMeasuredWidth () dan getMeasuredHeight ().

Pasangan kedua hanya dikenal sebagai lebar dan tinggi, atau kadang-kadang menggambar lebar dan tinggi menggambar. Dimensi ini menentukan ukuran sebenarnya dari tampilan di layar, pada waktu menggambar dan setelah tata letak. Nilai-nilai ini mungkin, tetapi tidak harus, berbeda dari lebar dan tinggi yang diukur. Lebar dan tinggi dapat diperoleh dengan memanggil getWidth () dan getHeight ().

Patrick Favre
sumber
1
Saya menggunakan view.post dalam sebuah fragmen. Saya perlu mengukur tampilan yang ketinggian orang tuanya 0dp dan memiliki set bobot. Semua yang saya coba mengembalikan ketinggian nol (Saya juga mencoba pendengar tata letak global). Ketika saya meletakkan kode view.post di onViewCreated dengan penundaan 125, ia bekerja, tetapi tidak tanpa penundaan. Ide ide?
Psest328
6
Sayang sekali Anda hanya dapat memilih jawaban sekali, penjelasan yang sangat bagus. Saya punya masalah dengan rotasi tampilan jika saya keluar dari aplikasi dan menyalakannya kembali dari aplikasi terbaru. Jika saya menggeseknya dari sana dan melakukan restart baru semuanya baik-baik saja. View.post () menyelamatkan hari saya!
Matthias
1
Terima kasih. Saya biasanya menggunakan View.post () (metode ke-2), tetapi jika dipanggil dari utas latar belakang, terkadang tidak berfungsi. Jadi, jangan lupa menulis runOnUiThread(new Runnable() {...}. Saat ini saya menggunakan variasi dari metode 1: addOnLayoutChangeListener. Jangan lupa untuk menghapusnya lalu di dalam: removeOnLayoutChangeListener. Ini bekerja dari utas latar juga.
CoolMind
2
Sayangnya bagi saya 'View.post ()' tidak berfungsi sepanjang waktu. Saya punya 2 proyek yang sangat mirip. Dalam satu proyek itu berfungsi, di sisi lain tidak berfungsi. :(. Dalam kedua situasi ini saya memanggil metode dari metode 'onCreate ()' dari suatu Kegiatan. Ada saran mengapa?
Adrian Buciuman
1
Saya menemukan masalahnya. Dalam satu proyek memiliki pandangan android:visibility="gone", dan di proyek lain properti visibilitas adalah invisible. Jika visibilitas adalah gonelebarnya akan selalu 0.
Adrian Buciuman
262

Kita bisa menggunakan

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
sumber
10
jadi bagaimana kita bisa bekerja di Fragmen? ini hanya berfungsi di Aktivitas?
Olkunmustafa
8
Ini harus melakukan hal itu tetapi seharusnya tidak menjadi jawabannya. Seorang pengguna ingin mendapatkan dimensi di setiap titik waktu alih-alih digambar di jendela. Juga metode ini dipanggil beberapa waktu atau setiap kali ada perubahan (Lihat sembunyikan, hilang, tambahkan tampilan baru dll.) Di jendela. Jadi gunakan dengan hati-hati :)
Dr. aNdRO
1
Ini adalah retasan dan sepertinya menggunakan ViewTreeObserver akan lebih baik.
i_am_jorf
Jangan bermaksud terdengar kasar, tetapi meninggalkan komentar yang dibuat secara otomatis (terutama dalam 2 baris kode) tidak memberikan kepercayaan diri bahwa Anda benar-benar tahu apa yang Anda lakukan.
Natix
Sayangnya, itu tidak berhasil untuk saya. Mencoba viewTreeObserver berhasil untuk saya.
Narendra Singh
109

Saya menggunakan solusi ini, yang menurut saya lebih baik daripada onWindowFocusChanged (). Jika Anda membuka DialogFragment, kemudian memutar telepon, onWindowFocusChanged akan dipanggil hanya ketika pengguna menutup dialog):

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

Sunting: karena removeGlobalOnLayoutListener sudah tidak digunakan lagi, sekarang Anda harus melakukan:

@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 :)
}
Tim Autin
sumber
4
sejauh ini ini adalah solusi terbaik! karena ini berfungsi juga pada solusi dinamis di mana lebar dan tinggi tidak diketahui dan dihitung. yaitu dalam tata letak relatif berdasarkan posisi / ukuran elemen lainnya. Sudah selesai dilakukan dengan baik!
George Pligoropoulos
2
Ini membutuhkan API 16 atau lebih tinggi (Jelly bean)
tomsv
7
Itu TIDAK MEMBUTUHKAN API 16! Satu-satunya masalah adalah metode removeGlobalOnLayoutListener tidak digunakan lagi oleh API 16, tetapi ada solusi sederhana yang kompatibel dengan semua level api - stackoverflow.com/questions/16189525/…
gingo
Jika Anda perlu menghapus pendengar sehingga tidak menyebut dirinya berkali-kali dan Anda mengalami masalah dengan API yang berbeda, saya sarankan Anda untuk mengatur boolean palsu di kepala kelas dan kemudian setelah menangkap nilai setel ke true sehingga itu tidak menangkap nilai lagi.
Mansour Fahad
Jika Anda menghapus pendengar setelah menjalankan pertama Anda mungkin mendapatkan dimensi yang salah: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: tinggi: lebar 1647: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: height : 1500 lebar: 996
Leos Literak
76

Seperti yang dinyatakan Ian dalam utas Pengembang Android ini :

Bagaimanapun, kesepakatannya adalah bahwa tata letak isi jendela terjadi setelah semua elemen dibangun dan ditambahkan ke tampilan induknya. Itu harus seperti ini, karena sampai Anda tahu komponen apa yang terkandung dalam Tampilan, dan apa yang dikandungnya, dan seterusnya, tidak ada cara yang masuk akal untuk Anda tata letak.

Intinya, jika Anda memanggil getWidth () dll dalam konstruktor, itu akan mengembalikan nol. Prosedurnya adalah membuat semua elemen tampilan di konstruktor, lalu tunggu metode onSizeChanged () tampilan Anda dipanggil - saat itulah pertama kali Anda mengetahui ukuran sebenarnya, jadi saat itulah Anda mengatur ukuran elemen GUI Anda.

Perlu diketahui juga bahwa onSizeChanged () kadang-kadang disebut dengan parameter nol - periksa untuk kasus ini, dan segera kembali (sehingga Anda tidak mendapatkan pembagian dengan nol saat menghitung tata letak Anda, dll.). Beberapa waktu kemudian akan dipanggil dengan nilai-nilai nyata.

kerem yokuva
sumber
8
onSizeChanged bekerja untuk saya. onWindowFocusedChanged tidak dipanggil dalam tampilan kustom saya.
aaronmarino
ini terlihat solusi yang bagus, tetapi apa yang harus saya selalu buat tampilan kustom saya untuk mengganti metode onSizeChanged?
M.ElSaka
Bottom line, if you call getWidth() etc. in a constructor, it will return zero.itu bingo. Akar masalah saya.
MikeNereson
Saya menyarankan solusi ini untuk fragmen, karena beberapa solusi lain mengembalikan 0 dalam kasus saya.
WindRider
66

Jika Anda perlu mendapatkan lebar beberapa widget sebelum ditampilkan di layar, Anda dapat menggunakan getMeasuredWidth () atau getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
sumber
5
Untuk beberapa alasan, ini adalah satu-satunya jawaban yang berhasil untuk saya - terima kasih!
Farbod Salamat-Zadeh
Bagi saya, solusi paling sederhana, yang berhasil bagi saya
Tima
Bekerja seperti pesona!
Morales Batovski
1
@CommonsWare bagaimana solusi ini memberikan tinggi dan lebar sebelum pengamat pohon tampilan pada panggilan balik tata letak global, sebuah penjelasan akan sangat membantu.
Mightian
22

Saya lebih suka menggunakan OnPreDrawListener()alih-alih addOnGlobalLayoutListener(), karena ini disebut sedikit lebih awal dari pendengar lainnya.

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });
Ayaz Alifov
sumber
3
Perhatikan bahwa itu return falseberarti untuk membatalkan izin menggambar saat ini . Untuk melanjutkan, return truesebagai gantinya.
Pang
9

Ekstensi Kotlin untuk mengamati tata letak global dan melakukan tugas yang diberikan ketika ketinggian siap secara dinamis.

Pemakaian:

view.height { Log.i("Info", "Here is your height:" + it) }

Penerapan:

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

AndroidX memiliki beberapa fungsi ekstensi yang membantu Anda dengan pekerjaan semacam ini, di dalam androidx.core.view

Anda perlu menggunakan Kotlin untuk ini.

Salah satu yang paling cocok di sini adalah doOnLayout:

Melakukan tindakan yang diberikan saat tampilan ini ditata. Jika tampilan telah ditata dan belum meminta tata letak, tindakan akan dilakukan segera jika tidak, tindakan akan dilakukan setelah tampilan diletakkan berikutnya.

Tindakan hanya akan dipanggil sekali pada tata letak berikutnya dan kemudian dihapus.

Dalam contoh Anda:

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

Ketergantungan: androidx.core:core-ktx:1.0.0

Vlad
sumber
Ini satu-satunya jawaban yang benar. android.googlesource.com/platform/frameworks/support/+/…
hrules6872
1
ekstensi ini sangat penting dan berguna
Ahmed na
5

Satu liner jika Anda menggunakan RxJava & RxBindings . Pendekatan serupa tanpa boilerplate. Ini juga memecahkan peretasan untuk menekan peringatan seperti dalam jawaban oleh Tim Autin .

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

Ini dia. Tidak perlu berhenti berlangganan, bahkan jika tidak pernah menelepon.

Odys
sumber
4

Itu terjadi karena pandangan perlu lebih banyak waktu untuk digembungkan. Jadi, alih-alih menelepon view.widthdan view.heightdi utas utama, Anda harus menggunakan view.post { ... }untuk memastikan bahwa Anda viewtelah meningkat. Di Kotlin:

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

Di Jawa, Anda juga dapat memanggil getWidth()dan getHeight()metode dalam Runnabledan meneruskan metode Runnableke view.post().

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Ehsan Nokandi
sumber
Ini bukan jawaban yang benar karena tampilan mungkin tidak diukur dan ditata ketika kode yang diposting dijalankan. Ini akan menjadi kasus tepi, tetapi .posttidak menjamin tampilan ditata.
d.aemon
1
Jawaban ini luar biasa dan bermanfaat bagi saya. Terima kasih banyak Ehsan
MMG
3

Tinggi dan lebar adalah nol karena tampilan belum dibuat pada saat Anda meminta tinggi dan lebar. Salah satu solusi paling sederhana adalah

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

Metode ini baik dibandingkan dengan metode lain karena pendek dan garing.

Shivam Yadav
sumber
3

Mungkin ini membantu seseorang:

Buat fungsi ekstensi untuk kelas Lihat

nama file: 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()
            }
        })
    }
}

Ini kemudian dapat digunakan pada tampilan apa pun dengan:

view.afterLayout {
    do something with view.height
}
Pepijn
sumber
2

Jawab dengan postsalah, karena ukurannya mungkin tidak dihitung ulang.
Hal penting lainnya adalah bahwa pandangan dan semua leluhurnya harus terlihat . Untuk itu saya menggunakan properti View.isShown.

Inilah fungsi kotlin saya, yang dapat ditempatkan di suatu tempat di utils:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

Dan penggunaannya adalah:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}
pavelperc
sumber
1

Jika Anda menggunakan 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
            }
        })
Hitesh Sahu
sumber
0

Tampilan yang hilang mengembalikan 0 sebagai tinggi jika aplikasi di latar belakang. Ini kode saya (1oo% berfungsi)

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

sumber
0

Kita perlu menunggu tampilan akan ditarik. Untuk tujuan ini gunakan OnPreDrawListener. Contoh 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)
Artem Botnev
sumber