伪球迷不用验 發表於 2025-9-28 10:26:28

Android开发教程之屏幕变更事件

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、什么是屏幕变更事件?</li><ul class="second_class_ul"><li>常见的配置变更类型</li></ul><li>二、默认行为:Activity 重建</li><ul class="second_class_ul"><li>生命周期流程</li><li>问题与挑战</li></ul><li>三、方案一:允许重建 + 正确保存状态</li><ul class="second_class_ul"><li>1. 使用&nbsp;onSaveInstanceState()&nbsp;保存临时状态</li><li>2. 使用&nbsp;ViewModel&nbsp;保留复杂数据</li></ul><li>四、方案二:阻止重建 + 手动处理变更</li><ul class="second_class_ul"><li>1. 声明要自行处理的配置变更</li><li>2. 重写&nbsp;onConfigurationChanged()&nbsp;方法</li><li>3. 动态调整 UI 示例</li></ul><li>五、为不同屏幕提供专属布局</li><ul class="second_class_ul"><li>1. 创建横屏专用布局</li><li>2. 横屏布局示例(layout-land/activity_main.xml)</li></ul><li>六、高级技巧与最佳实践</li><ul class="second_class_ul"><li>1. 监听软键盘弹出/隐藏</li><li>2. 折叠屏与多窗口支持</li><li>3. 使用 Jetpack Compose 实现响应式 UI</li><li>七、常见问题与避坑指南</li></ul><li>八、结语</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、什么是屏幕变更事件?</h2>
<p>当设备的<strong>配置(Configuration)</strong>&nbsp;发生变化时,Android 系统会触发&nbsp;<code>Configuration Change</code>&nbsp;事件。</p>
<p class="maodian"></p><h3>常见的配置变更类型</h3>
<table><tbody><tr><th>变更类型</th><th>示例</th></tr><tr><td><code>orientation</code></td><td>屏幕旋转(竖屏 &harr; 横屏)</td></tr><tr><td><code>screenLayout</code></td><td>屏幕尺寸/密度变化(如折叠屏展开)</td></tr><tr><td><code>keyboardHidden</code></td><td>软键盘弹出/隐藏</td></tr><tr><td><code>fontScale</code></td><td>系统字体大小调整</td></tr><tr><td><code>locale</code></td><td>系统语言切换</td></tr><tr><td><code>uiMode</code></td><td>夜间模式开启/关闭</td></tr></tbody></table>
<blockquote><p>⚠️&nbsp;<strong>默认行为</strong>:系统会<strong>销毁并重建 Activity</strong>(调用&nbsp;<code>onDestroy()</code>&nbsp;&rarr;&nbsp;<code>onCreate()</code>),以便加载适配新配置的资源。</p></blockquote>
<p class="maodian"></p><h2>二、默认行为:Activity 重建</h2>
<p class="maodian"></p><h3>生命周期流程</h3>
<div class="jb51code"><pre class="brush:java;">Activity A (竖屏)
    ↓ 用户旋转屏幕
onPause() → onStop() → onDestroy()
    ↓ 系统重建
onCreate() → onStart() → onResume()
    → Activity A (横屏)</pre></div>
<p class="maodian"></p><h3>问题与挑战</h3>
<ul><li><strong>状态丢失</strong>:<code>onCreate()</code>&nbsp;中初始化的数据可能丢失。</li><li><strong>性能损耗</strong>:重复执行&nbsp;<code>setContentView()</code>、数据库查询、网络请求。</li><li><strong>用户体验差</strong>:页面闪退或重新加载。</li></ul>
<p class="maodian"></p><h2>三、方案一:允许重建 + 正确保存状态</h2>
<p>如果选择接受默认重建行为,必须确保关键数据不丢失。</p>
<p class="maodian"></p><h3>1. 使用&nbsp;onSaveInstanceState()&nbsp;保存临时状态</h3>
<div class="jb51code"><pre class="brush:java;">public class MainActivity extends AppCompatActivity {
    private String userInput;

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

      EditText editText = findViewById(R.id.edit_text);
      
      // 恢复保存的状态
      if (savedInstanceState != null) {
            userInput = savedInstanceState.getString("USER_INPUT");
            editText.setText(userInput);
      }
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
      super.onSaveInstanceState(outState);
      // 保存用户输入
      EditText editText = findViewById(R.id.edit_text);
      outState.putString("USER_INPUT", editText.getText().toString());
    }
}</pre></div>
<blockquote><p>✅&nbsp;<strong>适用场景</strong>:轻量级状态(如文本框内容、滚动位置)。<br />❌&nbsp;<strong>不适用</strong>:大数据、文件句柄、网络连接。</p></blockquote>
<p class="maodian"></p><h3>2. 使用&nbsp;ViewModel&nbsp;保留复杂数据</h3>
<div class="jb51code"><pre class="brush:java;">public class MainViewModel extends ViewModel {
    private MutableLiveData&lt;List&lt;String&gt;&gt; dataList = new MutableLiveData&lt;&gt;();

    public LiveData&lt;List&lt;String&gt;&gt; getDataList() {
      return dataList;
    }

    public void loadData() {
      // 模拟耗时加载
      new Handler().postDelayed(() -&gt; {
            List&lt;String&gt; data = Arrays.asList("Item 1", "Item 2", "Item 3");
            dataList.setValue(data);
      }, 2000);
    }
}</pre></div>
<div class="jb51code"><pre class="brush:java;">// 在 Activity 中使用
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
viewModel.getDataList().observe(this, list -&gt; {
    // 更新 UI,即使 Activity 重建,数据依然存在
    adapter.submitList(list);
});</pre></div>
<blockquote><p>✅&nbsp;<strong>优势</strong>:生命周期独立于 Activity,配置变更时不销毁。</p></blockquote>
<p class="maodian"></p><h2>四、方案二:阻止重建 + 手动处理变更</h2>
<p>通过在&nbsp;<code>AndroidManifest.xml</code>&nbsp;中声明&nbsp;<code>android:configChanges</code>,可阻止系统自动重建 Activity。</p>
<p class="maodian"></p><h3>1. 声明要自行处理的配置变更</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;activity
    android:name=".MainActivity"
    android:exported="true"
    android:configChanges="orientation|screenSize|keyboardHidden"
    android:exported="true"&gt;
    &lt;intent-filter&gt;
      &lt;action android:name="android.intent.action.MAIN" /&gt;
      &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
    &lt;/intent-filter&gt;
&lt;/activity&gt;</pre></div>
<blockquote><p>🔍&nbsp;<strong>关键属性说明</strong>:</p>
<ul><li><code>orientation</code>:屏幕方向变更(横/竖屏)</li><li><code>screenSize</code>:屏幕尺寸变化(API 13+,常与 orientation 同时使用)</li><li><code>keyboardHidden</code>:软键盘显示/隐藏</li></ul></blockquote>
<p class="maodian"></p><h3>2. 重写&nbsp;onConfigurationChanged()&nbsp;方法</h3>
<div class="jb51code"><pre class="brush:java;">@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // 判断当前屏幕方向
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
      Toast.makeText(this, "已切换到横屏", Toast.LENGTH_SHORT).show();
      // 动态调整 UI
      adjustForLandscape();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
      Toast.makeText(this, "已切换到竖屏", Toast.LENGTH_SHORT).show();
      adjustForPortrait();
    }
}</pre></div>
<p class="maodian"></p><h3>3. 动态调整 UI 示例</h3>
<div class="jb51code"><pre class="brush:java;">private void adjustForLandscape() {
    // 横屏下隐藏某些 View
    findViewById(R.id.ad_banner).setVisibility(View.GONE);
   
    // 调整 RecyclerView 布局管理器
    RecyclerView recyclerView = findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
}

private void adjustForPortrait() {
    // 竖屏下显示广告
    findViewById(R.id.ad_banner).setVisibility(View.VISIBLE);
   
    // 恢复线性布局
    RecyclerView recyclerView = findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
}</pre></div>
<blockquote><p>✅&nbsp;<strong>优点</strong>:避免 Activity 重建,提升性能。<br />❌&nbsp;<strong>缺点</strong>:需手动处理所有 UI 变化,代码复杂度增加。</p></blockquote>
<p class="maodian"></p><h2>五、为不同屏幕提供专属布局</h2>
<p>最优雅的适配方式是使用&nbsp;<strong>资源限定符(Resource Qualifiers)</strong>,让系统自动加载合适的布局。</p>
<p class="maodian"></p><h3>1. 创建横屏专用布局</h3>
<div class="jb51code"><pre class="brush:xml;">res/
├── layout/
│   └── activity_main.xml          # 竖屏布局
└── layout-land/
    └── activity_main.xml          # 横屏布局</pre></div>
<p class="maodian"></p><h3>2. 横屏布局示例(layout-land/activity_main.xml)</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"&gt;

    &lt;!-- 左侧列表 --&gt;
    &lt;fragment
      android:id="@+id/fragment_list"
      android:name="com.example.NewsListFragment"
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="1" /&gt;

    &lt;!-- 右侧详情 --&gt;
    &lt;FrameLayout
      android:id="@+id/detail_container"
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="2" /&gt;

&lt;/LinearLayout&gt;</pre></div>
<blockquote><p>✅&nbsp;<strong>优势</strong>:完全解耦,UI 设计自由度高,适合平板或大屏设备。</p></blockquote>
<p class="maodian"></p><h2>六、高级技巧与最佳实践</h2>
<p class="maodian"></p><h3>1. 监听软键盘弹出/隐藏</h3>
<div class="jb51code"><pre class="brush:java;">// 在 onConfigurationChanged 中判断
if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) {
    // 软键盘弹出
} else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
    // 软键盘隐藏
}</pre></div>
<p class="maodian"></p><h3>2. 折叠屏与多窗口支持</h3>
<div class="jb51code"><pre class="brush:xml;">&lt;!-- 支持多窗口 --&gt;
&lt;activity
    android:name=".MainActivity"
    android:resizeableActivity="true"
    android:supportsPictureInPicture="true"
    android:configChanges="orientation|screenSize|smallestScreenSize"&gt;
&lt;/activity&gt;</pre></div>
<p class="maodian"></p><h3>3. 使用 Jetpack Compose 实现响应式 UI</h3>
<div class="jb51code"><pre class="brush:java;">@Composable
fun ResponsiveLayout() {
    val configuration = LocalConfiguration.current
    val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE

    if (isLandscape) {
      Row { /* 横屏布局 */ }
    } else {
      Column { /* 竖屏布局 */ }
    }
}</pre></div>
<p class="maodian"></p><h3>七、常见问题与避坑指南</h3>
<ul><li><p><strong>android:configChanges&nbsp;不生效?</strong>确保同时声明&nbsp;<code>orientation</code>&nbsp;和&nbsp;<code>screenSize</code>(API 13+)。</p></li><li><p><strong>横屏布局未加载?</strong>检查文件夹命名是否正确(<code>layout-land</code>),且无拼写错误。</p></li><li><p><strong>ViewModel 数据在重建后丢失?</strong>确保使用&nbsp;<code>ViewModelProvider(this)</code>&nbsp;而非&nbsp;<code>ViewModelProvider(requireActivity())</code>。</p></li><li><p><strong>动画在旋转后中断?</strong>将动画逻辑放在&nbsp;<code>ViewModel</code>&nbsp;或使用&nbsp;<code>onRetainNonConfigurationInstance()</code>。</p></li></ul>
<p class="maodian"></p><h2>八、结语</h2>
<p>到此这篇关于Android开发教程之屏幕变更事件的文章就介绍到这了,更多相关Android屏幕变更事件内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android实现屏幕旋转方法总结</li><li>android 屏幕亮度调节方法详解</li><li>Android调节屏幕亮度实现代码</li><li>android获取屏幕高度和宽度的实现方法</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Android开发教程之屏幕变更事件