Menentukan ukuran tampilan Android pada waktu proses

123

Saya mencoba menerapkan animasi ke tampilan di aplikasi Android saya setelah aktivitas saya dibuat. Untuk melakukan ini, saya perlu menentukan ukuran tampilan saat ini, dan kemudian menyiapkan animasi untuk diskalakan dari ukuran saat ini ke ukuran baru. Bagian ini harus diselesaikan saat runtime, karena tampilan berskala ke ukuran yang berbeda bergantung pada masukan dari pengguna. Tata letak saya ditentukan dalam XML.

Ini sepertinya tugas yang mudah, dan ada banyak pertanyaan SO mengenai hal ini meskipun tidak ada yang menyelesaikan masalah saya, jelas. Jadi mungkin saya melewatkan sesuatu yang jelas. Saya memahami pandangan saya dengan:

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

Ini bekerja baik, tapi saat memanggil getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, dll, mereka semua kembali 0. Saya juga mencoba secara manual menelepon measure()pada tampilan diikuti dengan panggilan untuk getMeasuredWidth(), tapi itu tidak berpengaruh.

Saya telah mencoba memanggil metode ini dan memeriksa objek di debugger di aktivitas saya onCreate()dan di onPostCreate(). Bagaimana cara mengetahui dimensi yang tepat dari tampilan ini saat runtime?

Nik Reiman
sumber
1
Oh, saya juga harus mencatat bahwa tampilan itu sendiri pasti tidak memiliki 0 lebar / tinggi. Itu muncul di layar dengan baik.
Nik Reiman
Bisakah Anda memposting tag <ImageView ... /> dari layout xml?
fhucho
Saya berjuang dengan banyak solusi SO untuk masalah ini sampai saya menyadari bahwa dalam kasus saya, dimensi View cocok dengan layar fisik (aplikasi saya "imersif" dan View serta semua lebar dan tinggi induknya disetel ke match_parent). Dalam kasus ini, solusi sederhana yang bisa dipanggil dengan aman bahkan sebelum View digambar (misalnya, dari metode onCreate () Aktivitas Anda) adalah hanya dengan menggunakan Display.getSize (); lihat stackoverflow.com/a/30929599/5025060 untuk detailnya. Saya tahu ini bukan yang diminta @NikReiman, hanya meninggalkan catatan untuk mereka yang mungkin dapat menggunakan metode yang lebih sederhana ini.
CODE-REaD

Jawaban:

180

Gunakan ViewTreeObserver pada Tampilan untuk menunggu tata letak pertama. Hanya setelah tata letak pertama, getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () akan berfungsi.

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();
    }
  });
}
Vikram Bodicherla
sumber
21
Berhati-hatilah dengan jawaban ini karena akan terus dipanggil jika tampilan yang mendasarinya adalah video / permukaan
Kevin Parker
7
Bisakah Anda menjelaskan lebih lanjut tentang @Kev? Dua hal, pertama-tama, karena ini adalah pendengar, saya mengharapkan satu panggilan balik, dan selanjutnya, segera setelah kita mendapatkan panggilan balik pertama, kita menghapus pendengar. Apakah saya melewatkan sesuatu?
Vikram Bodicherla
4
Sebelum memanggil metode ViewTreeObserver, sebaiknya periksa isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
4
ada alternatif lain untuk removeGlobalOnLayoutListener(this);? karena sudah usang.
Sibbs Berjudi
7
@FarticlePilter, gunakan removeOnGlobalLayoutListener () sebagai gantinya. Ini disebutkan di dokumen.
Vikram Bodicherla
64

Sebenarnya ada beberapa solusi, bergantung pada skenarionya:

  1. Metode aman, akan berfungsi tepat sebelum menggambar tampilan, setelah fase tata letak selesai:
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); 
}

Penggunaan sampel:

    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. Pada beberapa kasus, cukup mengukur ukuran tampilan secara manual:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Jika Anda mengetahui ukuran wadahnya:

    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. jika Anda memiliki tampilan kustom yang telah Anda perpanjang, Anda bisa mendapatkan ukurannya di metode "onMeasure", tetapi menurut saya ini berfungsi dengan baik hanya pada beberapa kasus:
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. Jika Anda menulis di Kotlin, Anda dapat menggunakan fungsi selanjutnya, yang bekerja di balik layar persis seperti runJustBeforeBeingDrawnyang saya tulis:

    view.doOnPreDraw { actionToBeTriggered() }

    Perhatikan bahwa Anda perlu menambahkan ini ke gradle (ditemukan lewat sini ):

    implementation 'androidx.core:core-ktx:#.#'
pengembang android
sumber
1
Tampaknya solusi # 1 tidak akan selalu berhasil OnPreDrawListener. Kasus teruji ketika Anda ingin menjalankan kode dari Activity onCreate()dan Anda segera aplikasi latar belakang. Mudah diulang terutama pada perangkat yang lebih lambat. Jika Anda mengganti OnPreDrawListenerdengan OnGlobalLayoutListener- Anda tidak akan melihat masalah yang sama.
penanya
@questioner Saya tidak mengerti, tapi saya senang ini membantu Anda pada akhirnya.
Pengembang android
Meskipun saya biasanya menggunakan ViewTreeObserveratau post, dalam kasus saya nomor 2 membantu ( view.measure).
CoolMind
3
@CoolMind Anda juga dapat menggunakan metode baru, jika Anda menggunakan Kotlin. Jawaban yang diperbarui untuk menyertakannya juga.
Pengembang android
mengapa kotlin memiliki fungsi tambahan untuk ini? google gila, bahasa ini tidak berguna (hanya harus tetap dengan java), itu jauh lebih tua dari swift tetapi hanya swift mendapat popularitas yang cukup tiobe.com/tiobe-index (posisi 12 swift dan kotlin 38)
user924
18

Apakah Anda menelepon getWidth()sebelum tampilan ditampilkan di layar?

Kesalahan umum yang dibuat oleh pengembang Android baru adalah menggunakan lebar dan tinggi tampilan di dalam konstruktornya. Saat konstruktor tampilan dipanggil, Android belum mengetahui seberapa besar tampilan tersebut, jadi ukurannya disetel ke nol. Ukuran sebenarnya dihitung selama tahap tata letak, yang terjadi setelah konstruksi tetapi sebelum apa pun digambar. Anda dapat menggunakan onSizeChanged()metode untuk diberi tahu tentang nilai-nilai ketika diketahui, atau Anda dapat menggunakan metode getWidth()dan getHeight()nanti, seperti dalam onDraw()metode.

Mark B
sumber
2
Jadi apakah itu berarti saya perlu mengganti ImageView hanya untuk menangkap onSizeChange()acara untuk mengetahui ukuran sebenarnya? Itu sepertinya sedikit berlebihan ... meskipun pada titik ini saya putus asa hanya untuk mendapatkan kodenya bekerja, jadi itu layak dicoba.
Nik Reiman
Saya lebih memikirkan seperti menunggu sampai onCreate () Anda selesai di Aktivitas Anda.
Mark B
1
Seperti disebutkan, saya mencoba meletakkan kode ini onPostCreate()yang dijalankan setelah tampilan sepenuhnya dibuat dan dimulai.
Nik Reiman
1
Dari manakah kutipan teks itu?
Kerumitan
1
kutipan ini untuk tampilan khusus, jangan posting untuk jawaban seperti itu ketika kami bertanya tentang ukuran tampilan secara umum
user924
17

Berdasarkan saran @ mbaird, saya menemukan solusi yang bisa diterapkan dengan mensubkelas ImageViewkelas dan menimpanya onLayout(). Saya kemudian membuat antarmuka pengamat yang diimplementasikan aktivitas saya dan meneruskan referensi ke dirinya sendiri ke kelas, yang memungkinkannya memberi tahu aktivitas ketika ukuran sebenarnya selesai.

Saya tidak 100% yakin bahwa ini adalah solusi terbaik (karena itu saya belum menandai jawaban ini sebagai benar dulu), tetapi berhasil dan menurut dokumentasi adalah pertama kalinya ketika seseorang dapat menemukan ukuran sebenarnya dari sebuah tampilan.

Nik Reiman
sumber
Senang mendengarnya. Jika saya menemukan sesuatu yang memungkinkan Anda untuk berkeliling karena harus membuat subkelas View, saya akan mempostingnya di sini. Saya sedikit terkejut onPostCreate () tidak berfungsi.
Mark B
Nah, saya mencoba ini dan tampaknya berhasil. Hanya saja bukan solusi terbaik karena metode ini sering dipanggil tidak hanya sekali di awal. :(
Tobias Reich
10

Berikut adalah kode untuk mendapatkan tata letak melalui mengganti tampilan jika API <11 (API 11 menyertakan fitur View.OnLayoutChangedListener):

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);
}
gregm
sumber
6

Anda dapat memeriksa pertanyaan ini . Anda dapat menggunakan metode View's post () .

Macarse
sumber
5

Gunakan kode di bawah ini, ini memberikan ukuran tampilan.

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

Ini bekerja untuk saya di 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);
Biro Csaba Norbert
sumber
4
Anda tidak perlu melakukan postDelayed jika sudah ada di click listener. Anda tidak dapat mengklik apa pun hingga semua tampilan diukur sendiri dan muncul di layar. Jadi mereka sudah diukur pada saat Anda dapat mengkliknya.
afollestad
Kode ini salah. Penundaan pasca tidak menjamin di centang berikutnya tampilan Anda diukur.
Ian Wong
Saya akan merekomendasikan untuk tidak menggunakan postDelayed (). Ini tidak mungkin terjadi, tetapi bagaimana jika tampilan Anda tidak diukur dalam 1 detik dan Anda menyebutnya runnable untuk mendapatkan lebar / tinggi tampilan. Itu masih akan menghasilkan 0. Edit: Oh dan btw bahwa 1 dalam milidetik, jadi pasti akan gagal.
Alex
4

Saya juga tersesat getMeasuredWidth()dan getMeasuredHeight() getHeight()dan getWidth()untuk waktu yang lama ......... kemudian saya menemukan bahwa mendapatkan lebar dan tinggi onSizeChanged()tampilan adalah cara terbaik untuk melakukan ini ........ Anda bisa secara dinamis dapatkan lebar CURRENT dan tinggi CURRENT dari tampilan Anda dengan mengganti onSizeChanged(metode).

mungkin ingin melihat ini yang memiliki cuplikan kode yang rumit. Entri Blog Baru: cara mendapatkan dimensi lebar dan tinggi dari Tampilan kustom (memperluas Tampilan) di Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
sumber
0

Anda bisa mendapatkan Posisi dan Dimensi tampilan di layar

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

        }
    });
}
Hitesh Sahu
sumber
-1

bekerja sempurna untuk saya:

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