開發者在 Manifest 中設置 android:configChanges="orientation|keyboard|keyboardHidden" 阻止 Activity 重建,但導致系統自動切換 layout-land/layout-port 佈局失效。需手動在 onConfigurationChanged 中加載對應佈局。
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
>
</activity>
關鍵:Android 3.2+ (API 13+) 必須包含 screenSize,否則屏幕尺寸變化仍會觸發重建。
res/layout/activity_main.xml - 豎屏佈局res/layout-land/activity_main.xml - 橫屏佈局(或命名為 activity_main_landscape.xml 放在 layout 目錄)public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始佈局加載(根據當前方向)
setContentViewBasedOnOrientation();
initViews(); // 初始化視圖引用、設置監聽器
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 保存當前狀態(如滾動位置、輸入框焦點、數據狀態)
Bundle savedState = saveUIState();
// 切換佈局
setContentViewBasedOnOrientation();
// 重新初始化視圖(findViewById 等)
initViews();
// 恢復狀態
restoreUIState(savedState);
}
private void setContentViewBasedOnOrientation() {
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setContentView(R.layout.activity_main_landscape); // 或 layout-land 下的同名文件
} else {
setContentView(R.layout.activity_main);
}
}
private void initViews() {
// 重新 findViewById 所有視圖
// 重新設置 OnClickListener、Adapter 等
// 示例:
// textView = findViewById(R.id.text_view);
// recyclerView = findViewById(R.id.recycler_view);
// button.setOnClickListener(this);
}
private Bundle saveUIState() {
Bundle bundle = new Bundle();
// 保存滾動位置
// bundle.putInt("scroll_y", scrollView.getScrollY());
// 保存編輯框文本
// bundle.putString("input_text", editText.getText().toString());
// 保存選中項
// bundle.putInt("selected_pos", listView.getCheckedItemPosition());
return bundle;
}
private void restoreUIState(Bundle state) {
if (state == null) return;
// 恢復滾動位置
// scrollView.post(() -> scrollView.scrollTo(0, state.getInt("scroll_y")));
// 恢復編輯框
// editText.setText(state.getString("input_text"));
}
}
| 問題 | 解決方案 |
|---|---|
| findViewById 返回 null | setContentView 後必須重新 findViewById,舊引用失效 |
| 監聽器失效 | 重新設置 OnClickListener、OnTouchListener、Adapter 等 |
| Fragment 狀態丟失 | 使用 FragmentManager 保存/恢復,或 ViewModel 存儲數據 |
| 輸入法狀態異常 | 手動控制 InputMethodManager 顯示/隱藏 |
| 圖片加載重複 | 使用 Glide/Picasso 等庫自動緩存,或手動管理 ImageView 引用 |
使用 ViewModel 存儲 UI 狀態數據,DataBinding 自動綁定佈局,配置變更時僅需重新綁定,大幅減少樣板代碼:
// ViewModel 持有數據,不隨 Activity 重建銷毀
public class MyViewModel extends ViewModel {
public MutableLiveData inputText = new MutableLiveData<>();
public MutableLiveData scrollPosition = new MutableLiveData<>();
}
// Activity 中
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
binding.setViewModel(viewModel);
binding.setLifecycleOwner(this);
// 配置變更後重新 setContentView 並綁定即可,數據自動保留
原答案提到的 <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"> 不需要,此權限用於系統級應用修改全局配置,普通應用處理自身配置變更無需聲明。