Làm thế nào bạn có thể biết khi một bố cục đã được vẽ?


201

Tôi có một chế độ xem tùy chỉnh để vẽ một bitmap có thể cuộn lên màn hình. Để khởi tạo nó, tôi cần chuyển kích thước tính bằng pixel của đối tượng bố trí cha. Nhưng trong các hàm onCreate và onResume, Bố cục chưa được vẽ và bố cục.getMeasuredHeight () trả về 0.

Như một giải pháp thay thế, tôi đã thêm một trình xử lý để chờ một giây và sau đó đo. Điều này hoạt động, nhưng nó cẩu thả, và tôi không biết tôi có thể cắt giảm bao nhiêu thời gian trước khi tôi kết thúc trước khi bố trí được vẽ.

Những gì tôi muốn biết là, làm thế nào tôi có thể phát hiện khi bố cục được vẽ? Có một sự kiện hoặc gọi lại?

Câu trả lời:


391

Bạn có thể thêm một người quan sát cây để bố trí. Điều này sẽ trả lại chiều rộng và chiều cao chính xác. onCreate()được gọi trước khi bố trí các khung nhìn con được thực hiện. Vì vậy, chiều rộng và chiều cao chưa được tính toán. Để có được chiều cao và chiều rộng, hãy đặt onCreate()phương thức này:

    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
Cảm ơn rât nhiều! Nhưng tôi tự hỏi tại sao không có phương pháp đơn giản hơn để làm điều này bởi vì đó là một vấn đề phổ biến.
felixd

1
Kinh ngạc. Tôi đã thử OnLayoutChangeListener và nó không hoạt động. Nhưng OnGlobalLayoutListener đã hoạt động. Cảm ơn bạn rất nhiều!
YoYoMyo

8
removeGlobalOnLayoutListener không được dùng ở cấp độ API 16. Sử dụng removeOnGlobalLayoutListener thay thế.
tounaobun

3
Một "gotcha" tôi đang thấy là nếu chế độ xem của bạn không hiển thị, onGlobalLayout được gọi, nhưng sau đó khi hiển thị thì getView được gọi, nhưng không phải là onGlobalLayout ... ugh.
Stephen McCormick

1
Một giải pháp nhanh chóng khác cho việc này là sử dụng view.post(Runnable), nó sạch hơn và nó cũng làm điều tương tự.
dvdciri

74

Một cách thực sự dễ dàng là chỉ cần gọi post()vào bố trí của bạn. Điều này sẽ chạy mã của bạn bước tiếp theo, sau khi chế độ xem của bạn đã được tạo.

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

4
Tôi không biết liệu bài đăng có thực sự chạy mã của bạn hay không sau khi chế độ xem đã được tạo. Không có gì đảm bảo, vì bài đăng sẽ chỉ thêm Runnable mà bạn tạo vào RunQueue, để được thực thi sau khi hàng đợi trước đó được thực thi trên UI Thread.
HendraWD

4
Ngược lại, post () thực thi sau khi cập nhật bước UI tiếp theo, xảy ra sau khi chế độ xem được tạo. Do đó, bạn được đảm bảo rằng nó sẽ thực thi sau khi chế độ xem được thực hiện.
Elliptica

Tôi đã thử nghiệm mã này nhiều lần, nó chỉ hoạt động tốt nhất trong UI-thread. Trong một chủ đề nền đôi khi nó không hoạt động.
CoolMind

3
@HendraWijayaDjiono, có thể bài đăng này sẽ trả lời: stackoverflow.com/questions/21937224/
Kẻ

1
Tôi gặp phải một vấn đề khi sử dụng câu trả lời đầu tiên không hoạt động mọi lúc, sử dụng bài đăng trên chế độ xem tôi cần cuộn (chế độ xem cuộn), cho phép tôi cuộn đến vị trí mong muốn ngay khi chế độ xem sẵn sàng phương thức cuộn Để gọi.
Danuofr

21

Để tránh mã không dùng nữa và cảnh báo, bạn có thể sử dụng:

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
Để sử dụng mã này, 'view' phải được khai báo là 'cuối cùng'.
BeccaP

2
sử dụng điều kiện này thay vì Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BESE
HendraWD

4

Tốt hơn với các chức năng mở rộng 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)
            }
        }
    })
}

Giải pháp rất đẹp và thanh lịch có thể được tái sử dụng.
Irika Negru

3

Một câu trả lời khác là:
Hãy thử kiểm tra kích thước Xem tại onWindowFocusChanged.


3

Một thay thế cho các phương pháp thông thường là móc vào bản vẽ của khung nhìn.

OnPreDrawListenerđược gọi nhiều lần khi hiển thị chế độ xem, do đó không có phép lặp cụ thể trong đó chế độ xem của bạn có chiều rộng hoặc chiều cao được đo hợp lệ. Điều này yêu cầu bạn liên tục xác minh ( view.getMeasuredWidth() <= 0) hoặc đặt giới hạn cho số lần bạn kiểm tra measuredWidthlớn hơn 0.

Cũng có khả năng khung nhìn sẽ không bao giờ được rút ra, điều này có thể chỉ ra các vấn đề khác với mã của bạn.

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

Câu hỏi là How can you tell when a layout has been drawn?và bạn đang cung cấp một phương pháp mà bạn gọi OnPreDrawListenerdon't stop interrogating that view until it has provided you with a reasonable answervì vậy sự phản đối đối với giải pháp của bạn là rõ ràng.
Giỏ hàng bị bỏ rơi

Những gì bạn cần biết không phải là khi nó được vẽ mà là khi nó được đo. Mã của tôi trả lời câu hỏi thực sự cần phải được hỏi.
jwehrle

Có một số vấn đề với giải pháp này? Có phải nó không giải quyết vấn đề phải đối mặt?
jwehrle

1
Chế độ xem không được đo cho đến khi nó được đặt ra, vì vậy phương án này là quá mức và thực hiện kiểm tra dự phòng. Bạn thừa nhận không giải quyết câu hỏi, nhưng những gì bạn cảm thấy nên được hỏi. Nhận xét ban đầu đã đưa ra cả hai điểm này, nhưng cũng có một số vấn đề với chính mã đã được gửi dưới dạng chỉnh sửa.
Giỏ hàng bị bỏ rơi

Đó là một chỉnh sửa tốt. Cảm ơn bạn. Tôi đoán những gì tôi đang hỏi là, Bạn có nghĩ rằng câu trả lời này nên được xóa hay không? Tôi gặp phải vấn đề của OP. Điều này đã giải quyết vấn đề. Tôi đã cung cấp nó trong đức tin tốt. Đó có phải là một sai lầm? Đó có phải là điều không nên làm trên StackOverflow không?
jwehrle

2

Chỉ cần kiểm tra nó bằng cách gọi phương thức bài trên bố cục hoặc xem của bạn

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

Điều này là không đúng sự thật và có thể che giấu các vấn đề có thể. postsẽ chỉ xếp hàng runnable để chạy sau, nó không đảm bảo tầm nhìn được đo, thậm chí ít được vẽ.
Irika Negru

@I MuffNegru có thể đọc stackoverflow.com/a/21938380
Bruno Bieri

@BrunoBieri, bạn có thể kiểm tra hành vi này bằng các hàm async thay đổi số đo chế độ xem và xem liệu tác vụ xếp hàng của bạn có được chạy sau mỗi lần đo và nhận các giá trị mong đợi hay không.
Irika Negru

1

Khi onMeasuređược gọi là khung nhìn được đo chiều rộng / chiều cao được đo. Sau này, bạn có thể gọi layout.getMeasuredHeight().


4
Bạn có phải tạo chế độ xem tùy chỉnh của riêng mình để biết khi nào onMeasure kích hoạt không?
Geek On Hugs

Có, bạn phải sử dụng chế độ xem tùy chỉnh để truy cập vào biện pháp.
Trevor Hart
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.