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

Xử lý thay đổi hướng màn hình (Orientation Change) Android

Handle Orientation Change - Xử lý xoay màn hình

Mặc định Android hủy và tạo lại Activity khi xoay màn hình (configuration change). Có 2 cách chính để xử lý:

  1. Cho phép recreate - Lưu/restore state qua onSaveInstanceState/onRestoreInstanceState
  2. Tự xử lý - Khai báo android:configChanges trong Manifest, override onConfigurationChanged()

Cách 1: Tự xử lý (Ví dụ này)

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

Khởi tạo project Android Studio.

Bước 2: Layout (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:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/actionEvent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Xoay màn hình để test"
        android:textSize="24sp"
        android:padding="24dp" />

</LinearLayout>

Bước 3: MainActivity.java

package com.example.myapplication;

import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private TextView actionEvent;
    
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        actionEvent = findViewById(R.id.actionEvent);
        updateOrientationText();
    }
    
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        updateOrientationText();
    }
    
    private void updateOrientationText() {
        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            actionEvent.setText("HƯỚNG: NGANG (LANDSCAPE)");
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            actionEvent.setText("HƯỚNG: DỌC (PORTRAIT)");
        } else {
            actionEvent.setText("HƯỚNG: KHÔNG XÁC ĐỊNH");
        }
    }
}

Bước 4: AndroidManifest.xml - Quan trọng!

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

    <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|keyboardHidden|smallestScreenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>
</manifest>

Giải thích android:configChanges

Cách 2: Cho phép Recreate (Best Practice hiện đại)

Không khai báo configChanges, dùng ViewModel + onSaveInstanceState:

ViewModel (Architecture Components)

// ViewModel tự survive configuration change
public class MyViewModel extends ViewModel {
    private MutableLiveData<String> data = new MutableLiveData<>();
    
    public LiveData<String> getData() { return data; }
    public void setData(String value) { data.setValue(value); }
}

// Activity
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
viewModel.getData().observe(this, s -> textView.setText(s));

onSaveInstanceState (Legacy)

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("key_text", textView.getText().toString());
    outState.putInt("key_counter", counter);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    textView.setText(savedInstanceState.getString("key_text"));
    counter = savedInstanceState.getInt("key_counter");
}

// Hoặc trong onCreate
if (savedInstanceState != null) {
    textView.setText(savedInstanceState.getString("key_text"));
}

So sánh 2 cách

Giá trị Ý nghĩa
orientation Xoay màn hình (portrait/landscape)
screenSize Thay đổi kích thước màn hình (API 13+) - BẮT BUỘC nếu dùng orientation
keyboardHidden Bàn phím ảo hiện/ẩn
smallestScreenSize Thay đổi smallest width (API 13+)
screenLayout Thay đổi layout direction (RTL/LTR)
uiMode Chế độ UI (night mode, car mode, watch)
locale Thay đổi ngôn ngữ
fontScale Thay đổi cỡ font hệ thống
density Thay đổi mật độ pixel
Tiêu chí configChanges (Tự xử lý) Recreate + ViewModel/onSaveInstanceState
Độ phức tạp Thấp Trung bình
Hiệu năng Tốt (không recreate) Recreate cost (nhỏ)
Layout landscape/portrait riêng Khó (phải tự inflate/swap) Dễ (res/layout-land/)
System handling Bỏ qua system logic Tận dụng system
Memory leak risk Cao hơn (view giữ reference cũ) Thấp (ViewModel lifecycle-aware)
Khuyến nghị Google Chỉ khi cần thiết đặc biệt Khuyến nghị chuẩn

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

  • Video player, game - không muốn interrupt playback
  • Camera preview - tái tạo camera tốn tài nguyên
  • WebView - giữ trạng thái trang web
  • Map/Navigation - giữ vị trí, zoom

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

  • UI thông thường - dùng ViewModel + layout-land
  • Form nhập liệu - ViewModel tự restore
  • Cần layout khác nhau portrait/landscape

Best Practices hiện đại

  1. Mặc định: Không dùng configChanges, dùng ViewModel + LiveData/Flow
  2. Layout riêng: Tạo res/layout-land/activity_main.xml cho landscape
  3. State preservation: ViewModel tự survive, hoặc onSaveInstanceState cho data nhỏ
  4. Chỉ dùng configChanges khi thực sự cần thiết (media, camera, webview)