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

Lấy kích thước View (width/height) trong Android

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

  1. Measure pass: Parent gọi measure(widthMeasureSpec, heightMeasureSpec) trên child → Child tính kích thước mong muốn → Lưu vào mMeasuredWidth/Height
  2. Layout pass: Parent gọi layout(l, t, r, b) → Child set frame thực tế
  3. 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ĩaVí dụ
EXACTLYKích thước cố địnhMATCH_PARENT, 200dp
AT_MOSTTối đa kích thước cho phépWRAP_CONTENT trong parent giới hạn
UNSPECIFIEDKhông giới hạn, view tự doScrollView 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