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

Phát hiện thay đổi hướng màn hình (Orientation) trên Android

Giới thiệu

Bài viết này hướng dẫn cách phát hiện thay đổi hướng màn hình (portrait ↔ landscape) trên Android bằng cách override phương thức onConfigurationChanged() và khai báo android:configChanges trong Manifest.

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

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

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="24sp"
        android:textStyle="bold" />

</LinearLayout>

Bước 3: Viết code xử lý (src/MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        
        // Kiểm tra hướng mới
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            Toast.makeText(this, "Chế độ ngang (Landscape)", Toast.LENGTH_SHORT).show();
            // TODO: Cập nhật layout cho landscape
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            Toast.makeText(this, "Chế độ dọc (Portrait)", Toast.LENGTH_SHORT).show();
            // TODO: Cập nhật layout cho portrait
        }
        
        // Các thay đổi cấu hình khác có thể kiểm tra:
        // newConfig.screenSize, newConfig.smallestScreenSizeDp
        // newConfig.densityDpi, newConfig.fontScale
        // newConfig.keyboardHidden, newConfig.hardKeyboardHidden
        // newConfig.uiMode (night mode, car mode, desk mode...)
    }
}

Bước 4: Cấu hình Manifest - QUAN TRỌNG (androidManifest.xml)

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

    <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"
            android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Xoay thiết bị ngang/dọc để thấy Toast thông báo. Không cần tạo layout-land riêng vì Activity không bị recreate.

Giải thích android:configChanges

Giá trị Ý nghĩa Khi nào cần
orientation Thay đổi giữa portrait/landscape Luôn cần khi muốn tự xử lý xoay màn hình
screenSize Thay đổi kích thước màn hình (API 13+) Bắt buộc kèm theo orientation từ API 13+
screenLayout Thay đổi layout (ví dụ: split screen, multi-window) Khi hỗ trợ multi-window
keyboardHidden Bàn phím ảo hiện/ẩn Khi muốn xử lý bàn phím thủ công
density Thay đổi mật độ pixel (display scaling) Hiếm khi dùng
fontScale Thay đổi cỡ chữ hệ thống Accessibility
uiMode Chế độ night/day, car, desk, watch, vr Khi hỗ trợ dark mode thủ công

Cách tiếp cận hiện đại (Khuyên dùng)

Thay vì dùng configChanges (làm phức tạp lifecycle, dễ leak memory, khó maintain), hãy dùng:

1. ViewBinding + Layout riêng cho landscape

// res/layout/activity_main.xml (portrait)
<LinearLayout ... android:orientation="vertical">...</LinearLayout>

// res/layout-land/activity_main.xml (landscape)
<LinearLayout ... android:orientation="horizontal">...</LinearLayout>

// Activity KHÔNG cần configChanges, Android tự recreate và load layout phù hợp
// Trong onCreate: binding = ActivityMainBinding.inflate(getLayoutInflater())

2. ViewModel + onSaveInstanceState (Giữ dữ liệu khi recreate)

public class MainViewModel extends ViewModel {
    private MutableLiveData data = new MutableLiveData<>();
    // Data sống sót qua configuration change
}

3. Jetpack Compose (Hiện đại nhất)

@Composable
fun MainScreen() {
    val configuration = LocalConfiguration.current
    val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
    
    if (isLandscape) {
        LandscapeLayout()
    } else {
        PortraitLayout()
    }
}

Khi nào NÊN dùng configChanges?

  • Video player toàn màn hình (tránh reload video khi xoay).
  • Game/OpenGL surface (context loss khi recreate).
  • WebView phức tạp (tránh reload trang).
  • Camera preview.

Khi nào KHÔNG nên dùng configChanges?

  • Ứng dụng thông thường (CRUD, list, form...).
  • Khi có thể dùng layout-land, layout-sw600dp, layout-night...
  • Khi dùng ViewModel/ViewBinding/Compose - hệ thống tự xử lý tốt.

Lưu ý

  • Nếu dùng configChanges, Activity KHÔNG bị destroy/recreate, nên onCreate không chạy lại, onSaveInstanceState không được gọi.
  • Phải tự cập nhật tất cả view, layout, resource phụ thuộc vào cấu hình trong onConfigurationChanged.
  • Dễ gây bug: quên cập nhật một view, leak context, layout vỡ khi multi-window...