Bagaimana Anda bisa tahu kapan tata letak telah digambar?


201

Saya memiliki tampilan khusus yang menarik bitmap yang dapat digulir ke layar. Untuk menginisialisasi, saya harus memasukkan ukuran dalam piksel objek tata letak induk. Tetapi selama fungsi onCreate dan onResume, Layout belum digambar, dan begitu juga layout.getMeasuredHeight () mengembalikan 0.

Sebagai solusinya, saya telah menambahkan penangan untuk menunggu satu detik dan kemudian mengukur. Ini berfungsi, tetapi ceroboh, dan saya tidak tahu berapa banyak saya dapat memangkas waktu sebelum saya berakhir sebelum tata letak ditarik.

Yang ingin saya ketahui adalah, bagaimana saya bisa mendeteksi ketika tata letak digambar? Apakah ada acara atau panggilan balik?

Jawaban:


391

Anda dapat menambahkan pengamat pohon ke tata letak. Ini harus mengembalikan lebar dan tinggi yang benar. onCreate()dipanggil sebelum tata letak tampilan anak dilakukan. Jadi lebar dan tingginya belum dihitung. Untuk mendapatkan tinggi dan lebar, letakkan ini pada onCreate()metode:

    final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
    ViewTreeObserver vto = layout.getViewTreeObserver(); 
    vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { 
        @Override 
        public void onGlobalLayout() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
            int width  = layout.getMeasuredWidth();
            int height = layout.getMeasuredHeight(); 

        } 
    });

28
Terima kasih banyak! Tetapi saya bertanya-tanya mengapa tidak ada metode yang lebih sederhana untuk melakukan ini karena ini adalah masalah umum.
felixd

1
Luar biasa. Saya mencoba OnLayoutChangeListener dan tidak berhasil. Tapi OnGlobalLayoutListener berfungsi. Terima kasih banyak!
YoYoMyo

8
removeGlobalOnLayoutListener tidak digunakan lagi di API level 16. Gunakan sebagai gantinya hapusOnGlobalLayoutListener.
tounaobun

3
Satu "gotcha" yang saya lihat adalah jika tampilan Anda tidak terlihat, onGlobalLayout dipanggil, tetapi nanti ketika getView dipanggil, tetapi bukan onGlobalLayout ... ugh.
Stephen McCormick

1
Solusi cepat lain untuk ini adalah menggunakannya view.post(Runnable), lebih bersih dan melakukan hal yang sama.
dvdciri

74

Cara yang sangat mudah adalah dengan memanggil post()tata letak Anda. Ini akan menjalankan kode Anda langkah selanjutnya, setelah tampilan Anda dibuat.

YOUR_LAYOUT.post( new Runnable() {
    @Override
    public void run() {
        int width  = YOUR_LAYOUT.getMeasuredWidth();
        int height = YOUR_LAYOUT.getMeasuredHeight(); 
    }
});

4
Saya tidak tahu apakah posting benar-benar akan menjalankan kode Anda setelah tampilan telah dibuat. Tidak ada jaminan, karena posting hanya akan menambahkan Runnable yang Anda buat ke RunQueue, yang akan dieksekusi setelah antrian sebelumnya dieksekusi pada UI Thread.
HendraWD

4
Sebaliknya, posting () dijalankan setelah pembaruan langkah UI berikutnya, yang terjadi setelah tampilan dibuat. Oleh karena itu, Anda dijamin akan menjalankannya setelah tampilan dibuat.
Elliptica

Saya menguji kode ini beberapa kali, hanya berfungsi terbaik di UI-thread. Di utas latar terkadang tidak berfungsi.
CoolMind

3
@HendraWijayaDjiono, mungkin posting ini akan menjawab: stackoverflow.com/questions/21937224/…
CoolMind

1
Saya mengalami masalah di mana menggunakan jawaban pertama tidak berfungsi setiap kali, menggunakan pos pada tampilan yang perlu saya gulir (tampilan gulir), memungkinkan saya untuk menggulir ke posisi yang diinginkan dengan benar segera setelah tampilan siap diambil gulir ke metode panggilan.
Danuofr

21

Untuk menghindari kode dan peringatan yang sudah usang Anda dapat menggunakan:

view.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    view.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
                yourFunctionHere();
            }
        });

1
Untuk menggunakan kode ini, 'tampilan' harus dinyatakan sebagai 'final'.
BeccaP

2
gunakan kondisi ini sebagai gantinya Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN
HendraWD

4

Lebih baik dengan fungsi ekstensi kotlin

inline fun View.waitForLayout(crossinline yourAction: () -> Unit) {
    val vto = viewTreeObserver
    vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            when {
                vto.isAlive -> {
                    vto.removeOnGlobalLayoutListener(this)
                    yourAction()
                }
                else -> viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

Solusi yang sangat bagus dan elegan yang dapat digunakan kembali.
Ionut Negru

3

Jawaban lain adalah:
Coba periksa dimensi Lihat di onWindowFocusChanged.


3

Alternatif untuk metode yang biasa adalah menghubungkan ke gambar tampilan.

OnPreDrawListenerdipanggil berkali-kali saat menampilkan tampilan, jadi tidak ada iterasi khusus di mana tampilan Anda memiliki lebar atau tinggi yang diukur valid. Ini mengharuskan Anda terus memverifikasi ( view.getMeasuredWidth() <= 0) atau menetapkan batas berapa kali Anda memeriksa measuredWidthlebih besar dari nol.

Ada juga kemungkinan bahwa tampilan tidak akan pernah ditarik, yang mungkin mengindikasikan masalah lain dengan kode Anda.

final View view = [ACQUIRE REFERENCE]; // Must be declared final for inner class
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        if (view.getMeasuredWidth() > 0) {     
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            //Do something with width and height here!
        }
        return true; // Continue with the draw pass, as not to stop it
    }
});

Pertanyaannya adalah How can you tell when a layout has been drawn?dan Anda menyediakan metode di mana Anda menelepon OnPreDrawListenerdan don't stop interrogating that view until it has provided you with a reasonable answerkeberatan terhadap solusi Anda harus jelas.
Abandoned Cart

Apa yang perlu Anda ketahui bukanlah kapan gambar itu diambil tetapi kapan itu diukur. Kode saya menjawab pertanyaan yang benar-benar perlu ditanyakan.
jwehrle

Apakah ada masalah dengan solusi ini? Apakah itu gagal menyelesaikan masalah yang dihadapi?
jwehrle

1
Tampilan tidak diukur sampai ditata, sehingga alternatif ini berlebihan dan melakukan pemeriksaan berlebihan. Anda mengaku tidak menjawab pertanyaan, tetapi apa yang menurut Anda harus ditanyakan. Komentar asli membuat kedua poin ini, tetapi ada juga beberapa masalah dengan kode itu sendiri yang telah dikirimkan sebagai edit.
Abandoned Cart

Itu hasil edit yang bagus. Terima kasih. Saya kira yang saya tanyakan adalah, Apakah Anda pikir jawaban ini harus dihapus atau tidak? Saya mengalami masalah OP. Ini memecahkan masalah. Saya menawarkannya dengan itikad baik. Apakah itu kesalahan? Apakah itu sesuatu yang tidak boleh dilakukan di StackOverflow?
jwehrle

2

Cukup periksa dengan memanggil metode posting pada tata letak atau tampilan Anda

 view.post( new Runnable() {
     @Override
     public void run() {
        // your layout is now drawn completely , use it here.
      }
});

Ini tidak benar dan dapat menyembunyikan kemungkinan masalah. posthanya akan mengantri runnable untuk dijalankan setelahnya, itu tidak memastikan tampilan diukur, bahkan lebih sedikit ditarik.
Ionut Negru

@IonutNegru mungkin membaca stackoverflow.com/a/21938380
Bruno Bieri

@ BrunoBieri, Anda dapat menguji perilaku ini dengan fungsi async yang mengubah pengukuran tampilan dan melihat apakah Anda sedang antri tugas dijalankan setelah setiap pengukuran dan menerima nilai yang diharapkan.
Ionut Negru

1

Saat onMeasuredipanggil tampilan akan diukur lebar / tingginya. Setelah ini, Anda bisa menelepon layout.getMeasuredHeight().


4
Tidakkah Anda harus membuat tampilan sendiri untuk mengetahui kapan onMeasure kebakaran?
Geeks On Hugs

Ya, Anda harus menggunakan tampilan khusus untuk mengakses sesuai ukuran.
Trevor Hart
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.