Khi tạo View động (programmatically) hoặc cần kích thước thực tế sau khi layout, không thể gọi getWidth()/getHeight() ngay trong onCreate() vì View chưa layout (trả về 0). Cần dùng getMeasuredWidth()/getMeasuredHeight() sau khi measure.
Các phương thức lấy kích thước
- getMeasuredWidth()/getMeasuredHeight(): Kích thước sau measure (mong muốn)
- getWidth()/getHeight(): Kích thước thực tế sau layout (có thể khác measured do parent constraint)
- getLayoutParams().width/height: Giá trị trong LayoutParams (WRAP_CONTENT, MATCH_PARENT, dp)
Quy trình Measure → Layout → Draw
- Measure pass: Parent gọi
measure(widthMeasureSpec, heightMeasureSpec)trên child → Child tính kích thước mong muốn → Lưu vàomMeasuredWidth/Height - Layout pass: Parent gọi
layout(l, t, r, b)→ Child set frame thực tế - Draw pass: Vẽ lên Canvas
Cách lấy kích thước đúng
1. View tĩnh (XML) - Dùng ViewTreeObserver
View view = findViewById(R.id.myView);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// View đã layout, có kích thước thực
int width = view.getWidth();
int height = view.getHeight();
// Quan trọng: Remove listener để không gọi nhiều lần
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Xử lý...
}
});
2. View động (tạo bằng code) - Dùng measure()
TextView textView = new TextView(this);
textView.setText("Sample text for measuring");
textView.setTextSize(18);
textView.setPadding(16, 16, 16, 16);
// Measure với UNSPECIFIED (view tự quyết định size)
textView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
// Lấy measured size
int measuredWidth = textView.getMeasuredWidth();
int measuredHeight = textView.getMeasuredHeight();
// Hoặc measure với constraint cụ thể
// width: EXACTLY 300dp, height: AT_MOST 200dp
textView.measure(
View.MeasureSpec.makeMeasureSpec(dpToPx(300), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(dpToPx(200), View.MeasureSpec.AT_MOST)
);
3. MeasureSpec giải thích
| Mode | Ý nghĩa | Ví dụ |
|---|---|---|
| EXACTLY | Kích thước cố định | MATCH_PARENT, 200dp |
| AT_MOST | Tối đa kích thước cho phép | WRAP_CONTENT trong parent giới hạn |
| UNSPECIFIED | Không giới hạn, view tự do | ScrollView child, measure trước khi add |
Ví dụ hoàn chỉnh: Tạo TextView động, measure, add vào layout
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Root container trong XML: LinearLayout id=rootView
LinearLayout rootView = findViewById(R.id.rootView);
// 1. Tạo TextView động
TextView dynamicText = new TextView(this);
dynamicText.setText("TextView tạo động - đo kích thước");
dynamicText.setTextSize(18);
dynamicText.setTextColor(0xFF000000);
dynamicText.setPadding(24, 24, 24, 24);
dynamicText.setBackgroundColor(0xFFFFEB3B);
dynamicText.setGravity(Gravity.CENTER);
// 2. Measure với UNSPECIFIED (tự do)
dynamicText.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
);
int measuredW = dynamicText.getMeasuredWidth();
int measuredH = dynamicText.getMeasuredHeight();
Log.d("ViewSize", "Measured: " + measuredW + "x" + measuredH);
// 3. Set LayoutParams để add vào parent
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 32, 0, 0);
dynamicText.setLayoutParams(params);
// 4. Add vào root view
rootView.addView(dynamicText);
// 5. Dùng ViewTreeObserver để lấy kích thước SAU KHI LAYOUT (thực tế)
dynamicText.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
dynamicText.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int actualW = dynamicText.getWidth();
int actualH = dynamicText.getHeight();
Log.d("ViewSize", "Actual after layout: " + actualW + "x" + actualH);
Toast.makeText(this,
"Measured: " + measuredW + "x" + measuredH + "\n" +
"Actual: " + actualW + "x" + actualH,
Toast.LENGTH_LONG).show();
});
}
// Helper dp to px
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
}
Layout activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kích thước View động"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp" />
</LinearLayout>
Khi nào dùng measured vs actual?
- Measured: Khi cần size để tính toán, vẽ custom view, tạo bitmap, quyết định layout con
- Actual (getWidth/getHeight): Khi cần vị trí, size thực tế trên màn hình (animation, scroll, collision detection)
Lưu ý quan trọng
- Không gọi getWidth/getHeight trong onCreate/onStart/onResume - chưa layout
- measure(0, 0) = UNSPECIFIED/UNSPECIFIED - view tự do, dùng cho view chưa add vào parent
- View.GONE/INVISIBLE vẫn có measured size (nếu đã measure), nhưng getWidth/getHeight = 0
- Custom View: override onMeasure() để tự tính measured size