Xác định kích thước của chế độ xem Android khi chạy


123

Tôi đang cố gắng áp dụng hình động cho chế độ xem trong ứng dụng Android của mình sau khi hoạt động của tôi được tạo. Để làm điều này, tôi cần xác định kích thước hiện tại của chế độ xem và sau đó thiết lập một hình động để chia tỷ lệ từ kích thước hiện tại sang kích thước mới. Phần này phải được thực hiện trong thời gian chạy, vì chế độ xem tỷ lệ thành các kích cỡ khác nhau tùy thuộc vào đầu vào từ người dùng. Bố cục của tôi được định nghĩa bằng XML.

Đây có vẻ là một nhiệm vụ dễ dàng, và có rất nhiều câu hỏi SO liên quan đến vấn đề này mặc dù không có câu hỏi nào giải quyết được vấn đề của tôi, rõ ràng. Vì vậy, có lẽ tôi đang thiếu một cái gì đó rõ ràng. Tôi xử lý quan điểm của mình bằng cách:

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

Đây hoạt động tốt, nhưng khi gọi getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, vv, họ tất cả trở lại 0. Tôi cũng thử bằng tay gọi measure()trên quan điểm tiếp theo là một cuộc gọi đến getMeasuredWidth(), nhưng điều đó không có tác dụng.

Tôi đã thử gọi các phương thức này và kiểm tra đối tượng trong trình gỡ lỗi trong hoạt động của tôi onCreate()và trong onPostCreate(). Làm cách nào tôi có thể tìm ra kích thước chính xác của chế độ xem này trong thời gian chạy?


1
Ồ, tôi cũng cần lưu ý rằng bản thân khung nhìn chắc chắn không có 0 chiều rộng / chiều cao. Nó xuất hiện trên màn hình tốt.
Nik Reiman

Bạn có thể đăng thẻ <ImageView ... /> từ bố cục xml không?
fhucho

Tôi đã vật lộn với nhiều giải pháp SO cho vấn đề này cho đến khi tôi nhận ra rằng trong trường hợp của mình, kích thước của Chế độ xem khớp với màn hình vật lý (ứng dụng của tôi là "nhập vai" và Chế độ xem và tất cả chiều rộng và chiều cao của cha mẹ được đặt thành match_parent ). Trong trường hợp này, một giải pháp đơn giản hơn có thể được gọi một cách an toàn ngay cả trước khi Chế độ xem được rút ra (ví dụ: từ phương thức onCreate () của Activity) của bạn chỉ là sử dụng Display.getSize (); xem stackoverflow.com/a/30929599/5025060 để biết chi tiết. Tôi biết đây không phải là những gì @NikReiman yêu cầu, chỉ để lại một ghi chú cho những người có thể sử dụng phương pháp đơn giản này.
MÃ-REaD

Câu trả lời:


180

Sử dụng ViewTreeObserver trên View để chờ bố cục đầu tiên. Chỉ sau khi bố trí đầu tiên, getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () sẽ hoạt động.

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

21
Hãy cẩn thận với câu trả lời này vì nó sẽ được gọi liên tục nếu chế độ xem bên dưới là video / bề mặt
Kevin Parker

7
Bạn có thể giải thích về điều đó @Kevin không? Hai điều, trước hết, vì nó là một người nghe, tôi sẽ mong đợi một cuộc gọi lại duy nhất, và tiếp theo, ngay khi chúng tôi nhận được cuộc gọi lại đầu tiên, chúng tôi sẽ loại bỏ người nghe. Tôi đã bỏ lỡ một cái gì đó?
Vikram Bod Richla

4
Trước khi gọi bất kỳ phương thức ViewTreeObserver nào, bạn nên kiểm tra isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Trần

4
bất kỳ thay thế cho removeGlobalOnLayoutListener(this);? bởi vì nó bị phản đối
Đánh bạc Sibbs

7
@FarticlePilter, thay vào đó hãy sử dụng removeOnGlobalLayoutListener (). Nó được đề cập trong các tài liệu.
Vikram Bod Richla

63

Thực tế có nhiều giải pháp, tùy thuộc vào kịch bản:

  1. Phương thức an toàn, sẽ hoạt động ngay trước khi vẽ chế độ xem, sau khi giai đoạn bố trí kết thúc:
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); 
}

Sử dụng mẫu:

    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. Trong một số trường hợp, nó đủ để đo kích thước của chế độ xem theo cách thủ công:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Nếu bạn biết kích thước của container:

    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. nếu bạn có chế độ xem tùy chỉnh mà bạn đã mở rộng, bạn có thể lấy kích thước của nó trên phương thức "onMeasure", nhưng tôi nghĩ rằng nó chỉ hoạt động tốt trong một số trường hợp:
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. Nếu bạn viết bằng Kotlin, bạn có thể sử dụng chức năng tiếp theo, đằng sau hậu trường hoạt động chính xác như runJustBeforeBeingDrawntôi đã viết:

    view.doOnPreDraw { actionToBeTriggered() }

    Lưu ý rằng bạn cần thêm phần này vào lớp (tìm thấy ở đây ):

    implementation 'androidx.core:core-ktx:#.#'

1
Có vẻ như giải pháp số 1 sẽ không luôn luôn hoạt động OnPreDrawListener. Đã kiểm tra trường hợp khi bạn muốn chạy mã từ Activity onCreate()và bạn ngay lập tức chạy ứng dụng nền. Dễ dàng lặp lại đặc biệt là trên thiết bị chậm hơn. Nếu bạn thay thế OnPreDrawListenerbằng OnGlobalLayoutListener- bạn sẽ không thấy vấn đề tương tự.
hỏi

@questioner Tôi không hiểu, nhưng tôi rất vui vì cuối cùng nó đã giúp bạn.
nhà phát triển Android

Mặc dù tôi thường sử dụng ViewTreeObserverhoặc post, trong trường hợp của tôi, số 2 đã giúp ( view.measure).
CoolMind

3
@CoolMind Bạn cũng có thể sử dụng một phương thức mới, trong trường hợp bạn sử dụng Kotlin. Cập nhật câu trả lời để bao gồm nó quá.
nhà phát triển Android

Tại sao kotlin có chức năng bổ sung cho việc này? google là điên, ngôn ngữ này là vô ích (chỉ nên ở lại với java), đó là nhiều tuổi hơn nhanh chóng nhưng chỉ nhanh chóng có đủ sự nổi tiếng tiobe.com/tiobe-index (nhanh chóng 12 vị trí và Kotlin 38)
user924

18

Bạn có đang gọi getWidth()trước khi màn hình thực sự được đặt trên màn hình không?

Một lỗi phổ biến được thực hiện bởi các nhà phát triển Android mới là sử dụng chiều rộng và chiều cao của chế độ xem bên trong hàm tạo của nó. Khi hàm tạo của chế độ xem được gọi, Android chưa biết chế độ xem sẽ lớn đến mức nào, vì vậy kích thước được đặt thành không. Các kích thước thực được tính toán trong giai đoạn bố trí, xảy ra sau khi xây dựng nhưng trước khi mọi thứ được rút ra. Bạn có thể sử dụng onSizeChanged()phương thức để được thông báo về các giá trị khi chúng được biết hoặc bạn có thể sử dụng các phương thức getWidth()getHeight()sau này, chẳng hạn như trong onDraw()phương thức.


2
Vì vậy, điều đó có nghĩa là tôi cần ghi đè ImageView chỉ để bắt onSizeChange()sự kiện để biết kích thước thật? Điều đó có vẻ như hơi quá mức ... mặc dù tại thời điểm này tôi tuyệt vọng chỉ để làm cho mã hoạt động, vì vậy nó đáng để thử.
Nik Reiman

Tôi đã suy nghĩ nhiều hơn như chờ đợi cho đến khi onCreate () của bạn kết thúc trong Hoạt động của bạn.
Đánh dấu B

1
Như đã đề cập, tôi đã thử đặt mã này onPostCreate()để chạy sau khi chế độ xem hoàn toàn được tạo và bắt đầu.
Nik Reiman

1
Văn bản được trích dẫn từ đâu?
Mâu thuẫn

1
trích dẫn này cho chế độ xem tùy chỉnh, không đăng nó cho câu trả lời như vậy khi chúng tôi hỏi về kích thước chế độ xem nói chung
user924

17

Dựa trên lời khuyên của @ mbaird, tôi đã tìm thấy một giải pháp khả thi bằng cách phân ImageViewlớp và ghi đè onLayout(). Sau đó, tôi đã tạo ra một giao diện quan sát mà hoạt động của tôi đã triển khai và chuyển một tham chiếu đến chính nó cho lớp, cho phép nó nói với hoạt động khi nó thực sự kết thúc kích cỡ.

Tôi không tin chắc 100% rằng đây là giải pháp tốt nhất (do đó tôi chưa đánh dấu câu trả lời này là đúng), nhưng nó hoạt động và theo tài liệu là lần đầu tiên khi người ta có thể tìm thấy kích thước thực của chế độ xem.


Tốt để biết. Nếu tôi tìm thấy bất cứ điều gì cho phép bạn có được xung quanh việc phải phân lớp thì View tôi sẽ đăng nó ở đây. Tôi hơi ngạc nhiên khi onPostCreate () không hoạt động.
Đánh dấu B

Vâng, tôi đã thử điều này và nó dường như làm việc. Chỉ không thực sự là giải pháp tốt nhất vì phương thức này được gọi thường xuyên không chỉ một lần khi bắt đầu. :(
Tobias Reich

10

Đây là mã để có được bố cục thông qua ghi đè chế độ xem nếu API <11 (API 11 bao gồm tính năng 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);
}


5

Sử dụng mã dưới đây, nó là cho kích thước của khung nhìn.

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

5

Điều này làm việc cho tôi trong 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);

4
Bạn không cần phải đăng bài khi đã ở trong trình nghe nhấp chuột. Bạn không thể nhấp vào bất cứ điều gì cho đến khi tất cả các lượt xem tự đo và xuất hiện trên màn hình. Do đó, chúng sẽ được đo theo thời gian bạn có thể nhấp vào chúng.
afollestad

Mã này là sai. Đăng chậm trễ không đảm bảo trong lần đánh dấu tiếp theo của bạn được đo.
Ian Wong

Tôi khuyên bạn không nên đi postDelayed (). Điều đó không có khả năng xảy ra nhưng nếu chế độ xem của bạn không được đo trong vòng 1 giây và bạn gọi đây là runnable để có được chiều rộng / chiều cao của chế độ xem. Nó vẫn sẽ có kết quả 0. Chỉnh sửa: Oh và btw rằng 1 là tính bằng mili giây, vì vậy chắc chắn nó sẽ thất bại.
Alex

4

Tôi cũng bị lạc xung quanh getMeasuredWidth()getMeasuredHeight() getHeight()getWidth()trong một thời gian dài .......... sau tôi phát hiện ra rằng nhận được chiều rộng và chiều cao của tầm nhìn trong onSizeChanged()là cách tốt nhất để làm điều này ........ bạn có thể tự động có được chiều rộng HIỆN TẠI và chiều cao HIỆN TẠI của chế độ xem của bạn bằng cách ghi đè onSizeChanged(phương thức).

có thể muốn xem cái này có đoạn mã phức tạp. Bài đăng trên Blog mới: cách lấy kích thước chiều rộng và chiều cao của customView (mở rộng Chế độ xem) trong Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get- thong-and-height-display.html


0

Bạn có thể nhận được cả Vị trí và Kích thước của chế độ xem trên màn hình

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

        }
    });
}

-1

làm việc hoàn hảo cho tôi:

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