การกำหนดขนาดของมุมมอง Android ที่รันไทม์


123

ฉันพยายามใช้แอนิเมชั่นกับมุมมองในแอพ Android ของฉันหลังจากสร้างกิจกรรมแล้ว ในการทำเช่นนี้ฉันต้องกำหนดขนาดปัจจุบันของมุมมองจากนั้นตั้งค่าภาพเคลื่อนไหวเพื่อปรับขนาดจากขนาดปัจจุบันเป็นขนาดใหม่ ส่วนนี้ต้องทำที่รันไทม์เนื่องจากมุมมองจะปรับขนาดให้แตกต่างกันขึ้นอยู่กับอินพุตจากผู้ใช้ เค้าโครงของฉันถูกกำหนดใน XML

ดูเหมือนว่าจะเป็นงานง่ายและมีคำถาม SO มากมายเกี่ยวกับเรื่องนี้แม้ว่าจะไม่มีใครช่วยแก้ปัญหาของฉันได้ ดังนั้นบางทีฉันอาจพลาดบางอย่างที่ชัดเจน ฉันเข้าใจมุมมองของฉันโดย:

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

นี้ทำงานได้ดี แต่เมื่อโทรgetWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().widthฯลฯ พวกเขากลับมาทุก 0. ฉันได้พยายามด้วยตนเองเรียกmeasure()ในมุมมองตามด้วยการโทรไปgetMeasuredWidth()แต่ไม่มีผล

ฉันได้ลองเรียกเมธอดเหล่านี้และตรวจสอบวัตถุในดีบักเกอร์ในกิจกรรมของฉันonCreate()และในonPostCreate(). ฉันจะหาขนาดที่แน่นอนของมุมมองนี้ที่รันไทม์ได้อย่างไร


1
โอ้ยังฉันควรทราบว่ามุมมองของตัวเองแน่นอนไม่ได้มี 0 กว้าง / สูง ปรากฏบนหน้าจอได้ดี
Nik Reiman

คุณสามารถโพสต์แท็ก <ImageView ... /> จากเลย์เอาต์ xml ได้หรือไม่
fhucho

ฉันต่อสู้กับวิธีแก้ปัญหา SO มากมายในปัญหานี้จนกระทั่งฉันตระหนักว่าในกรณีของฉันขนาดของ View ตรงกับหน้าจอจริง (แอปของฉัน "สมจริง" และมุมมองและความกว้างและความสูงทั้งหมดของผู้ปกครองถูกตั้งค่าเป็นmatch_parent) ในกรณีนี้วิธีแก้ปัญหาที่ง่ายกว่าซึ่งสามารถเรียกใช้ได้อย่างปลอดภัยก่อนที่ View จะถูกดึงออกมา (เช่นจากเมธอด onCreate () ของกิจกรรมของคุณ) เพียงแค่ใช้ Display.getSize (); ดูรายละเอียดstackoverflow.com/a/30929599/5025060 ฉันรู้ว่านี่ไม่ใช่สิ่งที่ @NikReiman ขอเพียงแค่ฝากข้อความถึงผู้ที่อาจใช้วิธีที่ง่ายกว่านี้ได้
CODE-REaD

คำตอบ:


180

ใช้ ViewTreeObserver บน View เพื่อรอเค้าโครงแรก หลังจากเลย์เอาต์แรกเท่านั้นที่จะ getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () ทำงาน

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
โปรดใช้ความระมัดระวังกับคำตอบนี้เนื่องจากจะถูกเรียกอย่างต่อเนื่องหากมุมมองพื้นฐานเป็นวิดีโอ / พื้นผิว
Kevin Parker

7
คุณสามารถอธิบายรายละเอียดเกี่ยวกับ @Kevin ได้หรือไม่? สองสิ่งก่อนอื่นเนื่องจากเป็นผู้ฟังฉันคาดหวังว่าจะมีการโทรกลับเพียงครั้งเดียวและถัดไปทันทีที่เราได้รับการโทรกลับครั้งแรกเราจะลบผู้ฟังออก ฉันพลาดอะไรไปหรือเปล่า?
Vikram Bodicherla

4
ก่อนที่จะเรียกใช้เมธอด ViewTreeObserver ใด ๆ ขอแนะนำให้ตรวจสอบ isAlive (): if (view.getViewTreeObserver (). isAlive ()) {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Peter Tran

4
ทางเลือกอื่นremoveGlobalOnLayoutListener(this);หรือไม่ เนื่องจากเลิกใช้งานแล้ว
Sibbs Gambling

7
@FarticlePilter ให้ใช้ removeOnGlobalLayoutListener () แทน มีการกล่าวถึงในเอกสาร
Vikram Bodicherla

64

มีวิธีแก้ปัญหาหลายวิธีขึ้นอยู่กับสถานการณ์:

  1. วิธีการที่ปลอดภัยจะใช้ได้ผลก่อนวาดมุมมองหลังจากเสร็จสิ้นขั้นตอนการจัดวาง:
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); 
}

ตัวอย่างการใช้งาน:

    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. ในบางกรณีการวัดขนาดของมุมมองด้วยตนเองก็เพียงพอแล้ว:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

หากคุณทราบขนาดของภาชนะ:

    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. หากคุณมีมุมมองแบบกำหนดเองที่ขยายออกไปคุณจะได้ขนาดของมันในเมธอด "onMeasure" แต่ฉันคิดว่ามันใช้ได้ดีในบางกรณีเท่านั้น:
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. หากคุณเขียนใน Kotlin คุณสามารถใช้ฟังก์ชันถัดไปซึ่งเบื้องหลังทำงานได้เหมือนกับrunJustBeforeBeingDrawnที่ฉันเขียนไว้:

    view.doOnPreDraw { actionToBeTriggered() }

    โปรดทราบว่าคุณต้องเพิ่มสิ่งนี้ลงใน gradle (พบได้ที่นี่ ):

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

1
ดูเหมือนว่าโซลูชัน # 1 จะไม่ได้ผลเสมอOnPreDrawListenerไป กรณีทดสอบเมื่อคุณต้องการเรียกใช้โค้ดจากกิจกรรมonCreate()และแอปพื้นหลังทันที ทำซ้ำได้ง่ายโดยเฉพาะบนอุปกรณ์ที่ช้าลง หากคุณแทนที่OnPreDrawListenerด้วยOnGlobalLayoutListener- คุณจะไม่พบปัญหาเดียวกัน
ถาม

@questioner ฉันไม่เข้าใจ แต่ฉันมีความสุขที่ได้ช่วยคุณในที่สุด
นักพัฒนา Android

แม้ว่าฉันมักจะใช้ViewTreeObserverหรือpostในกรณีของฉันหมายเลข 2 ช่วย ( view.measure)
CoolMind

3
@CoolMind คุณสามารถใช้วิธีการใหม่ในกรณีที่คุณใช้ Kotlin อัปเดตคำตอบเพื่อรวมไว้ด้วย
นักพัฒนา Android

ทำไม kotlin ถึงมีฟังก์ชั่นเพิ่มเติมสำหรับสิ่งนี้? google บ้าไปแล้วภาษานี้ไม่มีประโยชน์ (ควรอยู่กับ java) มันเก่ากว่า swift มาก แต่มีเพียง swift เท่านั้นที่ได้รับความนิยมเพียงพอtiobe.com/tiobe-index (ตำแหน่งที่รวดเร็ว 12 และ kotlin 38)
924

18

คุณโทรgetWidth()มาก่อนที่มุมมองจะวางบนหน้าจอจริงหรือ?

ข้อผิดพลาดทั่วไปของนักพัฒนา Android ใหม่คือการใช้ความกว้างและความสูงของมุมมองภายในตัวสร้าง เมื่อมีการเรียกตัวสร้างมุมมอง Android ยังไม่รู้ว่ามุมมองจะใหญ่แค่ไหนดังนั้นขนาดจึงถูกตั้งค่าเป็นศูนย์ ขนาดจริงจะคำนวณระหว่างขั้นตอนการจัดวางซึ่งเกิดขึ้นหลังการก่อสร้าง แต่ก่อนที่จะวาดอะไร คุณสามารถใช้onSizeChanged()เมธอดเพื่อรับแจ้งค่าเมื่อทราบค่าหรือคุณสามารถใช้getWidth()และ getHeight()วิธีการในภายหลังเช่นในonDraw()เมธอด


2
นั่นหมายความว่าฉันต้องลบล้าง ImageView เพื่อจับonSizeChange()เหตุการณ์เพื่อให้ทราบขนาดจริงหรือไม่? ดูเหมือนว่าจะใช้งานมากเกินไป ... แม้ว่าในตอนนี้ฉันก็หมดหวังที่จะให้โค้ดใช้งานได้ดังนั้นมันจึงคุ้มค่าที่จะยิง
Nik Reiman

ฉันคิดว่าจะรอจนกว่า onCreate () ของคุณจะเสร็จสิ้นในกิจกรรมของคุณ
Mark B

1
ดังที่ได้กล่าวไปแล้วฉันได้ลองใส่โค้ดนี้onPostCreate()ซึ่งจะทำงานหลังจากที่สร้างและเริ่มมุมมองอย่างสมบูรณ์
Nik Reiman

1
ข้อความที่ยกมามาจากไหน?
Intrications

1
คำพูดนี้สำหรับมุมมองที่กำหนดเองอย่าโพสต์สำหรับคำตอบดังกล่าวเมื่อเราถามเกี่ยวกับขนาดการดูโดยทั่วไป
user924

17

อยู่บนพื้นฐานของคำแนะนำ @ mbaird ของผมพบว่าวิธีการแก้ปัญหาที่สามารถทำงานได้โดย subclassing ชั้นเรียนและเอาชนะImageView onLayout()จากนั้นฉันก็สร้างอินเทอร์เฟซผู้สังเกตการณ์ซึ่งกิจกรรมของฉันนำไปใช้และส่งต่อการอ้างอิงถึงตัวเองไปยังชั้นเรียนซึ่งทำให้สามารถบอกกิจกรรมได้เมื่อปรับขนาดเสร็จแล้ว

ฉันไม่มั่นใจ 100% ว่านี่เป็นวิธีแก้ปัญหาที่ดีที่สุด (ด้วยเหตุนี้ฉันจึงยังไม่ได้ทำเครื่องหมายคำตอบนี้ว่าถูกต้อง) แต่ก็ใช้ได้ผลและตามเอกสารเป็นครั้งแรกที่สามารถพบขนาดจริงของมุมมองได้


ดีแล้วที่รู้. ถ้าฉันพบสิ่งใดที่จะทำให้คุณไปไหนมาไหนได้โดยต้องมีคลาสย่อยดูฉันจะโพสต์ไว้ที่นี่ ฉันแปลกใจเล็กน้อยที่PostCreate () ไม่ทำงาน
Mark B

ฉันลองแล้วดูเหมือนว่าจะได้ผล ไม่ใช่วิธีการแก้ปัญหาที่ดีที่สุดจริงๆเนื่องจากวิธีนี้มีการโทรบ่อยไม่เพียงครั้งเดียวในตอนเริ่มต้น :(
Tobias Reich

10

นี่คือรหัสสำหรับรับเลย์เอาต์ผ่านการแทนที่มุมมองหาก API <11 (API 11 มีคุณสมบัติ 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

ใช้โค้ดด้านล่างเพื่อให้ขนาดของมุมมอง

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

5

สิ่งนี้ใช้ได้กับฉันใน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
คุณไม่จำเป็นต้อง postDelayed เมื่ออยู่ใน click Listener คุณไม่สามารถคลิกอะไรได้เลยจนกว่ายอดดูทั้งหมดจะวัดตัวเองและปรากฏบนหน้าจอ ดังนั้นพวกเขาจะถูกวัดตามเวลาที่คุณสามารถคลิกได้
afollestad

รหัสนี้ไม่ถูกต้อง ไม่รับประกันความล่าช้าในการโพสต์ในการทำเครื่องหมายครั้งต่อไปการดูของคุณจะถูกวัด
Ian Wong

ฉันอยากจะแนะนำว่าอย่าไป postDelayed () ไม่น่าจะเกิดขึ้น แต่จะเกิดอะไรขึ้นถ้าคุณไม่ได้วัดมุมมองภายใน 1 วินาทีและคุณเรียกสิ่งนี้ว่า runnable เพื่อรับความกว้าง / ความสูงในการดู มันจะยังคงผลลัพธ์เป็น 0 แก้ไข: โอ้และ btw ว่า 1 อยู่ในหน่วยมิลลิวินาทีดังนั้นมันจะล้มเหลวอย่างแน่นอน
Alex

4

ผมยังหายไปรอบ ๆgetMeasuredWidth()และgetMeasuredHeight() getHeight()และgetWidth()เป็นเวลานาน .......... ต่อมาผมพบว่าได้รับความกว้างของมุมมองและความสูงในการonSizeChanged()เป็นวิธีที่ดีที่สุดที่จะทำเช่นนี้ ........ คุณสามารถแบบไดนามิก รับความกว้าง CURRENT และความสูง CURRENT ของมุมมองของคุณโดยการลบล้างonSizeChanged(เมธอด)

อาจลองดูสิ่งนี้ซึ่งมีข้อมูลโค้ดอย่างละเอียด บล็อกโพสต์ใหม่: วิธีรับขนาดความกว้างและความสูงของ customView (ขยายมุมมอง) ใน Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html


0

คุณสามารถรับได้ทั้งตำแหน่งและขนาดของมุมมองบนหน้าจอ

 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

ใช้งานได้ดีสำหรับฉัน:

 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;
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.