วิธีรับพิกัดที่แน่นอนของมุมมอง


390

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

ถ้าช่วยได้นี่เป็นเกม 'นำภาพกลับมาเรียงตามลำดับ' ฉันต้องการให้ผู้ใช้สามารถวาดกล่องเพื่อเลือกหลาย ๆ ชิ้นได้ ข้อสันนิษฐานของฉันคือวิธีที่ง่ายที่สุดในการทำเช่นนั้นคือไปgetRawX()และgetRawY()กลับจากMotionEventและเปรียบเทียบค่าเหล่านั้นกับมุมซ้ายบนของโครงร่างที่ถือส่วนต่างๆ เมื่อรู้ขนาดของชิ้นส่วนแล้วฉันสามารถกำหนดจำนวนชิ้นที่เลือกได้ ฉันรู้ว่าฉันสามารถใช้getX()และgetY()บนได้MotionEventแต่เนื่องจากนั่นส่งคืนตำแหน่งสัมพัทธ์ที่ทำให้การเลือกชิ้นส่วนใดยาก (เป็นไปไม่ได้ฉันรู้ แต่ดูเหมือนว่าจะซับซ้อนโดยไม่จำเป็น)

แก้ไข: นี่คือรหัสที่ฉันใช้เพื่อพยายามรับขนาดของคอนเทนเนอร์ที่ถือครองตามคำถามข้อใดข้อหนึ่ง TableLayoutเป็นตารางที่เก็บชิ้นส่วนปริศนาทั้งหมด

TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout);
Log.d(LOG_TAG, "Values " + tableLayout.getTop() + tableLayout.getLeft());

แก้ไข 2: นี่คือรหัสที่ฉันได้ลองทำตามคำตอบที่แนะนำเพิ่มเติม

public int[] tableLayoutCorners = new int[2];
(...)

TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout);
tableLayout.requestLayout();
Rect corners = new Rect();
tableLayout.getLocalVisibleRect(corners);
Log.d(LOG_TAG, "Top left " + corners.top + ", " + corners.left + ", " + corners.right
            + ", " + corners.bottom);

cells[4].getLocationOnScreen(tableLayoutCorners);
Log.d(LOG_TAG, "Values " + tableLayoutCorners[0] + ", " + tableLayoutCorners[1]);

รหัสนี้ถูกเพิ่มเข้ามาหลังจากการเริ่มต้นทั้งหมดเสร็จสิ้นแล้ว ภาพที่ได้รับการแบ่งออกเป็นอาร์เรย์ของ ImageViews A (เซลล์ [] อาร์เรย์) TableLayoutอยู่ภายใน เซลล์ [0] อยู่ด้านบนซ้ายImageViewและฉันเลือกเซลล์ [4] เนื่องจากอยู่ตรงกลางและแน่นอนที่สุดไม่ควรมีพิกัด (0,0)

รหัสที่แสดงด้านบนยังคงให้ 0s ทั้งหมดในบันทึกซึ่งฉันไม่เข้าใจจริงๆเพราะชิ้นส่วนปริศนาต่าง ๆ แสดงอย่างถูกต้อง (ฉันพยายาม public int สำหรับ tableLayoutCorners และการมองเห็นเริ่มต้นทั้งสองให้ผลลัพธ์เหมือนกัน)

ฉันไม่รู้ว่านี่เป็นสิ่งสำคัญหรือไม่ แต่ImageViewเดิมนั้นไม่ได้มีขนาด ขนาดของImageViews จะถูกกำหนดในระหว่างการเริ่มต้นโดยอัตโนมัติโดย View เมื่อฉันให้ภาพที่จะแสดง สิ่งนี้อาจช่วยให้ค่าของพวกเขาเป็น 0 ถึงแม้ว่ารหัสการบันทึกนั้นจะเกิดขึ้นหลังจากที่พวกเขาได้รับภาพและมีการปรับขนาดตัวเองโดยอัตโนมัติ? ในการโต้กลับนั้นฉันเพิ่มรหัสtableLayout.requestLayout()ตามที่แสดงด้านบน แต่นั่นไม่ได้ช่วย

คำตอบ:


632

การใช้งานและView.getLocationOnScreen() / หรือgetLocationInWindow()


3
ตอนนี้เป็นฟังก์ชั่นที่ฉันคาดว่าจะได้พบ น่าเสียดายที่ฉันต้องไม่ใช้งานอย่างถูกต้อง ฉันรู้ว่ามันเป็นข้อผิดพลาดที่รู้จักกันที่ getLocationOnScreen ให้ข้อยกเว้นตัวชี้โมฆะใน Android v1.5 (แพลตฟอร์มที่ฉันเข้ารหัส) แต่การทดสอบใน v2.1 ไม่ได้ผลดีกว่านี้ ทั้งสองวิธีให้ 0s กับทุกสิ่ง ฉันรวมข้อมูลโค้ดด้านบน
Steve Haley

76
คุณสามารถเรียกใช้ได้เฉพาะเค้าโครงหลังจากที่เกิดขึ้น คุณกำลังเรียกวิธีการก่อนที่มุมมองจะถูกจัดตำแหน่งบนหน้าจอ
Romain Guy

5
อ้าคุณพูดถูก แต่ก็ไม่เห็นชัดเจนนักว่าฉันกำลังทำอยู่ ขอบคุณ! สำหรับใครที่อยากรู้รายละเอียดเพิ่มเติมฉันได้เพิ่มคำตอบซึ่งอธิบายสิ่งที่ฉันหมายถึง
Steve Haley

9
คุณหมายถึงเช่น ViewTreeObserver และ OnGlobalLayoutListener หรือไม่ ดูที่ View.getViewTreeObserver () เพื่อเริ่มต้น
Romain Guy

8
@RomainGuy ใช่เช่นนั้น แต่สับสนน้อยกว่าต้องลงทะเบียน (แล้วยกเลิกการลงทะเบียน…) นี่เป็นพื้นที่ที่ iOS ใช้ดีกว่า Android ในแง่ของ SDK ฉันสามารถบอกได้ว่า iOS มีสิ่งที่น่ารำคาญมากมาย แต่การจัดการ UIView ไม่ใช่หนึ่งในนั้น :)
Martin Marconcini

127

คำตอบที่ยอมรับไม่ได้บอกวิธีรับสถานที่จริงดังนั้นนี่คือรายละเอียดเพิ่มเติมเล็กน้อย คุณผ่านintอาร์เรย์ที่มีความยาว 2 และค่าจะถูกแทนที่ด้วยพิกัด (x, y) ของมุมมอง (ของมุมบนด้านซ้าย)

int[] location = new int[2];
myView.getLocationOnScreen(location);
int x = location[0];
int y = location[1];

หมายเหตุ

  • การแทนที่getLocationOnScreenด้วยgetLocationInWindowควรให้ผลลัพธ์เดียวกันในกรณีส่วนใหญ่ (ดูคำตอบนี้ ) อย่างไรก็ตามหากคุณอยู่ในหน้าต่างที่เล็กกว่าเช่นกล่องโต้ตอบหรือแป้นพิมพ์แบบกำหนดเองคุณจะต้องเลือกว่าจะเลือกแบบไหนที่เหมาะกับความต้องการของคุณ
  • คุณจะได้รับ(0,0)ถ้าคุณเรียกใช้วิธีการนี้onCreateเนื่องจากมุมมองยังไม่ได้วาง คุณสามารถใช้ a ViewTreeObserverเพื่อฟังเมื่อเค้าโครงเสร็จสิ้นและคุณสามารถรับพิกัดที่วัดได้ (ดูคำตอบนี้)

86

ก่อนอื่นคุณต้องได้รับมุมมอง localVisible

สำหรับเช่น:

Rect rectf = new Rect();

//For coordinates location relative to the parent
anyView.getLocalVisibleRect(rectf);

//For coordinates location relative to the screen/display
anyView.getGlobalVisibleRect(rectf);

Log.d("WIDTH        :", String.valueOf(rectf.width()));
Log.d("HEIGHT       :", String.valueOf(rectf.height()));
Log.d("left         :", String.valueOf(rectf.left));
Log.d("right        :", String.valueOf(rectf.right));
Log.d("top          :", String.valueOf(rectf.top));
Log.d("bottom       :", String.valueOf(rectf.bottom));

หวังว่าจะช่วยได้


3
นั่นควรจะใช้ได้ ... แต่มันก็ให้ค่า 0 เช่นกัน ฉันรวมข้อมูลโค้ดข้างต้นหากช่วยให้คุณทราบว่าฉันทำอะไรผิด ขอบคุณอีกครั้ง.
Steve Haley

1
ดูความคิดเห็นอื่นของฉัน; ฉันกำลังเรียกสิ่งนี้โดยไม่ตั้งใจก่อนที่ Views จะวางโครงสร้างตัวเองเสร็จแล้ว ตอนนี้ใช้งานได้ดีขอบคุณ
Steve Haley

@ Naveen Murthy มันให้พิกัดไปยังมุมมองที่เฉพาะเจาะจงฉันต้องการพิกัดของมุมมองที่ถูกคลิกในรูปแบบรูต ..
Aniket

20
ส่งคืนตำแหน่งที่สัมพันธ์กับพาเรนต์ ใช้getGlobalVisibleRect()สำหรับ coords สัมบูรณ์
Jeffrey Blattman

3
@SteveHaley ฉันประสบปัญหาเดียวกับที่คุณทำด้วยเหตุผลบางอย่างที่ทำให้ฉัน 0 ในทุกวิธีที่ฉันลอง ดูเหมือนคุณจะคิดออกว่าทำไมมันถึงให้ศูนย์ ฉันพยายามที่จะรับพิกัดของมุมมองหลังจากที่มีการโหลดเลย์เอาต์ฉันยังคงได้ศูนย์ทำไมถึงเป็นเช่นนั้น ขอบคุณสำหรับความช่วยเหลือของคุณ
Pankaj Nimgade

46

การใช้ฟังเค้าโครงทั่วโลกทำงานได้ดีสำหรับฉันเสมอ มันมีข้อดีของความสามารถในการบันทึกสิ่งต่าง ๆ หากเปลี่ยนเค้าโครงเช่นถ้ามีการตั้งค่าเป็น View.GONE หรือมุมมองลูกจะถูกเพิ่มหรือลบ

public void onCreate(Bundle savedInstanceState)
{
     super.onCreate(savedInstanceState);

     // inflate your main layout here (use RelativeLayout or whatever your root ViewGroup type is
     LinearLayout mainLayout = (LinearLayout ) this.getLayoutInflater().inflate(R.layout.main, null); 

     // set a global layout listener which will be called when the layout pass is completed and the view is drawn
     mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(
       new ViewTreeObserver.OnGlobalLayoutListener() {
          public void onGlobalLayout() {
               //Remove the listener before proceeding
               if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
               } else {
                    mainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
               }

               // measure your views here
          }
       }
     );

     setContentView(mainLayout);
 }

อย่าลืมลบ OnGlobalLayoutListener ด้านในของ (ที่ส่วนท้าย) onGlobalLayout () {... } นอกจากนี้เพื่อให้แน่ใจตรวจสอบว่ามีอะไรผ่าน! = 0; ผู้ฟังสามารถเรียกสองครั้งหนึ่งครั้งด้วยค่าของ w = 0 / h = 0 ซึ่งเป็นครั้งที่สองที่มีค่าจริงถูกต้อง
MacD

วิธีนี้ทำให้พองเลย์เอาต์หลักแล้วใช้setContentView()ทำให้เกิดปัญหาในแบบพิเศษActivityในแอพของฉัน! นั่นคือกิจกรรมการสนทนา ( windowFrame:@null, windowActionBar:false, windowIsFloating:true, windowNoTitle:true) เลย์เอาต์หลักของมัน (a LinearLayout) ไม่ได้มีลูกโดยตรงที่กำหนดlayout_width(ทั้งหมดเป็นmatch_parentหรือwrap_content)
Mir-Ismaili

17

ตามความคิดเห็นของ Romain Guy นี่คือวิธีที่ฉันจะแก้ไข หวังว่ามันจะช่วยให้คนอื่นที่มีปัญหานี้ด้วย

ฉันพยายามวางตำแหน่งของมุมมองก่อนที่พวกเขาจะวางบนหน้าจอ แต่ก็ไม่ชัดเจนเลยว่าเกิดขึ้น บรรทัดเหล่านั้นถูกวางหลังจากรันโค้ดเริ่มต้นดังนั้นฉันจึงคิดว่าทุกอย่างพร้อม อย่างไรก็ตามรหัสนี้ยังอยู่ใน onCreate (); โดยการทดลองกับ Thread.sleep () ฉันค้นพบว่ารูปแบบไม่ได้ผ่านการสรุปจนกระทั่งหลังจาก onCreate () ไปจนถึง onResume () ได้ดำเนินการเสร็จสิ้นแล้ว ดังนั้นรหัสพยายามที่จะเรียกใช้ก่อนที่เค้าโครงวางบนหน้าจอ โดยการเพิ่มรหัสไปยัง OnClickListener (หรือฟังอื่น ๆ บางส่วน) ได้รับค่าที่ถูกต้องเพราะมันสามารถถูกไล่ออกหลังจากเค้าโครงเสร็จสิ้น


บรรทัดด้านล่างได้รับการแนะนำเป็นการแก้ไขโดยชุมชน:

โปรดใช้ onWindowfocuschanged(boolean hasFocus)


ดังนั้นคุณต้องพูดว่า setContentView (my_layout); จะไม่วางมุมมองทั้งหมด มุมมองทั้งหมดจะถูกวางหลังจาก onCreate () เสร็จแล้วเท่านั้น?
Ashwin

5
ไม่มาก มุมมองทั้งหมด 'มีอยู่' หลังจากsetContentView(R.layout.something)นั้น แต่ก็ไม่ได้รับการมองเห็น พวกเขาไม่มีขนาดหรือตำแหน่ง สิ่งนี้จะไม่เกิดขึ้นจนกว่าเลย์เอาต์แรกจะเสร็จสิ้นซึ่งจะเกิดขึ้นในบางจุดในอนาคต (โดยทั่วไปหลังจาก onResume ())
Steve Haley

14

วิธีแรก:

ในKotlinเราสามารถสร้างส่วนขยายอย่างง่ายสำหรับการดู:

fun View.getLocationOnScreen(): Point
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return Point(location[0],location[1])
}

และก็รับพิกัด:

val location = yourView.getLocationOnScreen()
val absX = location.x
val absY = location.y

วิธีที่สอง:

วิธีที่สองนั้นง่ายกว่า:

fun View.absX(): Int
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return location[0]
}

fun View.absY(): Int
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return location[1]
}

และเพียงแค่ได้รับ X แน่นอนด้วยview.absX()และ Y โดยview.absY()


6
รุ่นที่สั้นกว่าสำหรับตัวอย่างที่ 1 ของคุณ:val location = IntArray(2).apply(::getLocationOnScreen)
Tuan Chau

5

ฟังก์ชั่น utils ของฉันเพื่อรับตำแหน่งมุมมองมันจะส่งคืนPointออบเจกต์ด้วยx valueและy value

public static Point getLocationOnScreen(View view){
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    return new Point(location[0], location[1]);
}

การใช้

Point viewALocation = getLocationOnScreen(viewA);

5

คุณสามารถรับพิกัดของมุมมองโดยใช้getLocationOnScreen()หรือgetLocationInWindow()

หลังจากนั้นxและyควรเป็นมุมบนซ้ายของมุมมอง หากรูปแบบรูทของคุณเล็กกว่าหน้าจอ (เช่นในไดอะล็อก) การใช้getLocationInWindowจะสัมพันธ์กับคอนเทนเนอร์ไม่ใช่ทั้งหน้าจอ

โซลูชัน Java

int[] point = new int[2];
view.getLocationOnScreen(point); // or getLocationInWindow(point)
int x = point[0];
int y = point[1];

หมายเหตุ:หากค่าเป็น 0 เสมอคุณอาจเปลี่ยนมุมมองทันทีก่อนขอตำแหน่ง

เพื่อให้แน่ใจว่ามุมมองมีโอกาสที่จะอัปเดตให้เรียกใช้คำขอตำแหน่งของคุณหลังจากการคำนวณเลย์เอาต์ใหม่ของมุมมองโดยใช้view.post:

view.post(() -> {
    // Values should no longer be 0
    int[] point = new int[2];
    view.getLocationOnScreen(point); // or getLocationInWindow(point)
    int x = point[0];
    int y = point[1];
});

~~

โซลูชั่น Kotlin

val point = IntArray(2)
view.getLocationOnScreen(point) // or getLocationInWindow(point)
val (x, y) = point

หมายเหตุ:หากค่าเป็น 0 เสมอคุณอาจเปลี่ยนมุมมองทันทีก่อนขอตำแหน่ง

เพื่อให้แน่ใจว่ามุมมองมีโอกาสที่จะอัปเดตให้เรียกใช้คำขอตำแหน่งของคุณหลังจากการคำนวณเลย์เอาต์ใหม่ของมุมมองโดยใช้view.post:

view.post {
    // Values should no longer be 0
    val point = IntArray(2)
    view.getLocationOnScreen(point) // or getLocationInWindow(point)
    val (x, y) = point
}

ฉันขอแนะนำให้สร้างฟังก์ชันส่วนขยายสำหรับจัดการสิ่งนี้:

// To use, call:
val (x, y) = view.screenLocation

val View.screenLocation get(): IntArray {
    val point = IntArray(2)
    getLocationOnScreen(point)
    return point
}

และถ้าคุณต้องการความน่าเชื่อถือให้เพิ่ม:

view.screenLocationSafe { x, y -> Log.d("", "Use $x and $y here") }

fun View.screenLocationSafe(callback: (Int, Int) -> Unit) {
    post {
        val (x, y) = screenLocation
        callback(x, y)
    }
}

0

ใช้รหัสนี้เพื่อค้นหาลูกน้อง X และ Y ที่แน่นอน:

int[] array = new int[2];
ViewForWhichLocationIsToBeFound.getLocationOnScreen(array);
if (AppConstants.DEBUG)
    Log.d(AppConstants.TAG, "array  X = " + array[0] + ", Y = " + array[1]);
ViewWhichToBeMovedOnFoundLocation.setTranslationX(array[0] + 21);
ViewWhichToBeMovedOnFoundLocation.setTranslationY(array[1] - 165);

ฉันได้เพิ่ม / ลบค่าบางอย่างเพื่อปรับมุมมองของฉัน โปรดทำตามบรรทัดเหล่านี้เฉพาะหลังจากที่มุมมองเต็มไปหมดแล้ว


"ฉันได้เพิ่ม / ลบค่าบางอย่างเพื่อปรับมุมมองของฉัน" ทำไม??
ToolmakerSteve

ฉันหมายถึงตามความต้องการของฉัน (ตำแหน่งการดู) ฉันได้เพิ่มและลบค่าเพื่อปรับมุมมองของฉัน
Tarun

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

            }
        });
    }

0

นอกเหนือจากคำตอบข้างต้นสำหรับคำถามที่ไหนและเมื่อไหร่ที่คุณควรโทรgetLocationOnScreen?

ในข้อมูลใด ๆ ที่เกี่ยวข้องกับมุมมองใด ๆ ที่คุณต้องการจะสามารถใช้ได้เฉพาะเมื่อมุมมองถูกวางบนหน้าจอ คุณสามารถทำได้โดยใส่รหัสของคุณซึ่งขึ้นอยู่กับการสร้างมุมมองภายในนี้ (view.post (Runnable)):

view.post(new Runnable() {
            @Override
            public void run() {

               // This code will run view created and rendered on screen

               // So as the answer to this question, you can put
               int[] location = new int[2];
               myView.getLocationOnScreen(location);
               int x = location[0];
               int y = location[1];
            }
        });  
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.