Cara otoratif untuk mengganti onMeasure ()?

89

Apa cara yang benar untuk mengganti onMeasure ()? Saya telah melihat berbagai pendekatan. Misalnya, Professional Android Development menggunakan MeasureSpec untuk menghitung dimensi, lalu diakhiri dengan panggilan ke setMeasuredDimension (). Sebagai contoh:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

Di sisi lain, sesuai postingan ini , cara yang "benar" adalah menggunakan MeasureSpec, panggil setMeasuredDimensions (), diikuti dengan panggilan ke setLayoutParams (), dan diakhiri dengan panggilan ke super.onMeasure (). Sebagai contoh:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Jadi jalan mana yang benar? Tidak ada pendekatan yang berhasil 100% untuk saya.

Saya rasa apa yang saya tanyakan adalah apakah ada yang tahu tentang tutorial yang menjelaskan onMeasure (), layout, dimensi tampilan anak, dll.?

U Avalos
sumber
16
apakah menurut Anda jawabannya berguna / benar? mengapa tidak menandainya sebagai yang jawabannya?
superjos

Jawaban:

69

Solusi lainnya tidak komprehensif. Mereka mungkin berhasil dalam beberapa kasus, dan merupakan tempat yang baik untuk memulai, tetapi mereka mungkin tidak dijamin berhasil.

Saat onMeasure dipanggil, Anda mungkin atau mungkin tidak memiliki hak untuk mengubah ukuran. Nilai yang diteruskan ke onMeasure ( widthMeasureSpec, heightMeasureSpec) Anda berisi informasi tentang apa yang boleh dilakukan oleh tampilan anak Anda. Saat ini ada tiga nilai:

  1. MeasureSpec.UNSPECIFIED - Kamu bisa menjadi sebesar yang kamu mau
  2. MeasureSpec.AT_MOST- Sebesar yang Anda inginkan (hingga ukuran spesifikasi), Ini ada parentWidthdi contoh Anda.
  3. MeasureSpec.EXACTLY- Tidak ada pilihan. Orang tua telah memilih.

Ini dilakukan agar Android dapat membuat beberapa lintasan guna menemukan ukuran yang tepat untuk setiap item, lihat di sini untuk detail selengkapnya.

Jika Anda tidak mengikuti aturan ini, pendekatan Anda tidak dijamin berhasil.

Misalnya jika Anda ingin memeriksa apakah Anda diizinkan untuk mengubah ukuran sama sekali, Anda dapat melakukan hal berikut:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

Dengan menggunakan informasi ini, Anda akan mengetahui apakah Anda dapat mengubah nilai-nilai seperti pada kode Anda. Atau jika Anda diminta untuk melakukan sesuatu yang berbeda. Cara cepat dan mudah untuk menyelesaikan ukuran yang Anda inginkan adalah dengan menggunakan salah satu metode berikut:

int ResolveSizeAndState (int size, int measureSpec, int childMeasuredState)

int resolutionSize (int size, int measureSpec)

Sementara yang pertama hanya tersedia di Honeycomb, yang kedua tersedia di semua versi.

Catatan: Anda mungkin menemukan itu resizeWidthatau resizeHeightselalu salah. Saya menemukan ini menjadi kasus jika saya meminta MATCH_PARENT. Saya dapat memperbaikinya dengan meminta WRAP_CONTENTpada tata letak induk saya dan kemudian selama fase UNSPECIFIED meminta ukuran Integer.MAX_VALUE. Melakukannya akan memberi Anda ukuran maksimal yang diizinkan orang tua Anda pada lintasan berikutnya melalui onMeasure.

Grimmace
sumber
39

Dokumentasi adalah otoritas dalam masalah ini: http://developer.android.com/guide/topics/ui/how-android-draws.html dan http://developer.android.com/guide/topics/ui/custom -components.html

Untuk meringkas: di akhir onMeasuremetode yang diganti, Anda harus memanggilsetMeasuredDimension .

Anda tidak boleh menelepon super.onMeasuresetelah menelepon setMeasuredDimension, itu hanya akan menghapus apa pun yang Anda atur. Dalam beberapa situasi Anda mungkin ingin memanggil yang super.onMeasurepertama dan kemudian mengubah hasilnya dengan memanggil setMeasuredDimension.

Jangan panggil setLayoutParamsdi onMeasure. Tata letak terjadi dalam hitungan detik setelah pengukuran.

satur9nine
sumber
Pengalaman saya adalah jika saya mengganti onMeasure tanpa memanggil super.onMeasure, OnLayout tidak dipanggil pada tampilan anak.
William Jockusch
2

Saya pikir itu tergantung pada orang tua yang Anda timpa.

Misalnya, jika Anda memperluas ViewGroup (seperti FrameLayout), ketika Anda telah mengukur ukurannya, Anda harus memanggil seperti di bawah ini

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

karena Anda mungkin ingin ViewGroup untuk melakukan pekerjaan istirahat (melakukan beberapa hal pada tampilan anak)

Jika Anda memperluas Tampilan (seperti ImageView), Anda dapat memanggilnya this.setMeasuredDimension(width, height);, karena kelas induk akan melakukan sesuatu seperti yang biasa Anda lakukan.

Singkatnya, jika Anda menginginkan beberapa fitur yang ditawarkan kelas induk Anda secara gratis, Anda harus memanggil super.onMeasure()(berikan spesifikasi pengukuran mode MeasureSpec.EXACTLY biasanya), jika tidak, panggilan this.setMeasuredDimension(width, height);sudah cukup.

YON
sumber
1

berikut adalah cara saya memecahkan masalah:

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

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

Selain itu, diperlukan untuk komponen ViewPager

Yuli Reiri
sumber
3
Ini bukan solusi yang tepat. super.onMeasureakan menghapus set dimensi menggunakan setMeasuredDimension.
tomrozb
@tomrozb itu bukan humptydevelopers.com/2013/05/… dan Anda dapat memeriksa sumber android
Yuli Reiri
@YuliReiri: Solusi sempurna!
Shayan Tabatabaee
0

Jika mengubah ukuran tampilan di dalam onMeasuresemua yang Anda butuhkan adalah setMeasuredDimensionpanggilan. Jika Anda mengubah ukuran di luar onMeasureAnda perlu meneleponsetLayoutParams . Misalnya mengubah ukuran tampilan teks saat teks diubah.

Kyle O.
sumber
Anda masih bisa memanggil setMinWidth () dan setMaxWidth () dan memiliki standar onMeasure menanganinya. Saya telah menemukan bahwa lebih baik untuk tidak memanggil setLayoutParams dari dalam kelas View kustom. Ini benar-benar digunakan untuk ViewGroups atau Aktivitas untuk mengganti perilaku tampilan anak.
Dorrin
0

Tergantung pada kontrol yang Anda gunakan. Instruksi dalam dokumentasi berfungsi untuk beberapa kontrol (TextView, Button, ...), tetapi tidak untuk yang lain (LinearLayout, ...). Cara yang bekerja sangat baik bagi saya adalah memanggil super setelah saya selesai. Berdasarkan artikel di tautan di bawah ini.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html

Aviral
sumber
-1

Saya kira, setLayoutParams dan menghitung ulang pengukuran adalah solusi untuk mengubah ukuran tampilan anak dengan benar, karena hal ini biasanya dilakukan di onMeasure kelas turunan.

Namun, ini jarang berfungsi dengan benar (untuk alasan apa pun ...), lebih baik panggil measureChildren (saat mendapatkan ViewGroup) atau coba sesuatu yang serupa bila perlu.

M. Schenk
sumber
-5

Anda bisa mengambil potongan kode ini sebagai contoh onMeasure () ::

public class MyLayerLayout extends RelativeLayout {

    public MyLayerLayout(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Sayan
sumber
6
Kode ini pada dasarnya salah. Pertama, ini tidak memperhitungkan mode tata letak (lihat jawaban Grimmace). Itu buruk, tetapi Anda tidak akan melihat efeknya karena Anda juga memanggil super.onMeasure () di bagian paling akhir, yang menimpa nilai yang Anda setel (lihat jawaban satur9nine) dan sama sekali mengalahkan tujuan menimpa onMeasure ().
spaaarky21