onMeasure tùy chỉnh xem giải thích


316

Tôi đã cố gắng để làm thành phần tùy chỉnh. Tôi mở rộng Viewlớp và thực hiện một số bản vẽ trong onDrawphương thức ghi đè. Tại sao tôi cần ghi đè onMeasure? Nếu tôi không, mọi thứ đều đúng. Ai đó có thể giải thích nó? Tôi nên viết onMeasurephương pháp của mình như thế nào? Tôi đã xem vài bài hướng dẫn, nhưng mỗi bài có một chút khác biệt so với bài khác. Đôi khi họ gọi super.onMeasurevào cuối, đôi khi họ sử dụng setMeasuredDimensionvà không gọi nó. Đâu là sự khác biệt?

Sau tất cả, tôi muốn sử dụng một số thành phần chính xác. Tôi đã thêm các thành phần đó vào XMLtệp của mình , nhưng tôi không biết chúng nên lớn như thế nào. Tôi muốn đặt vị trí và kích thước của nó sau này (tại sao tôi cần đặt kích thước onMeasuretrong onDrawkhi tôi vẽ nó, cũng hoạt động tốt) trong lớp thành phần tùy chỉnh. Khi chính xác tôi cần phải làm điều đó?

Câu trả lời:


735

onMeasure()là cơ hội của bạn để nói với Android mức độ lớn mà bạn muốn chế độ xem tùy chỉnh của mình phụ thuộc vào các ràng buộc bố cục do phụ huynh cung cấp; đó cũng là cơ hội của chế độ xem tùy chỉnh của bạn để tìm hiểu những hạn chế bố cục đó là gì (trong trường hợp bạn muốn hành xử khác trong match_parenttình huống hơn là wrap_contenttình huống). Các ràng buộc này được đóng gói thành các MeasureSpecgiá trị được truyền vào phương thức. Đây là một mối tương quan sơ bộ của các giá trị chế độ:

  • Chính xác có nghĩa là layout_widthhoặc layout_heightgiá trị được đặt thành một giá trị cụ thể. Bạn có lẽ nên làm cho quan điểm của bạn kích thước này. Điều này cũng có thể được kích hoạt khi match_parentđược sử dụng, để đặt kích thước chính xác cho chế độ xem chính (đây là bố cục phụ thuộc trong khung).
  • AT_MOST thường có nghĩa là layout_widthhoặc layout_heightgiá trị được đặt thành match_parenthoặc wrap_contentkhi cần kích thước tối đa (đây là bố cục phụ thuộc trong khung) và kích thước của kích thước cha là giá trị. Bạn không nên lớn hơn kích thước này.
  • UNSPECifyED thường có nghĩa là layout_widthhoặc layout_heightgiá trị được đặt thành wrap_contentkhông có giới hạn. Bạn có thể là bất cứ kích thước nào bạn muốn. Một số bố cục cũng sử dụng cuộc gọi lại này để tìm ra kích thước mong muốn của bạn trước khi xác định thông số kỹ thuật nào thực sự vượt qua bạn trong yêu cầu đo thứ hai.

Hợp đồng tồn tại với onMeasure()đó là setMeasuredDimension() PHẢI được gọi ở cuối với kích thước bạn muốn có. Phương thức này được gọi bởi tất cả các triển khai khung, bao gồm cả triển khai mặc định được tìm thấy View, đó là lý do tại sao nó an toàn để gọi superthay thế nếu phù hợp với trường hợp sử dụng của bạn.

Cấp, bởi vì khung áp dụng triển khai mặc định, bạn có thể không cần ghi đè phương thức này, nhưng bạn có thể thấy cắt trong trường hợp không gian xem nhỏ hơn nội dung của bạn nếu bạn không và nếu bạn bố trí Chế độ xem tùy chỉnh với wrap_contentcả hai hướng, chế độ xem của bạn hoàn toàn không hiển thị vì khung không biết nó lớn đến mức nào!

Nói chung, nếu bạn đang ghi đè Viewvà không phải là một tiện ích hiện có khác, có lẽ nên cung cấp một triển khai, ngay cả khi nó đơn giản như một thứ như thế này:

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

Mong rằng sẽ giúp.


1
Xin chào @Devunwired giải thích tốt nhất cho đến nay tôi đọc. Lời giải thích của bạn đã trả lời rất nhiều câu hỏi tôi có và xóa một số nghi ngờ, nhưng vẫn còn một câu hỏi đó là: Nếu chế độ xem tùy chỉnh của tôi nằm trong Viewgroup cùng với một số Chế độ xem khác (không quan trọng là loại nào) mà Viewgroup sẽ có được tất cả các con của anh ấy cho mỗi một thăm dò cho ràng buộc LayoutParams của họ và yêu cầu mỗi đứa trẻ tự đo nó theo các ràng buộc của chúng?
pharaoh

47
Lưu ý rằng mã này sẽ không được thực hiện nếu bạn ghi đè onMeasure của bất kỳ lớp con Viewgroup nào. Các bài phỏng vấn của bạn sẽ không xuất hiện và tất cả sẽ có kích thước 0x0. Nếu bạn cần ghi đè onMeasure của một Viewgroup tùy chỉnh, hãy thay đổi widthMode, widthSize, heightMode và heightSize, biên dịch chúng trở lại để đoSpecs bằng cách sử dụng biện pháp đo lườngSpec.makeMeasureSpec và chuyển số nguyên kết quả thành super.onMeasure.
Alexey

1
Câu trả lời tuyệt vời. Lưu ý rằng, theo tài liệu của Google, trách nhiệm của View là xử lý phần đệm.
jonstaff

4
Quá phức tạp c ** p làm cho Android trở thành một hệ thống bố trí đau đớn để làm việc. Họ có thể vừa có getParent (). Get *** () ...
Oliver Dixon

2
Có các phương thức trợ giúp trong Viewlớp, được gọi resolveSizeAndStateresolveSize, sẽ thực hiện những điều mà mệnh đề 'nếu' làm - tôi thấy chúng hữu ích, đặc biệt nếu bạn phải viết các IF đó thường xuyên.
stan0

5

thực tế, câu trả lời của bạn không đầy đủ vì các giá trị cũng phụ thuộc vào hộp chứa. Trong trường hợp bố trí tương đối hoặc tuyến tính, các giá trị hoạt động như sau:

  • Chính xác match_parent là chính xác + kích thước của cha mẹ
  • AT_MOST quấn_content dẫn đến AT_MOST ĐoSpec
  • UNSPECEDED không bao giờ được kích hoạt

Trong trường hợp chế độ xem cuộn ngang, mã của bạn sẽ hoạt động.


57
Nếu bạn cảm thấy rằng một số câu trả lời ở đây là không đầy đủ, xin vui lòng thêm vào nó thay vì trả lời một phần.
Michaël

1
Onya tốt để liên kết điều này với cách bố trí hoạt động, nhưng trong trường hợp của tôi, onMeasure được gọi ba lần cho chế độ xem tùy chỉnh của tôi. Chế độ xem trong câu hỏi có chiều cao quấn_content và chiều rộng có trọng số (width = 0, weight = 1). Cuộc gọi đầu tiên có UNSPECifyED / UNSPECifyED, cuộc gọi thứ hai có AT_MOST / CHÍNH XÁC và cuộc gọi thứ ba đã CHÍNH XÁC / CHÍNH XÁC.
William T. Mallard

0

Nếu bạn không cần thay đổi điều gì đó trênMeasure - hoàn toàn không cần bạn phải ghi đè lên nó.

Mã Devunwired (câu trả lời được chọn và được bình chọn nhiều nhất ở đây) gần giống với cách triển khai SDK đã làm cho bạn (và tôi đã kiểm tra - nó đã thực hiện điều đó từ năm 2009).

Bạn có thể kiểm tra phương thức onMeasure tại đây :

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

Ghi đè mã SDK để được thay thế bằng cùng một mã chính xác không có ý nghĩa.

Tài liệu chính thức này tuyên bố "mặc định onMeasure () sẽ luôn đặt kích thước 100x100" - là sai.

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.