onMeasure penjelasan tampilan khusus


316

Saya mencoba melakukan komponen kustom. Saya memperpanjang Viewkelas dan melakukan beberapa menggambar dengan onDrawmetode yang ditimpa. Mengapa saya perlu mengganti onMeasure? Jika tidak, semuanya terlihat benar. Bisakah seseorang menjelaskannya? Bagaimana saya harus menulis onMeasuremetode saya ? Saya telah melihat beberapa tutorial, tetapi masing-masing sedikit berbeda dari yang lain. Kadang-kadang mereka menelepon super.onMeasurepada akhirnya, kadang-kadang mereka menggunakan setMeasuredDimensiondan tidak menyebutnya. Dimana perbedaannya?

Lagi pula saya ingin menggunakan beberapa komponen yang persis sama. Saya menambahkan komponen-komponen itu ke XMLfile saya , tetapi saya tidak tahu seberapa besar seharusnya. Saya ingin mengatur posisi dan ukurannya nanti (mengapa saya perlu mengatur ukuran onMeasurejika di onDrawsaat saya menggambar, berfungsi juga) di kelas komponen kustom. Kapan tepatnya saya perlu melakukan itu?

Jawaban:


735

onMeasure()adalah kesempatan Anda untuk memberi tahu Android seberapa besar Anda ingin tampilan khusus bergantung pada batasan tata letak yang disediakan oleh orang tua; ini juga merupakan kesempatan tampilan kustom Anda untuk mempelajari batasan tata letak tersebut (jika Anda ingin berperilaku berbeda dalam match_parentsituasi daripada wrap_contentsituasi). Kendala ini dikemas ke dalam MeasureSpecnilai - nilai yang dilewatkan ke dalam metode. Berikut ini adalah korelasi kasar dari nilai mode:

  • PERSIS berarti layout_widthatau layout_heightnilai ditetapkan untuk nilai tertentu. Anda mungkin harus melihat ukuran ini. Ini juga bisa dipicu ketika match_parentdigunakan, untuk mengatur ukuran persis ke tampilan induk (ini adalah tata letak yang bergantung pada kerangka kerja).
  • AT_MOST biasanya berarti nilai layout_widthatau layout_heightdiatur ke match_parentatau di wrap_contentmana ukuran maksimum diperlukan (ini tergantung tata letak dalam kerangka kerja), dan ukuran dimensi induk adalah nilainya. Anda tidak boleh lebih besar dari ukuran ini.
  • Tidak disebutkan biasanya berarti layout_widthatau layout_heightnilai ditetapkan untuk wrap_contenttanpa pembatasan. Anda bisa berapa pun ukuran yang Anda inginkan. Beberapa tata letak juga menggunakan panggilan balik ini untuk mengetahui ukuran yang Anda inginkan sebelum menentukan spesifikasi apa yang benar-benar melewati Anda lagi dalam permintaan ukuran kedua.

Kontrak yang ada dengan itu onMeasure()adalah setMeasuredDimension() HARUS dipanggil di akhir dengan ukuran yang Anda inginkan. Metode ini dipanggil oleh semua implementasi kerangka kerja, termasuk implementasi default yang ditemukan di Viewdalamnya, itulah sebabnya mengapa lebih aman untuk memanggilnya superjika cocok dengan use case Anda.

Memang, karena kerangka kerja memang menerapkan implementasi default, mungkin Anda tidak perlu menimpa metode ini, tetapi Anda mungkin melihat kliping dalam kasus di mana ruang tampilan lebih kecil dari konten Anda jika Anda tidak, dan jika Anda meletakkan tampilan kustom dengan wrap_contentdi kedua arah, tampilan Anda mungkin tidak muncul sama sekali karena kerangka tidak tahu seberapa besar itu!

Secara umum, jika Anda mengesampingkan Viewdan bukan widget lain yang ada, mungkin ide yang baik untuk memberikan implementasi, bahkan jika sesederhana seperti ini:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

Semoga Itu Membantu.


1
Hey @Devunwired penjelasan bagus yang terbaik yang saya baca sejauh ini. Penjelasan Anda menjawab banyak pertanyaan yang saya miliki dan menghapus beberapa keraguan, tetapi masih ada satu yang tersisa yaitu: Jika tampilan kustom saya berada di dalam ViewGroup bersama dengan beberapa orang lain Tampilan (tidak masalah jenis apa) yang ViewGroup akan dapatkan semua anaknya untuk setiap probe untuk kendala LayoutParams mereka dan meminta setiap anak untuk mengukur sendiri sesuai dengan kendala mereka?
Firaun

47
Perhatikan bahwa kode ini tidak akan berfungsi jika Anda mengganti onMeasure dari setiap subkelas ViewGroup. Subview Anda tidak akan muncul dan semuanya akan berukuran 0x0. Jika Anda perlu menimpa pada Ukuran ViewGroup kustom, ubah widthMode, widthSize, heightMode dan heightSize, kompilasi kembali untuk mengukurSpec menggunakan MeasureSpec.makeMeasureSpec dan berikan bilangan bulat yang dihasilkan ke super.onMeasure.
Alexey

1
Jawaban yang fantastis. Perhatikan bahwa, sesuai dokumentasi Google, adalah tanggung jawab View untuk menangani bantalan.
Jonstaff

4
Lebih rumit c ** p yang menjadikan Android sistem tata letak yang menyakitkan untuk digunakan. Mereka bisa saja mendapatkan getParent (). Dapatkan *** () ...
Oliver Dixon

2
Ada metode pembantu di Viewkelas, yang dipanggil resolveSizeAndStatedan resolveSize, yang harus melakukan apa yang klausa 'jika' lakukan - saya menemukan mereka berguna, terutama jika Anda harus sering menulis IF itu.
stan0

5

sebenarnya, jawaban Anda tidak lengkap karena nilainya juga tergantung pada wadah pembungkus. Dalam kasus tata letak relatif atau linier, nilai-nilai berperilaku seperti ini:

  • PERSIS match_parent PERSIS + ukuran induk
  • AT_MOST wrap_content menghasilkan AT_MOST MeasureSpec
  • TIDAK DIKENALKAN tidak pernah dipicu

Dalam kasus tampilan gulir horizontal, kode Anda akan berfungsi.


57
Jika Anda merasa bahwa beberapa jawaban di sini tidak lengkap, harap tambahkan jawaban itu daripada berikan jawaban parsial.
Michaël

1
Onya bagus untuk menautkan ini dengan cara tata letak bekerja, tetapi dalam kasus saya onMeasure disebut tiga kali untuk tampilan kustom saya. Tampilan tersebut memiliki tinggi wrap_content dan lebar tertimbang (lebar = 0, berat = 1). Panggilan pertama telah UNSPECIFIED / UNSPECIFIED, yang kedua AT_MOST / EXACTLY dan yang ketiga telah EXACTLY / EXACTLY.
William T. Mallard

0

Jika Anda tidak perlu mengubah sesuatu di Pengukuran - sama sekali tidak perlu Anda menimpanya.

Kode Devunwired (jawaban yang dipilih dan paling banyak dipilih di sini) hampir identik dengan apa yang sudah dilakukan oleh implementasi SDK untuk Anda (dan saya memeriksa - sudah melakukannya sejak 2009).

Anda dapat memeriksa metode onMeasure di sini :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

Mengganti kode SDK untuk diganti dengan kode yang sama persis tidak masuk akal.

Bagian dokumen resmi ini yang mengklaim "onMeasure default () akan selalu menetapkan ukuran 100x100" - salah.

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.