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