Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách xác định kích thước View tại runtime trên Android bằng Kotlin

Giới thiệu

Hướng dẫn này trình bày cách lấy chiều rộng và chiều cao thực tế của một View tại runtime (sau khi layout hoàn tất) trên Android sử dụng ViewTreeObserver.OnGlobalLayoutListener và Kotlin.

Vấn đề

Trong onCreate(), view chưa được layout nên view.width và view.height trả về 0. Cần đợi layout pass hoàn tất.

Các bước thực hiện

Bước 1: Tạo dự án mới

Tạo project: File → New Project.

Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="8dp"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:layout_gravity="center"
        android:src="@drawable/image" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/imageView"
        android:layout_centerInParent="true"
        android:layout_marginTop="20sp"
        android:textColor="@android:color/background_dark"
        android:textSize="16sp"
        android:textStyle="bold" />

</RelativeLayout>

Bước 3: Chuẩn bị hình ảnh

Copy một file ảnh (.png/.jpg/.jpeg) vào thư mục res/drawable và đặt tên là image.png (hoặc cập nhật android:src tương ứng).

Bước 4: Xử lý logic (src/MainActivity.kt)

import android.os.Bundle
import android.view.View
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var imageView: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "KotlinApp"

        imageView = findViewById(R.id.imageView)

        // Đăng ký listener để nhận callback khi layout hoàn tất
        imageView.viewTreeObserver.addOnGlobalLayoutListener(GetViewSize())
    }

    inner class GetViewSize : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            // Quan trọng: Remove listener để tránh gọi nhiều lần
            imageView.viewTreeObserver.removeOnGlobalLayoutListener(this)

            val view = findViewById(R.id.imageView)
            val width = view.width
            val height = view.height

            val textView = findViewById(R.id.textView)
            textView.text = String.format("Width: %dpx, Height: %dpx", width, height)
        }
    }
}

Bước 5: Cấu hình Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.q11">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. TextView sẽ hiển thị width/height thực tế của ImageView sau khi layout (ví dụ: "Width: 1080px, Height: 1500px").

Giải thích

  • ViewTreeObserver.OnGlobalLayoutListener: Interface lắng nghe sự thay đổi layout toàn cục.
  • onGlobalLayout(): Được gọi sau mỗi layout pass. Cần remove listener ngay lần đầu để tránh loop vô tận.
  • view.width/view.height: Kích thước tính bằng pixel.

Cách hiện đại: Extension function + Kotlin Coroutines

import android.view.View
import android.view.ViewTreeObserver
import kotlinx.coroutines.await
import kotlinx.coroutines.yield

suspend fun View.awaitLayout(): Boolean = suspendCancellableCoroutine { cont ->
    val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            cont.resume(true)
        }
    }
    viewTreeObserver.addOnGlobalLayoutListener(listener)
}

// Sử dụng trong lifecycleScope (Activity/Fragment)
lifecycleScope.launch {
    imageView.awaitLayout()
    val width = imageView.width
    val height = imageView.height
    textView.text = "Width: $width, Height: $height"
}

Các phương pháp khác

Phương pháp Ưu điểm Nhược điểm
view.doOnLayout { } (KTX) Ngắn gọn, tự remove listener Cần dependency androidx.core:core-ktx
view.addOnLayoutChangeListener Lắng nghe thay đổi size/position Gọi nhiều lần, cần logic filter
view.post { } Đơn giản, chạy sau layout pass Không đảm bảo layout đã xong 100%
view.measure() thủ công Ép đo lường ngay Phức tạp, cần MeasureSpec đúng

Best Practice

Dùng view.doOnLayout { } từ androidx.core:core-ktx (có sẵn trong project mới):

imageView.doOnLayout {
    textView.text = "Width: ${it.width}, Height: ${it.height}"
}